@tulip-systems/create-app 0.12.3 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +2866 -2949
- package/dist/index.mjs.map +1 -1
- package/dist/templates/basic/package.json +37 -38
- package/dist/templates/basic/src/app/layout.tsx +20 -10
- package/dist/templates/code/package.json +81 -71
- package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-black-italic.otf +0 -0
- package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-black.otf +0 -0
- package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-bold-italic.otf +0 -0
- package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-bold.otf +0 -0
- package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-extra-light-italic.otf +0 -0
- package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-extra-light.otf +0 -0
- package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-light-italic.otf +0 -0
- package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-light.otf +0 -0
- package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-medium-italic.otf +0 -0
- package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-medium.otf +0 -0
- package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-regular-italic.otf +0 -0
- package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-regular.otf +0 -0
- package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-thin-italic.otf +0 -0
- package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-thin.otf +0 -0
- package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-ultra-thin-italic.otf +0 -0
- package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-ultra-thin.otf +0 -0
- package/dist/templates/code/public/images/preview/hero-home.webp +0 -0
- package/dist/templates/code/public/sw.js +35 -0
- package/dist/templates/code/src/app/admin/contacts/_components/grid.tsx +1 -1
- package/dist/templates/code/src/app/admin/customers/_config/columns-data.tsx +1 -1
- package/dist/templates/code/src/app/admin/employees/_config/columns-data.tsx +1 -1
- package/dist/templates/code/src/app/admin/history/[id]/layout.tsx +33 -0
- package/dist/templates/code/src/app/admin/history/[id]/loading.tsx +11 -0
- package/dist/templates/code/src/app/admin/history/[id]/page.tsx +30 -0
- package/dist/templates/code/src/app/admin/history/_components/diff.client.tsx +377 -0
- package/dist/templates/code/src/app/admin/history/_components/field-comparison.tsx +153 -0
- package/dist/templates/code/src/app/admin/history/_components/tables.tsx +47 -0
- package/dist/templates/code/src/app/admin/history/_components/toolbars.tsx +54 -0
- package/dist/templates/code/src/app/admin/history/_config/columns-data.tsx +61 -0
- package/dist/templates/code/src/app/admin/history/_config/filters.ts +46 -0
- package/dist/templates/code/src/app/admin/history/_config/sorting.ts +30 -0
- package/dist/templates/code/src/app/admin/history/_lib/helpers.ts +98 -0
- package/dist/templates/code/src/app/admin/history/_lib/router.ts +100 -0
- package/dist/templates/code/src/app/admin/history/_lib/schema.ts +45 -0
- package/dist/templates/code/src/app/admin/history/_lib/service.server.ts +30 -0
- package/dist/templates/code/src/app/admin/history/_lib/validators.ts +118 -0
- package/dist/templates/code/src/app/admin/initiatives/_config/columns-data.tsx +1 -1
- package/dist/templates/code/src/app/admin/notifications/_components/command-toggle-push.tsx +115 -0
- package/dist/templates/code/src/app/admin/notifications/_components/tables.tsx +37 -0
- package/dist/templates/code/src/app/admin/notifications/_components/toolbars.tsx +21 -0
- package/dist/templates/code/src/app/admin/notifications/_config/columns-data.tsx +64 -0
- package/dist/templates/code/src/app/admin/notifications/_config/commands.tsx +40 -0
- package/dist/templates/code/src/app/admin/notifications/_lib/listeners.server.ts +69 -0
- package/dist/templates/code/src/app/admin/notifications/_lib/push.server.ts +80 -0
- package/dist/templates/code/src/app/admin/notifications/_lib/router.ts +82 -0
- package/dist/templates/code/src/app/admin/notifications/_lib/schema.ts +49 -0
- package/dist/templates/code/src/app/admin/notifications/_lib/validators.ts +19 -0
- package/dist/templates/code/src/app/admin/notifications/layout.tsx +17 -0
- package/dist/templates/code/src/app/admin/notifications/loading.tsx +9 -0
- package/dist/templates/code/src/app/admin/notifications/page.tsx +28 -0
- package/dist/templates/code/src/app/admin/projects/[id]/(default)/page.tsx +1 -1
- package/dist/templates/code/src/app/admin/projects/[id]/timeline/page.tsx +1 -1
- package/dist/templates/code/src/app/admin/projects/_config/columns-data.tsx +1 -1
- package/dist/templates/code/src/app/admin/projects/_lib/events.ts +64 -0
- package/dist/templates/code/src/app/admin/projects/_lib/listeners.server.ts +82 -0
- package/dist/templates/code/src/app/admin/projects/_lib/router.ts +145 -20
- package/dist/templates/code/src/app/admin/projects/timeline/page.tsx +1 -1
- package/dist/templates/code/src/app/admin/prospects/_config/columns-data.tsx +1 -1
- package/dist/templates/code/src/app/admin/queue/_components/create-command.tsx +97 -0
- package/dist/templates/code/src/app/admin/queue/_components/tables.tsx +37 -0
- package/dist/templates/code/src/app/admin/queue/_components/toolbars.tsx +16 -0
- package/dist/templates/code/src/app/admin/queue/_config/columns-data.tsx +44 -0
- package/dist/templates/code/src/app/admin/queue/_config/commands.tsx +39 -0
- package/dist/templates/code/src/app/admin/queue/_lib/queue.ts +27 -0
- package/dist/templates/code/src/app/admin/queue/_lib/router.ts +33 -0
- package/dist/templates/code/src/app/admin/queue/_lib/schema.ts +12 -0
- package/dist/templates/code/src/app/admin/queue/_lib/validators.ts +15 -0
- package/dist/templates/code/src/app/admin/quotations/(list)/layout.tsx +24 -0
- package/dist/templates/code/src/app/admin/quotations/(list)/page.tsx +23 -0
- package/dist/templates/code/src/app/admin/quotations/[id]/(dashboard)/layout.tsx +40 -0
- package/dist/templates/code/src/app/admin/quotations/[id]/(dashboard)/page.tsx +56 -0
- package/dist/templates/code/src/app/admin/quotations/[id]/preview/page.tsx +17 -0
- package/dist/templates/code/src/app/admin/quotations/_blocks/commands.tsx +24 -0
- package/dist/templates/code/src/app/admin/quotations/_blocks/hero/config.ts +14 -0
- package/dist/templates/code/src/app/admin/quotations/_blocks/hero/create.tsx +152 -0
- package/dist/templates/code/src/app/admin/quotations/_blocks/hero/inline.tsx +90 -0
- package/dist/templates/code/src/app/admin/quotations/_blocks/hero/preview.tsx +90 -0
- package/dist/templates/code/src/app/admin/quotations/_blocks/registry.ts +13 -0
- package/dist/templates/code/src/app/admin/quotations/_blocks/render.tsx +190 -0
- package/dist/templates/code/src/app/admin/quotations/_blocks/styles.ts +24 -0
- package/dist/templates/code/src/app/admin/quotations/_blocks/summary/config.ts +13 -0
- package/dist/templates/code/src/app/admin/quotations/_blocks/summary/create.tsx +175 -0
- package/dist/templates/code/src/app/admin/quotations/_blocks/summary/inline.tsx +93 -0
- package/dist/templates/code/src/app/admin/quotations/_blocks/summary/preview.tsx +25 -0
- package/dist/templates/code/src/app/admin/quotations/_blocks/totals/config.ts +18 -0
- package/dist/templates/code/src/app/admin/quotations/_blocks/totals/create.tsx +177 -0
- package/dist/templates/code/src/app/admin/quotations/_blocks/totals/inline.tsx +96 -0
- package/dist/templates/code/src/app/admin/quotations/_blocks/totals/preview.tsx +32 -0
- package/dist/templates/code/src/app/admin/quotations/_components/create-command.tsx +97 -0
- package/dist/templates/code/src/app/admin/quotations/_components/tables.tsx +68 -0
- package/dist/templates/code/src/app/admin/quotations/_components/toolbars.tsx +18 -0
- package/dist/templates/code/src/app/admin/quotations/_config/columns-data.tsx +35 -0
- package/dist/templates/code/src/app/admin/quotations/_config/commands.tsx +71 -0
- package/dist/templates/code/src/app/admin/quotations/_config/filters.ts +22 -0
- package/dist/templates/code/src/app/admin/quotations/_config/sorting.ts +23 -0
- package/dist/templates/code/src/app/admin/quotations/_lib/router.ts +260 -0
- package/dist/templates/code/src/app/admin/quotations/_lib/schema.ts +42 -0
- package/dist/templates/code/src/app/admin/quotations/_lib/validators.ts +20 -0
- package/dist/templates/code/src/app/admin/settings/history/layout.tsx +6 -0
- package/dist/templates/code/src/app/admin/settings/history/loading.tsx +5 -0
- package/dist/templates/code/src/app/admin/settings/history/page.tsx +13 -0
- package/dist/templates/code/src/app/admin/settings/layout.tsx +11 -0
- package/dist/templates/code/src/app/admin/settings/queue/loading.tsx +9 -0
- package/dist/templates/code/src/app/admin/settings/queue/page.tsx +21 -0
- package/dist/templates/code/src/app/admin/tasks/_components/domains-tabs-grid.tsx +1 -1
- package/dist/templates/code/src/app/admin/tasks/_config/columns-data.tsx +1 -1
- package/dist/templates/code/src/app/admin/tasks/_config/columns-inline.tsx +1 -1
- package/dist/templates/code/src/app/admin/tasks/_lib/events.ts +40 -0
- package/dist/templates/code/src/app/admin/tasks/_lib/listeners.server.ts +46 -0
- package/dist/templates/code/src/app/admin/tasks/_lib/router.ts +222 -90
- package/dist/templates/code/src/app/admin/time/_config/columns-data.tsx +1 -1
- package/dist/templates/code/src/app/admin/time/_config/columns-inline.tsx +1 -1
- package/dist/templates/code/src/app/api/[transport]/route.ts +31 -21
- package/dist/templates/code/src/app/api/queue/dispatch/route.ts +4 -0
- package/dist/templates/code/src/app/auth/forget-password/page.tsx +1 -1
- package/dist/templates/code/src/app/auth/layout.tsx +1 -1
- package/dist/templates/code/src/app/auth/reset-password/page.tsx +1 -1
- package/dist/templates/code/src/app/layout.tsx +25 -13
- package/dist/templates/code/src/env.ts +12 -0
- package/dist/templates/code/src/lib/config/paths.tsx +16 -0
- package/dist/templates/code/src/lib/utils/fonts.ts +102 -0
- package/dist/templates/code/src/server/auth/constants.ts +1 -1
- package/dist/templates/code/src/server/auth/init.ts +12 -5
- package/dist/templates/code/src/server/auth/permissions.ts +7 -0
- package/dist/templates/code/src/server/auth/schema.ts +100 -1
- package/dist/templates/code/src/server/db/schema.ts +4 -0
- package/dist/templates/code/src/server/events/handlers.ts +17 -0
- package/dist/templates/code/src/server/events/init.ts +18 -0
- package/dist/templates/code/src/server/events/listeners.ts +17 -0
- package/dist/templates/code/src/server/queue/handlers.ts +5 -0
- package/dist/templates/code/src/server/queue/init.ts +16 -0
- package/dist/templates/code/src/server/router/router.ts +23 -0
- package/dist/templates/spark/package.json +64 -65
- package/dist/templates/spark/src/app/admin/assignments/[id]/(default)/@dashboard/page.tsx +1 -1
- package/dist/templates/spark/src/app/admin/assignments/_config/columns-data.tsx +1 -1
- package/dist/templates/spark/src/app/admin/companies/_config/columns-data.tsx +1 -1
- package/dist/templates/spark/src/app/admin/contacts/_components/grid.tsx +1 -1
- package/dist/templates/spark/src/app/admin/customers/_config/columns-data.tsx +1 -1
- package/dist/templates/spark/src/app/admin/drive/_config/columns-data.tsx +1 -1
- package/dist/templates/spark/src/app/admin/emails/_components/send-email.tsx +1 -1
- package/dist/templates/spark/src/app/admin/emails/_config/columns-data.tsx +1 -1
- package/dist/templates/spark/src/app/admin/employees/_config/columns-data.tsx +1 -1
- package/dist/templates/spark/src/app/admin/executions/[id]/@dashboard/page.tsx +1 -1
- package/dist/templates/spark/src/app/admin/executions/_config/columns-data.tsx +1 -1
- package/dist/templates/spark/src/app/admin/products/[id]/(preview)/page.tsx +1 -1
- package/dist/templates/spark/src/app/admin/products/_config/columns-data.tsx +1 -1
- package/dist/templates/spark/src/app/admin/projects/[id]/(dashboard)/page.tsx +1 -1
- package/dist/templates/spark/src/app/admin/projects/[id]/messages/page.tsx +1 -1
- package/dist/templates/spark/src/app/admin/projects/[id]/statistics/page.tsx +1 -1
- package/dist/templates/spark/src/app/admin/projects/_config/columns-data.tsx +1 -1
- package/dist/templates/spark/src/app/admin/purchase-order-items/_config/columns-data.tsx +1 -1
- package/dist/templates/spark/src/app/admin/purchase-order-items/_config/columns-inline.tsx +1 -1
- package/dist/templates/spark/src/app/admin/purchase-order-items/_config/commands.tsx +1 -1
- package/dist/templates/spark/src/app/admin/purchase-orders/[id]/(default)/@dashboard/page.tsx +1 -1
- package/dist/templates/spark/src/app/admin/purchase-orders/_config/columns-data.tsx +1 -1
- package/dist/templates/spark/src/app/admin/quotation-items/_config/commands.tsx +1 -1
- package/dist/templates/spark/src/app/admin/quotations/[id]/(default)/@dashboard/page.tsx +1 -1
- package/dist/templates/spark/src/app/admin/quotations/_config/columns-data.tsx +1 -1
- package/dist/templates/spark/src/app/admin/run-tasks/_config/columns-data.tsx +1 -1
- package/dist/templates/spark/src/app/admin/runs/[id]/@dashboard/page.tsx +1 -1
- package/dist/templates/spark/src/app/admin/runs/_config/columns-data.tsx +1 -1
- package/dist/templates/spark/src/app/admin/sales-invoices/[id]/@dashboard/page.tsx +1 -1
- package/dist/templates/spark/src/app/admin/sales-invoices/_config/columns-data.tsx +1 -1
- package/dist/templates/spark/src/app/admin/sales-order-executions/_config/columns-data.tsx +1 -1
- package/dist/templates/spark/src/app/admin/sales-order-executions/_config/columns-inline.tsx +1 -1
- package/dist/templates/spark/src/app/admin/sales-order-executions/_config/commands.tsx +1 -1
- package/dist/templates/spark/src/app/admin/sales-order-items/_config/commands.tsx +1 -1
- package/dist/templates/spark/src/app/admin/sales-orders/[id]/(default)/@dashboard/page.tsx +1 -1
- package/dist/templates/spark/src/app/admin/sales-orders/[id]/(default)/layout.tsx +1 -1
- package/dist/templates/spark/src/app/admin/sales-orders/_config/columns-data.tsx +1 -1
- package/dist/templates/spark/src/app/admin/schedule/_components/schedule-card.tsx +1 -1
- package/dist/templates/spark/src/app/admin/schedule/_components/schedule-month-view.tsx +1 -1
- package/dist/templates/spark/src/app/admin/suppliers/_config/columns-data.tsx +1 -1
- package/dist/templates/spark/src/app/admin/tasks/_components/subproject-selector.tsx +1 -1
- package/dist/templates/spark/src/app/admin/tasks/_config/columns-data.tsx +1 -1
- package/dist/templates/spark/src/app/admin/tasks/_config/columns-inline.tsx +1 -1
- package/dist/templates/spark/src/app/admin/tasks/_config/commands.tsx +1 -1
- package/dist/templates/spark/src/app/admin/technical-sheets/[id]/page.tsx +1 -1
- package/dist/templates/spark/src/app/admin/technical-sheets/_config/columns-data.tsx +1 -1
- package/dist/templates/spark/src/app/admin/technical-sheets/_config/commands.tsx +1 -1
- package/dist/templates/spark/src/app/admin/templates/_config/columns-data.tsx +1 -1
- package/dist/templates/spark/src/app/auth/layout.tsx +1 -1
- package/dist/templates/spark/src/app/layout.tsx +25 -10
- package/dist/templates/spark/src/server/auth/init.ts +1 -0
- package/package.json +6 -6
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BlockDefinition } from "@tulip-systems/blocks";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export const quotationSummaryBlock = {
|
|
5
|
+
slug: "summary",
|
|
6
|
+
label: "Summary",
|
|
7
|
+
description: "Short commercial summary for the quotation.",
|
|
8
|
+
schema: z.object({
|
|
9
|
+
heading: z.string().min(1),
|
|
10
|
+
body: z.string().min(1),
|
|
11
|
+
highlights: z.array(z.string().min(1)),
|
|
12
|
+
}),
|
|
13
|
+
} as const satisfies BlockDefinition;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema";
|
|
4
|
+
import type { DefaultError } from "@tanstack/react-query";
|
|
5
|
+
import type { BlockCreateComponentProps } from "@tulip-systems/blocks";
|
|
6
|
+
import {
|
|
7
|
+
CreateBlock,
|
|
8
|
+
CreateBlockCancel,
|
|
9
|
+
CreateBlockContent,
|
|
10
|
+
CreateBlockDescription,
|
|
11
|
+
CreateBlockFields,
|
|
12
|
+
CreateBlockFooter,
|
|
13
|
+
CreateBlockHeader,
|
|
14
|
+
CreateBlockSubmit,
|
|
15
|
+
CreateBlockTitle,
|
|
16
|
+
CreateBlockTrigger,
|
|
17
|
+
CreateBlockTriggerContent,
|
|
18
|
+
CreateBlockTriggerDescription,
|
|
19
|
+
CreateBlockTriggerIcon,
|
|
20
|
+
CreateBlockTriggerTitle,
|
|
21
|
+
} from "@tulip-systems/blocks/client";
|
|
22
|
+
import { Input } from "@tulip-systems/ui/input";
|
|
23
|
+
import {
|
|
24
|
+
Form,
|
|
25
|
+
FormControl,
|
|
26
|
+
FormDescription,
|
|
27
|
+
FormField,
|
|
28
|
+
FormItem,
|
|
29
|
+
FormLabel,
|
|
30
|
+
FormMessage,
|
|
31
|
+
} from "@tulip-systems/ui/react-hook-form";
|
|
32
|
+
import { Textarea } from "@tulip-systems/ui/textarea";
|
|
33
|
+
import { BlocksIcon } from "lucide-react";
|
|
34
|
+
import { useForm } from "react-hook-form";
|
|
35
|
+
import type { z } from "zod";
|
|
36
|
+
import { quotationSummaryBlock } from "./config";
|
|
37
|
+
|
|
38
|
+
export type QuotationSummaryCreateValues = z.input<typeof quotationSummaryBlock.schema>;
|
|
39
|
+
|
|
40
|
+
type QuotationSummaryFormValues = Omit<QuotationSummaryCreateValues, "highlights"> & {
|
|
41
|
+
highlights: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type QuotationSummaryCreateBlockInput = {
|
|
45
|
+
slug: typeof quotationSummaryBlock.slug;
|
|
46
|
+
data: QuotationSummaryCreateValues;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type QuotationSummaryCreateBlockProps<
|
|
50
|
+
TData = unknown,
|
|
51
|
+
TError = DefaultError,
|
|
52
|
+
TVariables = undefined,
|
|
53
|
+
TOnMutateResult = unknown,
|
|
54
|
+
> = BlockCreateComponentProps<
|
|
55
|
+
QuotationSummaryCreateBlockInput,
|
|
56
|
+
TData,
|
|
57
|
+
TError,
|
|
58
|
+
TVariables,
|
|
59
|
+
TOnMutateResult
|
|
60
|
+
>;
|
|
61
|
+
|
|
62
|
+
export function QuotationSummaryCreateBlock<
|
|
63
|
+
TData = unknown,
|
|
64
|
+
TError = DefaultError,
|
|
65
|
+
TVariables = undefined,
|
|
66
|
+
TOnMutateResult = unknown,
|
|
67
|
+
>({
|
|
68
|
+
variables,
|
|
69
|
+
mutation,
|
|
70
|
+
}: QuotationSummaryCreateBlockProps<TData, TError, TVariables, TOnMutateResult>) {
|
|
71
|
+
const form = useForm<QuotationSummaryFormValues>({
|
|
72
|
+
resolver: standardSchemaResolver(
|
|
73
|
+
quotationSummaryBlock.schema.extend({ highlights: quotationSummaryBlock.schema.shape.body }),
|
|
74
|
+
),
|
|
75
|
+
defaultValues: {
|
|
76
|
+
heading: "Heldere scope, snelle oplevering",
|
|
77
|
+
body: "Beschrijf hier kort de waarde en scope van deze offerte.",
|
|
78
|
+
highlights: "Vaste opleverdatum, Duidelijke scope, Transparante prijs",
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<CreateBlock>
|
|
84
|
+
<CreateBlockTrigger>
|
|
85
|
+
<CreateBlockTriggerIcon>
|
|
86
|
+
<BlocksIcon />
|
|
87
|
+
</CreateBlockTriggerIcon>
|
|
88
|
+
|
|
89
|
+
<CreateBlockTriggerContent>
|
|
90
|
+
<CreateBlockTriggerTitle>{quotationSummaryBlock.label}</CreateBlockTriggerTitle>
|
|
91
|
+
<CreateBlockTriggerDescription>
|
|
92
|
+
{quotationSummaryBlock.description}
|
|
93
|
+
</CreateBlockTriggerDescription>
|
|
94
|
+
</CreateBlockTriggerContent>
|
|
95
|
+
</CreateBlockTrigger>
|
|
96
|
+
|
|
97
|
+
<Form {...form}>
|
|
98
|
+
<CreateBlockContent
|
|
99
|
+
className="sm:max-w-xl"
|
|
100
|
+
variables={(values: QuotationSummaryFormValues) =>
|
|
101
|
+
variables({
|
|
102
|
+
slug: quotationSummaryBlock.slug,
|
|
103
|
+
data: {
|
|
104
|
+
heading: values.heading,
|
|
105
|
+
body: values.body,
|
|
106
|
+
highlights: values.highlights
|
|
107
|
+
.split(",")
|
|
108
|
+
.map((item) => item.trim())
|
|
109
|
+
.filter(Boolean),
|
|
110
|
+
},
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
mutation={mutation}
|
|
114
|
+
>
|
|
115
|
+
<CreateBlockHeader>
|
|
116
|
+
<CreateBlockTitle>{quotationSummaryBlock.label} toevoegen</CreateBlockTitle>
|
|
117
|
+
<CreateBlockDescription>
|
|
118
|
+
Vul de verplichte velden in om het blok toe te voegen.
|
|
119
|
+
</CreateBlockDescription>
|
|
120
|
+
</CreateBlockHeader>
|
|
121
|
+
|
|
122
|
+
<CreateBlockFields>
|
|
123
|
+
<FormField
|
|
124
|
+
control={form.control}
|
|
125
|
+
name="heading"
|
|
126
|
+
render={({ field }) => (
|
|
127
|
+
<FormItem>
|
|
128
|
+
<FormLabel>Kop</FormLabel>
|
|
129
|
+
<FormControl>
|
|
130
|
+
<Input {...field} />
|
|
131
|
+
</FormControl>
|
|
132
|
+
<FormMessage />
|
|
133
|
+
</FormItem>
|
|
134
|
+
)}
|
|
135
|
+
/>
|
|
136
|
+
|
|
137
|
+
<FormField
|
|
138
|
+
control={form.control}
|
|
139
|
+
name="body"
|
|
140
|
+
render={({ field }) => (
|
|
141
|
+
<FormItem>
|
|
142
|
+
<FormLabel>Beschrijving</FormLabel>
|
|
143
|
+
<FormControl>
|
|
144
|
+
<Textarea {...field} />
|
|
145
|
+
</FormControl>
|
|
146
|
+
<FormMessage />
|
|
147
|
+
</FormItem>
|
|
148
|
+
)}
|
|
149
|
+
/>
|
|
150
|
+
|
|
151
|
+
<FormField
|
|
152
|
+
control={form.control}
|
|
153
|
+
name="highlights"
|
|
154
|
+
render={({ field }) => (
|
|
155
|
+
<FormItem>
|
|
156
|
+
<FormLabel>Highlights</FormLabel>
|
|
157
|
+
<FormControl>
|
|
158
|
+
<Input {...field} />
|
|
159
|
+
</FormControl>
|
|
160
|
+
<FormDescription>Komma-gescheiden lijst.</FormDescription>
|
|
161
|
+
<FormMessage />
|
|
162
|
+
</FormItem>
|
|
163
|
+
)}
|
|
164
|
+
/>
|
|
165
|
+
</CreateBlockFields>
|
|
166
|
+
|
|
167
|
+
<CreateBlockFooter>
|
|
168
|
+
<CreateBlockCancel>Annuleren</CreateBlockCancel>
|
|
169
|
+
<CreateBlockSubmit>Blok toevoegen</CreateBlockSubmit>
|
|
170
|
+
</CreateBlockFooter>
|
|
171
|
+
</CreateBlockContent>
|
|
172
|
+
</Form>
|
|
173
|
+
</CreateBlock>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { BlockComponentProps } from "@tulip-systems/blocks";
|
|
4
|
+
import {
|
|
5
|
+
InlineBlock,
|
|
6
|
+
InlineBlockContent,
|
|
7
|
+
InlineBlockDragHandle,
|
|
8
|
+
InlineBlockHeader,
|
|
9
|
+
InlineBlockSortable,
|
|
10
|
+
InlineBlockTrigger,
|
|
11
|
+
} from "@tulip-systems/blocks/client";
|
|
12
|
+
import { DropdownCommandMenu } from "@tulip-systems/commands/client";
|
|
13
|
+
import {
|
|
14
|
+
InlineField,
|
|
15
|
+
InlineFieldError,
|
|
16
|
+
InlineFieldLabel,
|
|
17
|
+
InlineStringInput,
|
|
18
|
+
} from "@tulip-systems/inline/client";
|
|
19
|
+
import { GripIcon } from "lucide-react";
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import { orpc } from "@/server/router/client";
|
|
22
|
+
import { quotationInlineBlockCommands } from "../commands";
|
|
23
|
+
import { quotationSummaryBlock } from "./config";
|
|
24
|
+
|
|
25
|
+
const summaryMutation = orpc.quotationBlocks.update.mutationOptions({
|
|
26
|
+
meta: { invalidates: [orpc.quotationBlocks.key()] },
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export function QuotationSummaryInlineBlock({
|
|
30
|
+
block,
|
|
31
|
+
}: BlockComponentProps<typeof quotationSummaryBlock>) {
|
|
32
|
+
return (
|
|
33
|
+
<InlineBlockSortable id={block.id}>
|
|
34
|
+
<InlineBlock>
|
|
35
|
+
<InlineBlockHeader>
|
|
36
|
+
<InlineBlockDragHandle>
|
|
37
|
+
<GripIcon className="h-4 w-4" />
|
|
38
|
+
<span className="sr-only">Blok verplaatsen</span>
|
|
39
|
+
</InlineBlockDragHandle>
|
|
40
|
+
<InlineBlockTrigger>{quotationSummaryBlock.label}</InlineBlockTrigger>
|
|
41
|
+
<DropdownCommandMenu
|
|
42
|
+
data={{ id: block.id }}
|
|
43
|
+
commands={quotationInlineBlockCommands}
|
|
44
|
+
className="ml-2 size-7"
|
|
45
|
+
/>
|
|
46
|
+
</InlineBlockHeader>
|
|
47
|
+
|
|
48
|
+
<InlineBlockContent className="grid gap-4 md:grid-cols-2">
|
|
49
|
+
<InlineField>
|
|
50
|
+
<InlineFieldLabel>Kop</InlineFieldLabel>
|
|
51
|
+
<InlineStringInput
|
|
52
|
+
initialValue={block.data.heading}
|
|
53
|
+
variables={(value) => ({ id: block.id, data: { heading: value ?? "" } })}
|
|
54
|
+
mutation={summaryMutation}
|
|
55
|
+
strategy={{ mutate: { on: "blur" }, validate: { blur: z.string().min(1) } }}
|
|
56
|
+
/>
|
|
57
|
+
<InlineFieldError />
|
|
58
|
+
</InlineField>
|
|
59
|
+
|
|
60
|
+
<InlineField className="md:col-span-2">
|
|
61
|
+
<InlineFieldLabel>Beschrijving</InlineFieldLabel>
|
|
62
|
+
<InlineStringInput
|
|
63
|
+
initialValue={block.data.body}
|
|
64
|
+
variables={(value) => ({ id: block.id, data: { body: value ?? "" } })}
|
|
65
|
+
mutation={summaryMutation}
|
|
66
|
+
strategy={{ mutate: { on: "blur" }, validate: { blur: z.string().min(1) } }}
|
|
67
|
+
/>
|
|
68
|
+
<InlineFieldError />
|
|
69
|
+
</InlineField>
|
|
70
|
+
|
|
71
|
+
<InlineField className="md:col-span-2">
|
|
72
|
+
<InlineFieldLabel>Highlights</InlineFieldLabel>
|
|
73
|
+
<InlineStringInput
|
|
74
|
+
initialValue={block.data.highlights.join(", ")}
|
|
75
|
+
variables={(value) => ({
|
|
76
|
+
id: block.id,
|
|
77
|
+
data: {
|
|
78
|
+
highlights: (value ?? "")
|
|
79
|
+
.split(",")
|
|
80
|
+
.map((item) => item.trim())
|
|
81
|
+
.filter(Boolean),
|
|
82
|
+
},
|
|
83
|
+
})}
|
|
84
|
+
mutation={summaryMutation}
|
|
85
|
+
strategy={{ mutate: { on: "blur" }, validate: { blur: z.string().min(1) } }}
|
|
86
|
+
/>
|
|
87
|
+
<InlineFieldError />
|
|
88
|
+
</InlineField>
|
|
89
|
+
</InlineBlockContent>
|
|
90
|
+
</InlineBlock>
|
|
91
|
+
</InlineBlockSortable>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BlockComponentProps } from "@tulip-systems/blocks";
|
|
2
|
+
import type { quotationSummaryBlock } from "./config";
|
|
3
|
+
|
|
4
|
+
export function QuotationSummaryPreview({
|
|
5
|
+
block,
|
|
6
|
+
}: BlockComponentProps<typeof quotationSummaryBlock>) {
|
|
7
|
+
const { body, heading, highlights } = block.data;
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<section className="grid gap-6 rounded-3xl border bg-background p-8 md:grid-cols-[1fr_18rem]">
|
|
11
|
+
<div>
|
|
12
|
+
<h2 className="font-semibold text-2xl tracking-tight">{heading}</h2>
|
|
13
|
+
<p className="mt-4 text-muted-foreground leading-7">{body}</p>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<ul className="space-y-3">
|
|
17
|
+
{highlights.map((highlight) => (
|
|
18
|
+
<li key={highlight} className="rounded-2xl bg-muted/50 px-4 py-3 text-sm">
|
|
19
|
+
{highlight}
|
|
20
|
+
</li>
|
|
21
|
+
))}
|
|
22
|
+
</ul>
|
|
23
|
+
</section>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { BlockDefinition } from "@tulip-systems/blocks";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export const quotationTotalsBlock = {
|
|
5
|
+
slug: "totals",
|
|
6
|
+
label: "Totals",
|
|
7
|
+
description: "Line totals and final quotation amount.",
|
|
8
|
+
schema: z.object({
|
|
9
|
+
currency: z.string().min(1),
|
|
10
|
+
items: z.array(
|
|
11
|
+
z.object({
|
|
12
|
+
label: z.string().min(1),
|
|
13
|
+
amount: z.number(),
|
|
14
|
+
}),
|
|
15
|
+
),
|
|
16
|
+
total: z.number(),
|
|
17
|
+
}),
|
|
18
|
+
} as const satisfies BlockDefinition;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema";
|
|
4
|
+
import type { DefaultError } from "@tanstack/react-query";
|
|
5
|
+
import type { BlockCreateComponentProps } from "@tulip-systems/blocks";
|
|
6
|
+
import {
|
|
7
|
+
CreateBlock,
|
|
8
|
+
CreateBlockCancel,
|
|
9
|
+
CreateBlockContent,
|
|
10
|
+
CreateBlockDescription,
|
|
11
|
+
CreateBlockFields,
|
|
12
|
+
CreateBlockFooter,
|
|
13
|
+
CreateBlockHeader,
|
|
14
|
+
CreateBlockSubmit,
|
|
15
|
+
CreateBlockTitle,
|
|
16
|
+
CreateBlockTrigger,
|
|
17
|
+
CreateBlockTriggerContent,
|
|
18
|
+
CreateBlockTriggerDescription,
|
|
19
|
+
CreateBlockTriggerIcon,
|
|
20
|
+
CreateBlockTriggerTitle,
|
|
21
|
+
} from "@tulip-systems/blocks/client";
|
|
22
|
+
import { Input } from "@tulip-systems/ui/input";
|
|
23
|
+
import {
|
|
24
|
+
Form,
|
|
25
|
+
FormControl,
|
|
26
|
+
FormField,
|
|
27
|
+
FormItem,
|
|
28
|
+
FormLabel,
|
|
29
|
+
FormMessage,
|
|
30
|
+
} from "@tulip-systems/ui/react-hook-form";
|
|
31
|
+
import { BlocksIcon } from "lucide-react";
|
|
32
|
+
import { useForm } from "react-hook-form";
|
|
33
|
+
import { z } from "zod";
|
|
34
|
+
import { quotationTotalsBlock } from "./config";
|
|
35
|
+
|
|
36
|
+
export type QuotationTotalsCreateValues = z.input<typeof quotationTotalsBlock.schema>;
|
|
37
|
+
|
|
38
|
+
const totalsCreateFormSchema = z.object({
|
|
39
|
+
currency: quotationTotalsBlock.schema.shape.currency,
|
|
40
|
+
itemLabel: quotationTotalsBlock.schema.shape.items.element.shape.label,
|
|
41
|
+
itemAmount: quotationTotalsBlock.schema.shape.items.element.shape.amount,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
type QuotationTotalsFormValues = z.input<typeof totalsCreateFormSchema>;
|
|
45
|
+
|
|
46
|
+
type QuotationTotalsCreateBlockInput = {
|
|
47
|
+
slug: typeof quotationTotalsBlock.slug;
|
|
48
|
+
data: QuotationTotalsCreateValues;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type QuotationTotalsCreateBlockProps<
|
|
52
|
+
TData = unknown,
|
|
53
|
+
TError = DefaultError,
|
|
54
|
+
TVariables = undefined,
|
|
55
|
+
TOnMutateResult = unknown,
|
|
56
|
+
> = BlockCreateComponentProps<
|
|
57
|
+
QuotationTotalsCreateBlockInput,
|
|
58
|
+
TData,
|
|
59
|
+
TError,
|
|
60
|
+
TVariables,
|
|
61
|
+
TOnMutateResult
|
|
62
|
+
>;
|
|
63
|
+
|
|
64
|
+
export function QuotationTotalsCreateBlock<
|
|
65
|
+
TData = unknown,
|
|
66
|
+
TError = DefaultError,
|
|
67
|
+
TVariables = undefined,
|
|
68
|
+
TOnMutateResult = unknown,
|
|
69
|
+
>({
|
|
70
|
+
variables,
|
|
71
|
+
mutation,
|
|
72
|
+
}: QuotationTotalsCreateBlockProps<TData, TError, TVariables, TOnMutateResult>) {
|
|
73
|
+
const form = useForm<QuotationTotalsFormValues>({
|
|
74
|
+
resolver: standardSchemaResolver(totalsCreateFormSchema),
|
|
75
|
+
defaultValues: {
|
|
76
|
+
currency: "EUR",
|
|
77
|
+
itemLabel: "Implementatie",
|
|
78
|
+
itemAmount: 0,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<CreateBlock>
|
|
84
|
+
<CreateBlockTrigger>
|
|
85
|
+
<CreateBlockTriggerIcon>
|
|
86
|
+
<BlocksIcon />
|
|
87
|
+
</CreateBlockTriggerIcon>
|
|
88
|
+
|
|
89
|
+
<CreateBlockTriggerContent>
|
|
90
|
+
<CreateBlockTriggerTitle>{quotationTotalsBlock.label}</CreateBlockTriggerTitle>
|
|
91
|
+
<CreateBlockTriggerDescription>
|
|
92
|
+
{quotationTotalsBlock.description}
|
|
93
|
+
</CreateBlockTriggerDescription>
|
|
94
|
+
</CreateBlockTriggerContent>
|
|
95
|
+
</CreateBlockTrigger>
|
|
96
|
+
|
|
97
|
+
<Form {...form}>
|
|
98
|
+
<CreateBlockContent
|
|
99
|
+
className="sm:max-w-xl"
|
|
100
|
+
variables={(values: QuotationTotalsFormValues) =>
|
|
101
|
+
variables({
|
|
102
|
+
slug: quotationTotalsBlock.slug,
|
|
103
|
+
data: {
|
|
104
|
+
currency: values.currency,
|
|
105
|
+
items: [{ label: values.itemLabel, amount: values.itemAmount }],
|
|
106
|
+
total: values.itemAmount,
|
|
107
|
+
},
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
mutation={mutation}
|
|
111
|
+
>
|
|
112
|
+
<CreateBlockHeader>
|
|
113
|
+
<CreateBlockTitle>{quotationTotalsBlock.label} toevoegen</CreateBlockTitle>
|
|
114
|
+
<CreateBlockDescription>
|
|
115
|
+
Vul de verplichte velden in om het blok toe te voegen.
|
|
116
|
+
</CreateBlockDescription>
|
|
117
|
+
</CreateBlockHeader>
|
|
118
|
+
|
|
119
|
+
<CreateBlockFields>
|
|
120
|
+
<FormField
|
|
121
|
+
control={form.control}
|
|
122
|
+
name="currency"
|
|
123
|
+
render={({ field }) => (
|
|
124
|
+
<FormItem>
|
|
125
|
+
<FormLabel>Valuta</FormLabel>
|
|
126
|
+
<FormControl>
|
|
127
|
+
<Input {...field} />
|
|
128
|
+
</FormControl>
|
|
129
|
+
<FormMessage />
|
|
130
|
+
</FormItem>
|
|
131
|
+
)}
|
|
132
|
+
/>
|
|
133
|
+
|
|
134
|
+
<div className="grid gap-4 md:grid-cols-2">
|
|
135
|
+
<FormField
|
|
136
|
+
control={form.control}
|
|
137
|
+
name="itemLabel"
|
|
138
|
+
render={({ field }) => (
|
|
139
|
+
<FormItem>
|
|
140
|
+
<FormLabel>Regel</FormLabel>
|
|
141
|
+
<FormControl>
|
|
142
|
+
<Input {...field} />
|
|
143
|
+
</FormControl>
|
|
144
|
+
<FormMessage />
|
|
145
|
+
</FormItem>
|
|
146
|
+
)}
|
|
147
|
+
/>
|
|
148
|
+
|
|
149
|
+
<FormField
|
|
150
|
+
control={form.control}
|
|
151
|
+
name="itemAmount"
|
|
152
|
+
render={({ field }) => (
|
|
153
|
+
<FormItem>
|
|
154
|
+
<FormLabel>Bedrag</FormLabel>
|
|
155
|
+
<FormControl>
|
|
156
|
+
<Input
|
|
157
|
+
{...field}
|
|
158
|
+
type="number"
|
|
159
|
+
onChange={(event) => field.onChange(event.currentTarget.valueAsNumber || 0)}
|
|
160
|
+
/>
|
|
161
|
+
</FormControl>
|
|
162
|
+
<FormMessage />
|
|
163
|
+
</FormItem>
|
|
164
|
+
)}
|
|
165
|
+
/>
|
|
166
|
+
</div>
|
|
167
|
+
</CreateBlockFields>
|
|
168
|
+
|
|
169
|
+
<CreateBlockFooter>
|
|
170
|
+
<CreateBlockCancel>Annuleren</CreateBlockCancel>
|
|
171
|
+
<CreateBlockSubmit>Blok toevoegen</CreateBlockSubmit>
|
|
172
|
+
</CreateBlockFooter>
|
|
173
|
+
</CreateBlockContent>
|
|
174
|
+
</Form>
|
|
175
|
+
</CreateBlock>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { BlockComponentProps } from "@tulip-systems/blocks";
|
|
4
|
+
import {
|
|
5
|
+
InlineBlock,
|
|
6
|
+
InlineBlockContent,
|
|
7
|
+
InlineBlockDragHandle,
|
|
8
|
+
InlineBlockHeader,
|
|
9
|
+
InlineBlockSortable,
|
|
10
|
+
InlineBlockTrigger,
|
|
11
|
+
} from "@tulip-systems/blocks/client";
|
|
12
|
+
import { DropdownCommandMenu } from "@tulip-systems/commands/client";
|
|
13
|
+
import {
|
|
14
|
+
InlineField,
|
|
15
|
+
InlineFieldError,
|
|
16
|
+
InlineFieldLabel,
|
|
17
|
+
InlineNumberInput,
|
|
18
|
+
InlineStringInput,
|
|
19
|
+
} from "@tulip-systems/inline/client";
|
|
20
|
+
import { GripIcon } from "lucide-react";
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
import { orpc } from "@/server/router/client";
|
|
23
|
+
import { quotationInlineBlockCommands } from "../commands";
|
|
24
|
+
import { quotationTotalsBlock } from "./config";
|
|
25
|
+
|
|
26
|
+
const totalsMutation = orpc.quotationBlocks.update.mutationOptions({
|
|
27
|
+
meta: { invalidates: [orpc.quotationBlocks.key()] },
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export function QuotationTotalsInlineBlock({
|
|
31
|
+
block,
|
|
32
|
+
}: BlockComponentProps<typeof quotationTotalsBlock>) {
|
|
33
|
+
return (
|
|
34
|
+
<InlineBlockSortable id={block.id}>
|
|
35
|
+
<InlineBlock>
|
|
36
|
+
<InlineBlockHeader>
|
|
37
|
+
<InlineBlockDragHandle>
|
|
38
|
+
<GripIcon className="h-4 w-4" />
|
|
39
|
+
<span className="sr-only">Blok verplaatsen</span>
|
|
40
|
+
</InlineBlockDragHandle>
|
|
41
|
+
<InlineBlockTrigger>{quotationTotalsBlock.label}</InlineBlockTrigger>
|
|
42
|
+
<DropdownCommandMenu
|
|
43
|
+
data={{ id: block.id }}
|
|
44
|
+
commands={quotationInlineBlockCommands}
|
|
45
|
+
className="ml-2 size-7"
|
|
46
|
+
/>
|
|
47
|
+
</InlineBlockHeader>
|
|
48
|
+
|
|
49
|
+
<InlineBlockContent className="grid gap-4 md:grid-cols-2">
|
|
50
|
+
<InlineField>
|
|
51
|
+
<InlineFieldLabel>Valuta</InlineFieldLabel>
|
|
52
|
+
<InlineStringInput
|
|
53
|
+
initialValue={block.data.currency}
|
|
54
|
+
variables={(value) => ({ id: block.id, data: { currency: value ?? "EUR" } })}
|
|
55
|
+
mutation={totalsMutation}
|
|
56
|
+
strategy={{ mutate: { on: "blur" }, validate: { blur: z.string().min(1) } }}
|
|
57
|
+
/>
|
|
58
|
+
<InlineFieldError />
|
|
59
|
+
</InlineField>
|
|
60
|
+
|
|
61
|
+
<InlineField>
|
|
62
|
+
<InlineFieldLabel>Totaal</InlineFieldLabel>
|
|
63
|
+
<InlineNumberInput
|
|
64
|
+
initialValue={block.data.total}
|
|
65
|
+
variables={(value) => ({ id: block.id, data: { total: value ?? 0 } })}
|
|
66
|
+
mutation={totalsMutation}
|
|
67
|
+
strategy={{ mutate: { on: "blur" }, validate: { blur: z.number().min(0) } }}
|
|
68
|
+
/>
|
|
69
|
+
<InlineFieldError />
|
|
70
|
+
</InlineField>
|
|
71
|
+
|
|
72
|
+
{block.data.items.slice(0, 2).map((item, index) => (
|
|
73
|
+
<InlineField key={item.label}>
|
|
74
|
+
<InlineFieldLabel>Regel {index + 1}</InlineFieldLabel>
|
|
75
|
+
<InlineStringInput
|
|
76
|
+
initialValue={`${item.label}: ${item.amount}`}
|
|
77
|
+
variables={(value) => ({
|
|
78
|
+
id: block.id,
|
|
79
|
+
data: {
|
|
80
|
+
items: block.data.items.with(index, {
|
|
81
|
+
...item,
|
|
82
|
+
label: value?.split(":")[0]?.trim() || item.label,
|
|
83
|
+
}),
|
|
84
|
+
},
|
|
85
|
+
})}
|
|
86
|
+
mutation={totalsMutation}
|
|
87
|
+
strategy={{ mutate: { on: "blur" }, validate: { blur: z.string().min(1) } }}
|
|
88
|
+
/>
|
|
89
|
+
<InlineFieldError />
|
|
90
|
+
</InlineField>
|
|
91
|
+
))}
|
|
92
|
+
</InlineBlockContent>
|
|
93
|
+
</InlineBlock>
|
|
94
|
+
</InlineBlockSortable>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { BlockComponentProps } from "@tulip-systems/blocks";
|
|
2
|
+
import type { quotationTotalsBlock } from "./config";
|
|
3
|
+
|
|
4
|
+
function formatCurrency(value: number, currency: string) {
|
|
5
|
+
return new Intl.NumberFormat("nl-BE", { style: "currency", currency }).format(value);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function QuotationTotalsPreview({
|
|
9
|
+
block,
|
|
10
|
+
}: BlockComponentProps<typeof quotationTotalsBlock>) {
|
|
11
|
+
const { currency, items, total } = block.data;
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<section className="rounded-3xl border bg-card p-8 shadow-sm">
|
|
15
|
+
<h2 className="font-semibold text-2xl tracking-tight">Prijsopbouw</h2>
|
|
16
|
+
|
|
17
|
+
<div className="mt-6 divide-y rounded-2xl border">
|
|
18
|
+
{items.map((item) => (
|
|
19
|
+
<div key={item.label} className="flex items-center justify-between gap-4 p-4">
|
|
20
|
+
<span className="text-muted-foreground">{item.label}</span>
|
|
21
|
+
<span className="font-medium">{formatCurrency(item.amount, currency)}</span>
|
|
22
|
+
</div>
|
|
23
|
+
))}
|
|
24
|
+
|
|
25
|
+
<div className="flex items-center justify-between gap-4 bg-muted/40 p-4 font-semibold text-lg">
|
|
26
|
+
<span>Totaal</span>
|
|
27
|
+
<span>{formatCurrency(total, currency)}</span>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</section>
|
|
31
|
+
);
|
|
32
|
+
}
|