Compare commits
11 Commits
efba82337c
...
v1.8.1
| Author | SHA1 | Date | |
|---|---|---|---|
| f8847a7a10 | |||
| 117b23db1e | |||
| d6f9a24823 | |||
| 422e4fccba | |||
| 57ec4d7544 | |||
| a4d021c658 | |||
| 269d19bbef | |||
| 30ff08c66d | |||
| 81deaf447f | |||
| a0ebc58d6d | |||
| 7498c24c9a |
82
.agent/workflows/cms-workflow.md
Normal file
82
.agent/workflows/cms-workflow.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
---
|
||||||
|
description: How to manage and deploy Directus CMS infrastructure changes.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Directus CMS Infrastructure Workflow
|
||||||
|
|
||||||
|
This workflow ensures "Industrial Grade" consistency and stability across local, testing, and production environments for the `at-mintel` Directus CMS.
|
||||||
|
|
||||||
|
## 1. Local Development Lifecycle
|
||||||
|
|
||||||
|
### Starting the CMS
|
||||||
|
To start the local Directus instance with extensions:
|
||||||
|
```bash
|
||||||
|
cd packages/cms-infra
|
||||||
|
npm run up
|
||||||
|
```
|
||||||
|
|
||||||
|
### Modifying Schema
|
||||||
|
1. **Directus UI**: Make your changes directly in the local Directus Admin UI (Collections, Fields, Relations).
|
||||||
|
2. **Take Snapshot**:
|
||||||
|
```bash
|
||||||
|
cd packages/cms-infra
|
||||||
|
npm run snapshot:local
|
||||||
|
```
|
||||||
|
This updates `packages/cms-infra/schema/snapshot.yaml`.
|
||||||
|
3. **Commit**: Commit the updated `snapshot.yaml`.
|
||||||
|
|
||||||
|
## 2. Deploying Schema Changes
|
||||||
|
|
||||||
|
### To Local Environment (Reconciliation)
|
||||||
|
If you pull changes from Git and need to apply them to your local database:
|
||||||
|
```bash
|
||||||
|
cd packages/cms-infra
|
||||||
|
npm run schema:apply:local
|
||||||
|
```
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> This command automatically runs `scripts/cms-reconcile.sh` to prevent "Field already exists" errors by registering database columns in Directus metadata first.
|
||||||
|
|
||||||
|
### To Production (Infra)
|
||||||
|
To deploy the local snapshot to the production server:
|
||||||
|
```bash
|
||||||
|
cd packages/cms-infra
|
||||||
|
npm run schema:apply:infra
|
||||||
|
```
|
||||||
|
This script:
|
||||||
|
1. Syncs built extensions via rsync.
|
||||||
|
2. Injects the `snapshot.yaml` into the remote container.
|
||||||
|
3. Runs `directus schema apply`.
|
||||||
|
4. Restarts Directus to clear the schema cache.
|
||||||
|
|
||||||
|
## 3. Data Synchronization
|
||||||
|
|
||||||
|
### Pulling from Production
|
||||||
|
To update your local environment with production data and assets:
|
||||||
|
```bash
|
||||||
|
cd packages/cms-infra
|
||||||
|
npm run sync:pull
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pushing to Production
|
||||||
|
> [!CAUTION]
|
||||||
|
> This will overwrite production data. Use with extreme care.
|
||||||
|
```bash
|
||||||
|
cd packages/cms-infra
|
||||||
|
npm run sync:push
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Extension Management
|
||||||
|
When modifying extensions in `packages/*-manager`:
|
||||||
|
1. Extensions are automatically built and synced when running `npm run up`.
|
||||||
|
2. To sync manually without restarting the stack:
|
||||||
|
```bash
|
||||||
|
cd packages/cms-infra
|
||||||
|
npm run build:extensions
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Troubleshooting "Field already exists"
|
||||||
|
If `schema:apply` fails with "Field already exists", run:
|
||||||
|
```bash
|
||||||
|
./scripts/cms-reconcile.sh
|
||||||
|
```
|
||||||
|
This script ensures the database state matches Directus's internal field registry (`directus_fields`).
|
||||||
36
.env
Normal file
36
.env
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Project
|
||||||
|
IMAGE_TAG=v1.8.0
|
||||||
|
PROJECT_NAME=at-mintel
|
||||||
|
PROJECT_COLOR=#82ed20
|
||||||
|
GITEA_TOKEN=ccce002e30fe16a31a6c9d5a414740af2f72a582
|
||||||
|
|
||||||
|
# Authentication
|
||||||
|
GATEKEEPER_PASSWORD=mintel
|
||||||
|
AUTH_COOKIE_NAME=mintel_gatekeeper_session
|
||||||
|
|
||||||
|
# Host Config (Local)
|
||||||
|
TRAEFIK_HOST=at-mintel.localhost
|
||||||
|
DIRECTUS_HOST=cms.localhost
|
||||||
|
|
||||||
|
# Next.js
|
||||||
|
NEXT_PUBLIC_BASE_URL=http://at-mintel.localhost
|
||||||
|
|
||||||
|
# Directus
|
||||||
|
DIRECTUS_URL=http://cms.localhost
|
||||||
|
DIRECTUS_KEY=F9IIfahEjPq6NZhKyRLw516D8GotuFj79EGK7pGfIWg=
|
||||||
|
DIRECTUS_SECRET=OZfxMu8lBxzaEnFGRKreNBoJpRiRu58U+HsVg2yWk4o=
|
||||||
|
CORS_ENABLED=true
|
||||||
|
CORS_ORIGIN=true
|
||||||
|
LOG_LEVEL=debug
|
||||||
|
DIRECTUS_ADMIN_EMAIL=mmintel@mintel.me
|
||||||
|
DIRECTUS_ADMIN_PASSWORD=Tim300493.
|
||||||
|
DIRECTUS_DB_NAME=directus
|
||||||
|
DIRECTUS_DB_USER=directus
|
||||||
|
DIRECTUS_DB_PASSWORD=mintel-db-pass
|
||||||
|
|
||||||
|
# Sentry / Glitchtip
|
||||||
|
SENTRY_DSN=
|
||||||
|
|
||||||
|
# Analytics (Umami)
|
||||||
|
NEXT_PUBLIC_UMAMI_WEBSITE_ID=
|
||||||
|
NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# Project
|
# Project
|
||||||
IMAGE_TAG=v1.7.12
|
IMAGE_TAG=v1.8.0
|
||||||
PROJECT_NAME=sample-website
|
PROJECT_NAME=sample-website
|
||||||
PROJECT_COLOR=#82ed20
|
PROJECT_COLOR=#82ed20
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sample-website",
|
"name": "sample-website",
|
||||||
"version": "1.7.12",
|
"version": "1.8.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
1
directus/extensions/acquisition-manager/index.js
Normal file
1
directus/extensions/acquisition-manager/index.js
Normal file
File diff suppressed because one or more lines are too long
30
directus/extensions/acquisition-manager/package.json
Normal file
30
directus/extensions/acquisition-manager/package.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "acquisition-manager",
|
||||||
|
"description": "Custom High-Fidelity Acquisition Management for Directus",
|
||||||
|
"icon": "account_balance_wallet",
|
||||||
|
"version": "1.8.0",
|
||||||
|
"type": "module",
|
||||||
|
"keywords": [
|
||||||
|
"directus",
|
||||||
|
"directus-extension",
|
||||||
|
"directus-extension-module"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"directus:extension": {
|
||||||
|
"type": "module",
|
||||||
|
"path": "index.js",
|
||||||
|
"source": "src/index.ts",
|
||||||
|
"host": "*",
|
||||||
|
"name": "Acquisition Manager"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "directus-extension build && (cp -f dist/index.js index.js 2>/dev/null || true)",
|
||||||
|
"dev": "directus-extension build -w"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@directus/extensions-sdk": "11.0.2",
|
||||||
|
"vue": "^3.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
27
directus/extensions/acquisition/package.json
Normal file
27
directus/extensions/acquisition/package.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "acquisition",
|
||||||
|
"version": "1.8.0",
|
||||||
|
"type": "module",
|
||||||
|
"directus:extension": {
|
||||||
|
"type": "endpoint",
|
||||||
|
"path": "dist/index.js",
|
||||||
|
"source": "src/index.ts",
|
||||||
|
"host": "^11.0.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "node build.mjs",
|
||||||
|
"dev": "node build.mjs --watch"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@directus/extensions-sdk": "11.0.2",
|
||||||
|
"@mintel/acquisition": "workspace:*",
|
||||||
|
"@mintel/mail": "workspace:*",
|
||||||
|
"esbuild": "^0.25.0",
|
||||||
|
"typescript": "^5.6.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"jquery": "^3.7.1",
|
||||||
|
"react": "^19.2.4",
|
||||||
|
"react-dom": "^19.2.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
directus/extensions/customer-manager/index.js
Normal file
1
directus/extensions/customer-manager/index.js
Normal file
File diff suppressed because one or more lines are too long
30
directus/extensions/customer-manager/package.json
Normal file
30
directus/extensions/customer-manager/package.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "customer-manager",
|
||||||
|
"description": "Custom High-Fidelity Customer & Company Management for Directus",
|
||||||
|
"icon": "supervisor_account",
|
||||||
|
"version": "1.8.0",
|
||||||
|
"type": "module",
|
||||||
|
"keywords": [
|
||||||
|
"directus",
|
||||||
|
"directus-extension",
|
||||||
|
"directus-extension-module"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"directus:extension": {
|
||||||
|
"type": "module",
|
||||||
|
"path": "index.js",
|
||||||
|
"source": "src/index.ts",
|
||||||
|
"host": "*",
|
||||||
|
"name": "Customer Manager"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "directus-extension build && (cp -f dist/index.js index.js 2>/dev/null || true)",
|
||||||
|
"dev": "directus-extension build -w"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@directus/extensions-sdk": "11.0.2",
|
||||||
|
"vue": "^3.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
directus/extensions/feedback-commander/index.js
Normal file
1
directus/extensions/feedback-commander/index.js
Normal file
File diff suppressed because one or more lines are too long
30
directus/extensions/feedback-commander/package.json
Normal file
30
directus/extensions/feedback-commander/package.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "feedback-commander",
|
||||||
|
"description": "Custom High-Fidelity Feedback Management Extension for Directus",
|
||||||
|
"icon": "view_kanban",
|
||||||
|
"version": "1.8.0",
|
||||||
|
"type": "module",
|
||||||
|
"keywords": [
|
||||||
|
"directus",
|
||||||
|
"directus-extension",
|
||||||
|
"directus-extension-module"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"directus:extension": {
|
||||||
|
"type": "module",
|
||||||
|
"path": "index.js",
|
||||||
|
"source": "src/index.ts",
|
||||||
|
"host": "*",
|
||||||
|
"name": "Feedback Commander"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "directus-extension build && (cp -f dist/index.js index.js 2>/dev/null || true)",
|
||||||
|
"dev": "directus-extension build -w"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@directus/extensions-sdk": "11.0.2",
|
||||||
|
"vue": "^3.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
directus/extensions/people-manager/index.js
Normal file
1
directus/extensions/people-manager/index.js
Normal file
File diff suppressed because one or more lines are too long
30
directus/extensions/people-manager/package.json
Normal file
30
directus/extensions/people-manager/package.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "people-manager",
|
||||||
|
"description": "Custom High-Fidelity People Management for Directus",
|
||||||
|
"icon": "person",
|
||||||
|
"version": "1.8.0",
|
||||||
|
"type": "module",
|
||||||
|
"keywords": [
|
||||||
|
"directus",
|
||||||
|
"directus-extension",
|
||||||
|
"directus-extension-module"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"directus:extension": {
|
||||||
|
"type": "module",
|
||||||
|
"path": "index.js",
|
||||||
|
"source": "src/index.ts",
|
||||||
|
"host": "*",
|
||||||
|
"name": "People Manager"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "directus-extension build && (cp -f dist/index.js index.js 2>/dev/null || true)",
|
||||||
|
"dev": "directus-extension build -w"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@directus/extensions-sdk": "11.0.2",
|
||||||
|
"vue": "^3.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
directus/uploads/directus-health-file
Normal file
1
directus/uploads/directus-health-file
Normal file
@@ -0,0 +1 @@
|
|||||||
|
S9WsV
|
||||||
@@ -24,6 +24,12 @@ services:
|
|||||||
|
|
||||||
directus:
|
directus:
|
||||||
image: registry.infra.mintel.me/mintel/directus:${IMAGE_TAG:-latest}
|
image: registry.infra.mintel.me/mintel/directus:${IMAGE_TAG:-latest}
|
||||||
|
healthcheck:
|
||||||
|
test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8055/server/health" ]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 5s
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
networks:
|
||||||
- infra
|
- infra
|
||||||
@@ -35,7 +41,7 @@ services:
|
|||||||
ADMIN_EMAIL: ${DIRECTUS_ADMIN_EMAIL:-admin@mintel.me}
|
ADMIN_EMAIL: ${DIRECTUS_ADMIN_EMAIL:-admin@mintel.me}
|
||||||
ADMIN_PASSWORD: ${DIRECTUS_ADMIN_PASSWORD:-mintel-admin}
|
ADMIN_PASSWORD: ${DIRECTUS_ADMIN_PASSWORD:-mintel-admin}
|
||||||
DB_CLIENT: 'pg'
|
DB_CLIENT: 'pg'
|
||||||
DB_HOST: 'directus-db'
|
DB_HOST: 'at-mintel-directus-db'
|
||||||
DB_PORT: '5432'
|
DB_PORT: '5432'
|
||||||
DB_DATABASE: ${DIRECTUS_DB_NAME:-directus}
|
DB_DATABASE: ${DIRECTUS_DB_NAME:-directus}
|
||||||
DB_USER: ${DIRECTUS_DB_USER:-directus}
|
DB_USER: ${DIRECTUS_DB_USER:-directus}
|
||||||
@@ -53,7 +59,7 @@ services:
|
|||||||
- "traefik.http.routers.sample-website-directus.rule=Host(`${DIRECTUS_HOST:-cms.sample-website.localhost}`)"
|
- "traefik.http.routers.sample-website-directus.rule=Host(`${DIRECTUS_HOST:-cms.sample-website.localhost}`)"
|
||||||
- "traefik.http.services.sample-website-directus.loadbalancer.server.port=8055"
|
- "traefik.http.services.sample-website-directus.loadbalancer.server.port=8055"
|
||||||
|
|
||||||
directus-db:
|
at-mintel-directus-db:
|
||||||
image: postgres:15-alpine
|
image: postgres:15-alpine
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
"pino-pretty": "^13.1.3",
|
"pino-pretty": "^13.1.3",
|
||||||
"require-in-the-middle": "^8.0.1"
|
"require-in-the-middle": "^8.0.1"
|
||||||
},
|
},
|
||||||
"version": "1.7.12",
|
"version": "1.8.0",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"next": "16.1.6",
|
"next": "16.1.6",
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
import { build } from 'esbuild';
|
|
||||||
import { resolve, dirname } from 'path';
|
|
||||||
import { mkdirSync } from 'fs';
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
|
||||||
const __dirname = dirname(__filename);
|
|
||||||
|
|
||||||
const entryPoint = resolve(__dirname, 'src/index.ts');
|
|
||||||
const outfile = resolve(__dirname, 'dist/index.js');
|
|
||||||
|
|
||||||
try {
|
|
||||||
mkdirSync(dirname(outfile), { recursive: true });
|
|
||||||
} catch (e) { }
|
|
||||||
|
|
||||||
console.log(`Building from ${entryPoint} to ${outfile}...`);
|
|
||||||
|
|
||||||
build({
|
|
||||||
entryPoints: [entryPoint],
|
|
||||||
bundle: true,
|
|
||||||
platform: 'node',
|
|
||||||
target: 'node18',
|
|
||||||
outfile: outfile,
|
|
||||||
format: 'esm',
|
|
||||||
external: [],
|
|
||||||
plugins: [{
|
|
||||||
name: 'mock-jquery',
|
|
||||||
setup(build) {
|
|
||||||
build.onResolve({ filter: /^jquery$/ }, args => ({ path: args.path, namespace: 'mock-jquery' }));
|
|
||||||
build.onLoad({ filter: /.*/, namespace: 'mock-jquery' }, () => ({ contents: 'export default {};', loader: 'js' }));
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
name: 'mock-canvas',
|
|
||||||
setup(build) {
|
|
||||||
build.onResolve({ filter: /^canvas$/ }, args => ({ path: args.path, namespace: 'mock-canvas' }));
|
|
||||||
build.onLoad({ filter: /.*/, namespace: 'mock-canvas' }, () => ({ contents: 'export default {};', loader: 'js' }));
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}).then(() => {
|
|
||||||
console.log("Build succeeded!");
|
|
||||||
}).catch((e) => {
|
|
||||||
console.error("Build failed:", e);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@mintel/acquisition",
|
|
||||||
"version": "1.7.12",
|
|
||||||
"type": "module",
|
|
||||||
"main": "dist/index.js",
|
|
||||||
"module": "dist/index.js",
|
|
||||||
"scripts": {
|
|
||||||
"build": "node build.js",
|
|
||||||
"dev": "node build.js --watch"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@directus/extensions-sdk": "11.0.2",
|
|
||||||
"esbuild": "^0.25.0",
|
|
||||||
"typescript": "^5.6.3"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@mintel/mail": "workspace:*",
|
|
||||||
"axios": "^1.7.9",
|
|
||||||
"crawlee": "^3.12.2",
|
|
||||||
"cheerio": "^1.0.0",
|
|
||||||
"react": "^19.2.4",
|
|
||||||
"react-dom": "^19.2.4",
|
|
||||||
"@react-pdf/renderer": "^4.3.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,401 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import {
|
|
||||||
View as PDFView,
|
|
||||||
Text as PDFText,
|
|
||||||
StyleSheet,
|
|
||||||
Image as PDFImage,
|
|
||||||
} from "@react-pdf/renderer";
|
|
||||||
|
|
||||||
// INDUSTRIAL DESIGN SYSTEM TOKENS
|
|
||||||
export const COLORS = {
|
|
||||||
CHARCOAL: "#0f172a", // Slate 900
|
|
||||||
TEXT_MAIN: "#334155", // Slate 700
|
|
||||||
TEXT_DIM: "#64748b", // Slate 500
|
|
||||||
TEXT_LIGHT: "#94a3b8", // Slate 400
|
|
||||||
DIVIDER: "#cbd5e1", // Slate 300
|
|
||||||
GRID: "#f1f5f9", // Slate 100
|
|
||||||
BLUEPRINT: "#e2e8f0", // Slate 200
|
|
||||||
WHITE: "#ffffff",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FONT_SIZES = {
|
|
||||||
HERO: 24, // Main Page Titles
|
|
||||||
HEADING: 14, // Section Headers
|
|
||||||
BODY: 11, // Standard Content
|
|
||||||
LABEL: 10, // Bold Labels / Keys
|
|
||||||
SMALL: 9, // Descriptions / Footnotes
|
|
||||||
TINY: 8, // Metadata / Unit prices
|
|
||||||
};
|
|
||||||
|
|
||||||
export const pdfStyles = StyleSheet.create({
|
|
||||||
page: {
|
|
||||||
paddingTop: 45, // DIN 5008
|
|
||||||
paddingLeft: 70, // ~25mm
|
|
||||||
paddingRight: 57, // ~20mm
|
|
||||||
paddingBottom: 80, // Safe buffer for absolute footer
|
|
||||||
backgroundColor: COLORS.WHITE,
|
|
||||||
fontFamily: "Helvetica",
|
|
||||||
fontSize: FONT_SIZES.BODY,
|
|
||||||
color: COLORS.CHARCOAL,
|
|
||||||
},
|
|
||||||
titlePage: {
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
backgroundColor: COLORS.WHITE,
|
|
||||||
fontFamily: "Helvetica",
|
|
||||||
color: COLORS.CHARCOAL,
|
|
||||||
padding: 0,
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "flex-start",
|
|
||||||
marginBottom: 20,
|
|
||||||
minHeight: 120,
|
|
||||||
},
|
|
||||||
addressBlock: {
|
|
||||||
width: "55%",
|
|
||||||
marginTop: 45,
|
|
||||||
},
|
|
||||||
senderLine: {
|
|
||||||
fontSize: FONT_SIZES.TINY,
|
|
||||||
textDecoration: "underline",
|
|
||||||
color: COLORS.TEXT_DIM,
|
|
||||||
marginBottom: 8,
|
|
||||||
},
|
|
||||||
recipientAddress: {
|
|
||||||
fontSize: FONT_SIZES.BODY,
|
|
||||||
lineHeight: 1.4,
|
|
||||||
},
|
|
||||||
brandLogoContainer: {
|
|
||||||
width: "40%",
|
|
||||||
alignItems: "flex-end",
|
|
||||||
},
|
|
||||||
brandIconContainer: {
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
backgroundColor: "#0f172a",
|
|
||||||
borderRadius: 8,
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
marginBottom: 12,
|
|
||||||
},
|
|
||||||
brandIconText: {
|
|
||||||
color: COLORS.WHITE,
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: "bold",
|
|
||||||
},
|
|
||||||
titleInfo: {
|
|
||||||
marginBottom: 24,
|
|
||||||
},
|
|
||||||
mainTitle: {
|
|
||||||
fontSize: FONT_SIZES.HEADING,
|
|
||||||
fontWeight: "bold",
|
|
||||||
marginBottom: 4,
|
|
||||||
color: COLORS.CHARCOAL,
|
|
||||||
letterSpacing: 0.5,
|
|
||||||
},
|
|
||||||
subTitle: {
|
|
||||||
fontSize: FONT_SIZES.BODY,
|
|
||||||
color: COLORS.TEXT_DIM,
|
|
||||||
marginTop: 2,
|
|
||||||
lineHeight: 1.4,
|
|
||||||
},
|
|
||||||
section: {
|
|
||||||
marginBottom: 32,
|
|
||||||
},
|
|
||||||
sectionTitle: {
|
|
||||||
fontSize: FONT_SIZES.LABEL,
|
|
||||||
fontWeight: "bold",
|
|
||||||
textTransform: "uppercase",
|
|
||||||
letterSpacing: 1,
|
|
||||||
color: COLORS.TEXT_LIGHT,
|
|
||||||
marginBottom: 8,
|
|
||||||
},
|
|
||||||
footer: {
|
|
||||||
position: "absolute",
|
|
||||||
bottom: 32,
|
|
||||||
left: 70,
|
|
||||||
right: 57,
|
|
||||||
borderTopWidth: 1,
|
|
||||||
borderTopColor: COLORS.GRID,
|
|
||||||
paddingTop: 16,
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "flex-start",
|
|
||||||
},
|
|
||||||
footerColumn: {
|
|
||||||
flex: 1,
|
|
||||||
alignItems: "flex-start",
|
|
||||||
},
|
|
||||||
footerLogo: {
|
|
||||||
height: 20,
|
|
||||||
width: "auto",
|
|
||||||
objectFit: "contain",
|
|
||||||
marginBottom: 8,
|
|
||||||
},
|
|
||||||
footerText: {
|
|
||||||
fontSize: FONT_SIZES.TINY,
|
|
||||||
color: COLORS.TEXT_LIGHT,
|
|
||||||
lineHeight: 1.4,
|
|
||||||
},
|
|
||||||
asymmetryContainer: {
|
|
||||||
flexDirection: "row",
|
|
||||||
gap: 32,
|
|
||||||
},
|
|
||||||
asymmetryLeft: {
|
|
||||||
width: "32%",
|
|
||||||
},
|
|
||||||
asymmetryRight: {
|
|
||||||
width: "63%",
|
|
||||||
},
|
|
||||||
specRow: {
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
paddingVertical: 6,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: COLORS.GRID,
|
|
||||||
},
|
|
||||||
specLabel: {
|
|
||||||
fontSize: FONT_SIZES.TINY,
|
|
||||||
fontWeight: "bold",
|
|
||||||
color: COLORS.TEXT_LIGHT,
|
|
||||||
textTransform: "uppercase",
|
|
||||||
letterSpacing: 0.5,
|
|
||||||
},
|
|
||||||
specValue: {
|
|
||||||
fontSize: FONT_SIZES.SMALL,
|
|
||||||
color: COLORS.CHARCOAL,
|
|
||||||
fontWeight: "bold",
|
|
||||||
},
|
|
||||||
blueprintBox: {
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: COLORS.GRID,
|
|
||||||
padding: 16,
|
|
||||||
backgroundColor: "#fafafa",
|
|
||||||
},
|
|
||||||
footerLabel: {
|
|
||||||
fontWeight: "bold",
|
|
||||||
color: COLORS.TEXT_DIM,
|
|
||||||
},
|
|
||||||
pageNumber: {
|
|
||||||
fontSize: FONT_SIZES.TINY,
|
|
||||||
color: COLORS.DIVIDER,
|
|
||||||
fontWeight: "bold",
|
|
||||||
marginTop: 8,
|
|
||||||
textAlign: "right",
|
|
||||||
},
|
|
||||||
foldingMark: {
|
|
||||||
position: "absolute",
|
|
||||||
left: 20,
|
|
||||||
width: 10,
|
|
||||||
borderTopWidth: 0.5,
|
|
||||||
borderTopColor: COLORS.DIVIDER,
|
|
||||||
},
|
|
||||||
divider: {
|
|
||||||
width: "100%",
|
|
||||||
height: 1,
|
|
||||||
backgroundColor: COLORS.DIVIDER,
|
|
||||||
marginVertical: 12,
|
|
||||||
},
|
|
||||||
industrialListItem: {
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "flex-start",
|
|
||||||
marginBottom: 6,
|
|
||||||
},
|
|
||||||
industrialBulletBox: {
|
|
||||||
width: 6,
|
|
||||||
height: 6,
|
|
||||||
backgroundColor: COLORS.DIVIDER,
|
|
||||||
marginRight: 8,
|
|
||||||
marginTop: 5,
|
|
||||||
},
|
|
||||||
industrialTitle: {
|
|
||||||
fontSize: FONT_SIZES.HERO,
|
|
||||||
fontWeight: "bold",
|
|
||||||
color: COLORS.CHARCOAL,
|
|
||||||
marginBottom: 6,
|
|
||||||
letterSpacing: 0,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const IndustrialListItem = ({
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) => (
|
|
||||||
<PDFView style={pdfStyles.industrialListItem}>
|
|
||||||
<PDFView style={pdfStyles.industrialBulletBox} />
|
|
||||||
{children}
|
|
||||||
</PDFView>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Divider = ({ style = {} }: { style?: any }) => (
|
|
||||||
<PDFView style={[pdfStyles.divider, style]} />
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Footer = ({
|
|
||||||
logo,
|
|
||||||
companyData,
|
|
||||||
showDetails = true,
|
|
||||||
showPageNumber = true,
|
|
||||||
}: {
|
|
||||||
logo?: string;
|
|
||||||
companyData: any;
|
|
||||||
showDetails?: boolean;
|
|
||||||
showPageNumber?: boolean;
|
|
||||||
}) => (
|
|
||||||
<PDFView style={pdfStyles.footer}>
|
|
||||||
<PDFView style={pdfStyles.footerColumn}>
|
|
||||||
{logo ? (
|
|
||||||
<PDFImage src={logo} style={pdfStyles.footerLogo} />
|
|
||||||
) : (
|
|
||||||
<PDFText style={{ fontSize: 12, fontWeight: "bold", marginBottom: 8 }}>
|
|
||||||
marc mintel
|
|
||||||
</PDFText>
|
|
||||||
)}
|
|
||||||
</PDFView>
|
|
||||||
{showDetails && (
|
|
||||||
<>
|
|
||||||
<PDFView style={pdfStyles.footerColumn}>
|
|
||||||
<PDFText style={pdfStyles.footerText}>
|
|
||||||
<PDFText style={pdfStyles.footerLabel}>{companyData.name}</PDFText>
|
|
||||||
{"\n"}
|
|
||||||
{companyData.address1}
|
|
||||||
{"\n"}
|
|
||||||
{companyData.address2}
|
|
||||||
{"\n"}UST: {companyData.ustId}
|
|
||||||
</PDFText>
|
|
||||||
</PDFView>
|
|
||||||
<PDFView style={[pdfStyles.footerColumn, { alignItems: "flex-end" }]}>
|
|
||||||
{showPageNumber && (
|
|
||||||
<PDFText
|
|
||||||
style={pdfStyles.pageNumber}
|
|
||||||
render={({ pageNumber, totalPages }) =>
|
|
||||||
`${pageNumber} / ${totalPages}`
|
|
||||||
}
|
|
||||||
fixed
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</PDFView>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</PDFView>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Header = ({
|
|
||||||
sender,
|
|
||||||
recipient,
|
|
||||||
icon,
|
|
||||||
showAddress = true,
|
|
||||||
}: {
|
|
||||||
sender?: string;
|
|
||||||
recipient?: {
|
|
||||||
title: string;
|
|
||||||
subtitle?: string;
|
|
||||||
email?: string;
|
|
||||||
address?: string;
|
|
||||||
phone?: string;
|
|
||||||
taxId?: string;
|
|
||||||
};
|
|
||||||
icon?: string;
|
|
||||||
showAddress?: boolean;
|
|
||||||
}) => (
|
|
||||||
<PDFView
|
|
||||||
style={[
|
|
||||||
pdfStyles.header,
|
|
||||||
showAddress ? {} : { minHeight: 40, marginBottom: 0 },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<PDFView style={pdfStyles.addressBlock}>
|
|
||||||
{showAddress && sender && (
|
|
||||||
<>
|
|
||||||
<PDFText style={pdfStyles.senderLine}>{sender}</PDFText>
|
|
||||||
{recipient && (
|
|
||||||
<PDFView style={pdfStyles.recipientAddress}>
|
|
||||||
<PDFText style={{ fontWeight: "bold" }}>
|
|
||||||
{recipient.title}
|
|
||||||
</PDFText>
|
|
||||||
{recipient.subtitle && <PDFText>{recipient.subtitle}</PDFText>}
|
|
||||||
{recipient.address && <PDFText>{recipient.address}</PDFText>}
|
|
||||||
{recipient.phone && <PDFText>{recipient.phone}</PDFText>}
|
|
||||||
{recipient.email && <PDFText>{recipient.email}</PDFText>}
|
|
||||||
{recipient.taxId && <PDFText>USt-ID: {recipient.taxId}</PDFText>}
|
|
||||||
</PDFView>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</PDFView>
|
|
||||||
<PDFView style={pdfStyles.brandLogoContainer}>
|
|
||||||
<PDFView style={pdfStyles.brandIconContainer}>
|
|
||||||
{icon ? (
|
|
||||||
<PDFImage src={icon} style={{ width: 24, height: 24 }} />
|
|
||||||
) : (
|
|
||||||
<PDFText style={pdfStyles.brandIconText}>M</PDFText>
|
|
||||||
)}
|
|
||||||
</PDFView>
|
|
||||||
</PDFView>
|
|
||||||
</PDFView>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const DocumentTitle = ({
|
|
||||||
title,
|
|
||||||
subLines,
|
|
||||||
isHero = false,
|
|
||||||
}: {
|
|
||||||
title: string;
|
|
||||||
subLines?: string[];
|
|
||||||
isHero?: boolean;
|
|
||||||
}) => (
|
|
||||||
<PDFView style={pdfStyles.titleInfo}>
|
|
||||||
<PDFText
|
|
||||||
style={[
|
|
||||||
pdfStyles.mainTitle,
|
|
||||||
{ fontSize: isHero ? FONT_SIZES.HERO : FONT_SIZES.HEADING },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</PDFText>
|
|
||||||
{subLines?.map((line, i) => (
|
|
||||||
<PDFText
|
|
||||||
key={i}
|
|
||||||
style={[
|
|
||||||
pdfStyles.subTitle,
|
|
||||||
i === 1 ? { fontWeight: "bold", color: COLORS.CHARCOAL } : {},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{line}
|
|
||||||
</PDFText>
|
|
||||||
))}
|
|
||||||
</PDFView>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const TechnicalSpec = ({
|
|
||||||
label,
|
|
||||||
value,
|
|
||||||
}: {
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
}) => (
|
|
||||||
<PDFView style={pdfStyles.specRow}>
|
|
||||||
<PDFText style={pdfStyles.specLabel}>{label}</PDFText>
|
|
||||||
<PDFText style={pdfStyles.specValue}>{value}</PDFText>
|
|
||||||
</PDFView>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const AsymmetryView = ({
|
|
||||||
left,
|
|
||||||
right,
|
|
||||||
style = {},
|
|
||||||
}: {
|
|
||||||
left: React.ReactNode;
|
|
||||||
right: React.ReactNode;
|
|
||||||
style?: any;
|
|
||||||
}) => (
|
|
||||||
<PDFView style={[pdfStyles.asymmetryContainer, style]}>
|
|
||||||
<PDFView style={pdfStyles.asymmetryLeft}>{left}</PDFView>
|
|
||||||
<PDFView style={pdfStyles.asymmetryRight}>{right}</PDFView>
|
|
||||||
</PDFView>
|
|
||||||
);
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import {
|
|
||||||
View as PDFView,
|
|
||||||
Text as PDFText,
|
|
||||||
StyleSheet,
|
|
||||||
} from "@react-pdf/renderer";
|
|
||||||
import { DocumentTitle, COLORS, FONT_SIZES } from "../SharedUI.js";
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
section: { marginBottom: 24 },
|
|
||||||
sectionTitle: {
|
|
||||||
fontSize: FONT_SIZES.LABEL,
|
|
||||||
fontWeight: "bold",
|
|
||||||
marginBottom: 8,
|
|
||||||
color: COLORS.CHARCOAL,
|
|
||||||
},
|
|
||||||
visionText: {
|
|
||||||
fontSize: FONT_SIZES.BODY,
|
|
||||||
color: COLORS.TEXT_MAIN,
|
|
||||||
lineHeight: 1.4,
|
|
||||||
textAlign: "justify",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const BriefingModule = ({ state }: any) => (
|
|
||||||
<>
|
|
||||||
<DocumentTitle title="Projektdetails" isHero={true} />
|
|
||||||
{state.briefingSummary && (
|
|
||||||
<PDFView style={styles.section}>
|
|
||||||
<PDFText style={styles.sectionTitle}>Briefing Analyse</PDFText>
|
|
||||||
<PDFText
|
|
||||||
style={{
|
|
||||||
fontSize: FONT_SIZES.BODY,
|
|
||||||
color: COLORS.TEXT_MAIN,
|
|
||||||
lineHeight: 1.6,
|
|
||||||
textAlign: "justify",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{state.briefingSummary}
|
|
||||||
</PDFText>
|
|
||||||
</PDFView>
|
|
||||||
)}
|
|
||||||
{state.designVision && (
|
|
||||||
<PDFView
|
|
||||||
style={[
|
|
||||||
styles.section,
|
|
||||||
{
|
|
||||||
padding: 12,
|
|
||||||
borderLeftWidth: 2,
|
|
||||||
borderLeftColor: COLORS.DIVIDER,
|
|
||||||
backgroundColor: COLORS.GRID,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<PDFText
|
|
||||||
style={[
|
|
||||||
styles.sectionTitle,
|
|
||||||
{ color: COLORS.CHARCOAL, marginBottom: 4 },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
Strategische Vision
|
|
||||||
</PDFText>
|
|
||||||
<PDFText style={styles.visionText}>{state.designVision}</PDFText>
|
|
||||||
</PDFView>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import { View as PDFView, Text as PDFText, StyleSheet } from "@react-pdf/renderer";
|
|
||||||
import { DocumentTitle, COLORS, FONT_SIZES, IndustrialListItem } from "../SharedUI.js";
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
section: { marginBottom: 24 },
|
|
||||||
categoryBox: {
|
|
||||||
marginBottom: 20,
|
|
||||||
padding: 12,
|
|
||||||
backgroundColor: COLORS.GRID,
|
|
||||||
borderLeftWidth: 2,
|
|
||||||
borderLeftColor: COLORS.DIVIDER,
|
|
||||||
},
|
|
||||||
categoryTitle: {
|
|
||||||
fontSize: FONT_SIZES.TINY,
|
|
||||||
fontWeight: "bold",
|
|
||||||
color: COLORS.TEXT_LIGHT,
|
|
||||||
textTransform: "uppercase",
|
|
||||||
marginBottom: 10,
|
|
||||||
letterSpacing: 1,
|
|
||||||
},
|
|
||||||
pageTitle: {
|
|
||||||
fontSize: FONT_SIZES.LABEL,
|
|
||||||
fontWeight: "bold",
|
|
||||||
color: COLORS.CHARCOAL,
|
|
||||||
marginBottom: 2,
|
|
||||||
},
|
|
||||||
pageDesc: {
|
|
||||||
fontSize: FONT_SIZES.TINY,
|
|
||||||
color: COLORS.TEXT_DIM,
|
|
||||||
lineHeight: 1.4,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const SitemapModule = ({ state }: any) => (
|
|
||||||
<>
|
|
||||||
<DocumentTitle title="Informations-Architektur" isHero={true} />
|
|
||||||
<PDFView style={styles.section}>
|
|
||||||
{state.sitemap?.map((cat: any, i: number) => (
|
|
||||||
<PDFView key={i} style={styles.categoryBox}>
|
|
||||||
<PDFText style={styles.categoryTitle}>{cat.category}</PDFText>
|
|
||||||
{cat.pages?.map((p: any, j: number) => (
|
|
||||||
<IndustrialListItem key={j}>
|
|
||||||
<PDFView style={{ marginBottom: 8 }}>
|
|
||||||
<PDFText style={styles.pageTitle}>{p.title}</PDFText>
|
|
||||||
<PDFText style={styles.pageDesc}>{p.desc}</PDFText>
|
|
||||||
</PDFView>
|
|
||||||
</IndustrialListItem>
|
|
||||||
))}
|
|
||||||
</PDFView>
|
|
||||||
))}
|
|
||||||
</PDFView>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export * from "./logic/pricing/types.js";
|
|
||||||
export * from "./logic/pricing/constants.js";
|
|
||||||
export * from "./logic/pricing/calculator.js";
|
|
||||||
export * from "./services/AcquisitionService.js";
|
|
||||||
export * from "./services/PdfEngine.js";
|
|
||||||
export * from "./components/EstimationPDF.js";
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { fileURLToPath } from 'url';
|
|
||||||
import { dirname } from 'path';
|
|
||||||
import { createRequire } from 'module';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const url = import.meta?.url;
|
|
||||||
// Hardcode fallback path for Directus Docker environment
|
|
||||||
const fallbackPath = '/directus/extensions/acquisition/dist/index.js';
|
|
||||||
const filename = url ? fileURLToPath(url) : fallbackPath;
|
|
||||||
const dir = dirname(filename);
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
globalThis.__filename = filename;
|
|
||||||
// @ts-ignore
|
|
||||||
globalThis.__dirname = dir;
|
|
||||||
// @ts-ignore
|
|
||||||
globalThis.require = createRequire(url || `file://${fallbackPath}`);
|
|
||||||
|
|
||||||
console.log(`[Shim] Loaded. __dirname: ${dir}`);
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("[Shim] Failed to shim __dirname/require", e);
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@
|
|||||||
"name": "acquisition-manager",
|
"name": "acquisition-manager",
|
||||||
"description": "Custom High-Fidelity Acquisition Management for Directus",
|
"description": "Custom High-Fidelity Acquisition Management for Directus",
|
||||||
"icon": "account_balance_wallet",
|
"icon": "account_balance_wallet",
|
||||||
"version": "1.7.12",
|
"version": "1.8.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"directus",
|
"directus",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<v-icon :name="getStatusIcon(lead.status)" :color="getStatusColor(lead.status)" />
|
<v-icon :name="getStatusIcon(lead.status)" :color="getStatusColor(lead.status)" />
|
||||||
</v-list-item-icon>
|
</v-list-item-icon>
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
<v-text-overflow :text="lead.company_name" />
|
<v-text-overflow :text="getCompanyName(lead)" />
|
||||||
</v-list-item-content>
|
</v-list-item-content>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
<template v-else>
|
<template v-else>
|
||||||
<header class="header">
|
<header class="header">
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
<h1 class="title">{{ selectedLead.company_name }}</h1>
|
<h1 class="title">{{ getCompanyName(selectedLead) }}</h1>
|
||||||
<p class="subtitle">
|
<p class="subtitle">
|
||||||
<v-icon name="language" x-small />
|
<v-icon name="language" x-small />
|
||||||
<a :href="selectedLead.website_url" target="_blank" class="url-link">
|
<a :href="selectedLead.website_url" target="_blank" class="url-link">
|
||||||
@@ -156,7 +156,15 @@
|
|||||||
<div class="drawer-content">
|
<div class="drawer-content">
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<span class="label">Firma</span>
|
<span class="label">Organisation / Firma (Zentral)</span>
|
||||||
|
<v-select
|
||||||
|
v-model="newLead.company"
|
||||||
|
:items="companyOptions"
|
||||||
|
placeholder="Bestehende Firma auswählen..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<span class="label">Organisation / Firma (Legacy / Neu)</span>
|
||||||
<v-input v-model="newLead.company_name" placeholder="z.B. Schmidt GmbH" autofocus />
|
<v-input v-model="newLead.company_name" placeholder="z.B. Schmidt GmbH" autofocus />
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
@@ -208,17 +216,26 @@ const savingLead = ref(false);
|
|||||||
const notice = ref<{ type: string; message: string } | null>(null);
|
const notice = ref<{ type: string; message: string } | null>(null);
|
||||||
|
|
||||||
const newLead = ref({
|
const newLead = ref({
|
||||||
company_name: '',
|
company_name: '', // Legacy
|
||||||
|
company: null,
|
||||||
website_url: '',
|
website_url: '',
|
||||||
contact_name: '',
|
contact_name: '', // Legacy
|
||||||
contact_email: '',
|
contact_email: '', // Legacy
|
||||||
contact_person: null,
|
contact_person: null,
|
||||||
briefing: '',
|
briefing: '',
|
||||||
status: 'new'
|
status: 'new'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const companies = ref<any[]>([]);
|
||||||
const people = ref<any[]>([]);
|
const people = ref<any[]>([]);
|
||||||
|
|
||||||
|
const companyOptions = computed(() =>
|
||||||
|
companies.value.map(c => ({
|
||||||
|
text: c.name,
|
||||||
|
value: c.id
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
const peopleOptions = computed(() =>
|
const peopleOptions = computed(() =>
|
||||||
people.value.map(p => ({
|
people.value.map(p => ({
|
||||||
text: `${p.first_name} ${p.last_name}`,
|
text: `${p.first_name} ${p.last_name}`,
|
||||||
@@ -226,7 +243,16 @@ const peopleOptions = computed(() =>
|
|||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
function getPersonName(id: string) {
|
function getCompanyName(lead: any) {
|
||||||
|
if (lead.company) {
|
||||||
|
return typeof lead.company === 'object' ? lead.company.name : (companies.value.find(c => c.id === lead.company)?.name || lead.company_name);
|
||||||
|
}
|
||||||
|
return lead.company_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPersonName(id: string | any) {
|
||||||
|
if (!id) return '';
|
||||||
|
if (typeof id === 'object') return `${id.first_name} ${id.last_name}`;
|
||||||
const person = people.value.find(p => p.id === id);
|
const person = people.value.find(p => p.id === id);
|
||||||
return person ? `${person.first_name} ${person.last_name}` : id;
|
return person ? `${person.first_name} ${person.last_name}` : id;
|
||||||
}
|
}
|
||||||
@@ -238,20 +264,32 @@ function goToPerson(id: string) {
|
|||||||
|
|
||||||
const selectedLead = computed(() => leads.value.find(l => l.id === selectedLeadId.value));
|
const selectedLead = computed(() => leads.value.find(l => l.id === selectedLeadId.value));
|
||||||
|
|
||||||
onMounted(fetchLeads);
|
onMounted(fetchData);
|
||||||
|
|
||||||
async function fetchLeads() {
|
async function fetchData() {
|
||||||
const [leadsResp, peopleResp] = await Promise.all([
|
const [leadsResp, peopleResp, companiesResp] = await Promise.all([
|
||||||
api.get('/items/leads', { params: { sort: '-date_created' } }),
|
api.get('/items/leads', {
|
||||||
api.get('/items/people', { params: { sort: 'last_name' } })
|
params: {
|
||||||
|
sort: '-date_created',
|
||||||
|
fields: '*.*'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
api.get('/items/people', { params: { sort: 'last_name' } }),
|
||||||
|
api.get('/items/companies', { params: { sort: 'name' } })
|
||||||
]);
|
]);
|
||||||
leads.value = leadsResp.data.data;
|
leads.value = leadsResp.data.data;
|
||||||
people.value = peopleResp.data.data;
|
people.value = peopleResp.data.data;
|
||||||
|
companies.value = companiesResp.data.data;
|
||||||
|
|
||||||
if (!selectedLeadId.value && leads.value.length > 0) {
|
if (!selectedLeadId.value && leads.value.length > 0) {
|
||||||
selectedLeadId.value = leads.value[0].id;
|
selectedLeadId.value = leads.value[0].id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchLeads() {
|
||||||
|
await fetchData();
|
||||||
|
}
|
||||||
|
|
||||||
function selectLead(id: string) {
|
function selectLead(id: string) {
|
||||||
selectedLeadId.value = id;
|
selectedLeadId.value = id;
|
||||||
}
|
}
|
||||||
@@ -318,7 +356,10 @@ function openPdf() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function saveLead() {
|
async function saveLead() {
|
||||||
if (!newLead.value.company_name) return;
|
if (!newLead.value.company_name && !newLead.value.company) {
|
||||||
|
notice.value = { type: 'danger', message: 'Firma oder Firmenname erforderlich.' };
|
||||||
|
return;
|
||||||
|
}
|
||||||
savingLead.value = true;
|
savingLead.value = true;
|
||||||
try {
|
try {
|
||||||
const payload = {
|
const payload = {
|
||||||
@@ -332,6 +373,7 @@ async function saveLead() {
|
|||||||
selectedLeadId.value = payload.id;
|
selectedLeadId.value = payload.id;
|
||||||
newLead.value = {
|
newLead.value = {
|
||||||
company_name: '',
|
company_name: '',
|
||||||
|
company: null,
|
||||||
website_url: '',
|
website_url: '',
|
||||||
contact_name: '',
|
contact_name: '',
|
||||||
contact_email: '',
|
contact_email: '',
|
||||||
|
|||||||
@@ -21,25 +21,24 @@ build({
|
|||||||
platform: 'node',
|
platform: 'node',
|
||||||
target: 'node18',
|
target: 'node18',
|
||||||
outfile: outfile,
|
outfile: outfile,
|
||||||
format: 'esm',
|
jsx: 'automatic',
|
||||||
// Bundle everything, including Directus SDK, to avoid resolution issues in Docker
|
loader: {
|
||||||
external: [],
|
'.tsx': 'tsx',
|
||||||
|
'.ts': 'ts',
|
||||||
|
'.js': 'js',
|
||||||
|
},
|
||||||
|
external: ["@react-pdf/renderer", "react", "react-dom", "jsdom", "jsdom/*", "jquery", "jquery/*", "canvas", "fs", "path", "os", "http", "https", "zlib", "stream", "util", "url", "net", "tls", "crypto"],
|
||||||
plugins: [{
|
plugins: [{
|
||||||
name: 'mock-jquery',
|
|
||||||
setup(build) {
|
|
||||||
build.onResolve({ filter: /^jquery$/ }, args => ({ path: args.path, namespace: 'mock-jquery' }));
|
|
||||||
build.onLoad({ filter: /.*/, namespace: 'mock-jquery' }, () => ({ contents: 'export default {};', loader: 'js' }));
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
name: 'mock-canvas',
|
name: 'mock-canvas',
|
||||||
setup(build) {
|
setup(build) {
|
||||||
build.onResolve({ filter: /^canvas$/ }, args => ({ path: args.path, namespace: 'mock-canvas' }));
|
build.onResolve({ filter: /^canvas/ }, args => ({ path: args.path, namespace: 'mock-canvas' }));
|
||||||
build.onLoad({ filter: /.*/, namespace: 'mock-canvas' }, () => ({ contents: 'export default {};', loader: 'js' }));
|
build.onLoad({ filter: /.*/, namespace: 'mock-canvas' }, () => ({ contents: 'export default {};', loader: 'js' }));
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
name: 'mock-jsdom',
|
name: 'mock-jsdom',
|
||||||
setup(build) {
|
setup(build) {
|
||||||
return;
|
build.onResolve({ filter: /^jsdom/ }, args => ({ path: args.path, namespace: 'mock-jsdom' }));
|
||||||
|
build.onLoad({ filter: /.*/, namespace: 'mock-jsdom' }, () => ({ contents: 'export default {};', loader: 'js' }));
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "acquisition",
|
"name": "acquisition",
|
||||||
"version": "1.7.12",
|
"version": "1.8.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"directus:extension": {
|
"directus:extension": {
|
||||||
"type": "endpoint",
|
"type": "endpoint",
|
||||||
@@ -9,12 +9,12 @@
|
|||||||
"host": "^11.0.0"
|
"host": "^11.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node build.js",
|
"build": "node build.mjs",
|
||||||
"dev": "node build.js --watch"
|
"dev": "node build.mjs --watch"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@directus/extensions-sdk": "11.0.2",
|
"@directus/extensions-sdk": "11.0.2",
|
||||||
"@mintel/acquisition": "workspace:*",
|
"@mintel/pdf": "workspace:*",
|
||||||
"@mintel/mail": "workspace:*",
|
"@mintel/mail": "workspace:*",
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"typescript": "^5.6.3"
|
"typescript": "^5.6.3"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import "./shim";
|
|
||||||
import { defineEndpoint } from "@directus/extensions-sdk";
|
import { defineEndpoint } from "@directus/extensions-sdk";
|
||||||
import { AcquisitionService, PdfEngine } from "../../acquisition-library/src/index";
|
import { AcquisitionService, PdfEngine } from "@mintel/pdf/server";
|
||||||
import { render, SiteAuditTemplate, ProjectEstimateTemplate } from "@mintel/mail";
|
import { render, SiteAuditTemplate, ProjectEstimateTemplate } from "@mintel/mail";
|
||||||
import { createElement } from "react";
|
import { createElement } from "react";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
@@ -40,22 +39,25 @@ export default defineEndpoint((router, { services, env }) => {
|
|||||||
|
|
||||||
router.post("/audit-email/:id", async (req: any, res: any) => {
|
router.post("/audit-email/:id", async (req: any, res: any) => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
const { ItemsService, MailService } = services;
|
||||||
const leadsService = new ItemsService("leads", { schema: req.schema, accountability: req.accountability });
|
const leadsService = new ItemsService("leads", { schema: req.schema, accountability: req.accountability });
|
||||||
const peopleService = new ItemsService("people", { schema: req.schema, accountability: req.accountability });
|
const peopleService = new ItemsService("people", { schema: req.schema, accountability: req.accountability });
|
||||||
|
const companiesService = new ItemsService("companies", { schema: req.schema, accountability: req.accountability });
|
||||||
const mailService = new MailService({ schema: req.schema, accountability: req.accountability });
|
const mailService = new MailService({ schema: req.schema, accountability: req.accountability });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const lead = await leadsService.readOne(id);
|
const lead = await leadsService.readOne(id, { fields: ["*", "company.*", "contact_person.*"] });
|
||||||
if (!lead || !lead.ai_state) return res.status(400).send({ error: "Lead or Audit not ready" });
|
if (!lead || !lead.ai_state) return res.status(400).send({ error: "Lead or Audit not ready" });
|
||||||
|
|
||||||
let recipientEmail = lead.contact_email;
|
let recipientEmail = lead.contact_email;
|
||||||
let companyName = lead.company_name;
|
let companyName = lead.company?.name || lead.company_name;
|
||||||
|
|
||||||
if (lead.contact_person) {
|
if (lead.contact_person) {
|
||||||
const person = await peopleService.readOne(lead.contact_person);
|
recipientEmail = lead.contact_person.email || recipientEmail;
|
||||||
if (person && person.email) {
|
|
||||||
recipientEmail = person.email;
|
if (lead.contact_person.company) {
|
||||||
companyName = person.company || lead.company_name;
|
const personCompany = await companiesService.readOne(lead.contact_person.company);
|
||||||
|
companyName = personCompany?.name || companyName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,20 +122,22 @@ export default defineEndpoint((router, { services, env }) => {
|
|||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const leadsService = new ItemsService("leads", { schema: req.schema, accountability: req.accountability });
|
const leadsService = new ItemsService("leads", { schema: req.schema, accountability: req.accountability });
|
||||||
const peopleService = new ItemsService("people", { schema: req.schema, accountability: req.accountability });
|
const peopleService = new ItemsService("people", { schema: req.schema, accountability: req.accountability });
|
||||||
|
const companiesService = new ItemsService("companies", { schema: req.schema, accountability: req.accountability });
|
||||||
const mailService = new MailService({ schema: req.schema, accountability: req.accountability });
|
const mailService = new MailService({ schema: req.schema, accountability: req.accountability });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const lead = await leadsService.readOne(id);
|
const lead = await leadsService.readOne(id, { fields: ["*", "company.*", "contact_person.*"] });
|
||||||
if (!lead || !lead.audit_pdf_path) return res.status(400).send({ error: "PDF not generated" });
|
if (!lead || !lead.audit_pdf_path) return res.status(400).send({ error: "PDF not generated" });
|
||||||
|
|
||||||
let recipientEmail = lead.contact_email;
|
let recipientEmail = lead.contact_email;
|
||||||
let companyName = lead.company_name;
|
let companyName = lead.company?.name || lead.company_name;
|
||||||
|
|
||||||
if (lead.contact_person) {
|
if (lead.contact_person) {
|
||||||
const person = await peopleService.readOne(lead.contact_person);
|
recipientEmail = lead.contact_person.email || recipientEmail;
|
||||||
if (person && person.email) {
|
|
||||||
recipientEmail = person.email;
|
if (lead.contact_person.company) {
|
||||||
companyName = person.company || lead.company_name;
|
const personCompany = await companiesService.readOne(lead.contact_person.company);
|
||||||
|
companyName = personCompany?.name || companyName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import { fileURLToPath } from 'url';
|
|
||||||
import { dirname } from 'path';
|
|
||||||
import { createRequire } from 'module';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const url = import.meta?.url;
|
|
||||||
// Hardcode fallback path for Directus Docker environment
|
|
||||||
const fallbackPath = '/directus/extensions/acquisition/dist/index.js';
|
|
||||||
const filename = url ? fileURLToPath(url) : fallbackPath;
|
|
||||||
const dir = dirname(filename);
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
globalThis.__filename = filename;
|
|
||||||
// @ts-ignore
|
|
||||||
globalThis.__dirname = dir;
|
|
||||||
// @ts-ignore
|
|
||||||
globalThis.require = createRequire(url || `file://${fallbackPath}`);
|
|
||||||
|
|
||||||
console.log(`[Shim] Loaded. __dirname: ${dir}`);
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("[Shim] Failed to shim __dirname/require", e);
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mintel/cli",
|
"name": "@mintel/cli",
|
||||||
"version": "1.7.12",
|
"version": "1.8.0",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"registry": "https://npm.infra.mintel.me"
|
"registry": "https://npm.infra.mintel.me"
|
||||||
|
|||||||
41
packages/cloner-library/build.mjs
Normal file
41
packages/cloner-library/build.mjs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { build } from 'esbuild';
|
||||||
|
import { resolve, dirname } from 'path';
|
||||||
|
import { mkdirSync } from 'fs';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
const entryPoints = [
|
||||||
|
resolve(__dirname, 'src/index.ts')
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
mkdirSync(resolve(__dirname, 'dist'), { recursive: true });
|
||||||
|
} catch (e) { }
|
||||||
|
|
||||||
|
console.log(`Building entry point...`);
|
||||||
|
|
||||||
|
build({
|
||||||
|
entryPoints: entryPoints,
|
||||||
|
bundle: true,
|
||||||
|
platform: 'node',
|
||||||
|
target: 'node18',
|
||||||
|
outdir: resolve(__dirname, 'dist'),
|
||||||
|
format: 'esm',
|
||||||
|
loader: {
|
||||||
|
'.ts': 'ts',
|
||||||
|
'.js': 'js',
|
||||||
|
},
|
||||||
|
external: ["playwright", "crawlee", "axios", "cheerio", "fs", "path", "os", "http", "https", "url", "stream", "util", "child_process"],
|
||||||
|
}).then(() => {
|
||||||
|
console.log("Build succeeded!");
|
||||||
|
}).catch((e) => {
|
||||||
|
if (e.errors) {
|
||||||
|
console.error("Build failed with errors:");
|
||||||
|
e.errors.forEach(err => console.error(` ${err.text} at ${err.location?.file}:${err.location?.line}`));
|
||||||
|
} else {
|
||||||
|
console.error("Build failed:", e);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
30
packages/cloner-library/package.json
Normal file
30
packages/cloner-library/package.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "@mintel/cloner",
|
||||||
|
"version": "1.8.0",
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"module": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "node build.mjs",
|
||||||
|
"dev": "node build.mjs --watch"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"esbuild": "^0.25.0",
|
||||||
|
"typescript": "^5.6.3",
|
||||||
|
"@types/node": "^22.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"playwright": "^1.40.0",
|
||||||
|
"crawlee": "^3.7.0",
|
||||||
|
"axios": "^1.6.0",
|
||||||
|
"cheerio": "^1.0.0-rc.12"
|
||||||
|
}
|
||||||
|
}
|
||||||
93
packages/cloner-library/src/AssetManager.ts
Normal file
93
packages/cloner-library/src/AssetManager.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
export interface AssetMap {
|
||||||
|
[originalUrl: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AssetManager {
|
||||||
|
private userAgent: string;
|
||||||
|
|
||||||
|
constructor(userAgent: string = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36") {
|
||||||
|
this.userAgent = userAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sanitizePath(rawPath: string): string {
|
||||||
|
return rawPath
|
||||||
|
.split("/")
|
||||||
|
.map((p) => p.replace(/[^a-z0-9._-]/gi, "_"))
|
||||||
|
.join("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async downloadFile(url: string, assetsDir: string): Promise<string | null> {
|
||||||
|
if (url.startsWith("//")) url = `https:${url}`;
|
||||||
|
if (!url.startsWith("http")) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const u = new URL(url);
|
||||||
|
const relPath = this.sanitizePath(u.hostname + u.pathname);
|
||||||
|
const dest = path.join(assetsDir, relPath);
|
||||||
|
|
||||||
|
if (fs.existsSync(dest)) return `./assets/${relPath}`;
|
||||||
|
|
||||||
|
const res = await axios.get(url, {
|
||||||
|
responseType: "arraybuffer",
|
||||||
|
headers: { "User-Agent": this.userAgent },
|
||||||
|
timeout: 15000,
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status !== 200) return null;
|
||||||
|
|
||||||
|
if (!fs.existsSync(path.dirname(dest)))
|
||||||
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
||||||
|
fs.writeFileSync(dest, Buffer.from(res.data));
|
||||||
|
return `./assets/${relPath}`;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async processCssRecursively(
|
||||||
|
cssContent: string,
|
||||||
|
cssUrl: string,
|
||||||
|
assetsDir: string,
|
||||||
|
urlMap: AssetMap,
|
||||||
|
depth = 0,
|
||||||
|
): Promise<string> {
|
||||||
|
if (depth > 5) return cssContent;
|
||||||
|
|
||||||
|
const urlRegex = /(?:url\(["']?|@import\s+["'])([^"'\)]+)["']?\)?/gi;
|
||||||
|
let match;
|
||||||
|
let newContent = cssContent;
|
||||||
|
|
||||||
|
while ((match = urlRegex.exec(cssContent)) !== null) {
|
||||||
|
const originalUrl = match[1];
|
||||||
|
if (originalUrl.startsWith("data:") || originalUrl.startsWith("blob:"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const absUrl = new URL(originalUrl, cssUrl).href;
|
||||||
|
const local = await this.downloadFile(absUrl, assetsDir);
|
||||||
|
|
||||||
|
if (local) {
|
||||||
|
const u = new URL(cssUrl);
|
||||||
|
const cssPath = u.hostname + u.pathname;
|
||||||
|
const assetPath = new URL(absUrl).hostname + new URL(absUrl).pathname;
|
||||||
|
|
||||||
|
const rel = path.relative(
|
||||||
|
path.dirname(this.sanitizePath(cssPath)),
|
||||||
|
this.sanitizePath(assetPath),
|
||||||
|
);
|
||||||
|
|
||||||
|
newContent = newContent.split(originalUrl).join(rel);
|
||||||
|
urlMap[absUrl] = local;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
184
packages/cloner-library/src/PageCloner.ts
Normal file
184
packages/cloner-library/src/PageCloner.ts
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
import { chromium, Browser, BrowserContext, Page } from "playwright";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import axios from "axios";
|
||||||
|
import { AssetManager, AssetMap } from "./AssetManager.js";
|
||||||
|
|
||||||
|
export interface PageClonerOptions {
|
||||||
|
outputDir: string;
|
||||||
|
userAgent?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PageCloner {
|
||||||
|
private options: PageClonerOptions;
|
||||||
|
private assetManager: AssetManager;
|
||||||
|
private userAgent: string;
|
||||||
|
|
||||||
|
constructor(options: PageClonerOptions) {
|
||||||
|
this.options = options;
|
||||||
|
this.userAgent = options.userAgent || "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36";
|
||||||
|
this.assetManager = new AssetManager(this.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async clone(targetUrl: string): Promise<string> {
|
||||||
|
const urlObj = new URL(targetUrl);
|
||||||
|
const domainSlug = urlObj.hostname.replace("www.", "");
|
||||||
|
const domainDir = path.resolve(this.options.outputDir, domainSlug);
|
||||||
|
const assetsDir = path.join(domainDir, "assets");
|
||||||
|
|
||||||
|
if (!fs.existsSync(assetsDir)) fs.mkdirSync(assetsDir, { recursive: true });
|
||||||
|
|
||||||
|
let pageSlug = urlObj.pathname.split("/").filter(Boolean).join("-");
|
||||||
|
if (!pageSlug) pageSlug = "index";
|
||||||
|
const htmlFilename = `${pageSlug}.html`;
|
||||||
|
|
||||||
|
console.log(`🚀 INDUSTRIAL CLONE: ${targetUrl}`);
|
||||||
|
|
||||||
|
const browser = await chromium.launch({ headless: true });
|
||||||
|
const context = await browser.newContext({
|
||||||
|
userAgent: this.userAgent,
|
||||||
|
viewport: { width: 1920, height: 1080 },
|
||||||
|
});
|
||||||
|
const page = await context.newPage();
|
||||||
|
|
||||||
|
const urlMap: AssetMap = {};
|
||||||
|
const foundAssets = new Set<string>();
|
||||||
|
|
||||||
|
page.on("response", (response) => {
|
||||||
|
if (response.status() === 200) {
|
||||||
|
const url = response.url();
|
||||||
|
if (url.match(/\.(css|js|png|jpg|jpeg|gif|svg|woff2?|ttf|otf|mp4|webm|webp|ico)/i)) {
|
||||||
|
foundAssets.add(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await page.goto(targetUrl, { waitUntil: "networkidle", timeout: 90000 });
|
||||||
|
|
||||||
|
// Scroll Wave
|
||||||
|
await page.evaluate(async () => {
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
let totalHeight = 0;
|
||||||
|
const distance = 400;
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
const scrollHeight = document.body.scrollHeight;
|
||||||
|
window.scrollBy(0, distance);
|
||||||
|
totalHeight += distance;
|
||||||
|
if (totalHeight >= scrollHeight) {
|
||||||
|
clearInterval(timer);
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const fullHeight = await page.evaluate(() => document.body.scrollHeight);
|
||||||
|
await page.setViewportSize({ width: 1920, height: fullHeight + 1000 });
|
||||||
|
await page.waitForTimeout(3000);
|
||||||
|
|
||||||
|
// Sanitization
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const assetPattern = /\.(jpg|jpeg|png|gif|svg|webp|mp4|webm|woff2?|ttf|otf)/i;
|
||||||
|
document.querySelectorAll("*").forEach((el) => {
|
||||||
|
if (["META", "LINK", "HEAD", "SCRIPT", "STYLE", "SVG", "PATH"].includes(el.tagName)) return;
|
||||||
|
const htmlEl = el as HTMLElement;
|
||||||
|
const style = window.getComputedStyle(htmlEl);
|
||||||
|
if (style.opacity === "0" || style.visibility === "hidden") {
|
||||||
|
htmlEl.style.setProperty("opacity", "1", "important");
|
||||||
|
htmlEl.style.setProperty("visibility", "visible", "important");
|
||||||
|
}
|
||||||
|
for (const attr of Array.from(el.attributes)) {
|
||||||
|
const name = attr.name.toLowerCase();
|
||||||
|
const val = attr.value;
|
||||||
|
if (assetPattern.test(val) || name.includes("src") || name.includes("image")) {
|
||||||
|
if (el.tagName === "IMG") {
|
||||||
|
const img = el as HTMLImageElement;
|
||||||
|
if (name.includes("srcset")) img.srcset = val;
|
||||||
|
else if (!img.src || img.src.includes("data:")) img.src = val;
|
||||||
|
}
|
||||||
|
if (el.tagName === "SOURCE") (el as HTMLSourceElement).srcset = val;
|
||||||
|
if (el.tagName === "VIDEO" || el.tagName === "AUDIO") (el as HTMLMediaElement).src = val;
|
||||||
|
if (val.match(/^(https?:\/\/|\/\/|\/)/) && !name.includes("href")) {
|
||||||
|
const bg = htmlEl.style.backgroundImage;
|
||||||
|
if (!bg || bg === "none") htmlEl.style.backgroundImage = `url('${val}')`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (document.body) {
|
||||||
|
document.body.style.setProperty("opacity", "1", "important");
|
||||||
|
document.body.style.setProperty("visibility", "visible", "important");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.waitForLoadState("networkidle");
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
|
let content = await page.content();
|
||||||
|
const regexPatterns = [
|
||||||
|
/(?:src|href|url|data-[a-z-]+|srcset)=["']([^"'<>\s]+?\.(?:css|js|png|jpg|jpeg|gif|svg|woff2?|ttf|otf|mp4|webm|webp|ico)(?:\?[^"']*)?)["']/gi,
|
||||||
|
/url\(["']?([^"'\)]+)["']?\)/gi,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pattern of regexPatterns) {
|
||||||
|
let match;
|
||||||
|
while ((match = pattern.exec(content)) !== null) {
|
||||||
|
try { foundAssets.add(new URL(match[1], targetUrl).href); } catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const url of foundAssets) {
|
||||||
|
const local = await this.assetManager.downloadFile(url, assetsDir);
|
||||||
|
if (local) {
|
||||||
|
urlMap[url] = local;
|
||||||
|
const clean = url.split("?")[0];
|
||||||
|
urlMap[clean] = local;
|
||||||
|
if (clean.endsWith(".css")) {
|
||||||
|
try {
|
||||||
|
const { data } = await axios.get(url, { headers: { "User-Agent": this.userAgent } });
|
||||||
|
const processedCss = await this.assetManager.processCssRecursively(data, url, assetsDir, urlMap);
|
||||||
|
const relPath = this.assetManager.sanitizePath(new URL(url).hostname + new URL(url).pathname);
|
||||||
|
fs.writeFileSync(path.join(assetsDir, relPath), processedCss);
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let finalContent = content;
|
||||||
|
const sortedUrls = Object.keys(urlMap).sort((a, b) => b.length - a.length);
|
||||||
|
if (sortedUrls.length > 0) {
|
||||||
|
const escaped = sortedUrls.map((u) => u.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
||||||
|
const masterRegex = new RegExp(`(${escaped.join("|")})`, "g");
|
||||||
|
finalContent = finalContent.replace(masterRegex, (match) => urlMap[match] || match);
|
||||||
|
}
|
||||||
|
|
||||||
|
const commonDirs = ["/wp-content/", "/wp-includes/", "/assets/", "/static/", "/images/"];
|
||||||
|
for (const dir of commonDirs) {
|
||||||
|
const localDir = `./assets/${urlObj.hostname}${dir}`;
|
||||||
|
finalContent = finalContent.split(`"${dir}`).join(`"${localDir}`).split(`'${dir}`).join(`'${localDir}`).split(`(${dir}`).join(`(${localDir}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const domainPattern = new RegExp(`https?://(www\\.)?${urlObj.hostname.replace(/\./g, "\\.")}[^"']*`, "gi");
|
||||||
|
finalContent = finalContent.replace(domainPattern, () => "./");
|
||||||
|
|
||||||
|
finalContent = finalContent.replace(/<script\b[^>]*>([\s\S]*?)<\/script>/gi, (match, scriptContent) => {
|
||||||
|
const lower = scriptContent.toLowerCase();
|
||||||
|
return (lower.includes("google-analytics") || lower.includes("gtag") || lower.includes("fbq") || lower.includes("lazy") || lower.includes("tracker")) ? "" : match;
|
||||||
|
});
|
||||||
|
|
||||||
|
const headEnd = finalContent.indexOf("</head>");
|
||||||
|
if (headEnd > -1) {
|
||||||
|
const stabilityCss = `\n<style>* { transition: none !important; animation: none !important; scroll-behavior: auto !important; } [data-aos], .reveal, .lazypath, .lazy-load, [data-src] { opacity: 1 !important; visibility: visible !important; transform: none !important; clip-path: none !important; } img, video, iframe { max-width: 100%; display: block; } a { pointer-events: none; cursor: default; } </style>`;
|
||||||
|
finalContent = finalContent.slice(0, headEnd) + stabilityCss + finalContent.slice(headEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalPath = path.join(domainDir, htmlFilename);
|
||||||
|
fs.writeFileSync(finalPath, finalContent);
|
||||||
|
return finalPath;
|
||||||
|
} finally {
|
||||||
|
await browser.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
123
packages/cloner-library/src/WebsiteCloner.ts
Normal file
123
packages/cloner-library/src/WebsiteCloner.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import { PlaywrightCrawler, RequestQueue } from 'crawlee';
|
||||||
|
import * as path from 'node:path';
|
||||||
|
import * as fs from 'node:fs';
|
||||||
|
import { execSync } from 'node:child_process';
|
||||||
|
|
||||||
|
export interface WebsiteClonerOptions {
|
||||||
|
baseOutputDir: string;
|
||||||
|
maxRequestsPerCrawl?: number;
|
||||||
|
maxConcurrency?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WebsiteCloner {
|
||||||
|
private options: WebsiteClonerOptions;
|
||||||
|
|
||||||
|
constructor(options: WebsiteClonerOptions) {
|
||||||
|
this.options = {
|
||||||
|
maxRequestsPerCrawl: 100,
|
||||||
|
maxConcurrency: 3,
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async clone(targetUrl: string, outputDirName?: string): Promise<string> {
|
||||||
|
const urlObj = new URL(targetUrl);
|
||||||
|
const domain = urlObj.hostname;
|
||||||
|
const finalOutputDirName = outputDirName || domain.replace(/\./g, '-');
|
||||||
|
const baseOutputDir = path.resolve(this.options.baseOutputDir, finalOutputDirName);
|
||||||
|
|
||||||
|
if (fs.existsSync(baseOutputDir)) {
|
||||||
|
fs.rmSync(baseOutputDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
fs.mkdirSync(baseOutputDir, { recursive: true });
|
||||||
|
|
||||||
|
console.log(`🚀 Starting perfect recursive clone of ${targetUrl}...`);
|
||||||
|
console.log(`📂 Output: ${baseOutputDir}`);
|
||||||
|
|
||||||
|
const requestQueue = await RequestQueue.open();
|
||||||
|
await requestQueue.addRequest({ url: targetUrl });
|
||||||
|
|
||||||
|
const crawler = new PlaywrightCrawler({
|
||||||
|
requestQueue,
|
||||||
|
maxRequestsPerCrawl: this.options.maxRequestsPerCrawl,
|
||||||
|
maxConcurrency: this.options.maxConcurrency,
|
||||||
|
|
||||||
|
async requestHandler({ request, enqueueLinks, log }) {
|
||||||
|
const url = request.url;
|
||||||
|
log.info(`Capturing ${url}...`);
|
||||||
|
|
||||||
|
const u = new URL(url);
|
||||||
|
let relPath = u.pathname;
|
||||||
|
if (relPath === '/' || relPath === '') relPath = '/index.html';
|
||||||
|
if (!relPath.endsWith('.html') && !path.extname(relPath)) relPath += '/index.html';
|
||||||
|
if (relPath.startsWith('/')) relPath = relPath.substring(1);
|
||||||
|
|
||||||
|
const fullPath = path.join(baseOutputDir, relPath);
|
||||||
|
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Note: This assumes single-file-cli is available in the environment
|
||||||
|
execSync(`npx single-file-cli "${url}" "${fullPath}" --browser-headless=true --browser-wait-until=networkidle0`, {
|
||||||
|
stdio: 'inherit'
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log.error(`Failed to capture ${url} with SingleFile`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await enqueueLinks({
|
||||||
|
strategy: 'same-domain',
|
||||||
|
transformRequestFunction: (req) => {
|
||||||
|
if (/\.(download|pdf|zip|gz|exe|png|jpg|jpeg|gif|svg|css|js)$/i.test(req.url)) return false;
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await crawler.run();
|
||||||
|
|
||||||
|
console.log('🔗 Rewriting internal links for offline navigation...');
|
||||||
|
const allFiles = this.getFiles(baseOutputDir).filter(f => f.endsWith('.html'));
|
||||||
|
|
||||||
|
for (const file of allFiles) {
|
||||||
|
let content = fs.readFileSync(file, 'utf8');
|
||||||
|
const fileRelToRoot = path.relative(baseOutputDir, file);
|
||||||
|
|
||||||
|
content = content.replace(/href="([^"]+)"/g, (match, href) => {
|
||||||
|
if (href.startsWith(targetUrl) || href.startsWith('/') || (!href.includes('://') && !href.startsWith('data:'))) {
|
||||||
|
try {
|
||||||
|
const linkUrl = new URL(href, targetUrl);
|
||||||
|
if (linkUrl.hostname === domain) {
|
||||||
|
let linkPath = linkUrl.pathname;
|
||||||
|
if (linkPath === '/' || linkPath === '') linkPath = '/index.html';
|
||||||
|
if (!linkPath.endsWith('.html') && !path.extname(linkPath)) linkPath += '/index.html';
|
||||||
|
if (linkPath.startsWith('/')) linkPath = linkPath.substring(1);
|
||||||
|
|
||||||
|
const relativeLink = path.relative(path.dirname(fileRelToRoot), linkPath);
|
||||||
|
return `href="${relativeLink}"`;
|
||||||
|
}
|
||||||
|
} catch (e) { }
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.writeFileSync(file, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n✅ Done! Perfect clone complete in: ${baseOutputDir}`);
|
||||||
|
return baseOutputDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getFiles(dir: string, fileList: string[] = []) {
|
||||||
|
const files = fs.readdirSync(dir);
|
||||||
|
for (const file of files) {
|
||||||
|
const name = path.join(dir, file);
|
||||||
|
if (fs.statSync(name).isDirectory()) {
|
||||||
|
this.getFiles(name, fileList);
|
||||||
|
} else {
|
||||||
|
fileList.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fileList;
|
||||||
|
}
|
||||||
|
}
|
||||||
3
packages/cloner-library/src/index.ts
Normal file
3
packages/cloner-library/src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./AssetManager.js";
|
||||||
|
export * from "./PageCloner.js";
|
||||||
|
export * from "./WebsiteCloner.js";
|
||||||
17
packages/cloner-library/tsconfig.json
Normal file
17
packages/cloner-library/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig/base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src",
|
||||||
|
"declaration": true,
|
||||||
|
"emitDeclarationOnly": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"target": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"noEmit": false
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -1,12 +1,17 @@
|
|||||||
{
|
{
|
||||||
"name": "@mintel/cms-infra",
|
"name": "@mintel/cms-infra",
|
||||||
"version": "1.7.12",
|
"version": "1.8.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"up": "npm run build:extensions && docker compose up -d",
|
"up": "npm run build:extensions && docker compose up -d",
|
||||||
"down": "docker compose down",
|
"down": "docker compose down",
|
||||||
"logs": "docker compose logs -f",
|
"logs": "docker compose logs -f",
|
||||||
"build:extensions": "../../scripts/sync-extensions.sh"
|
"build:extensions": "../../scripts/sync-extensions.sh",
|
||||||
|
"schema:apply:local": "../../scripts/cms-apply.sh local",
|
||||||
|
"schema:apply:infra": "../../scripts/cms-apply.sh infra",
|
||||||
|
"snapshot:local": "../../scripts/cms-snapshot.sh",
|
||||||
|
"sync:push": "../../scripts/sync-directus.sh push infra",
|
||||||
|
"sync:pull": "../../scripts/sync-directus.sh pull infra"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1203
packages/cms-infra/schema/current.yaml
Normal file
1203
packages/cms-infra/schema/current.yaml
Normal file
File diff suppressed because it is too large
Load Diff
2046
packages/cms-infra/schema/current_v2.yaml
Normal file
2046
packages/cms-infra/schema/current_v2.yaml
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
|||||||
"name": "customer-manager",
|
"name": "customer-manager",
|
||||||
"description": "Custom High-Fidelity Customer & Company Management for Directus",
|
"description": "Custom High-Fidelity Customer & Company Management for Directus",
|
||||||
"icon": "supervisor_account",
|
"icon": "supervisor_account",
|
||||||
"version": "1.7.12",
|
"version": "1.8.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"directus",
|
"directus",
|
||||||
|
|||||||
@@ -127,6 +127,15 @@
|
|||||||
<v-input v-model="employeeForm.email" placeholder="E-Mail Adresse" type="email" />
|
<v-input v-model="employeeForm.email" placeholder="E-Mail Adresse" type="email" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<span class="label">Zentrale Person (Verknüpfung)</span>
|
||||||
|
<v-select
|
||||||
|
v-model="employeeForm.person"
|
||||||
|
:items="peopleOptions"
|
||||||
|
placeholder="Person aus dem People Manager auswählen..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<v-divider v-if="isEditingEmployee" />
|
<v-divider v-if="isEditingEmployee" />
|
||||||
|
|
||||||
<div v-if="isEditingEmployee" class="field">
|
<div v-if="isEditingEmployee" class="field">
|
||||||
@@ -158,7 +167,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, nextTick } from 'vue';
|
import { ref, onMounted, nextTick, computed } from 'vue';
|
||||||
import { useApi } from '@directus/extensions-sdk';
|
import { useApi } from '@directus/extensions-sdk';
|
||||||
|
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
@@ -183,6 +192,7 @@ const employeeForm = ref({
|
|||||||
first_name: '',
|
first_name: '',
|
||||||
last_name: '',
|
last_name: '',
|
||||||
email: '',
|
email: '',
|
||||||
|
person: null,
|
||||||
temporary_password: ''
|
temporary_password: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -192,14 +202,22 @@ const tableHeaders = [
|
|||||||
{ text: 'Zuletzt eingeladen', value: 'last_invited', sortable: true }
|
{ text: 'Zuletzt eingeladen', value: 'last_invited', sortable: true }
|
||||||
];
|
];
|
||||||
|
|
||||||
async function fetchCompanies() {
|
const people = ref<any[]>([]);
|
||||||
const res = await api.get('/items/companies', {
|
|
||||||
params: {
|
const peopleOptions = computed(() =>
|
||||||
fields: ['id', 'name'],
|
people.value.map(p => ({
|
||||||
sort: 'name',
|
text: `${p.first_name} ${p.last_name} (${p.email})`,
|
||||||
},
|
value: p.id
|
||||||
});
|
}))
|
||||||
companies.value = res.data.data;
|
);
|
||||||
|
|
||||||
|
async function fetchData() {
|
||||||
|
const [companiesResp, peopleResp] = await Promise.all([
|
||||||
|
api.get('/items/companies', { params: { sort: 'name', fields: ['id', 'name'] } }),
|
||||||
|
api.get('/items/people', { params: { sort: 'last_name' } })
|
||||||
|
]);
|
||||||
|
companies.value = companiesResp.data.data;
|
||||||
|
people.value = peopleResp.data.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function selectCompany(company: any) {
|
async function selectCompany(company: any) {
|
||||||
@@ -209,7 +227,7 @@ async function selectCompany(company: any) {
|
|||||||
const res = await api.get('/items/client_users', {
|
const res = await api.get('/items/client_users', {
|
||||||
params: {
|
params: {
|
||||||
filter: { company: { _eq: company.id } },
|
filter: { company: { _eq: company.id } },
|
||||||
fields: ['*'],
|
fields: ['*', 'person.*'],
|
||||||
sort: 'first_name',
|
sort: 'first_name',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -273,6 +291,7 @@ async function openEditEmployee(item: any) {
|
|||||||
first_name: item.first_name || '',
|
first_name: item.first_name || '',
|
||||||
last_name: item.last_name || '',
|
last_name: item.last_name || '',
|
||||||
email: item.email || '',
|
email: item.email || '',
|
||||||
|
person: item.person?.id || item.person || null,
|
||||||
temporary_password: item.temporary_password || ''
|
temporary_password: item.temporary_password || ''
|
||||||
};
|
};
|
||||||
isEditingEmployee.value = true;
|
isEditingEmployee.value = true;
|
||||||
@@ -288,7 +307,8 @@ async function saveEmployee() {
|
|||||||
await api.patch(`/items/client_users/${employeeForm.value.id}`, {
|
await api.patch(`/items/client_users/${employeeForm.value.id}`, {
|
||||||
first_name: employeeForm.value.first_name,
|
first_name: employeeForm.value.first_name,
|
||||||
last_name: employeeForm.value.last_name,
|
last_name: employeeForm.value.last_name,
|
||||||
email: employeeForm.value.email
|
email: employeeForm.value.email,
|
||||||
|
person: employeeForm.value.person
|
||||||
});
|
});
|
||||||
notice.value = { type: 'success', message: 'Mitarbeiter aktualisiert!' };
|
notice.value = { type: 'success', message: 'Mitarbeiter aktualisiert!' };
|
||||||
} else {
|
} else {
|
||||||
@@ -296,7 +316,8 @@ async function saveEmployee() {
|
|||||||
first_name: employeeForm.value.first_name,
|
first_name: employeeForm.value.first_name,
|
||||||
last_name: employeeForm.value.last_name,
|
last_name: employeeForm.value.last_name,
|
||||||
email: employeeForm.value.email,
|
email: employeeForm.value.email,
|
||||||
company: selectedCompany.value.id
|
company: selectedCompany.value.id,
|
||||||
|
person: employeeForm.value.person
|
||||||
});
|
});
|
||||||
notice.value = { type: 'success', message: 'Mitarbeiter angelegt!' };
|
notice.value = { type: 'success', message: 'Mitarbeiter angelegt!' };
|
||||||
}
|
}
|
||||||
@@ -343,7 +364,7 @@ function formatDate(dateStr: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchCompanies();
|
fetchData();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mintel/eslint-config",
|
"name": "@mintel/eslint-config",
|
||||||
"version": "1.7.12",
|
"version": "1.8.0",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"registry": "https://npm.infra.mintel.me"
|
"registry": "https://npm.infra.mintel.me"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "feedback-commander",
|
"name": "feedback-commander",
|
||||||
"description": "Custom High-Fidelity Feedback Management Extension for Directus",
|
"description": "Custom High-Fidelity Feedback Management Extension for Directus",
|
||||||
"icon": "view_kanban",
|
"icon": "view_kanban",
|
||||||
"version": "1.7.12",
|
"version": "1.8.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"directus",
|
"directus",
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
<div class="card-text">{{ item.text }}</div>
|
<div class="card-text">{{ item.text }}</div>
|
||||||
<footer class="card-footer">
|
<footer class="card-footer">
|
||||||
<div class="meta-tags">
|
<div class="meta-tags">
|
||||||
<v-chip x-small outline>{{ item.project }}</v-chip>
|
<v-chip x-small outline>{{ item.company?.name || item.project }}</v-chip>
|
||||||
<v-icon :name="item.type === 'bug' ? 'bug_report' : 'lightbulb'" :color="item.type === 'bug' ? '#E91E63' : '#FFC107'" small />
|
<v-icon :name="item.type === 'bug' ? 'bug_report' : 'lightbulb'" :color="item.type === 'bug' ? '#E91E63' : '#FFC107'" small />
|
||||||
</div>
|
</div>
|
||||||
<v-icon v-if="selectedItem?.id === item.id" name="chevron_right" small />
|
<v-icon v-if="selectedItem?.id === item.id" name="chevron_right" small />
|
||||||
@@ -142,7 +142,8 @@
|
|||||||
<TransitionGroup name="thread-list">
|
<TransitionGroup name="thread-list">
|
||||||
<div v-for="reply in comments" :key="reply.id" class="reply-bubble">
|
<div v-for="reply in comments" :key="reply.id" class="reply-bubble">
|
||||||
<header class="reply-header">
|
<header class="reply-header">
|
||||||
<span class="reply-user">{{ reply.user_name }}</span>
|
<span class="reply-user">{{ reply.user_name || 'System' }}</span>
|
||||||
|
<span v-if="reply.person" class="reply-person">({{ reply.person.first_name }} {{ reply.person.last_name }})</span>
|
||||||
<span class="reply-date">{{ formatDate(reply.date_created || reply.id) }}</span>
|
<span class="reply-date">{{ formatDate(reply.date_created || reply.id) }}</span>
|
||||||
</header>
|
</header>
|
||||||
<div class="reply-text">{{ reply.text }}</div>
|
<div class="reply-text">{{ reply.text }}</div>
|
||||||
@@ -168,8 +169,12 @@
|
|||||||
<v-card-title>Context</v-card-title>
|
<v-card-title>Context</v-card-title>
|
||||||
<v-card-text class="meta-list">
|
<v-card-text class="meta-list">
|
||||||
<div class="meta-item">
|
<div class="meta-item">
|
||||||
<label><v-icon name="public" x-small /> Website</label>
|
<label><v-icon name="business" x-small /> Organisation / Firma</label>
|
||||||
<strong>{{ selectedItem.project }}</strong>
|
<strong>{{ selectedItem.company?.name || selectedItem.project }}</strong>
|
||||||
|
</div>
|
||||||
|
<div v-if="selectedItem.person" class="meta-item">
|
||||||
|
<label><v-icon name="person" x-small /> Zentrale Person</label>
|
||||||
|
<strong>{{ selectedItem.person.first_name }} {{ selectedItem.person.last_name }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="meta-item">
|
<div class="meta-item">
|
||||||
<label><v-icon name="link" x-small /> Source Path</label>
|
<label><v-icon name="link" x-small /> Source Path</label>
|
||||||
@@ -238,13 +243,14 @@ const statusOptions = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const projects = computed(() => {
|
const projects = computed(() => {
|
||||||
const projSet = new Set(items.value.map(i => i.project).filter(Boolean));
|
const projSet = new Set(items.value.map(i => i.company?.name || i.project).filter(Boolean));
|
||||||
return Array.from(projSet).sort();
|
return Array.from(projSet).sort();
|
||||||
});
|
});
|
||||||
|
|
||||||
const filteredItems = computed(() => {
|
const filteredItems = computed(() => {
|
||||||
return items.value.filter(item => {
|
return items.value.filter(item => {
|
||||||
const matchProject = currentProject.value === 'all' || item.project === currentProject.value;
|
const projectName = item.company?.name || item.project;
|
||||||
|
const matchProject = currentProject.value === 'all' || projectName === currentProject.value;
|
||||||
const status = item.status || 'open';
|
const status = item.status || 'open';
|
||||||
const matchStatus = currentStatusFilter.value === 'all' || status === currentStatusFilter.value;
|
const matchStatus = currentStatusFilter.value === 'all' || status === currentStatusFilter.value;
|
||||||
return matchProject && matchStatus;
|
return matchProject && matchStatus;
|
||||||
@@ -258,7 +264,8 @@ async function fetchData() {
|
|||||||
const response = await api.get('/items/visual_feedback', {
|
const response = await api.get('/items/visual_feedback', {
|
||||||
params: {
|
params: {
|
||||||
sort: '-date_created,-id',
|
sort: '-date_created,-id',
|
||||||
limit: 300
|
limit: 300,
|
||||||
|
fields: ['*', 'company.*', 'person.*']
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
items.value = response.data.data;
|
items.value = response.data.data;
|
||||||
@@ -278,7 +285,8 @@ async function selectItem(item) {
|
|||||||
const response = await api.get('/items/visual_feedback_comments', {
|
const response = await api.get('/items/visual_feedback_comments', {
|
||||||
params: {
|
params: {
|
||||||
filter: { feedback_id: { _eq: item.id } },
|
filter: { feedback_id: { _eq: item.id } },
|
||||||
sort: '-date_created,-id'
|
sort: '-date_created,-id',
|
||||||
|
fields: ['*', 'person.*']
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
comments.value = response.data.data;
|
comments.value = response.data.data;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mintel/gatekeeper",
|
"name": "@mintel/gatekeeper",
|
||||||
"version": "1.7.12",
|
"version": "1.8.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mintel/husky-config",
|
"name": "@mintel/husky-config",
|
||||||
"version": "1.7.12",
|
"version": "1.8.0",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"registry": "https://npm.infra.mintel.me"
|
"registry": "https://npm.infra.mintel.me"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mintel/infra",
|
"name": "@mintel/infra",
|
||||||
"version": "1.7.12",
|
"version": "1.8.0",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"registry": "https://npm.infra.mintel.me"
|
"registry": "https://npm.infra.mintel.me"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mintel/mail",
|
"name": "@mintel/mail",
|
||||||
"version": "1.7.12",
|
"version": "1.8.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mintel/next-config",
|
"name": "@mintel/next-config",
|
||||||
"version": "1.7.12",
|
"version": "1.8.0",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"registry": "https://npm.infra.mintel.me"
|
"registry": "https://npm.infra.mintel.me"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mintel/next-feedback",
|
"name": "@mintel/next-feedback",
|
||||||
"version": "1.7.12",
|
"version": "1.8.0",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"registry": "https://npm.infra.mintel.me"
|
"registry": "https://npm.infra.mintel.me"
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@directus/sdk": "^21.0.0",
|
"@directus/sdk": "^21.0.0",
|
||||||
|
"@medv/finder": "^4.0.2",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"framer-motion": "^11.5.4",
|
"framer-motion": "^11.5.4",
|
||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { MessageSquare, X, Check, Plus, List, Send, User } from "lucide-react";
|
|||||||
import { clsx } from "clsx";
|
import { clsx } from "clsx";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
import html2canvas from "html2canvas";
|
import html2canvas from "html2canvas";
|
||||||
|
import { finder } from "@medv/finder";
|
||||||
|
|
||||||
function cn(...inputs: any[]) {
|
function cn(...inputs: any[]) {
|
||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
@@ -107,15 +108,16 @@ export function FeedbackOverlay() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getSelector = (el: HTMLElement): string => {
|
const getSelector = (el: HTMLElement): string => {
|
||||||
if (el.id) return `#${el.id}`;
|
return finder(el, {
|
||||||
const path = [];
|
root: document.body,
|
||||||
let curr: HTMLElement | null = el;
|
className: (name) =>
|
||||||
while (curr && curr.parentElement) {
|
!name.startsWith('record-mode-') &&
|
||||||
const index = Array.from(curr.parentElement.children).indexOf(curr) + 1;
|
!name.startsWith('feedback-') &&
|
||||||
path.unshift(`${curr.tagName.toLowerCase()}:nth-child(${index})`);
|
!name.includes('[') &&
|
||||||
curr = curr.parentElement;
|
!name.includes('/') &&
|
||||||
}
|
!name.match(/^[a-z]-[0-9]/),
|
||||||
return path.join(" > ");
|
idName: (name) => !name.startsWith('__next') && !name.includes(':'),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -6,7 +6,4 @@ export default defineConfig({
|
|||||||
dts: true,
|
dts: true,
|
||||||
clean: true,
|
clean: true,
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
banner: {
|
|
||||||
js: "'use client';",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mintel/next-observability",
|
"name": "@mintel/next-observability",
|
||||||
"version": "1.7.12",
|
"version": "1.8.0",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"registry": "https://npm.infra.mintel.me"
|
"registry": "https://npm.infra.mintel.me"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mintel/next-utils",
|
"name": "@mintel/next-utils",
|
||||||
"version": "1.7.12",
|
"version": "1.8.0",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"registry": "https://npm.infra.mintel.me"
|
"registry": "https://npm.infra.mintel.me"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mintel/observability",
|
"name": "@mintel/observability",
|
||||||
"version": "1.7.12",
|
"version": "1.8.0",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"registry": "https://npm.infra.mintel.me"
|
"registry": "https://npm.infra.mintel.me"
|
||||||
|
|||||||
57
packages/pdf-library/build.mjs
Normal file
57
packages/pdf-library/build.mjs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { build } from 'esbuild';
|
||||||
|
import { resolve, dirname } from 'path';
|
||||||
|
import { mkdirSync } from 'fs';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
const entryPoints = [
|
||||||
|
resolve(__dirname, 'src/index.ts'),
|
||||||
|
resolve(__dirname, 'src/server.ts')
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
mkdirSync(resolve(__dirname, 'dist'), { recursive: true });
|
||||||
|
} catch (e) { }
|
||||||
|
|
||||||
|
console.log(`Building entry points...`);
|
||||||
|
|
||||||
|
build({
|
||||||
|
entryPoints: entryPoints,
|
||||||
|
bundle: true,
|
||||||
|
platform: 'node',
|
||||||
|
target: 'node18',
|
||||||
|
outdir: resolve(__dirname, 'dist'),
|
||||||
|
format: 'esm',
|
||||||
|
jsx: 'automatic',
|
||||||
|
loader: {
|
||||||
|
'.tsx': 'tsx',
|
||||||
|
'.ts': 'ts',
|
||||||
|
'.js': 'js',
|
||||||
|
},
|
||||||
|
external: ["@react-pdf/renderer", "react", "react-dom", "jsdom", "jsdom/*", "jquery", "jquery/*", "canvas", "fs", "path", "os", "http", "https", "zlib", "stream", "util", "url", "net", "tls", "crypto"],
|
||||||
|
plugins: [{
|
||||||
|
name: 'mock-canvas',
|
||||||
|
setup(build) {
|
||||||
|
build.onResolve({ filter: /^canvas/ }, args => ({ path: args.path, namespace: 'mock-canvas' }));
|
||||||
|
build.onLoad({ filter: /.*/, namespace: 'mock-canvas' }, () => ({ contents: 'export default {};', loader: 'js' }));
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
name: 'mock-jsdom',
|
||||||
|
setup(build) {
|
||||||
|
build.onResolve({ filter: /^jsdom/ }, args => ({ path: args.path, namespace: 'mock-jsdom' }));
|
||||||
|
build.onLoad({ filter: /.*/, namespace: 'mock-jsdom' }, () => ({ contents: 'export default {};', loader: 'js' }));
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}).then(() => {
|
||||||
|
console.log("Build succeeded!");
|
||||||
|
}).catch((e) => {
|
||||||
|
if (e.errors) {
|
||||||
|
console.error("Build failed with errors:");
|
||||||
|
e.errors.forEach(err => console.error(` ${err.text} at ${err.location?.file}:${err.location?.line}`));
|
||||||
|
} else {
|
||||||
|
console.error("Build failed:", e);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
38
packages/pdf-library/package.json
Normal file
38
packages/pdf-library/package.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "@mintel/pdf",
|
||||||
|
"version": "1.8.0",
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"module": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"./server": {
|
||||||
|
"types": "./dist/server.d.ts",
|
||||||
|
"import": "./dist/server.js",
|
||||||
|
"default": "./dist/server.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "node build.mjs",
|
||||||
|
"dev": "node build.mjs --watch"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@directus/extensions-sdk": "11.0.2",
|
||||||
|
"esbuild": "^0.25.0",
|
||||||
|
"typescript": "^5.6.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@crawlee/cheerio": "^3.16.0",
|
||||||
|
"@mintel/mail": "workspace:*",
|
||||||
|
"@react-pdf/renderer": "^4.3.0",
|
||||||
|
"axios": "^1.7.9",
|
||||||
|
"cheerio": "^1.0.0",
|
||||||
|
"react": "^19.2.4",
|
||||||
|
"react-dom": "^19.2.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
241
packages/pdf-library/src/components/AgbsPDF.tsx
Normal file
241
packages/pdf-library/src/components/AgbsPDF.tsx
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import {
|
||||||
|
Page as PDFPage,
|
||||||
|
Text as PDFText,
|
||||||
|
View as PDFView,
|
||||||
|
StyleSheet as PDFStyleSheet,
|
||||||
|
} from "@react-pdf/renderer";
|
||||||
|
import {
|
||||||
|
pdfStyles,
|
||||||
|
Header,
|
||||||
|
Footer,
|
||||||
|
FoldingMarks,
|
||||||
|
DocumentTitle,
|
||||||
|
} from "./pdf/SharedUI.js";
|
||||||
|
import { SimpleLayout } from "./pdf/SimpleLayout.js";
|
||||||
|
|
||||||
|
const localStyles = PDFStyleSheet.create({
|
||||||
|
sectionContainer: {
|
||||||
|
marginTop: 0,
|
||||||
|
},
|
||||||
|
agbSection: {
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
labelRow: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "baseline",
|
||||||
|
marginBottom: 6,
|
||||||
|
},
|
||||||
|
monoNumber: {
|
||||||
|
fontSize: 7,
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: "#94a3b8",
|
||||||
|
letterSpacing: 2,
|
||||||
|
width: 25,
|
||||||
|
},
|
||||||
|
sectionTitle: {
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: "#000000",
|
||||||
|
textTransform: "uppercase",
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
},
|
||||||
|
officialText: {
|
||||||
|
fontSize: 8,
|
||||||
|
lineHeight: 1.5,
|
||||||
|
color: "#334155",
|
||||||
|
textAlign: "justify",
|
||||||
|
paddingLeft: 25,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const AGBSection = ({
|
||||||
|
index,
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
index: string;
|
||||||
|
title: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) => (
|
||||||
|
<PDFView style={localStyles.agbSection} wrap={false}>
|
||||||
|
<PDFView style={localStyles.labelRow}>
|
||||||
|
<PDFText style={localStyles.monoNumber}>{index}</PDFText>
|
||||||
|
<PDFText style={localStyles.sectionTitle}>{title}</PDFText>
|
||||||
|
</PDFView>
|
||||||
|
<PDFText style={localStyles.officialText}>{children}</PDFText>
|
||||||
|
</PDFView>
|
||||||
|
);
|
||||||
|
|
||||||
|
interface AgbsPDFProps {
|
||||||
|
headerIcon?: string;
|
||||||
|
footerLogo?: string;
|
||||||
|
mode?: "estimation" | "full";
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AgbsPDF = ({
|
||||||
|
headerIcon,
|
||||||
|
footerLogo,
|
||||||
|
mode = "full",
|
||||||
|
}: AgbsPDFProps) => {
|
||||||
|
const date = new Date().toLocaleDateString("de-DE", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
});
|
||||||
|
|
||||||
|
const companyData = {
|
||||||
|
name: "Marc Mintel",
|
||||||
|
address1: "Georg-Meistermann-Straße 7",
|
||||||
|
address2: "54586 Schüller",
|
||||||
|
ustId: "DE367588065",
|
||||||
|
};
|
||||||
|
|
||||||
|
const bankData = {
|
||||||
|
name: "N26",
|
||||||
|
bic: "NTSBDEB1XXX",
|
||||||
|
iban: "DE50 1001 1001 2620 4328 65",
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = (
|
||||||
|
<>
|
||||||
|
<DocumentTitle
|
||||||
|
title="Allgemeine Geschäftsbedingungen"
|
||||||
|
subLines={[`Stand: ${date}`]}
|
||||||
|
/>
|
||||||
|
<PDFView style={localStyles.sectionContainer}>
|
||||||
|
<AGBSection index="01" title="Geltungsbereich">
|
||||||
|
Diese Allgemeinen Geschäftsbedingungen gelten für alle Verträge
|
||||||
|
zwischen Marc Mintel (nachfolgend „Auftragnehmer“) und dem jeweiligen
|
||||||
|
Kunden (nachfolgend „Auftraggeber“). Abweichende oder ergänzende
|
||||||
|
Bedingungen des Auftraggebers werden nicht Vertragsbestandteil, auch
|
||||||
|
wenn ihrer Geltung nicht ausdrücklich widersprochen wird.
|
||||||
|
</AGBSection>
|
||||||
|
|
||||||
|
<AGBSection index="02" title="Vertragsgegenstand">
|
||||||
|
Der Auftragnehmer erbringt Dienstleistungen im Bereich:
|
||||||
|
Webentwicklung, technische Umsetzung digitaler Systeme, Funktionen,
|
||||||
|
Schnittstellen und Automatisierungen sowie Hosting, Betrieb und
|
||||||
|
Wartung, sofern ausdrücklich vereinbard. Der Auftragnehmer schuldet
|
||||||
|
ausschließlich die vereinbarte technische Leistung, nicht jedoch einen
|
||||||
|
wirtschaftlichen Erfolg, bestimmte Umsätze, Conversions, Reichweiten,
|
||||||
|
Suchmaschinen-Rankings oder rechtliche Ergebnisse.
|
||||||
|
</AGBSection>
|
||||||
|
|
||||||
|
<AGBSection index="03" title="Mitwirkungspflichten des Auftraggebers">
|
||||||
|
Der Auftraggeber verpflichtet sich, alle zur Leistungserbringung
|
||||||
|
erforderlichen Inhalte, Informationen, Zugänge und Entscheidungen
|
||||||
|
rechtzeitig, vollständig und korrekt bereitzustellen. Hierzu zählen
|
||||||
|
insbesondere Texte, Bilder, Videos, Produktdaten, Freigaben, Feedback,
|
||||||
|
Zugangsdaten sowie rechtlich erforderliche Inhalte (z. B. Impressum,
|
||||||
|
DSGVO). Verzögerungen oder Unterlassungen führen zu Verschiebungen
|
||||||
|
aller Termine ohne Schadensersatzanspruch.
|
||||||
|
</AGBSection>
|
||||||
|
|
||||||
|
<AGBSection index="04" title="Ausführungs- und Bearbeitungszeiten">
|
||||||
|
Angegebene Bearbeitungszeiten sind unverbindliche Schätzungen, keine
|
||||||
|
garantierten Fristen. Fixe Termine oder Deadlines gelten nur, wenn sie
|
||||||
|
ausdrücklich schriftlich als verbindlich vereinbart wurden.
|
||||||
|
</AGBSection>
|
||||||
|
|
||||||
|
<AGBSection index="05" title="Abnahme">
|
||||||
|
Die Leistung gilt als abgenommen, wenn der Auftraggeber sie produktiv
|
||||||
|
nutzt oder innerhalb von 7 Tagen nach Bereitstellung keine
|
||||||
|
wesentlichen Mängel angezeigt werden. Optische Abweichungen,
|
||||||
|
Geschmacksfragen oder subjektive Einschätzungen stellen keine Mängel
|
||||||
|
dar.
|
||||||
|
</AGBSection>
|
||||||
|
|
||||||
|
<AGBSection index="06" title="Haftung">
|
||||||
|
Der Auftragnehmer haftet nur für Schäden, die auf vorsätzlicher oder
|
||||||
|
grob fahrlässiger Pflichtverletzung beruhen. Eine Haftung für
|
||||||
|
entgangenen Gewinn, Umsatzausfälle, Datenverlust,
|
||||||
|
Betriebsunterbrechungen, mittelbare oder Folgeschäden ist
|
||||||
|
ausgeschlossen, soweit gesetzlich zulässig.
|
||||||
|
</AGBSection>
|
||||||
|
|
||||||
|
<AGBSection index="07" title="Verfügbarkeit & Betrieb">
|
||||||
|
Bei vereinbartem Hosting oder Betrieb schuldet der Auftragnehmer keine
|
||||||
|
permanente Verfügbarkeit. Wartungsarbeiten, Updates,
|
||||||
|
Sicherheitsmaßnahmen oder externe Störungen können zu zeitweisen
|
||||||
|
Einschränkungen führen und begründen keine Haftungsansprüche.
|
||||||
|
</AGBSection>
|
||||||
|
|
||||||
|
<AGBSection index="07a" title="Betriebs- und Pflegeleistung">
|
||||||
|
Die Betriebs- und Pflegeleistung umfasst ausschließlich die
|
||||||
|
Sicherstellung des technischen Betriebs, Wartung, Updates,
|
||||||
|
Fehlerbehebung der bestehenden Systeme sowie Pflege bestehender
|
||||||
|
Datensätze ohne Strukturänderung. Nicht Bestandteil sind die
|
||||||
|
Erstellung neuer Inhalte (Blogartikel, News, Produkte), redaktionelle
|
||||||
|
Tätigkeiten, strategische Planung oder der Aufbau neuer
|
||||||
|
Features/Datenmodelle. Leistungen darüber hinaus gelten als
|
||||||
|
Neuentwicklung.
|
||||||
|
</AGBSection>
|
||||||
|
|
||||||
|
<AGBSection index="08" title="Drittanbieter & externe Systeme">
|
||||||
|
Der Auftragnehmer übernimmt keine Verantwortung für Leistungen,
|
||||||
|
Ausfälle oder Änderungen externer Dienste, APIs, Schnittstellen oder
|
||||||
|
Plattformen Dritter. Eine Funktionsfähigkeit kann nur im Rahmen der
|
||||||
|
jeweils aktuellen externen Schnittstellen gewährleistet werden.
|
||||||
|
</AGBSection>
|
||||||
|
|
||||||
|
<AGBSection index="09" title="Inhalte & Rechtliches">
|
||||||
|
Der Auftraggeber ist allein verantwortlich für Inhalte, rechtliche
|
||||||
|
Konformität (DSGVO, Urheberrecht etc.) sowie bereitgestellte Daten.
|
||||||
|
Der Auftragnehmer übernimmt keine rechtliche Prüfung.
|
||||||
|
</AGBSection>
|
||||||
|
|
||||||
|
<AGBSection index="10" title="Vergütung & Zahlungsverzug">
|
||||||
|
Alle Preise netto zzgl. MwSt. Rechnungen sind innerhalb von 7 Tagen
|
||||||
|
fällig. Bei Zahlungsverzug ist der Auftragnehmer berechtigt,
|
||||||
|
Leistungen auszusetzen, Systeme offline zu nehmen oder laufende
|
||||||
|
Arbeiten zu stoppen.
|
||||||
|
</AGBSection>
|
||||||
|
|
||||||
|
<AGBSection index="11" title="Kündigung laufender Leistungen">
|
||||||
|
Laufende Leistungen (z. B. Hosting & Betrieb) können mit einer Frist
|
||||||
|
von 4 Wochen zum Monatsende gekündigt werden, sofern nichts anderes
|
||||||
|
vereinbart ist.
|
||||||
|
</AGBSection>
|
||||||
|
|
||||||
|
<AGBSection index="12" title="Schlussbestimmungen">
|
||||||
|
Es gilt das Recht der Bundesrepublik Deutschland. Gerichtsstand ist
|
||||||
|
der Sitz des Auftragnehmers. Sollte eine Bestimmung unwirksam sein,
|
||||||
|
bleibt die Wirksamkeit der übrigen Regelungen unberührt.
|
||||||
|
</AGBSection>
|
||||||
|
</PDFView>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mode === "full") {
|
||||||
|
return (
|
||||||
|
<SimpleLayout
|
||||||
|
companyData={companyData}
|
||||||
|
bankData={bankData}
|
||||||
|
footerLogo={footerLogo}
|
||||||
|
icon={headerIcon}
|
||||||
|
pageNumber="10"
|
||||||
|
showPageNumber={false}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</SimpleLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PDFPage size="A4" style={pdfStyles.page}>
|
||||||
|
<FoldingMarks />
|
||||||
|
<Header icon={headerIcon} showAddress={false} />
|
||||||
|
{content}
|
||||||
|
<Footer
|
||||||
|
logo={footerLogo}
|
||||||
|
companyData={companyData}
|
||||||
|
bankData={bankData}
|
||||||
|
showDetails={false}
|
||||||
|
showPageNumber={false}
|
||||||
|
/>
|
||||||
|
</PDFPage>
|
||||||
|
);
|
||||||
|
};
|
||||||
79
packages/pdf-library/src/components/CombinedQuotePDF.tsx
Normal file
79
packages/pdf-library/src/components/CombinedQuotePDF.tsx
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import { Document as PDFDocument } from "@react-pdf/renderer";
|
||||||
|
import { EstimationPDF } from "./EstimationPDF.js";
|
||||||
|
import { AgbsPDF } from "./AgbsPDF.js";
|
||||||
|
import { SimpleLayout } from "./pdf/SimpleLayout.js";
|
||||||
|
import { ClosingModule } from "./pdf/modules/CommonModules.js";
|
||||||
|
|
||||||
|
interface CombinedProps {
|
||||||
|
estimationProps: any;
|
||||||
|
showAgbs?: boolean;
|
||||||
|
techDetails?: any[];
|
||||||
|
principles?: any[];
|
||||||
|
maintenanceDetails?: any[];
|
||||||
|
standardsDetails?: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CombinedQuotePDF = ({
|
||||||
|
estimationProps,
|
||||||
|
showAgbs = true,
|
||||||
|
techDetails,
|
||||||
|
principles,
|
||||||
|
maintenanceDetails,
|
||||||
|
standardsDetails,
|
||||||
|
mode = "full",
|
||||||
|
}: CombinedProps & { mode?: "estimation" | "full" }) => {
|
||||||
|
const date = new Date().toLocaleDateString("de-DE", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
});
|
||||||
|
|
||||||
|
const companyData = {
|
||||||
|
name: "Marc Mintel",
|
||||||
|
address1: "Georg-Meistermann-Straße 7",
|
||||||
|
address2: "54586 Schüller",
|
||||||
|
ustId: "DE367588065",
|
||||||
|
};
|
||||||
|
|
||||||
|
const bankData = {
|
||||||
|
name: "N26",
|
||||||
|
bic: "NTSBDEB1XXX",
|
||||||
|
iban: "DE50 1001 1001 2620 4328 65",
|
||||||
|
};
|
||||||
|
|
||||||
|
const layoutProps = {
|
||||||
|
date,
|
||||||
|
icon: estimationProps.headerIcon,
|
||||||
|
footerLogo: estimationProps.footerLogo,
|
||||||
|
companyData,
|
||||||
|
bankData,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PDFDocument
|
||||||
|
title={`Mintel - ${estimationProps.state.companyName || estimationProps.state.name}`}
|
||||||
|
>
|
||||||
|
<EstimationPDF
|
||||||
|
{...estimationProps}
|
||||||
|
mode={mode}
|
||||||
|
techDetails={techDetails}
|
||||||
|
principles={principles}
|
||||||
|
maintenanceDetails={maintenanceDetails}
|
||||||
|
standardsDetails={standardsDetails}
|
||||||
|
/>
|
||||||
|
{showAgbs && (
|
||||||
|
<AgbsPDF
|
||||||
|
mode={mode}
|
||||||
|
headerIcon={estimationProps.headerIcon}
|
||||||
|
footerLogo={estimationProps.footerLogo}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<SimpleLayout {...layoutProps} pageNumber="END" showPageNumber={false}>
|
||||||
|
<ClosingModule />
|
||||||
|
</SimpleLayout>
|
||||||
|
</PDFDocument>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -18,6 +18,8 @@ import { calculatePositions } from "../logic/pricing/calculator.js";
|
|||||||
interface PDFProps {
|
interface PDFProps {
|
||||||
state: any;
|
state: any;
|
||||||
totalPrice: number;
|
totalPrice: number;
|
||||||
|
monthlyPrice?: number;
|
||||||
|
totalPagesCount?: number;
|
||||||
pricing: any;
|
pricing: any;
|
||||||
headerIcon?: string;
|
headerIcon?: string;
|
||||||
footerLogo?: string;
|
footerLogo?: string;
|
||||||
55
packages/pdf-library/src/components/pdf/DINLayout.tsx
Normal file
55
packages/pdf-library/src/components/pdf/DINLayout.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Page as PDFPage } from '@react-pdf/renderer';
|
||||||
|
import { FoldingMarks, Header, Footer, pdfStyles } from './SharedUI';
|
||||||
|
|
||||||
|
interface DINLayoutProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
sender?: string;
|
||||||
|
recipient?: {
|
||||||
|
title: string;
|
||||||
|
subtitle?: string;
|
||||||
|
address?: string;
|
||||||
|
phone?: string;
|
||||||
|
email?: string;
|
||||||
|
taxId?: string;
|
||||||
|
};
|
||||||
|
icon?: string;
|
||||||
|
footerLogo?: string;
|
||||||
|
companyData: any;
|
||||||
|
bankData: any;
|
||||||
|
showAddress?: boolean;
|
||||||
|
showFooterDetails?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DINLayout = ({
|
||||||
|
children,
|
||||||
|
sender,
|
||||||
|
recipient,
|
||||||
|
icon,
|
||||||
|
footerLogo,
|
||||||
|
companyData,
|
||||||
|
bankData,
|
||||||
|
showAddress = true,
|
||||||
|
showFooterDetails = true
|
||||||
|
}: DINLayoutProps) => {
|
||||||
|
return (
|
||||||
|
<PDFPage size="A4" style={pdfStyles.page}>
|
||||||
|
<FoldingMarks />
|
||||||
|
<Header
|
||||||
|
sender={sender}
|
||||||
|
recipient={recipient}
|
||||||
|
icon={icon}
|
||||||
|
showAddress={showAddress}
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
<Footer
|
||||||
|
logo={footerLogo}
|
||||||
|
companyData={companyData}
|
||||||
|
bankData={bankData}
|
||||||
|
showDetails={showFooterDetails}
|
||||||
|
/>
|
||||||
|
</PDFPage>
|
||||||
|
);
|
||||||
|
};
|
||||||
728
packages/pdf-library/src/components/pdf/SharedUI.tsx
Normal file
728
packages/pdf-library/src/components/pdf/SharedUI.tsx
Normal file
@@ -0,0 +1,728 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import {
|
||||||
|
View as PDFView,
|
||||||
|
Text as PDFText,
|
||||||
|
StyleSheet,
|
||||||
|
Image as PDFImage,
|
||||||
|
} from "@react-pdf/renderer";
|
||||||
|
|
||||||
|
// INDUSTRIAL DESIGN SYSTEM TOKENS
|
||||||
|
export const COLORS = {
|
||||||
|
CHARCOAL: "#0f172a", // Slate 900
|
||||||
|
TEXT_MAIN: "#334155", // Slate 700
|
||||||
|
TEXT_DIM: "#64748b", // Slate 500
|
||||||
|
TEXT_LIGHT: "#94a3b8", // Slate 400
|
||||||
|
DIVIDER: "#cbd5e1", // Slate 300
|
||||||
|
GRID: "#f1f5f9", // Slate 100
|
||||||
|
BLUEPRINT: "#e2e8f0", // Slate 200
|
||||||
|
WHITE: "#ffffff",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FONT_SIZES = {
|
||||||
|
HERO: 24, // Main Page Titles
|
||||||
|
HEADING: 14, // Section Headers
|
||||||
|
BODY: 11, // Standard Content
|
||||||
|
LABEL: 10, // Bold Labels / Keys
|
||||||
|
SMALL: 9, // Descriptions / Footnotes
|
||||||
|
TINY: 8, // Metadata / Unit prices
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mintel Industrial Glyphs (strictly 1px stroke, 12x12px grid)
|
||||||
|
export const IndustrialGlyph = ({
|
||||||
|
type,
|
||||||
|
color = COLORS.TEXT_LIGHT,
|
||||||
|
size = 12,
|
||||||
|
}: {
|
||||||
|
type: string;
|
||||||
|
color?: string;
|
||||||
|
size?: number;
|
||||||
|
}) => {
|
||||||
|
const stroke = 1;
|
||||||
|
const scale = size / 12;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case "base": // Skeletal cube base
|
||||||
|
return (
|
||||||
|
<PDFView style={{ width: size, height: size, position: "relative" }}>
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 2 * scale,
|
||||||
|
left: 2 * scale,
|
||||||
|
width: 8 * scale,
|
||||||
|
height: 8 * scale,
|
||||||
|
borderWidth: stroke,
|
||||||
|
borderColor: color,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: 4 * scale,
|
||||||
|
height: 4 * scale,
|
||||||
|
borderWidth: stroke,
|
||||||
|
borderColor: color,
|
||||||
|
backgroundColor: "white",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PDFView>
|
||||||
|
);
|
||||||
|
case "pages": // Layered rectangles
|
||||||
|
return (
|
||||||
|
<PDFView style={{ width: size, height: size, position: "relative" }}>
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 3 * scale,
|
||||||
|
left: 3 * scale,
|
||||||
|
width: 6 * scale,
|
||||||
|
height: 8 * scale,
|
||||||
|
borderWidth: stroke,
|
||||||
|
borderColor: color,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: 6 * scale,
|
||||||
|
height: 8 * scale,
|
||||||
|
borderWidth: stroke,
|
||||||
|
borderColor: color,
|
||||||
|
backgroundColor: "white",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PDFView>
|
||||||
|
);
|
||||||
|
case "modules": // Four small squares grid
|
||||||
|
return (
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
flexDirection: "row",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
gap: 2 * scale,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
width: 4 * scale,
|
||||||
|
height: 4 * scale,
|
||||||
|
borderWidth: stroke,
|
||||||
|
borderColor: color,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
width: 4 * scale,
|
||||||
|
height: 4 * scale,
|
||||||
|
borderWidth: stroke,
|
||||||
|
borderColor: color,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
width: 4 * scale,
|
||||||
|
height: 4 * scale,
|
||||||
|
borderWidth: stroke,
|
||||||
|
borderColor: color,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
width: 4 * scale,
|
||||||
|
height: 4 * scale,
|
||||||
|
borderWidth: stroke,
|
||||||
|
borderColor: color,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PDFView>
|
||||||
|
);
|
||||||
|
case "logic": // Diamond with center point
|
||||||
|
return (
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
width: 8 * scale,
|
||||||
|
height: 8 * scale,
|
||||||
|
borderWidth: stroke,
|
||||||
|
borderColor: color,
|
||||||
|
transform: "rotate(45deg)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
width: 2 * scale,
|
||||||
|
height: 2 * scale,
|
||||||
|
backgroundColor: color,
|
||||||
|
position: "absolute",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PDFView>
|
||||||
|
);
|
||||||
|
case "interface": // Three horizontal lines of varying length
|
||||||
|
return (
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
justifyContent: "center",
|
||||||
|
gap: 2 * scale,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
width: 10 * scale,
|
||||||
|
height: stroke,
|
||||||
|
backgroundColor: color,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<PDFView
|
||||||
|
style={{ width: 6 * scale, height: stroke, backgroundColor: color }}
|
||||||
|
/>
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
width: 10 * scale,
|
||||||
|
height: stroke,
|
||||||
|
backgroundColor: color,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PDFView>
|
||||||
|
);
|
||||||
|
case "management": // Framed grid
|
||||||
|
return (
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
borderWidth: stroke,
|
||||||
|
borderColor: color,
|
||||||
|
padding: 1 * scale,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: 2 * scale,
|
||||||
|
backgroundColor: color,
|
||||||
|
marginBottom: 1 * scale,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: 2 * scale,
|
||||||
|
backgroundColor: color,
|
||||||
|
marginBottom: 1 * scale,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<PDFView
|
||||||
|
style={{ width: "100%", height: 2 * scale, backgroundColor: color }}
|
||||||
|
/>
|
||||||
|
</PDFView>
|
||||||
|
);
|
||||||
|
case "reveal": // Ascending bars
|
||||||
|
return (
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "flex-end",
|
||||||
|
gap: 1 * scale,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
width: 2 * scale,
|
||||||
|
height: 4 * scale,
|
||||||
|
backgroundColor: color,
|
||||||
|
opacity: 0.4,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
width: 2 * scale,
|
||||||
|
height: 7 * scale,
|
||||||
|
backgroundColor: color,
|
||||||
|
opacity: 0.7,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
width: 2 * scale,
|
||||||
|
height: 10 * scale,
|
||||||
|
backgroundColor: color,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PDFView>
|
||||||
|
);
|
||||||
|
case "maintenance": // Circle with vertical notch
|
||||||
|
return (
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
borderRadius: 6 * scale,
|
||||||
|
borderWidth: stroke,
|
||||||
|
borderColor: color,
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
width: stroke,
|
||||||
|
height: 4 * scale,
|
||||||
|
backgroundColor: color,
|
||||||
|
marginTop: 1 * scale,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PDFView>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
borderWidth: stroke,
|
||||||
|
borderColor: COLORS.BLUEPRINT,
|
||||||
|
borderStyle: "dashed",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pdfStyles = StyleSheet.create({
|
||||||
|
page: {
|
||||||
|
paddingTop: 45, // DIN 5008
|
||||||
|
paddingLeft: 70, // ~25mm
|
||||||
|
paddingRight: 57, // ~20mm
|
||||||
|
paddingBottom: 80, // Safe buffer for absolute footer
|
||||||
|
backgroundColor: COLORS.WHITE,
|
||||||
|
fontFamily: "Helvetica",
|
||||||
|
fontSize: FONT_SIZES.BODY,
|
||||||
|
color: COLORS.CHARCOAL,
|
||||||
|
},
|
||||||
|
titlePage: {
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
backgroundColor: COLORS.WHITE,
|
||||||
|
fontFamily: "Helvetica",
|
||||||
|
color: COLORS.CHARCOAL,
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "flex-start",
|
||||||
|
marginBottom: 20,
|
||||||
|
minHeight: 120,
|
||||||
|
},
|
||||||
|
addressBlock: {
|
||||||
|
width: "55%",
|
||||||
|
marginTop: 45,
|
||||||
|
},
|
||||||
|
senderLine: {
|
||||||
|
fontSize: FONT_SIZES.TINY,
|
||||||
|
textDecoration: "underline",
|
||||||
|
color: COLORS.TEXT_DIM,
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
recipientAddress: {
|
||||||
|
fontSize: FONT_SIZES.BODY,
|
||||||
|
lineHeight: 1.4,
|
||||||
|
},
|
||||||
|
brandLogoContainer: {
|
||||||
|
width: "40%",
|
||||||
|
alignItems: "flex-end",
|
||||||
|
},
|
||||||
|
brandIconContainer: {
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
backgroundColor: "#0f172a",
|
||||||
|
borderRadius: 8,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
brandIconText: {
|
||||||
|
color: COLORS.WHITE,
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
|
titleInfo: {
|
||||||
|
marginBottom: 24,
|
||||||
|
},
|
||||||
|
mainTitle: {
|
||||||
|
fontSize: FONT_SIZES.HEADING,
|
||||||
|
fontWeight: "bold",
|
||||||
|
marginBottom: 4,
|
||||||
|
color: COLORS.CHARCOAL,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
},
|
||||||
|
subTitle: {
|
||||||
|
fontSize: FONT_SIZES.BODY,
|
||||||
|
color: COLORS.TEXT_DIM,
|
||||||
|
marginTop: 2,
|
||||||
|
lineHeight: 1.4,
|
||||||
|
},
|
||||||
|
section: {
|
||||||
|
marginBottom: 32,
|
||||||
|
},
|
||||||
|
sectionTitle: {
|
||||||
|
fontSize: FONT_SIZES.LABEL,
|
||||||
|
fontWeight: "bold",
|
||||||
|
textTransform: "uppercase",
|
||||||
|
letterSpacing: 1,
|
||||||
|
color: COLORS.TEXT_LIGHT,
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
position: "absolute",
|
||||||
|
bottom: 32,
|
||||||
|
left: 70,
|
||||||
|
right: 57,
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderTopColor: COLORS.GRID,
|
||||||
|
paddingTop: 16,
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "flex-start",
|
||||||
|
},
|
||||||
|
footerColumn: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: "flex-start",
|
||||||
|
},
|
||||||
|
footerLogo: {
|
||||||
|
height: 20,
|
||||||
|
width: "auto",
|
||||||
|
objectFit: "contain",
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
footerText: {
|
||||||
|
fontSize: FONT_SIZES.TINY,
|
||||||
|
color: COLORS.TEXT_LIGHT,
|
||||||
|
lineHeight: 1.4,
|
||||||
|
},
|
||||||
|
asymmetryContainer: {
|
||||||
|
flexDirection: "row",
|
||||||
|
gap: 32,
|
||||||
|
},
|
||||||
|
asymmetryLeft: {
|
||||||
|
width: "32%",
|
||||||
|
},
|
||||||
|
asymmetryRight: {
|
||||||
|
width: "63%",
|
||||||
|
},
|
||||||
|
specRow: {
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
paddingVertical: 6,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: COLORS.GRID,
|
||||||
|
},
|
||||||
|
specLabel: {
|
||||||
|
fontSize: FONT_SIZES.TINY,
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: COLORS.TEXT_LIGHT,
|
||||||
|
textTransform: "uppercase",
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
},
|
||||||
|
specValue: {
|
||||||
|
fontSize: FONT_SIZES.SMALL,
|
||||||
|
color: COLORS.CHARCOAL,
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
|
blueprintBox: {
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: COLORS.GRID,
|
||||||
|
padding: 16,
|
||||||
|
backgroundColor: "#fafafa",
|
||||||
|
},
|
||||||
|
footerLabel: {
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: COLORS.TEXT_DIM,
|
||||||
|
},
|
||||||
|
pageNumber: {
|
||||||
|
fontSize: FONT_SIZES.TINY,
|
||||||
|
color: COLORS.DIVIDER,
|
||||||
|
fontWeight: "bold",
|
||||||
|
marginTop: 8,
|
||||||
|
textAlign: "right",
|
||||||
|
},
|
||||||
|
foldingMark: {
|
||||||
|
position: "absolute",
|
||||||
|
left: 20,
|
||||||
|
width: 10,
|
||||||
|
borderTopWidth: 0.5,
|
||||||
|
borderTopColor: COLORS.DIVIDER,
|
||||||
|
},
|
||||||
|
divider: {
|
||||||
|
width: "100%",
|
||||||
|
height: 1,
|
||||||
|
backgroundColor: COLORS.DIVIDER,
|
||||||
|
marginVertical: 12,
|
||||||
|
},
|
||||||
|
industrialListItem: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "flex-start",
|
||||||
|
marginBottom: 6,
|
||||||
|
},
|
||||||
|
industrialBulletBox: {
|
||||||
|
width: 6,
|
||||||
|
height: 6,
|
||||||
|
backgroundColor: COLORS.DIVIDER,
|
||||||
|
marginRight: 8,
|
||||||
|
marginTop: 5,
|
||||||
|
},
|
||||||
|
industrialTitle: {
|
||||||
|
fontSize: FONT_SIZES.HERO,
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: COLORS.CHARCOAL,
|
||||||
|
marginBottom: 6,
|
||||||
|
letterSpacing: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const IndustrialListItem = ({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) => (
|
||||||
|
<PDFView style={pdfStyles.industrialListItem}>
|
||||||
|
<PDFView style={pdfStyles.industrialBulletBox} />
|
||||||
|
{children}
|
||||||
|
</PDFView>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Divider = ({ style = {} }: { style?: any }) => (
|
||||||
|
<PDFView style={[pdfStyles.divider, style]} />
|
||||||
|
);
|
||||||
|
|
||||||
|
export const FoldingMarks = () => (
|
||||||
|
<>
|
||||||
|
<PDFView style={[pdfStyles.foldingMark, { top: 297.6 }]} fixed />
|
||||||
|
<PDFView style={[pdfStyles.foldingMark, { top: 420.9, width: 15 }]} fixed />
|
||||||
|
<PDFView style={[pdfStyles.foldingMark, { top: 595.3 }]} fixed />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Footer = ({
|
||||||
|
logo,
|
||||||
|
companyData,
|
||||||
|
bankData,
|
||||||
|
showDetails = true,
|
||||||
|
showPageNumber = true,
|
||||||
|
}: {
|
||||||
|
logo?: string;
|
||||||
|
companyData: any;
|
||||||
|
bankData?: any;
|
||||||
|
showDetails?: boolean;
|
||||||
|
showPageNumber?: boolean;
|
||||||
|
}) => (
|
||||||
|
<PDFView style={pdfStyles.footer}>
|
||||||
|
<PDFView style={pdfStyles.footerColumn}>
|
||||||
|
{logo ? (
|
||||||
|
<PDFImage src={logo} style={pdfStyles.footerLogo} />
|
||||||
|
) : (
|
||||||
|
<PDFText style={{ fontSize: 12, fontWeight: "bold", marginBottom: 8 }}>
|
||||||
|
marc mintel
|
||||||
|
</PDFText>
|
||||||
|
)}
|
||||||
|
</PDFView>
|
||||||
|
{showDetails && (
|
||||||
|
<>
|
||||||
|
<PDFView style={pdfStyles.footerColumn}>
|
||||||
|
<PDFText style={pdfStyles.footerText}>
|
||||||
|
<PDFText style={pdfStyles.footerLabel}>{companyData.name}</PDFText>
|
||||||
|
{"\n"}
|
||||||
|
{companyData.address1}
|
||||||
|
{"\n"}
|
||||||
|
{companyData.address2}
|
||||||
|
{"\n"}UST: {companyData.ustId}
|
||||||
|
</PDFText>
|
||||||
|
</PDFView>
|
||||||
|
<PDFView style={[pdfStyles.footerColumn, { alignItems: "flex-end" }]}>
|
||||||
|
{showPageNumber && (
|
||||||
|
<PDFText
|
||||||
|
style={pdfStyles.pageNumber}
|
||||||
|
render={({ pageNumber, totalPages }) =>
|
||||||
|
`${pageNumber} / ${totalPages}`
|
||||||
|
}
|
||||||
|
fixed
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</PDFView>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!showDetails && (
|
||||||
|
<PDFView style={[pdfStyles.footerColumn, { alignItems: "flex-end" }]}>
|
||||||
|
{showPageNumber && (
|
||||||
|
<PDFText
|
||||||
|
style={pdfStyles.pageNumber}
|
||||||
|
render={({ pageNumber, totalPages }) =>
|
||||||
|
`${pageNumber} / ${totalPages}`
|
||||||
|
}
|
||||||
|
fixed
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</PDFView>
|
||||||
|
)}
|
||||||
|
</PDFView>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Header = ({
|
||||||
|
sender,
|
||||||
|
recipient,
|
||||||
|
icon,
|
||||||
|
showAddress = true,
|
||||||
|
}: {
|
||||||
|
sender?: string;
|
||||||
|
recipient?: {
|
||||||
|
title: string;
|
||||||
|
subtitle?: string;
|
||||||
|
email?: string;
|
||||||
|
address?: string;
|
||||||
|
phone?: string;
|
||||||
|
taxId?: string;
|
||||||
|
};
|
||||||
|
icon?: string;
|
||||||
|
showAddress?: boolean;
|
||||||
|
}) => (
|
||||||
|
<PDFView
|
||||||
|
style={[
|
||||||
|
pdfStyles.header,
|
||||||
|
showAddress ? {} : { minHeight: 40, marginBottom: 0 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<PDFView style={pdfStyles.addressBlock}>
|
||||||
|
{showAddress && sender && (
|
||||||
|
<>
|
||||||
|
<PDFText style={pdfStyles.senderLine}>{sender}</PDFText>
|
||||||
|
{recipient && (
|
||||||
|
<PDFView style={pdfStyles.recipientAddress}>
|
||||||
|
<PDFText style={{ fontWeight: "bold" }}>
|
||||||
|
{recipient.title}
|
||||||
|
</PDFText>
|
||||||
|
{recipient.subtitle && <PDFText>{recipient.subtitle}</PDFText>}
|
||||||
|
{recipient.address && <PDFText>{recipient.address}</PDFText>}
|
||||||
|
{recipient.phone && <PDFText>{recipient.phone}</PDFText>}
|
||||||
|
{recipient.email && <PDFText>{recipient.email}</PDFText>}
|
||||||
|
{recipient.taxId && <PDFText>USt-ID: {recipient.taxId}</PDFText>}
|
||||||
|
</PDFView>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</PDFView>
|
||||||
|
<PDFView style={pdfStyles.brandLogoContainer}>
|
||||||
|
<PDFView style={pdfStyles.brandIconContainer}>
|
||||||
|
{icon ? (
|
||||||
|
<PDFImage src={icon} style={{ width: 24, height: 24 }} />
|
||||||
|
) : (
|
||||||
|
<PDFText style={pdfStyles.brandIconText}>M</PDFText>
|
||||||
|
)}
|
||||||
|
</PDFView>
|
||||||
|
</PDFView>
|
||||||
|
</PDFView>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const DocumentTitle = ({
|
||||||
|
title,
|
||||||
|
subLines,
|
||||||
|
isHero = false,
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
subLines?: string[];
|
||||||
|
isHero?: boolean;
|
||||||
|
}) => (
|
||||||
|
<PDFView style={pdfStyles.titleInfo}>
|
||||||
|
<PDFText
|
||||||
|
style={[
|
||||||
|
pdfStyles.mainTitle,
|
||||||
|
{ fontSize: isHero ? FONT_SIZES.HERO : FONT_SIZES.HEADING },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</PDFText>
|
||||||
|
{subLines?.map((line, i) => (
|
||||||
|
<PDFText
|
||||||
|
key={i}
|
||||||
|
style={[
|
||||||
|
pdfStyles.subTitle,
|
||||||
|
i === 1 ? { fontWeight: "bold", color: COLORS.CHARCOAL } : {},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{line}
|
||||||
|
</PDFText>
|
||||||
|
))}
|
||||||
|
</PDFView>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const TechnicalSpec = ({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
}: {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}) => (
|
||||||
|
<PDFView style={pdfStyles.specRow}>
|
||||||
|
<PDFText style={pdfStyles.specLabel}>{label}</PDFText>
|
||||||
|
<PDFText style={pdfStyles.specValue}>{value}</PDFText>
|
||||||
|
</PDFView>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const AsymmetryView = ({
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
style = {},
|
||||||
|
}: {
|
||||||
|
left: React.ReactNode;
|
||||||
|
right: React.ReactNode;
|
||||||
|
style?: any;
|
||||||
|
}) => (
|
||||||
|
<PDFView style={[pdfStyles.asymmetryContainer, style]}>
|
||||||
|
<PDFView style={pdfStyles.asymmetryLeft}>{left}</PDFView>
|
||||||
|
<PDFView style={pdfStyles.asymmetryRight}>{right}</PDFView>
|
||||||
|
</PDFView>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const IndustrialCard = ({
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
style = {},
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
style?: any;
|
||||||
|
}) => (
|
||||||
|
<PDFView style={[pdfStyles.blueprintBox, { marginBottom: 12 }, style]}>
|
||||||
|
<PDFText
|
||||||
|
style={{
|
||||||
|
fontSize: FONT_SIZES.TINY,
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: COLORS.TEXT_LIGHT,
|
||||||
|
letterSpacing: 1,
|
||||||
|
marginBottom: 6,
|
||||||
|
textTransform: "uppercase",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</PDFText>
|
||||||
|
{children}
|
||||||
|
</PDFView>
|
||||||
|
);
|
||||||
@@ -33,6 +33,7 @@ interface SimpleLayoutProps {
|
|||||||
icon?: string;
|
icon?: string;
|
||||||
footerLogo?: string;
|
footerLogo?: string;
|
||||||
companyData: any;
|
companyData: any;
|
||||||
|
bankData?: any;
|
||||||
showPageNumber?: boolean;
|
showPageNumber?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ export const SimpleLayout = ({
|
|||||||
icon,
|
icon,
|
||||||
footerLogo,
|
footerLogo,
|
||||||
companyData,
|
companyData,
|
||||||
|
bankData,
|
||||||
showPageNumber = true
|
showPageNumber = true
|
||||||
}: SimpleLayoutProps) => {
|
}: SimpleLayoutProps) => {
|
||||||
return (
|
return (
|
||||||
@@ -56,6 +58,7 @@ export const SimpleLayout = ({
|
|||||||
<Footer
|
<Footer
|
||||||
logo={footerLogo}
|
logo={footerLogo}
|
||||||
companyData={companyData}
|
companyData={companyData}
|
||||||
|
bankData={bankData}
|
||||||
showDetails={false}
|
showDetails={false}
|
||||||
showPageNumber={showPageNumber}
|
showPageNumber={showPageNumber}
|
||||||
/>
|
/>
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import {
|
||||||
|
View as PDFView,
|
||||||
|
Text as PDFText,
|
||||||
|
StyleSheet,
|
||||||
|
} from "@react-pdf/renderer";
|
||||||
|
import {
|
||||||
|
DocumentTitle,
|
||||||
|
IndustrialListItem,
|
||||||
|
IndustrialCard,
|
||||||
|
Divider,
|
||||||
|
COLORS,
|
||||||
|
FONT_SIZES,
|
||||||
|
} from "../SharedUI";
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
industrialTextLead: {
|
||||||
|
fontSize: FONT_SIZES.BODY,
|
||||||
|
color: COLORS.TEXT_MAIN,
|
||||||
|
lineHeight: 1.4,
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
industrialText: {
|
||||||
|
fontSize: FONT_SIZES.BODY,
|
||||||
|
color: COLORS.TEXT_DIM,
|
||||||
|
lineHeight: 1.4,
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
industrialGrid2: { flexDirection: "row" },
|
||||||
|
industrialCol: { width: "46%" },
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AboutModule = () => (
|
||||||
|
<>
|
||||||
|
<DocumentTitle
|
||||||
|
title="Expertise & Profil"
|
||||||
|
subLines={["Entwicklung & Technischer Partner für den Mittelstand"]}
|
||||||
|
isHero={true}
|
||||||
|
/>
|
||||||
|
<Divider style={{ marginVertical: 16, backgroundColor: COLORS.GRID }} />
|
||||||
|
|
||||||
|
<PDFView style={{ marginTop: 24 }}>
|
||||||
|
<PDFText style={styles.industrialTextLead}>
|
||||||
|
Begleitung mittelständischer Unternehmen und Agenturen bei der
|
||||||
|
Realisierung anspruchsvoller Web-Projekte. Als Senior Software Developer
|
||||||
|
mit over 15 Jahren Erfahrung wird das gesamte technische Spektrum
|
||||||
|
abgedeckt – von der Architektur bis zum fertigen Produkt.
|
||||||
|
</PDFText>
|
||||||
|
|
||||||
|
<PDFView style={[styles.industrialGrid2, { marginTop: 20 }]}>
|
||||||
|
<PDFView style={[styles.industrialCol, { marginRight: "8%" }]}>
|
||||||
|
<PDFText
|
||||||
|
style={[
|
||||||
|
styles.industrialText,
|
||||||
|
{ fontWeight: "bold", color: COLORS.CHARCOAL, marginBottom: 8 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
Erfahrung & Substanz
|
||||||
|
</PDFText>
|
||||||
|
<PDFText style={styles.industrialText}>
|
||||||
|
Der Werdegang umfasst alle Ebenen der Webentwicklung: von der
|
||||||
|
Teamleitung in Kreativagenturen bis zur Softwareentwicklung für
|
||||||
|
internationale Konzerne.
|
||||||
|
</PDFText>
|
||||||
|
<PDFText style={styles.industrialText}>
|
||||||
|
Die Kenntnis komplexer Enterprise-Systeme wird mit der Agilität
|
||||||
|
kombiniert, die im Mittelstand gefordert ist. Dieses Wissen
|
||||||
|
ermöglicht den Bau von Lösungen, die technologisch auf Augenhöhe mit
|
||||||
|
Konzern-Standards sind, jedoch ohne unnötigen bürokratischen
|
||||||
|
Overhead auskommen.
|
||||||
|
</PDFText>
|
||||||
|
</PDFView>
|
||||||
|
|
||||||
|
<PDFView style={styles.industrialCol}>
|
||||||
|
<PDFText
|
||||||
|
style={[
|
||||||
|
styles.industrialText,
|
||||||
|
{ fontWeight: "bold", color: COLORS.CHARCOAL, marginBottom: 8 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
Fokus Einzelentwicklung
|
||||||
|
</PDFText>
|
||||||
|
<PDFText style={styles.industrialText}>
|
||||||
|
Die Umsetzung erfolgt bewusst als spezialisierter Einzelentwickler.
|
||||||
|
Dies garantiert maximale Geschwindigkeit, direkte Kommunikationswege
|
||||||
|
und volle technologische Verantwortung.
|
||||||
|
</PDFText>
|
||||||
|
<PDFText style={styles.industrialText}>
|
||||||
|
Als direkter technischer Sparringspartner bleibt die Codebasis von
|
||||||
|
der ersten bis zur letzten Zeile transparent und wartbar. Diese
|
||||||
|
Unmittelbarkeit stellt sicher, dass Ergebnisse sowohl technisch
|
||||||
|
sauber als auch wirtschaftlich sinnvoll realisiert werden.
|
||||||
|
</PDFText>
|
||||||
|
</PDFView>
|
||||||
|
</PDFView>
|
||||||
|
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
marginTop: 32,
|
||||||
|
paddingVertical: 16,
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderTopColor: COLORS.GRID,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PDFText
|
||||||
|
style={[
|
||||||
|
styles.industrialText,
|
||||||
|
{ fontWeight: "bold", color: COLORS.CHARCOAL, marginBottom: 4 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
Infrastruktur & Souveränität
|
||||||
|
</PDFText>
|
||||||
|
<PDFText style={styles.industrialText}>
|
||||||
|
Es wird keine instabile Prototyp-Software geliefert, sondern
|
||||||
|
produktionsreife Systeme, die technisch skalierbar bleiben. Die
|
||||||
|
Codebasis folgt modernen Standards – bei wachsenden Ansprüchen oder
|
||||||
|
dem Wechsel zu einer Agentur kann der Quellcode jederzeit nahtlos
|
||||||
|
übernommen und weitergeführt werden.
|
||||||
|
</PDFText>
|
||||||
|
</PDFView>
|
||||||
|
</PDFView>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const CrossSellModule = ({ state }: any) => {
|
||||||
|
const isWebsite = state.projectType === "website";
|
||||||
|
const title = isWebsite ? "Weitere Potenziale" : "Websites & Ökosysteme";
|
||||||
|
const subtitle = isWebsite
|
||||||
|
? "Automatisierung und Prozessoptimierung"
|
||||||
|
: "Technische Infrastruktur ohne Kompromisse";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DocumentTitle title={title} subLines={[subtitle]} isHero={true} />
|
||||||
|
<Divider style={{ marginVertical: 16, backgroundColor: COLORS.GRID }} />
|
||||||
|
<PDFView style={[styles.industrialGrid2, { marginTop: 16 }]}>
|
||||||
|
{isWebsite ? (
|
||||||
|
<>
|
||||||
|
<PDFView style={[styles.industrialCol, { marginRight: "8%" }]}>
|
||||||
|
<PDFText style={styles.industrialTextLead}>
|
||||||
|
Über die klassische Webpräsenz hinaus werden maßgeschneiderte
|
||||||
|
Lösungen zur Automatisierung von Routine-Prozessen angeboten.
|
||||||
|
Dies ermöglicht eine signifikante Effizienzsteigerung im
|
||||||
|
Tagesgeschäft.
|
||||||
|
</PDFText>
|
||||||
|
<PDFText style={[styles.industrialText, { fontWeight: "bold" }]}>
|
||||||
|
Keine Abos. Keine komplexen neuen Systeme. Gezielte
|
||||||
|
Zeitersparnis.
|
||||||
|
</PDFText>
|
||||||
|
<PDFView
|
||||||
|
style={{
|
||||||
|
marginTop: 24,
|
||||||
|
padding: 16,
|
||||||
|
backgroundColor: "#f8fafc",
|
||||||
|
borderLeftWidth: 2,
|
||||||
|
borderLeftColor: COLORS.GRID,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PDFText
|
||||||
|
style={[
|
||||||
|
styles.industrialText,
|
||||||
|
{
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: COLORS.CHARCOAL,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
Individuelle Analyse
|
||||||
|
</PDFText>
|
||||||
|
<PDFText style={styles.industrialText}>
|
||||||
|
Spezifische Prozesse werden auf technisches
|
||||||
|
Automatisierungspotenzial untersucht. Das Ergebnis liefert
|
||||||
|
Klarheit über die wirtschaftliche Sinnhaftigkeit einer
|
||||||
|
Umsetzung.
|
||||||
|
</PDFText>
|
||||||
|
</PDFView>
|
||||||
|
</PDFView>
|
||||||
|
<PDFView style={styles.industrialCol}>
|
||||||
|
<IndustrialCard title="DOKUMENT-AUTOMATION">
|
||||||
|
<PDFText style={styles.industrialText}>
|
||||||
|
Erstellung von PDF-Angeboten, Berichten oder Protokollen in
|
||||||
|
Sekunden statt Stunden.
|
||||||
|
</PDFText>
|
||||||
|
</IndustrialCard>
|
||||||
|
<IndustrialCard title="EXCEL-LOGIK">
|
||||||
|
<PDFText style={styles.industrialText}>
|
||||||
|
Intelligente Tabellen und automatisierte Auswertungen
|
||||||
|
bestehender Datensätze.
|
||||||
|
</PDFText>
|
||||||
|
</IndustrialCard>
|
||||||
|
<IndustrialCard title="KI-ASSISTENZ">
|
||||||
|
<PDFText style={styles.industrialText}>
|
||||||
|
Effiziente Verarbeitung von analogen Dokumenten oder
|
||||||
|
handschriftlichen Notizen mittels KI.
|
||||||
|
</PDFText>
|
||||||
|
</IndustrialCard>
|
||||||
|
</PDFView>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<PDFView style={{ width: "100%" }}>
|
||||||
|
<PDFText style={styles.industrialTextLead}>
|
||||||
|
Bereitstellung einer stabilen technischen Basis ohne
|
||||||
|
Abhängigkeiten von Baukasten-Systemen oder Agenturen.
|
||||||
|
</PDFText>
|
||||||
|
<PDFText style={styles.industrialText}>
|
||||||
|
Entwicklung performanter Frontends und skalierbarer Backends. Die
|
||||||
|
Auslieferung erfolgt als kontrollierbarer und nachhaltiger
|
||||||
|
Quellcode.
|
||||||
|
</PDFText>
|
||||||
|
</PDFView>
|
||||||
|
)}
|
||||||
|
</PDFView>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import {
|
||||||
|
View as PDFView,
|
||||||
|
Text as PDFText,
|
||||||
|
StyleSheet,
|
||||||
|
} from "@react-pdf/renderer";
|
||||||
|
import { DocumentTitle, COLORS, FONT_SIZES } from "../SharedUI";
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
section: { marginBottom: 24 },
|
||||||
|
sectionTitle: {
|
||||||
|
fontSize: FONT_SIZES.LABEL,
|
||||||
|
fontWeight: "bold",
|
||||||
|
marginBottom: 8,
|
||||||
|
color: COLORS.CHARCOAL,
|
||||||
|
},
|
||||||
|
visionText: {
|
||||||
|
fontSize: FONT_SIZES.BODY,
|
||||||
|
color: COLORS.TEXT_MAIN,
|
||||||
|
lineHeight: 1.4,
|
||||||
|
textAlign: "justify",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const BriefingModule = ({ state }: any) => (
|
||||||
|
<>
|
||||||
|
<DocumentTitle title="Projektdetails" isHero={true} />
|
||||||
|
{state.briefingSummary && (
|
||||||
|
<PDFView style={styles.section}>
|
||||||
|
<PDFText style={styles.sectionTitle}>Briefing Analyse</PDFText>
|
||||||
|
<PDFText
|
||||||
|
style={{
|
||||||
|
fontSize: FONT_SIZES.BODY,
|
||||||
|
color: COLORS.TEXT_MAIN,
|
||||||
|
lineHeight: 1.6,
|
||||||
|
textAlign: "justify",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{state.briefingSummary}
|
||||||
|
</PDFText>
|
||||||
|
</PDFView>
|
||||||
|
)}
|
||||||
|
{state.designVision && (
|
||||||
|
<PDFView
|
||||||
|
style={[
|
||||||
|
styles.section,
|
||||||
|
{
|
||||||
|
padding: 12,
|
||||||
|
borderLeftWidth: 2,
|
||||||
|
borderLeftColor: COLORS.DIVIDER,
|
||||||
|
backgroundColor: COLORS.GRID,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<PDFText
|
||||||
|
style={[
|
||||||
|
styles.sectionTitle,
|
||||||
|
{ color: COLORS.CHARCOAL, marginBottom: 4 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
Strategische Vision
|
||||||
|
</PDFText>
|
||||||
|
<PDFText style={styles.visionText}>{state.designVision}</PDFText>
|
||||||
|
</PDFView>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import {
|
||||||
|
View as PDFView,
|
||||||
|
Text as PDFText,
|
||||||
|
StyleSheet,
|
||||||
|
} from "@react-pdf/renderer";
|
||||||
|
import { DocumentTitle, COLORS, FONT_SIZES } from "../SharedUI";
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
section: { marginBottom: 32 },
|
||||||
|
intro: {
|
||||||
|
fontSize: FONT_SIZES.BODY,
|
||||||
|
color: COLORS.TEXT_DIM,
|
||||||
|
lineHeight: 1.4,
|
||||||
|
marginBottom: 24,
|
||||||
|
textAlign: "justify",
|
||||||
|
},
|
||||||
|
sitemapTree: { marginTop: 8 },
|
||||||
|
rootNode: {
|
||||||
|
padding: 12,
|
||||||
|
backgroundColor: COLORS.GRID,
|
||||||
|
marginBottom: 20,
|
||||||
|
borderLeftWidth: 2,
|
||||||
|
borderLeftColor: COLORS.CHARCOAL,
|
||||||
|
},
|
||||||
|
rootTitle: {
|
||||||
|
fontSize: FONT_SIZES.HEADING,
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: COLORS.CHARCOAL,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
},
|
||||||
|
categorySection: { marginBottom: 20 },
|
||||||
|
categoryHeader: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
paddingBottom: 6,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: COLORS.BLUEPRINT,
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
categoryIcon: {
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
backgroundColor: COLORS.GRID,
|
||||||
|
borderInlineWidth: 1,
|
||||||
|
borderColor: COLORS.DIVIDER,
|
||||||
|
marginRight: 10,
|
||||||
|
},
|
||||||
|
categoryTitle: {
|
||||||
|
fontSize: FONT_SIZES.BODY,
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: COLORS.CHARCOAL,
|
||||||
|
textTransform: "uppercase",
|
||||||
|
letterSpacing: 1,
|
||||||
|
},
|
||||||
|
pagesGrid: { flexDirection: "row", flexWrap: "wrap" },
|
||||||
|
pageCard: {
|
||||||
|
width: "48%",
|
||||||
|
marginRight: "2%",
|
||||||
|
marginBottom: 12,
|
||||||
|
padding: 10,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: COLORS.GRID,
|
||||||
|
backgroundColor: "#fafafa",
|
||||||
|
},
|
||||||
|
pageTitle: {
|
||||||
|
fontSize: FONT_SIZES.BODY,
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: COLORS.TEXT_MAIN,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
pageDesc: {
|
||||||
|
fontSize: FONT_SIZES.TINY,
|
||||||
|
color: COLORS.TEXT_DIM,
|
||||||
|
lineHeight: 1.3,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SitemapModule = ({ state }: any) => (
|
||||||
|
<>
|
||||||
|
<DocumentTitle title="Informationsarchitektur" isHero={true} />
|
||||||
|
<PDFView style={styles.section}>
|
||||||
|
<PDFText style={styles.intro}>
|
||||||
|
Die folgende Struktur definiert die logische Hierarchie und
|
||||||
|
Benutzerführung. Sie dient als Bauplan für die technische Umsetzung und
|
||||||
|
stellt sicher, dass alle relevanten Geschäftsbereiche intuitiv
|
||||||
|
auffindbar sind.
|
||||||
|
</PDFText>
|
||||||
|
|
||||||
|
<PDFView style={styles.sitemapTree}>
|
||||||
|
<PDFView style={styles.rootNode}>
|
||||||
|
<PDFText style={styles.rootTitle}>Seitenstruktur</PDFText>
|
||||||
|
</PDFView>
|
||||||
|
|
||||||
|
{state.sitemap?.map((cat: any, i: number) => (
|
||||||
|
<PDFView key={i} style={styles.categorySection} wrap={false}>
|
||||||
|
<PDFView style={styles.categoryHeader}>
|
||||||
|
<PDFView style={styles.categoryIcon} />
|
||||||
|
<PDFText style={styles.categoryTitle}>{cat.category}</PDFText>
|
||||||
|
</PDFView>
|
||||||
|
|
||||||
|
<PDFView style={styles.pagesGrid}>
|
||||||
|
{cat.pages.map((p: any, j: number) => (
|
||||||
|
<PDFView
|
||||||
|
key={j}
|
||||||
|
style={[
|
||||||
|
styles.pageCard,
|
||||||
|
j % 2 === 1 ? { marginRight: 0 } : {},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<PDFText style={styles.pageTitle}>{p.title}</PDFText>
|
||||||
|
{p.desc && (
|
||||||
|
<PDFText style={styles.pageDesc}>{p.desc}</PDFText>
|
||||||
|
)}
|
||||||
|
</PDFView>
|
||||||
|
))}
|
||||||
|
</PDFView>
|
||||||
|
</PDFView>
|
||||||
|
))}
|
||||||
|
</PDFView>
|
||||||
|
</PDFView>
|
||||||
|
</>
|
||||||
|
);
|
||||||
15
packages/pdf-library/src/index.ts
Normal file
15
packages/pdf-library/src/index.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export * from "./logic/pricing/types.js";
|
||||||
|
export * from "./logic/pricing/constants.js";
|
||||||
|
export * from "./logic/pricing/calculator.js";
|
||||||
|
export * from "./components/EstimationPDF.js";
|
||||||
|
export * from "./components/pdf/SimpleLayout.js";
|
||||||
|
export * from "./components/pdf/SharedUI.js";
|
||||||
|
export * from "./components/pdf/modules/FrontPageModule.js";
|
||||||
|
export * from "./components/pdf/modules/BriefingModule.js";
|
||||||
|
export * from "./components/pdf/modules/SitemapModule.js";
|
||||||
|
export * from "./components/pdf/modules/EstimationModule.js";
|
||||||
|
export * from "./components/pdf/modules/CommonModules.js";
|
||||||
|
export * from "./components/pdf/modules/BrandingModules.js";
|
||||||
|
export * from "./components/pdf/modules/TransparenzModule.js";
|
||||||
|
export * from "./components/AgbsPDF.js";
|
||||||
|
export * from "./components/CombinedQuotePDF.js";
|
||||||
3
packages/pdf-library/src/server.ts
Normal file
3
packages/pdf-library/src/server.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./index.js";
|
||||||
|
export * from "./services/AcquisitionService.js";
|
||||||
|
export * from "./services/PdfEngine.js";
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CheerioCrawler } from "crawlee";
|
import { CheerioCrawler } from "@crawlee/cheerio";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { FileCacheAdapter } from "../utils/cache/FileCacheAdapter.js";
|
import { FileCacheAdapter } from "../utils/cache/FileCacheAdapter.js";
|
||||||
import { initialState } from "../logic/pricing/constants.js";
|
import { initialState } from "../logic/pricing/constants.js";
|
||||||
24
packages/pdf-library/tsconfig.json
Normal file
24
packages/pdf-library/tsconfig.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationDir": "dist",
|
||||||
|
"emitDeclarationOnly": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"outDir": "dist"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"dist",
|
||||||
|
"build.mjs"
|
||||||
|
]
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@
|
|||||||
"name": "people-manager",
|
"name": "people-manager",
|
||||||
"description": "Custom High-Fidelity People Management for Directus",
|
"description": "Custom High-Fidelity People Management for Directus",
|
||||||
"icon": "person",
|
"icon": "person",
|
||||||
"version": "1.7.12",
|
"version": "1.8.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"directus",
|
"directus",
|
||||||
|
|||||||
@@ -64,8 +64,8 @@
|
|||||||
|
|
||||||
<div class="details-grid">
|
<div class="details-grid">
|
||||||
<div class="detail-item">
|
<div class="detail-item">
|
||||||
<span class="label">Organisation</span>
|
<span class="label">Organisation / Firma</span>
|
||||||
<p class="value">{{ selectedPerson.company || '---' }}</p>
|
<p class="value">{{ getCompanyName(selectedPerson) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="detail-item">
|
<div class="detail-item">
|
||||||
<span class="label">Telefon</span>
|
<span class="label">Telefon</span>
|
||||||
@@ -98,8 +98,16 @@
|
|||||||
<v-input v-model="form.email" placeholder="email@beispiel.de" type="email" />
|
<v-input v-model="form.email" placeholder="email@beispiel.de" type="email" />
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<span class="label">Organisation / Firma</span>
|
<span class="label">Zentrale Firma</span>
|
||||||
<v-input v-model="form.company" placeholder="z.B. Mintel" />
|
<v-select
|
||||||
|
v-model="form.company"
|
||||||
|
:items="companyOptions"
|
||||||
|
placeholder="Bestehende Firma auswählen..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<span class="label">Firma (Legacy / Neu)</span>
|
||||||
|
<v-input v-model="form.company_name" placeholder="z.B. Mintel" />
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<span class="label">Telefon</span>
|
<span class="label">Telefon</span>
|
||||||
@@ -119,11 +127,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted, computed } from 'vue';
|
||||||
import { useApi } from '@directus/extensions-sdk';
|
import { useApi } from '@directus/extensions-sdk';
|
||||||
|
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
const people = ref([]);
|
const people = ref([]);
|
||||||
|
const companies = ref([]);
|
||||||
const selectedPerson = ref(null);
|
const selectedPerson = ref(null);
|
||||||
const feedback = ref(null);
|
const feedback = ref(null);
|
||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
@@ -135,18 +144,43 @@ const form = ref({
|
|||||||
first_name: '',
|
first_name: '',
|
||||||
last_name: '',
|
last_name: '',
|
||||||
email: '',
|
email: '',
|
||||||
company: '',
|
company: null,
|
||||||
|
company_name: '',
|
||||||
phone: ''
|
phone: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
async function fetchPeople() {
|
const companyOptions = computed(() =>
|
||||||
|
companies.value.map(c => ({
|
||||||
|
text: c.name,
|
||||||
|
value: c.id
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
function getCompanyName(person: any) {
|
||||||
|
if (!person) return '---';
|
||||||
|
if (person.company) {
|
||||||
|
return typeof person.company === 'object' ? person.company.name : (companies.value.find(c => c.id === person.company)?.name || person.company_name);
|
||||||
|
}
|
||||||
|
return person.company_name || '---';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchData() {
|
||||||
try {
|
try {
|
||||||
const response = await api.get('/items/people', {
|
const [peopleResp, companiesResp] = await Promise.all([
|
||||||
params: { sort: 'last_name' }
|
api.get('/items/people', {
|
||||||
});
|
params: {
|
||||||
people.ref = response.data.data;
|
sort: 'last_name',
|
||||||
|
fields: '*.*'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
api.get('/items/companies', {
|
||||||
|
params: { sort: 'name' }
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
people.value = peopleResp.data.data;
|
||||||
|
companies.value = companiesResp.data.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch people:', error);
|
console.error('Failed to fetch data:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,13 +190,39 @@ function selectPerson(person) {
|
|||||||
|
|
||||||
function openCreateDrawer() {
|
function openCreateDrawer() {
|
||||||
isEditing.value = false;
|
isEditing.value = false;
|
||||||
form.value = { id: null, first_name: '', last_name: '', email: '', company: '', phone: '' };
|
form.value = {
|
||||||
|
id: null,
|
||||||
|
first_name: '',
|
||||||
|
last_name: '',
|
||||||
|
email: '',
|
||||||
|
company: null,
|
||||||
|
company_name: '',
|
||||||
|
phone: ''
|
||||||
|
};
|
||||||
drawerActive.value = true;
|
drawerActive.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function openEditDrawer() {
|
function openEditDrawer() {
|
||||||
isEditing.value = true;
|
isEditing.value = true;
|
||||||
form.value = { ...selectedPerson.value };
|
const person = selectedPerson.value;
|
||||||
|
let companyId = null;
|
||||||
|
let companyName = person.company_name || '';
|
||||||
|
|
||||||
|
if (person.company) {
|
||||||
|
if (typeof person.company === 'object') {
|
||||||
|
companyId = person.company.id;
|
||||||
|
} else if (person.company.length === 36) { // Assume UUID
|
||||||
|
companyId = person.company;
|
||||||
|
} else {
|
||||||
|
companyName = person.company;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form.value = {
|
||||||
|
...person,
|
||||||
|
company: companyId,
|
||||||
|
company_name: companyName
|
||||||
|
};
|
||||||
drawerActive.value = true;
|
drawerActive.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,9 +242,9 @@ async function savePerson() {
|
|||||||
feedback.value = { type: 'success', message: 'Person angelegt!' };
|
feedback.value = { type: 'success', message: 'Person angelegt!' };
|
||||||
}
|
}
|
||||||
drawerActive.value = false;
|
drawerActive.value = false;
|
||||||
await fetchPeople();
|
await fetchData();
|
||||||
if (isEditing.value) {
|
if (isEditing.value) {
|
||||||
selectedPerson.value = form.value;
|
selectedPerson.value = people.value.find(p => p.id === form.value.id);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
feedback.value = { type: 'danger', message: error.message };
|
feedback.value = { type: 'danger', message: error.message };
|
||||||
@@ -200,13 +260,13 @@ async function deletePerson() {
|
|||||||
await api.delete(`/items/people/${selectedPerson.value.id}`);
|
await api.delete(`/items/people/${selectedPerson.value.id}`);
|
||||||
feedback.value = { type: 'success', message: 'Person gelöscht.' };
|
feedback.value = { type: 'success', message: 'Person gelöscht.' };
|
||||||
selectedPerson.value = null;
|
selectedPerson.value = null;
|
||||||
await fetchPeople();
|
await fetchData();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
feedback.value = { type: 'danger', message: error.message };
|
feedback.value = { type: 'danger', message: error.message };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(fetchPeople);
|
onMounted(fetchData);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mintel/tsconfig",
|
"name": "@mintel/tsconfig",
|
||||||
"version": "1.7.12",
|
"version": "1.8.0",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"registry": "https://npm.infra.mintel.me"
|
"registry": "https://npm.infra.mintel.me"
|
||||||
|
|||||||
371
pnpm-lock.yaml
generated
371
pnpm-lock.yaml
generated
@@ -158,50 +158,16 @@ importers:
|
|||||||
react-dom:
|
react-dom:
|
||||||
specifier: ^19.2.4
|
specifier: ^19.2.4
|
||||||
version: 19.2.4(react@19.2.4)
|
version: 19.2.4(react@19.2.4)
|
||||||
devDependencies:
|
|
||||||
'@directus/extensions-sdk':
|
|
||||||
specifier: 11.0.2
|
|
||||||
version: 11.0.2(@types/node@22.19.10)(@unhead/vue@1.11.20(vue@3.4.21(typescript@5.9.3)))(knex@3.1.0)(lightningcss@1.30.2)(pinia@2.3.1(typescript@5.9.3)(vue@3.4.21(typescript@5.9.3)))(pino@10.3.1)(sass@1.97.3)(terser@5.46.0)(typescript@5.9.3)
|
|
||||||
'@mintel/acquisition':
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../acquisition-library
|
|
||||||
'@mintel/mail':
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../mail
|
|
||||||
esbuild:
|
|
||||||
specifier: ^0.25.0
|
|
||||||
version: 0.25.12
|
|
||||||
typescript:
|
|
||||||
specifier: ^5.6.3
|
|
||||||
version: 5.9.3
|
|
||||||
|
|
||||||
packages/acquisition-library:
|
|
||||||
dependencies:
|
|
||||||
'@mintel/mail':
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../mail
|
|
||||||
'@react-pdf/renderer':
|
|
||||||
specifier: ^4.3.0
|
|
||||||
version: 4.3.2(react@19.2.4)
|
|
||||||
axios:
|
|
||||||
specifier: ^1.7.9
|
|
||||||
version: 1.13.5
|
|
||||||
cheerio:
|
|
||||||
specifier: ^1.0.0
|
|
||||||
version: 1.2.0
|
|
||||||
crawlee:
|
|
||||||
specifier: ^3.12.2
|
|
||||||
version: 3.16.0(@types/node@22.19.10)
|
|
||||||
react:
|
|
||||||
specifier: ^19.2.4
|
|
||||||
version: 19.2.4
|
|
||||||
react-dom:
|
|
||||||
specifier: ^19.2.4
|
|
||||||
version: 19.2.4(react@19.2.4)
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@directus/extensions-sdk':
|
'@directus/extensions-sdk':
|
||||||
specifier: 11.0.2
|
specifier: 11.0.2
|
||||||
version: 11.0.2(@types/node@22.19.10)(@unhead/vue@1.11.20(vue@3.5.28(typescript@5.9.3)))(knex@3.1.0)(lightningcss@1.30.2)(pinia@2.3.1(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3)))(pino@10.3.1)(sass@1.97.3)(terser@5.46.0)(typescript@5.9.3)
|
version: 11.0.2(@types/node@22.19.10)(@unhead/vue@1.11.20(vue@3.5.28(typescript@5.9.3)))(knex@3.1.0)(lightningcss@1.30.2)(pinia@2.3.1(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3)))(pino@10.3.1)(sass@1.97.3)(terser@5.46.0)(typescript@5.9.3)
|
||||||
|
'@mintel/mail':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../mail
|
||||||
|
'@mintel/pdf':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../pdf-library
|
||||||
esbuild:
|
esbuild:
|
||||||
specifier: ^0.25.0
|
specifier: ^0.25.0
|
||||||
version: 0.25.12
|
version: 0.25.12
|
||||||
@@ -249,8 +215,42 @@ importers:
|
|||||||
specifier: ^5.0.0
|
specifier: ^5.0.0
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
|
||||||
|
packages/cloner-library:
|
||||||
|
dependencies:
|
||||||
|
axios:
|
||||||
|
specifier: ^1.6.0
|
||||||
|
version: 1.13.5
|
||||||
|
cheerio:
|
||||||
|
specifier: ^1.0.0-rc.12
|
||||||
|
version: 1.2.0
|
||||||
|
crawlee:
|
||||||
|
specifier: ^3.7.0
|
||||||
|
version: 3.16.0(@types/node@22.19.10)(playwright@1.58.2)
|
||||||
|
playwright:
|
||||||
|
specifier: ^1.40.0
|
||||||
|
version: 1.58.2
|
||||||
|
devDependencies:
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^22.0.0
|
||||||
|
version: 22.19.10
|
||||||
|
esbuild:
|
||||||
|
specifier: ^0.25.0
|
||||||
|
version: 0.25.12
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.6.3
|
||||||
|
version: 5.9.3
|
||||||
|
|
||||||
packages/cms-infra: {}
|
packages/cms-infra: {}
|
||||||
|
|
||||||
|
packages/company-manager:
|
||||||
|
devDependencies:
|
||||||
|
'@directus/extensions-sdk':
|
||||||
|
specifier: 11.0.2
|
||||||
|
version: 11.0.2(@types/node@22.19.10)(@unhead/vue@1.11.20(vue@3.5.28(typescript@5.9.3)))(knex@3.1.0)(lightningcss@1.30.2)(pinia@2.3.1(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3)))(pino@10.3.1)(sass@1.97.3)(terser@5.46.0)(typescript@5.9.3)
|
||||||
|
vue:
|
||||||
|
specifier: ^3.4.0
|
||||||
|
version: 3.5.28(typescript@5.9.3)
|
||||||
|
|
||||||
packages/customer-manager:
|
packages/customer-manager:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@directus/extensions-sdk':
|
'@directus/extensions-sdk':
|
||||||
@@ -260,6 +260,21 @@ importers:
|
|||||||
specifier: ^3.4.0
|
specifier: ^3.4.0
|
||||||
version: 3.5.28(typescript@5.9.3)
|
version: 3.5.28(typescript@5.9.3)
|
||||||
|
|
||||||
|
packages/directus-extension-toolkit:
|
||||||
|
devDependencies:
|
||||||
|
'@directus/extensions-sdk':
|
||||||
|
specifier: 11.0.2
|
||||||
|
version: 11.0.2(@types/node@22.19.10)(@unhead/vue@1.11.20(vue@3.5.28(typescript@5.9.3)))(knex@3.1.0)(lightningcss@1.30.2)(pinia@2.3.1(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3)))(pino@10.3.1)(sass@1.97.3)(terser@5.46.0)(typescript@5.9.3)
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.0.0
|
||||||
|
version: 5.9.3
|
||||||
|
vite:
|
||||||
|
specifier: ^5.0.0
|
||||||
|
version: 5.4.21(@types/node@22.19.10)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
|
||||||
|
vue:
|
||||||
|
specifier: ^3.4.0
|
||||||
|
version: 3.5.28(typescript@5.9.3)
|
||||||
|
|
||||||
packages/eslint-config:
|
packages/eslint-config:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint/eslintrc':
|
'@eslint/eslintrc':
|
||||||
@@ -410,7 +425,7 @@ importers:
|
|||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^3.0.4
|
specifier: ^3.0.4
|
||||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.10)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
|
version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.10)(@vitest/ui@4.0.18(vitest@4.0.18))(happy-dom@20.5.3)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
|
||||||
|
|
||||||
packages/next-config:
|
packages/next-config:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -429,6 +444,9 @@ importers:
|
|||||||
'@directus/sdk':
|
'@directus/sdk':
|
||||||
specifier: ^21.0.0
|
specifier: ^21.0.0
|
||||||
version: 21.1.0
|
version: 21.1.0
|
||||||
|
'@medv/finder':
|
||||||
|
specifier: ^4.0.2
|
||||||
|
version: 4.0.2
|
||||||
clsx:
|
clsx:
|
||||||
specifier: ^2.1.1
|
specifier: ^2.1.1
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
@@ -569,7 +587,41 @@ importers:
|
|||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^2.0.0
|
specifier: ^2.0.0
|
||||||
version: 2.1.9(@types/node@22.19.10)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
|
version: 2.1.9(@types/node@22.19.10)(@vitest/ui@4.0.18(vitest@4.0.18))(happy-dom@20.5.3)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
|
||||||
|
|
||||||
|
packages/pdf-library:
|
||||||
|
dependencies:
|
||||||
|
'@crawlee/cheerio':
|
||||||
|
specifier: ^3.16.0
|
||||||
|
version: 3.16.0
|
||||||
|
'@mintel/mail':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../mail
|
||||||
|
'@react-pdf/renderer':
|
||||||
|
specifier: ^4.3.0
|
||||||
|
version: 4.3.2(react@19.2.4)
|
||||||
|
axios:
|
||||||
|
specifier: ^1.7.9
|
||||||
|
version: 1.13.5
|
||||||
|
cheerio:
|
||||||
|
specifier: ^1.0.0
|
||||||
|
version: 1.2.0
|
||||||
|
react:
|
||||||
|
specifier: ^19.2.4
|
||||||
|
version: 19.2.4
|
||||||
|
react-dom:
|
||||||
|
specifier: ^19.2.4
|
||||||
|
version: 19.2.4(react@19.2.4)
|
||||||
|
devDependencies:
|
||||||
|
'@directus/extensions-sdk':
|
||||||
|
specifier: 11.0.2
|
||||||
|
version: 11.0.2(@types/node@22.19.10)(@unhead/vue@1.11.20(vue@3.5.28(typescript@5.9.3)))(knex@3.1.0)(lightningcss@1.30.2)(pinia@2.3.1(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3)))(pino@10.3.1)(sass@1.97.3)(terser@5.46.0)(typescript@5.9.3)
|
||||||
|
esbuild:
|
||||||
|
specifier: ^0.25.0
|
||||||
|
version: 0.25.12
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.6.3
|
||||||
|
version: 5.9.3
|
||||||
|
|
||||||
packages/people-manager:
|
packages/people-manager:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
@@ -582,6 +634,15 @@ importers:
|
|||||||
|
|
||||||
packages/tsconfig: {}
|
packages/tsconfig: {}
|
||||||
|
|
||||||
|
packages/unified-dashboard:
|
||||||
|
devDependencies:
|
||||||
|
'@directus/extensions-sdk':
|
||||||
|
specifier: 11.0.2
|
||||||
|
version: 11.0.2(@types/node@22.19.10)(@unhead/vue@1.11.20(vue@3.5.28(typescript@5.9.3)))(knex@3.1.0)(lightningcss@1.30.2)(pinia@2.3.1(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3)))(pino@10.3.1)(sass@1.97.3)(terser@5.46.0)(typescript@5.9.3)
|
||||||
|
vue:
|
||||||
|
specifier: ^3.4.0
|
||||||
|
version: 3.5.28(typescript@5.9.3)
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
'@acemir/cssom@0.9.31':
|
'@acemir/cssom@0.9.31':
|
||||||
@@ -2059,6 +2120,9 @@ packages:
|
|||||||
'@manypkg/get-packages@1.1.3':
|
'@manypkg/get-packages@1.1.3':
|
||||||
resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==}
|
resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==}
|
||||||
|
|
||||||
|
'@medv/finder@4.0.2':
|
||||||
|
resolution: {integrity: sha512-RraNY9SCcx4KZV0Dh6BEW6XEW2swkqYca74pkFFRw6hHItSHiy+O/xMnpbofjYbzXj0tSpBGthUF1hHTsr3vIQ==}
|
||||||
|
|
||||||
'@napi-rs/wasm-runtime@0.2.12':
|
'@napi-rs/wasm-runtime@0.2.12':
|
||||||
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
|
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
|
||||||
|
|
||||||
@@ -4928,6 +4992,11 @@ packages:
|
|||||||
fs.realpath@1.0.0:
|
fs.realpath@1.0.0:
|
||||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||||
|
|
||||||
|
fsevents@2.3.2:
|
||||||
|
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||||
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
@@ -6364,6 +6433,16 @@ packages:
|
|||||||
pkg-types@1.3.1:
|
pkg-types@1.3.1:
|
||||||
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
|
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
|
||||||
|
|
||||||
|
playwright-core@1.58.2:
|
||||||
|
resolution: {integrity: sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
playwright@1.58.2:
|
||||||
|
resolution: {integrity: sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
po-parser@2.1.1:
|
po-parser@2.1.1:
|
||||||
resolution: {integrity: sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==}
|
resolution: {integrity: sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==}
|
||||||
|
|
||||||
@@ -8440,14 +8519,14 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@crawlee/browser-pool@3.16.0':
|
'@crawlee/browser-pool@3.16.0(playwright@1.58.2)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@apify/log': 2.5.32
|
'@apify/log': 2.5.32
|
||||||
'@apify/timeout': 0.3.2
|
'@apify/timeout': 0.3.2
|
||||||
'@crawlee/core': 3.16.0
|
'@crawlee/core': 3.16.0
|
||||||
'@crawlee/types': 3.16.0
|
'@crawlee/types': 3.16.0
|
||||||
fingerprint-generator: 2.1.80
|
fingerprint-generator: 2.1.80
|
||||||
fingerprint-injector: 2.1.80
|
fingerprint-injector: 2.1.80(playwright@1.58.2)
|
||||||
lodash.merge: 4.6.2
|
lodash.merge: 4.6.2
|
||||||
nanoid: 3.3.11
|
nanoid: 3.3.11
|
||||||
ow: 0.28.2
|
ow: 0.28.2
|
||||||
@@ -8456,19 +8535,23 @@ snapshots:
|
|||||||
quick-lru: 5.1.1
|
quick-lru: 5.1.1
|
||||||
tiny-typed-emitter: 2.1.0
|
tiny-typed-emitter: 2.1.0
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
optionalDependencies:
|
||||||
|
playwright: 1.58.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@crawlee/browser@3.16.0':
|
'@crawlee/browser@3.16.0(playwright@1.58.2)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@apify/timeout': 0.3.2
|
'@apify/timeout': 0.3.2
|
||||||
'@crawlee/basic': 3.16.0
|
'@crawlee/basic': 3.16.0
|
||||||
'@crawlee/browser-pool': 3.16.0
|
'@crawlee/browser-pool': 3.16.0(playwright@1.58.2)
|
||||||
'@crawlee/types': 3.16.0
|
'@crawlee/types': 3.16.0
|
||||||
'@crawlee/utils': 3.16.0
|
'@crawlee/utils': 3.16.0
|
||||||
ow: 0.28.2
|
ow: 0.28.2
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
type-fest: 4.41.0
|
type-fest: 4.41.0
|
||||||
|
optionalDependencies:
|
||||||
|
playwright: 1.58.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -8585,13 +8668,13 @@ snapshots:
|
|||||||
proper-lockfile: 4.1.2
|
proper-lockfile: 4.1.2
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@crawlee/playwright@3.16.0':
|
'@crawlee/playwright@3.16.0(playwright@1.58.2)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@apify/datastructures': 2.0.3
|
'@apify/datastructures': 2.0.3
|
||||||
'@apify/log': 2.5.32
|
'@apify/log': 2.5.32
|
||||||
'@apify/timeout': 0.3.2
|
'@apify/timeout': 0.3.2
|
||||||
'@crawlee/browser': 3.16.0
|
'@crawlee/browser': 3.16.0(playwright@1.58.2)
|
||||||
'@crawlee/browser-pool': 3.16.0
|
'@crawlee/browser-pool': 3.16.0(playwright@1.58.2)
|
||||||
'@crawlee/core': 3.16.0
|
'@crawlee/core': 3.16.0
|
||||||
'@crawlee/types': 3.16.0
|
'@crawlee/types': 3.16.0
|
||||||
'@crawlee/utils': 3.16.0
|
'@crawlee/utils': 3.16.0
|
||||||
@@ -8603,16 +8686,18 @@ snapshots:
|
|||||||
ow: 0.28.2
|
ow: 0.28.2
|
||||||
string-comparison: 1.3.0
|
string-comparison: 1.3.0
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
optionalDependencies:
|
||||||
|
playwright: 1.58.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- puppeteer
|
- puppeteer
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@crawlee/puppeteer@3.16.0':
|
'@crawlee/puppeteer@3.16.0(playwright@1.58.2)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@apify/datastructures': 2.0.3
|
'@apify/datastructures': 2.0.3
|
||||||
'@apify/log': 2.5.32
|
'@apify/log': 2.5.32
|
||||||
'@crawlee/browser': 3.16.0
|
'@crawlee/browser': 3.16.0(playwright@1.58.2)
|
||||||
'@crawlee/browser-pool': 3.16.0
|
'@crawlee/browser-pool': 3.16.0(playwright@1.58.2)
|
||||||
'@crawlee/types': 3.16.0
|
'@crawlee/types': 3.16.0
|
||||||
'@crawlee/utils': 3.16.0
|
'@crawlee/utils': 3.16.0
|
||||||
cheerio: 1.0.0-rc.12
|
cheerio: 1.0.0-rc.12
|
||||||
@@ -8708,57 +8793,6 @@ snapshots:
|
|||||||
|
|
||||||
'@directus/constants@11.0.3': {}
|
'@directus/constants@11.0.3': {}
|
||||||
|
|
||||||
'@directus/extensions-sdk@11.0.2(@types/node@22.19.10)(@unhead/vue@1.11.20(vue@3.4.21(typescript@5.9.3)))(knex@3.1.0)(lightningcss@1.30.2)(pinia@2.3.1(typescript@5.9.3)(vue@3.4.21(typescript@5.9.3)))(pino@10.3.1)(sass@1.97.3)(terser@5.46.0)(typescript@5.9.3)':
|
|
||||||
dependencies:
|
|
||||||
'@directus/composables': 10.1.12(vue@3.4.21(typescript@5.9.3))
|
|
||||||
'@directus/constants': 11.0.3
|
|
||||||
'@directus/extensions': 1.0.2(@unhead/vue@1.11.20(vue@3.4.21(typescript@5.9.3)))(knex@3.1.0)(pinia@2.3.1(typescript@5.9.3)(vue@3.4.21(typescript@5.9.3)))(pino@10.3.1)(vue@3.4.21(typescript@5.9.3))
|
|
||||||
'@directus/themes': 0.3.6(@unhead/vue@1.11.20(vue@3.4.21(typescript@5.9.3)))(pinia@2.3.1(typescript@5.9.3)(vue@3.4.21(typescript@5.9.3)))(vue@3.4.21(typescript@5.9.3))
|
|
||||||
'@directus/types': 11.0.8(knex@3.1.0)(vue@3.4.21(typescript@5.9.3))
|
|
||||||
'@directus/utils': 11.0.7(vue@3.4.21(typescript@5.9.3))
|
|
||||||
'@rollup/plugin-commonjs': 25.0.7(rollup@3.29.4)
|
|
||||||
'@rollup/plugin-json': 6.1.0(rollup@3.29.4)
|
|
||||||
'@rollup/plugin-node-resolve': 15.2.3(rollup@3.29.4)
|
|
||||||
'@rollup/plugin-replace': 5.0.5(rollup@3.29.4)
|
|
||||||
'@rollup/plugin-terser': 0.4.4(rollup@3.29.4)
|
|
||||||
'@rollup/plugin-virtual': 3.0.2(rollup@3.29.4)
|
|
||||||
'@vitejs/plugin-vue': 4.6.2(vite@4.5.2(@types/node@22.19.10)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0))(vue@3.4.21(typescript@5.9.3))
|
|
||||||
chalk: 5.3.0
|
|
||||||
commander: 10.0.1
|
|
||||||
esbuild: 0.17.19
|
|
||||||
execa: 7.2.0
|
|
||||||
fs-extra: 11.2.0
|
|
||||||
inquirer: 9.2.16
|
|
||||||
ora: 6.3.1
|
|
||||||
rollup: 3.29.4
|
|
||||||
rollup-plugin-esbuild: 5.0.0(esbuild@0.17.19)(rollup@3.29.4)
|
|
||||||
rollup-plugin-styles: 4.0.0(rollup@3.29.4)
|
|
||||||
vite: 4.5.2(@types/node@22.19.10)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
|
|
||||||
vue: 3.4.21(typescript@5.9.3)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@types/node'
|
|
||||||
- '@unhead/vue'
|
|
||||||
- better-sqlite3
|
|
||||||
- debug
|
|
||||||
- knex
|
|
||||||
- less
|
|
||||||
- lightningcss
|
|
||||||
- mysql
|
|
||||||
- mysql2
|
|
||||||
- pg
|
|
||||||
- pg-native
|
|
||||||
- pinia
|
|
||||||
- pino
|
|
||||||
- sass
|
|
||||||
- sqlite3
|
|
||||||
- stylus
|
|
||||||
- sugarss
|
|
||||||
- supports-color
|
|
||||||
- tedious
|
|
||||||
- terser
|
|
||||||
- typescript
|
|
||||||
- vue-router
|
|
||||||
|
|
||||||
'@directus/extensions-sdk@11.0.2(@types/node@22.19.10)(@unhead/vue@1.11.20(vue@3.5.28(typescript@5.9.3)))(knex@3.1.0)(lightningcss@1.30.2)(pinia@2.3.1(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3)))(pino@10.3.1)(sass@1.97.3)(terser@5.46.0)(typescript@5.9.3)':
|
'@directus/extensions-sdk@11.0.2(@types/node@22.19.10)(@unhead/vue@1.11.20(vue@3.5.28(typescript@5.9.3)))(knex@3.1.0)(lightningcss@1.30.2)(pinia@2.3.1(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3)))(pino@10.3.1)(sass@1.97.3)(terser@5.46.0)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@directus/composables': 10.1.12(vue@3.4.21(typescript@5.9.3))
|
'@directus/composables': 10.1.12(vue@3.4.21(typescript@5.9.3))
|
||||||
@@ -8810,32 +8844,6 @@ snapshots:
|
|||||||
- typescript
|
- typescript
|
||||||
- vue-router
|
- vue-router
|
||||||
|
|
||||||
'@directus/extensions@1.0.2(@unhead/vue@1.11.20(vue@3.4.21(typescript@5.9.3)))(knex@3.1.0)(pinia@2.3.1(typescript@5.9.3)(vue@3.4.21(typescript@5.9.3)))(pino@10.3.1)(vue@3.4.21(typescript@5.9.3))':
|
|
||||||
dependencies:
|
|
||||||
'@directus/constants': 11.0.3
|
|
||||||
'@directus/themes': 0.3.6(@unhead/vue@1.11.20(vue@3.4.21(typescript@5.9.3)))(pinia@2.3.1(typescript@5.9.3)(vue@3.4.21(typescript@5.9.3)))(vue@3.4.21(typescript@5.9.3))
|
|
||||||
'@directus/types': 11.0.8(knex@3.1.0)(vue@3.4.21(typescript@5.9.3))
|
|
||||||
'@directus/utils': 11.0.7(vue@3.4.21(typescript@5.9.3))
|
|
||||||
'@types/express': 4.17.21
|
|
||||||
fs-extra: 11.2.0
|
|
||||||
lodash-es: 4.17.21
|
|
||||||
zod: 3.22.4
|
|
||||||
optionalDependencies:
|
|
||||||
knex: 3.1.0
|
|
||||||
pino: 10.3.1
|
|
||||||
vue: 3.4.21(typescript@5.9.3)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@unhead/vue'
|
|
||||||
- better-sqlite3
|
|
||||||
- mysql
|
|
||||||
- mysql2
|
|
||||||
- pg
|
|
||||||
- pg-native
|
|
||||||
- pinia
|
|
||||||
- sqlite3
|
|
||||||
- supports-color
|
|
||||||
- tedious
|
|
||||||
|
|
||||||
'@directus/extensions@1.0.2(@unhead/vue@1.11.20(vue@3.5.28(typescript@5.9.3)))(knex@3.1.0)(pinia@2.3.1(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3)))(pino@10.3.1)(vue@3.4.21(typescript@5.9.3))':
|
'@directus/extensions@1.0.2(@unhead/vue@1.11.20(vue@3.5.28(typescript@5.9.3)))(knex@3.1.0)(pinia@2.3.1(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3)))(pino@10.3.1)(vue@3.4.21(typescript@5.9.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@directus/constants': 11.0.3
|
'@directus/constants': 11.0.3
|
||||||
@@ -8879,17 +8887,6 @@ snapshots:
|
|||||||
|
|
||||||
'@directus/system-data@1.0.2': {}
|
'@directus/system-data@1.0.2': {}
|
||||||
|
|
||||||
'@directus/themes@0.3.6(@unhead/vue@1.11.20(vue@3.4.21(typescript@5.9.3)))(pinia@2.3.1(typescript@5.9.3)(vue@3.4.21(typescript@5.9.3)))(vue@3.4.21(typescript@5.9.3))':
|
|
||||||
dependencies:
|
|
||||||
'@directus/utils': 11.0.7(vue@3.4.21(typescript@5.9.3))
|
|
||||||
'@sinclair/typebox': 0.32.15
|
|
||||||
'@unhead/vue': 1.11.20(vue@3.4.21(typescript@5.9.3))
|
|
||||||
decamelize: 6.0.0
|
|
||||||
flat: 6.0.1
|
|
||||||
lodash-es: 4.17.21
|
|
||||||
pinia: 2.3.1(typescript@5.9.3)(vue@3.4.21(typescript@5.9.3))
|
|
||||||
vue: 3.4.21(typescript@5.9.3)
|
|
||||||
|
|
||||||
'@directus/themes@0.3.6(@unhead/vue@1.11.20(vue@3.5.28(typescript@5.9.3)))(pinia@2.3.1(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3)))(vue@3.4.21(typescript@5.9.3))':
|
'@directus/themes@0.3.6(@unhead/vue@1.11.20(vue@3.5.28(typescript@5.9.3)))(pinia@2.3.1(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3)))(vue@3.4.21(typescript@5.9.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@directus/utils': 11.0.7(vue@3.4.21(typescript@5.9.3))
|
'@directus/utils': 11.0.7(vue@3.4.21(typescript@5.9.3))
|
||||||
@@ -9567,6 +9564,8 @@ snapshots:
|
|||||||
globby: 11.1.0
|
globby: 11.1.0
|
||||||
read-yaml-file: 1.1.0
|
read-yaml-file: 1.1.0
|
||||||
|
|
||||||
|
'@medv/finder@4.0.2': {}
|
||||||
|
|
||||||
'@napi-rs/wasm-runtime@0.2.12':
|
'@napi-rs/wasm-runtime@0.2.12':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/core': 1.8.1
|
'@emnapi/core': 1.8.1
|
||||||
@@ -10816,7 +10815,6 @@ snapshots:
|
|||||||
'@types/node@22.19.10':
|
'@types/node@22.19.10':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.21.0
|
undici-types: 6.21.0
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@types/parse-json@4.0.2': {}
|
'@types/parse-json@4.0.2': {}
|
||||||
|
|
||||||
@@ -10980,14 +10978,6 @@ snapshots:
|
|||||||
'@unhead/schema': 1.11.20
|
'@unhead/schema': 1.11.20
|
||||||
packrup: 0.1.2
|
packrup: 0.1.2
|
||||||
|
|
||||||
'@unhead/vue@1.11.20(vue@3.4.21(typescript@5.9.3))':
|
|
||||||
dependencies:
|
|
||||||
'@unhead/schema': 1.11.20
|
|
||||||
'@unhead/shared': 1.11.20
|
|
||||||
hookable: 5.5.3
|
|
||||||
unhead: 1.11.20
|
|
||||||
vue: 3.4.21(typescript@5.9.3)
|
|
||||||
|
|
||||||
'@unhead/vue@1.11.20(vue@3.5.28(typescript@5.9.3))':
|
'@unhead/vue@1.11.20(vue@3.5.28(typescript@5.9.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@unhead/schema': 1.11.20
|
'@unhead/schema': 1.11.20
|
||||||
@@ -11104,13 +11094,13 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
vite: 5.4.21(@types/node@22.19.10)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
|
vite: 5.4.21(@types/node@22.19.10)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
|
||||||
|
|
||||||
'@vitest/mocker@3.2.4(vite@7.3.1(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))':
|
'@vitest/mocker@3.2.4(vite@5.4.21(@types/node@22.19.10)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/spy': 3.2.4
|
'@vitest/spy': 3.2.4
|
||||||
estree-walker: 3.0.3
|
estree-walker: 3.0.3
|
||||||
magic-string: 0.30.21
|
magic-string: 0.30.21
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
vite: 7.3.1(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
|
vite: 5.4.21(@types/node@22.19.10)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
|
||||||
|
|
||||||
'@vitest/mocker@4.0.18(vite@7.3.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))':
|
'@vitest/mocker@4.0.18(vite@7.3.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -11947,22 +11937,24 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
crawlee@3.16.0(@types/node@22.19.10):
|
crawlee@3.16.0(@types/node@22.19.10)(playwright@1.58.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@crawlee/basic': 3.16.0
|
'@crawlee/basic': 3.16.0
|
||||||
'@crawlee/browser': 3.16.0
|
'@crawlee/browser': 3.16.0(playwright@1.58.2)
|
||||||
'@crawlee/browser-pool': 3.16.0
|
'@crawlee/browser-pool': 3.16.0(playwright@1.58.2)
|
||||||
'@crawlee/cheerio': 3.16.0
|
'@crawlee/cheerio': 3.16.0
|
||||||
'@crawlee/cli': 3.16.0(@types/node@22.19.10)
|
'@crawlee/cli': 3.16.0(@types/node@22.19.10)
|
||||||
'@crawlee/core': 3.16.0
|
'@crawlee/core': 3.16.0
|
||||||
'@crawlee/http': 3.16.0
|
'@crawlee/http': 3.16.0
|
||||||
'@crawlee/jsdom': 3.16.0
|
'@crawlee/jsdom': 3.16.0
|
||||||
'@crawlee/linkedom': 3.16.0
|
'@crawlee/linkedom': 3.16.0
|
||||||
'@crawlee/playwright': 3.16.0
|
'@crawlee/playwright': 3.16.0(playwright@1.58.2)
|
||||||
'@crawlee/puppeteer': 3.16.0
|
'@crawlee/puppeteer': 3.16.0(playwright@1.58.2)
|
||||||
'@crawlee/utils': 3.16.0
|
'@crawlee/utils': 3.16.0
|
||||||
import-local: 3.2.0
|
import-local: 3.2.0
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
optionalDependencies:
|
||||||
|
playwright: 1.58.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/node'
|
- '@types/node'
|
||||||
- bufferutil
|
- bufferutil
|
||||||
@@ -12881,10 +12873,12 @@ snapshots:
|
|||||||
header-generator: 2.1.80
|
header-generator: 2.1.80
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
fingerprint-injector@2.1.80:
|
fingerprint-injector@2.1.80(playwright@1.58.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
fingerprint-generator: 2.1.80
|
fingerprint-generator: 2.1.80
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
optionalDependencies:
|
||||||
|
playwright: 1.58.2
|
||||||
|
|
||||||
fix-dts-default-cjs-exports@1.0.1:
|
fix-dts-default-cjs-exports@1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -12981,6 +12975,9 @@ snapshots:
|
|||||||
|
|
||||||
fs.realpath@1.0.0: {}
|
fs.realpath@1.0.0: {}
|
||||||
|
|
||||||
|
fsevents@2.3.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -14405,16 +14402,6 @@ snapshots:
|
|||||||
|
|
||||||
pify@4.0.1: {}
|
pify@4.0.1: {}
|
||||||
|
|
||||||
pinia@2.3.1(typescript@5.9.3)(vue@3.4.21(typescript@5.9.3)):
|
|
||||||
dependencies:
|
|
||||||
'@vue/devtools-api': 6.6.4
|
|
||||||
vue: 3.4.21(typescript@5.9.3)
|
|
||||||
vue-demi: 0.14.10(vue@3.4.21(typescript@5.9.3))
|
|
||||||
optionalDependencies:
|
|
||||||
typescript: 5.9.3
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@vue/composition-api'
|
|
||||||
|
|
||||||
pinia@2.3.1(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3)):
|
pinia@2.3.1(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vue/devtools-api': 6.6.4
|
'@vue/devtools-api': 6.6.4
|
||||||
@@ -14473,6 +14460,14 @@ snapshots:
|
|||||||
mlly: 1.8.0
|
mlly: 1.8.0
|
||||||
pathe: 2.0.3
|
pathe: 2.0.3
|
||||||
|
|
||||||
|
playwright-core@1.58.2: {}
|
||||||
|
|
||||||
|
playwright@1.58.2:
|
||||||
|
dependencies:
|
||||||
|
playwright-core: 1.58.2
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents: 2.3.2
|
||||||
|
|
||||||
po-parser@2.1.1: {}
|
po-parser@2.1.1: {}
|
||||||
|
|
||||||
possible-typed-array-names@1.1.0: {}
|
possible-typed-array-names@1.1.0: {}
|
||||||
@@ -15827,16 +15822,15 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- terser
|
- terser
|
||||||
|
|
||||||
vite-node@3.2.4(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2):
|
vite-node@3.2.4(@types/node@22.19.10)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
cac: 6.7.14
|
cac: 6.7.14
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
es-module-lexer: 1.7.0
|
es-module-lexer: 1.7.0
|
||||||
pathe: 2.0.3
|
pathe: 2.0.3
|
||||||
vite: 7.3.1(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
|
vite: 5.4.21(@types/node@22.19.10)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/node'
|
- '@types/node'
|
||||||
- jiti
|
|
||||||
- less
|
- less
|
||||||
- lightningcss
|
- lightningcss
|
||||||
- sass
|
- sass
|
||||||
@@ -15845,8 +15839,6 @@ snapshots:
|
|||||||
- sugarss
|
- sugarss
|
||||||
- supports-color
|
- supports-color
|
||||||
- terser
|
- terser
|
||||||
- tsx
|
|
||||||
- yaml
|
|
||||||
|
|
||||||
vite@4.5.2(@types/node@22.19.10)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0):
|
vite@4.5.2(@types/node@22.19.10)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -15890,25 +15882,7 @@ snapshots:
|
|||||||
tsx: 4.21.0
|
tsx: 4.21.0
|
||||||
yaml: 2.8.2
|
yaml: 2.8.2
|
||||||
|
|
||||||
vite@7.3.1(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2):
|
vitest@2.1.9(@types/node@22.19.10)(@vitest/ui@4.0.18(vitest@4.0.18))(happy-dom@20.5.3)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0):
|
||||||
dependencies:
|
|
||||||
esbuild: 0.27.3
|
|
||||||
fdir: 6.5.0(picomatch@4.0.3)
|
|
||||||
picomatch: 4.0.3
|
|
||||||
postcss: 8.5.6
|
|
||||||
rollup: 4.57.1
|
|
||||||
tinyglobby: 0.2.15
|
|
||||||
optionalDependencies:
|
|
||||||
'@types/node': 22.19.10
|
|
||||||
fsevents: 2.3.3
|
|
||||||
jiti: 2.6.1
|
|
||||||
lightningcss: 1.30.2
|
|
||||||
sass: 1.97.3
|
|
||||||
terser: 5.46.0
|
|
||||||
tsx: 4.21.0
|
|
||||||
yaml: 2.8.2
|
|
||||||
|
|
||||||
vitest@2.1.9(@types/node@22.19.10)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0):
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/expect': 2.1.9
|
'@vitest/expect': 2.1.9
|
||||||
'@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@22.19.10)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0))
|
'@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@22.19.10)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0))
|
||||||
@@ -15946,11 +15920,11 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- terser
|
- terser
|
||||||
|
|
||||||
vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.10)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2):
|
vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.10)(@vitest/ui@4.0.18(vitest@4.0.18))(happy-dom@20.5.3)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/chai': 5.2.3
|
'@types/chai': 5.2.3
|
||||||
'@vitest/expect': 3.2.4
|
'@vitest/expect': 3.2.4
|
||||||
'@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
|
'@vitest/mocker': 3.2.4(vite@5.4.21(@types/node@22.19.10)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0))
|
||||||
'@vitest/pretty-format': 3.2.4
|
'@vitest/pretty-format': 3.2.4
|
||||||
'@vitest/runner': 3.2.4
|
'@vitest/runner': 3.2.4
|
||||||
'@vitest/snapshot': 3.2.4
|
'@vitest/snapshot': 3.2.4
|
||||||
@@ -15968,8 +15942,8 @@ snapshots:
|
|||||||
tinyglobby: 0.2.15
|
tinyglobby: 0.2.15
|
||||||
tinypool: 1.1.1
|
tinypool: 1.1.1
|
||||||
tinyrainbow: 2.0.0
|
tinyrainbow: 2.0.0
|
||||||
vite: 7.3.1(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
|
vite: 5.4.21(@types/node@22.19.10)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
|
||||||
vite-node: 3.2.4(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
|
vite-node: 3.2.4(@types/node@22.19.10)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
|
||||||
why-is-node-running: 2.3.0
|
why-is-node-running: 2.3.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/debug': 4.1.12
|
'@types/debug': 4.1.12
|
||||||
@@ -15978,7 +15952,6 @@ snapshots:
|
|||||||
happy-dom: 20.5.3
|
happy-dom: 20.5.3
|
||||||
jsdom: 27.4.0
|
jsdom: 27.4.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- jiti
|
|
||||||
- less
|
- less
|
||||||
- lightningcss
|
- lightningcss
|
||||||
- msw
|
- msw
|
||||||
@@ -15988,8 +15961,6 @@ snapshots:
|
|||||||
- sugarss
|
- sugarss
|
||||||
- supports-color
|
- supports-color
|
||||||
- terser
|
- terser
|
||||||
- tsx
|
|
||||||
- yaml
|
|
||||||
|
|
||||||
vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@20.19.33)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2):
|
vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@20.19.33)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -16032,10 +16003,6 @@ snapshots:
|
|||||||
- tsx
|
- tsx
|
||||||
- yaml
|
- yaml
|
||||||
|
|
||||||
vue-demi@0.14.10(vue@3.4.21(typescript@5.9.3)):
|
|
||||||
dependencies:
|
|
||||||
vue: 3.4.21(typescript@5.9.3)
|
|
||||||
|
|
||||||
vue-demi@0.14.10(vue@3.5.28(typescript@5.9.3)):
|
vue-demi@0.14.10(vue@3.5.28(typescript@5.9.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
vue: 3.5.28(typescript@5.9.3)
|
vue: 3.5.28(typescript@5.9.3)
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ fi
|
|||||||
case $ENV in
|
case $ENV in
|
||||||
local)
|
local)
|
||||||
PROJECT="infra-cms"
|
PROJECT="infra-cms"
|
||||||
CMD_PREFIX="docker-compose -f packages/cms-infra/docker-compose.yml"
|
# Derive monorepo root
|
||||||
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
ROOT_DIR="$( dirname "$SCRIPT_DIR" )"
|
||||||
|
CMD_PREFIX="docker compose -f $ROOT_DIR/packages/cms-infra/docker-compose.yml"
|
||||||
|
|
||||||
LOCAL_CONTAINER=$($CMD_PREFIX ps -q $PROJECT)
|
LOCAL_CONTAINER=$($CMD_PREFIX ps -q $PROJECT)
|
||||||
if [ -z "$LOCAL_CONTAINER" ]; then
|
if [ -z "$LOCAL_CONTAINER" ]; then
|
||||||
@@ -24,6 +27,9 @@ case $ENV in
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "🧹 Reconciling database metadata..."
|
||||||
|
./scripts/cms-reconcile.sh
|
||||||
|
|
||||||
echo "🚀 Applying schema to LOCAL $PROJECT..."
|
echo "🚀 Applying schema to LOCAL $PROJECT..."
|
||||||
docker exec "$LOCAL_CONTAINER" npx directus schema apply -y /directus/schema/snapshot.yaml
|
docker exec "$LOCAL_CONTAINER" npx directus schema apply -y /directus/schema/snapshot.yaml
|
||||||
;;
|
;;
|
||||||
|
|||||||
56
scripts/cms-reconcile.sh
Executable file
56
scripts/cms-reconcile.sh
Executable file
@@ -0,0 +1,56 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
# Derive monorepo root
|
||||||
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
ROOT_DIR="$( dirname "$SCRIPT_DIR" )"
|
||||||
|
DB_PATH="$ROOT_DIR/packages/cms-infra/database/data.db"
|
||||||
|
|
||||||
|
if [ ! -f "$DB_PATH" ]; then
|
||||||
|
echo "❌ Database not found at $DB_PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
reconcile_table() {
|
||||||
|
local TABLE=$1
|
||||||
|
echo "🔍 Reconciling table: $TABLE"
|
||||||
|
|
||||||
|
# 1. Get all columns from SQLite
|
||||||
|
COLUMNS=$(sqlite3 "$DB_PATH" "PRAGMA table_info($TABLE);" | cut -d'|' -f2)
|
||||||
|
|
||||||
|
for COL in $COLUMNS; do
|
||||||
|
# Skip system columns if needed, but usually it's safer to just check if they exist in Directus
|
||||||
|
|
||||||
|
# 2. Check if field exists in directus_fields
|
||||||
|
EXISTS=$(sqlite3 "$DB_PATH" "SELECT count(*) FROM directus_fields WHERE collection = '$TABLE' AND field = '$COL';")
|
||||||
|
|
||||||
|
if [ "$EXISTS" -eq 0 ]; then
|
||||||
|
echo "➕ Registering missing field: $TABLE.$COL"
|
||||||
|
|
||||||
|
# Determine a basic interface based on column name or type (very simplified)
|
||||||
|
INTERFACE="input"
|
||||||
|
case $COL in
|
||||||
|
*id) INTERFACE="numeric" ;;
|
||||||
|
*text) INTERFACE="input-multiline" ;;
|
||||||
|
company|person|user_created|user_updated|feedback_id) INTERFACE="select-dropdown-m2o" ;;
|
||||||
|
date_created|date_updated) INTERFACE="datetime" ;;
|
||||||
|
screenshot|logo) INTERFACE="file" ;;
|
||||||
|
status|type) INTERFACE="select-dropdown" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
sqlite3 "$DB_PATH" "INSERT INTO directus_fields (collection, field, interface) VALUES ('$TABLE', '$COL', '$INTERFACE');"
|
||||||
|
else
|
||||||
|
echo "✅ Field already registered: $TABLE.$COL"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run for known problematic tables
|
||||||
|
reconcile_table "visual_feedback"
|
||||||
|
reconcile_table "visual_feedback_comments"
|
||||||
|
reconcile_table "people"
|
||||||
|
reconcile_table "leads"
|
||||||
|
reconcile_table "client_users"
|
||||||
|
reconcile_table "companies"
|
||||||
|
|
||||||
|
echo "✨ SQL Reconciliation complete!"
|
||||||
@@ -37,7 +37,7 @@ esac
|
|||||||
|
|
||||||
# Detect local containers
|
# Detect local containers
|
||||||
echo "🔍 Detecting local database..."
|
echo "🔍 Detecting local database..."
|
||||||
LOCAL_DB_CONTAINER=$(docker compose ps -q directus-db)
|
LOCAL_DB_CONTAINER=$(docker compose ps -q at-mintel-directus-db)
|
||||||
if [ -z "$LOCAL_DB_CONTAINER" ]; then
|
if [ -z "$LOCAL_DB_CONTAINER" ]; then
|
||||||
echo "❌ Local directus-db container not found. Is it running? (npm run dev)"
|
echo "❌ Local directus-db container not found. Is it running? (npm run dev)"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
EXTENSIONS_ROOT="$REPO_ROOT/packages"
|
EXTENSIONS_ROOT="$REPO_ROOT/packages"
|
||||||
TARGET_DIR="$REPO_ROOT/packages/cms-infra/extensions"
|
TARGET_DIR="$REPO_ROOT/directus/extensions"
|
||||||
|
|
||||||
# List of extensions to sync - including modules and endpoints
|
# List of extensions to sync - including modules and endpoints
|
||||||
EXTENSIONS=(
|
EXTENSIONS=(
|
||||||
@@ -20,6 +20,10 @@ echo "🚀 Starting extension sync..."
|
|||||||
# Ensure target directory exists
|
# Ensure target directory exists
|
||||||
mkdir -p "$TARGET_DIR"
|
mkdir -p "$TARGET_DIR"
|
||||||
|
|
||||||
|
# Build the acquisition library first so extensions use the updated build
|
||||||
|
echo "📦 Building acquisition-library..."
|
||||||
|
(cd "$REPO_ROOT/packages/acquisition-library" && pnpm build)
|
||||||
|
|
||||||
for EXT in "${EXTENSIONS[@]}"; do
|
for EXT in "${EXTENSIONS[@]}"; do
|
||||||
EXT_PATH="$EXTENSIONS_ROOT/$EXT"
|
EXT_PATH="$EXTENSIONS_ROOT/$EXT"
|
||||||
|
|
||||||
@@ -55,10 +59,11 @@ for EXT in "${EXTENSIONS[@]}"; do
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Sync node_modules if they exist (sometimes needed if not everything is bundled)
|
# Sync node_modules if they exist (sometimes needed if not everything is bundled)
|
||||||
if [ -d "$EXT_PATH/node_modules" ]; then
|
# Deactivated: Causes global scope pollution and login issues in Directus
|
||||||
echo "📚 Syncing node_modules for $EXT..."
|
# if [ -d "$EXT_PATH/node_modules" ]; then
|
||||||
rsync -a --delete "$EXT_PATH/node_modules/" "$TARGET_DIR/$EXT/node_modules/"
|
# echo "📚 Syncing node_modules for $EXT..."
|
||||||
fi
|
# rsync -aL --delete "$EXT_PATH/node_modules/" "$TARGET_DIR/$EXT/node_modules/"
|
||||||
|
# fi
|
||||||
|
|
||||||
echo "✅ $EXT synced."
|
echo "✅ $EXT synced."
|
||||||
else
|
else
|
||||||
|
|||||||
Reference in New Issue
Block a user