Compare commits
6 Commits
efba82337c
...
v1.8.0
| Author | SHA1 | Date | |
|---|---|---|---|
| a4d021c658 | |||
| 269d19bbef | |||
| 30ff08c66d | |||
| 81deaf447f | |||
| a0ebc58d6d | |||
| 7498c24c9a |
36
.env
Normal file
36
.env
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Project
|
||||||
|
IMAGE_TAG=latest
|
||||||
|
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
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.7.12",
|
||||||
|
"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.7.12",
|
||||||
|
"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.7.12",
|
||||||
|
"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.7.12",
|
||||||
|
"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.7.12",
|
||||||
|
"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 @@
|
|||||||
|
Qy-qP
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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(() => {
|
||||||
@@ -9,8 +9,8 @@
|
|||||||
"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",
|
||||||
@@ -24,4 +24,4 @@
|
|||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^19.2.4"
|
"react-dom": "^19.2.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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/acquisition";
|
||||||
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";
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
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.7.12",
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
740
pnpm-lock.yaml
generated
740
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -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