@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
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
desc,
|
|
14
14
|
eq,
|
|
15
15
|
exists,
|
|
16
|
+
getTableColumns,
|
|
16
17
|
gte,
|
|
17
18
|
ilike,
|
|
18
19
|
inArray,
|
|
@@ -22,9 +23,12 @@ import {
|
|
|
22
23
|
or,
|
|
23
24
|
sql,
|
|
24
25
|
} from "drizzle-orm";
|
|
26
|
+
import { after } from "next/server";
|
|
25
27
|
import z from "zod";
|
|
28
|
+
import type { JSONValue } from "@/app/admin/history/_lib/validators";
|
|
26
29
|
import { getTableData } from "@/app/lib/utils/table-data.server";
|
|
27
30
|
import { customers, initiatives } from "@/server/db/schema";
|
|
31
|
+
import { events } from "@/server/events/init";
|
|
28
32
|
import { permissionMiddleware } from "@/server/router/middleware";
|
|
29
33
|
import { protectedProcedure } from "@/server/router/procedures";
|
|
30
34
|
import type { ProjectColumn } from "../_config/columns-data";
|
|
@@ -34,6 +38,12 @@ import {
|
|
|
34
38
|
projectTableSortsParser,
|
|
35
39
|
projectTableSortsSchema,
|
|
36
40
|
} from "../_config/sorting";
|
|
41
|
+
import {
|
|
42
|
+
projectArchivedEvent,
|
|
43
|
+
projectCreatedEvent,
|
|
44
|
+
projectRestoredEvent,
|
|
45
|
+
projectUpdatedEvent,
|
|
46
|
+
} from "./events";
|
|
37
47
|
import { ensureProjectDriveFolder } from "./helpers.server";
|
|
38
48
|
import { projects } from "./schema";
|
|
39
49
|
import {
|
|
@@ -215,50 +225,135 @@ export const projectsRouter = {
|
|
|
215
225
|
.use(permissionMiddleware({ project: ["create"] }))
|
|
216
226
|
.input(createProjectSchema)
|
|
217
227
|
.handler(async ({ context, input }) => {
|
|
218
|
-
const [
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
228
|
+
const [created] = await context.db
|
|
229
|
+
.insert(projects)
|
|
230
|
+
.values(input)
|
|
231
|
+
.returning({
|
|
232
|
+
...getTableColumns(projects),
|
|
233
|
+
after: sql<Record<string, JSONValue>>`to_jsonb(NEW)`,
|
|
223
234
|
});
|
|
224
|
-
|
|
235
|
+
|
|
236
|
+
if (!created) throw new ORPCError("INTERNAL_SERVER_ERROR");
|
|
237
|
+
const { after: snapshot, ...result } = created;
|
|
238
|
+
|
|
239
|
+
after(async () => {
|
|
240
|
+
await events.publish(
|
|
241
|
+
projectCreatedEvent,
|
|
242
|
+
{
|
|
243
|
+
actor: { id: context.user.id, name: context.user.name },
|
|
244
|
+
projectId: result.id,
|
|
245
|
+
key: result.key,
|
|
246
|
+
title: result.title,
|
|
247
|
+
status: result.status,
|
|
248
|
+
after: snapshot,
|
|
249
|
+
},
|
|
250
|
+
{ occurredAt: new Date().toISOString() },
|
|
251
|
+
);
|
|
252
|
+
});
|
|
225
253
|
|
|
226
254
|
return result;
|
|
227
255
|
}),
|
|
228
256
|
update: protectedProcedure
|
|
229
257
|
.use(permissionMiddleware({ project: ["update"] }))
|
|
230
258
|
.input(z.object({ id: z.string(), data: updateProjectSchema.partial() }))
|
|
231
|
-
.output(z.void())
|
|
232
259
|
.handler(async ({ context, input }) => {
|
|
233
260
|
const [result] = await context.db
|
|
234
261
|
.update(projects)
|
|
235
262
|
.set(input.data)
|
|
236
263
|
.where(eq(projects.id, input.id))
|
|
237
|
-
.returning(
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
message: "Project kon niet worden gewijzigd",
|
|
264
|
+
.returning({
|
|
265
|
+
...getTableColumns(projects),
|
|
266
|
+
before: sql<Record<string, JSONValue>>`to_jsonb(OLD)`,
|
|
267
|
+
after: sql<Record<string, JSONValue>>`to_jsonb(NEW)`,
|
|
242
268
|
});
|
|
243
|
-
|
|
269
|
+
|
|
270
|
+
if (!result) return;
|
|
271
|
+
const { before: previous, after: next, ...project } = result;
|
|
272
|
+
after(async () => {
|
|
273
|
+
await events.publish(
|
|
274
|
+
projectUpdatedEvent,
|
|
275
|
+
{
|
|
276
|
+
actor: { id: context.user.id, name: context.user.name },
|
|
277
|
+
projectId: project.id,
|
|
278
|
+
key: project.key,
|
|
279
|
+
title: project.title,
|
|
280
|
+
before: previous,
|
|
281
|
+
after: next,
|
|
282
|
+
},
|
|
283
|
+
{ occurredAt: new Date().toISOString() },
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
return project;
|
|
244
287
|
}),
|
|
245
288
|
archive: protectedProcedure
|
|
246
289
|
.use(permissionMiddleware({ project: ["archive"] }))
|
|
247
290
|
.input(bulkActionSchema)
|
|
248
291
|
.handler(async ({ context, input }) => {
|
|
249
|
-
await context.db
|
|
292
|
+
const updated = await context.db
|
|
250
293
|
.update(projects)
|
|
251
294
|
.set({ deletedAt: new Date() })
|
|
252
|
-
.where(inArray(projects.id, input.ids))
|
|
295
|
+
.where(and(inArray(projects.id, input.ids), isNull(projects.deletedAt)))
|
|
296
|
+
.returning({
|
|
297
|
+
id: projects.id,
|
|
298
|
+
key: projects.key,
|
|
299
|
+
title: projects.title,
|
|
300
|
+
before: sql<Record<string, JSONValue>>`to_jsonb(OLD)`,
|
|
301
|
+
after: sql<Record<string, JSONValue>>`to_jsonb(NEW)`,
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
after(async () => {
|
|
305
|
+
await Promise.all(
|
|
306
|
+
updated.map((project) =>
|
|
307
|
+
events.publish(
|
|
308
|
+
projectArchivedEvent,
|
|
309
|
+
{
|
|
310
|
+
actor: { id: context.user.id, name: context.user.name },
|
|
311
|
+
projectId: project.id,
|
|
312
|
+
key: project.key,
|
|
313
|
+
title: project.title,
|
|
314
|
+
before: project.before,
|
|
315
|
+
after: project.after,
|
|
316
|
+
},
|
|
317
|
+
{ occurredAt: new Date().toISOString() },
|
|
318
|
+
),
|
|
319
|
+
),
|
|
320
|
+
);
|
|
321
|
+
});
|
|
253
322
|
}),
|
|
254
323
|
restore: protectedProcedure
|
|
255
324
|
.use(permissionMiddleware({ project: ["archive"] }))
|
|
256
325
|
.input(bulkActionSchema)
|
|
257
326
|
.handler(async ({ context, input }) => {
|
|
258
|
-
await context.db
|
|
327
|
+
const updated = await context.db
|
|
259
328
|
.update(projects)
|
|
260
329
|
.set({ deletedAt: null })
|
|
261
|
-
.where(inArray(projects.id, input.ids))
|
|
330
|
+
.where(and(inArray(projects.id, input.ids), isNotNull(projects.deletedAt)))
|
|
331
|
+
.returning({
|
|
332
|
+
id: projects.id,
|
|
333
|
+
key: projects.key,
|
|
334
|
+
title: projects.title,
|
|
335
|
+
before: sql<Record<string, JSONValue>>`to_jsonb(OLD)`,
|
|
336
|
+
after: sql<Record<string, JSONValue>>`to_jsonb(NEW)`,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
after(async () => {
|
|
340
|
+
await Promise.all(
|
|
341
|
+
updated.map((project) =>
|
|
342
|
+
events.publish(
|
|
343
|
+
projectRestoredEvent,
|
|
344
|
+
{
|
|
345
|
+
actor: { id: context.user.id, name: context.user.name },
|
|
346
|
+
projectId: project.id,
|
|
347
|
+
key: project.key,
|
|
348
|
+
title: project.title,
|
|
349
|
+
before: project.before,
|
|
350
|
+
after: project.after,
|
|
351
|
+
},
|
|
352
|
+
{ occurredAt: new Date().toISOString() },
|
|
353
|
+
),
|
|
354
|
+
),
|
|
355
|
+
);
|
|
356
|
+
});
|
|
262
357
|
}),
|
|
263
358
|
delete: protectedProcedure
|
|
264
359
|
.use(permissionMiddleware({ project: ["delete"] }))
|
|
@@ -270,10 +365,40 @@ export const projectsRouter = {
|
|
|
270
365
|
.use(permissionMiddleware({ project: ["update"] }))
|
|
271
366
|
.input(bulkActionSchema.extend({ status: projectStatusSchema }))
|
|
272
367
|
.handler(async ({ context, input }) => {
|
|
273
|
-
await context.db
|
|
368
|
+
const updated = await context.db
|
|
274
369
|
.update(projects)
|
|
275
370
|
.set({ status: input.status })
|
|
276
|
-
.where(
|
|
277
|
-
|
|
371
|
+
.where(
|
|
372
|
+
and(
|
|
373
|
+
inArray(projects.id, input.ids),
|
|
374
|
+
sql`${projects.status} IS DISTINCT FROM ${input.status}`,
|
|
375
|
+
),
|
|
376
|
+
)
|
|
377
|
+
.returning({
|
|
378
|
+
id: projects.id,
|
|
379
|
+
key: projects.key,
|
|
380
|
+
title: projects.title,
|
|
381
|
+
before: sql<Record<string, JSONValue>>`to_jsonb(OLD)`,
|
|
382
|
+
after: sql<Record<string, JSONValue>>`to_jsonb(NEW)`,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
after(async () => {
|
|
386
|
+
await Promise.all(
|
|
387
|
+
updated.map((project) =>
|
|
388
|
+
events.publish(
|
|
389
|
+
projectUpdatedEvent,
|
|
390
|
+
{
|
|
391
|
+
actor: { id: context.user.id, name: context.user.name },
|
|
392
|
+
projectId: project.id,
|
|
393
|
+
key: project.key,
|
|
394
|
+
title: project.title,
|
|
395
|
+
before: project.before,
|
|
396
|
+
after: project.after,
|
|
397
|
+
},
|
|
398
|
+
{ occurredAt: new Date().toISOString() },
|
|
399
|
+
),
|
|
400
|
+
),
|
|
401
|
+
);
|
|
402
|
+
});
|
|
278
403
|
}),
|
|
279
404
|
};
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import { Temporal } from "@js-temporal/polyfill";
|
|
4
4
|
import { useSuspenseQuery } from "@tanstack/react-query";
|
|
5
|
+
import { Link } from "@tulip-systems/core/navigation";
|
|
5
6
|
import { Button } from "@tulip-systems/ui/button";
|
|
6
7
|
import { Select, SelectContent, SelectItem, SelectTrigger } from "@tulip-systems/ui/select";
|
|
7
8
|
import { findStatus } from "@tulip-systems/ui/status";
|
|
8
9
|
import { MinusIcon, PlusIcon } from "lucide-react";
|
|
9
|
-
import Link from "next/link";
|
|
10
10
|
import { parseAsStringEnum, useQueryState } from "nuqs";
|
|
11
11
|
import { useState } from "react";
|
|
12
12
|
import {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { Link } from "@tulip-systems/core/navigation";
|
|
3
4
|
import { TableCell, type TableColumnDef, TableColumnHeader } from "@tulip-systems/data-tables";
|
|
4
5
|
import {
|
|
5
6
|
createTableSelectCell,
|
|
6
7
|
TableStatusCell,
|
|
7
8
|
TableTextCell,
|
|
8
9
|
} from "@tulip-systems/data-tables/client";
|
|
9
|
-
import Link from "next/link";
|
|
10
10
|
import type { Customer } from "../../customers/_lib/validators";
|
|
11
11
|
import type { Prospect } from "../_lib/validators";
|
|
12
12
|
import { ProspectStatusField } from "./status";
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema";
|
|
4
|
+
import type { DefaultError, MutationOptions } from "@tanstack/react-query";
|
|
5
|
+
import {
|
|
6
|
+
CommandFormDialog,
|
|
7
|
+
CommandFormDialogCancel,
|
|
8
|
+
CommandFormDialogContent,
|
|
9
|
+
CommandFormDialogFields,
|
|
10
|
+
CommandFormDialogFooter,
|
|
11
|
+
CommandFormDialogHeader,
|
|
12
|
+
CommandFormDialogSubmit,
|
|
13
|
+
CommandFormDialogTitle,
|
|
14
|
+
CommandFormDialogTrigger,
|
|
15
|
+
} from "@tulip-systems/commands/client";
|
|
16
|
+
import { Input } from "@tulip-systems/ui/input";
|
|
17
|
+
import {
|
|
18
|
+
Form,
|
|
19
|
+
FormControl,
|
|
20
|
+
FormField,
|
|
21
|
+
FormItem,
|
|
22
|
+
FormLabel,
|
|
23
|
+
FormMessage,
|
|
24
|
+
} from "@tulip-systems/ui/react-hook-form";
|
|
25
|
+
import type React from "react";
|
|
26
|
+
import { useForm } from "react-hook-form";
|
|
27
|
+
import {
|
|
28
|
+
type EnqueueQueueTaskInput,
|
|
29
|
+
type EnqueueQueueTaskSchema,
|
|
30
|
+
enqueueQueueTaskSchema,
|
|
31
|
+
} from "../_lib/validators";
|
|
32
|
+
|
|
33
|
+
export type EnqueueQueueTaskCommandProps<
|
|
34
|
+
TData = unknown,
|
|
35
|
+
TError = DefaultError,
|
|
36
|
+
TVariables = undefined,
|
|
37
|
+
TOnMutateResult = unknown,
|
|
38
|
+
> = {
|
|
39
|
+
variables: (values: EnqueueQueueTaskSchema) => TVariables;
|
|
40
|
+
mutation: MutationOptions<TData, TError, TVariables, TOnMutateResult>;
|
|
41
|
+
children?: React.ReactNode;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export function EnqueueQueueTaskCommand<
|
|
45
|
+
TData = unknown,
|
|
46
|
+
TError = DefaultError,
|
|
47
|
+
TVariables = undefined,
|
|
48
|
+
TOnMutateResult = unknown,
|
|
49
|
+
>({
|
|
50
|
+
variables,
|
|
51
|
+
mutation,
|
|
52
|
+
children,
|
|
53
|
+
}: EnqueueQueueTaskCommandProps<TData, TError, TVariables, TOnMutateResult>) {
|
|
54
|
+
const form = useForm<EnqueueQueueTaskInput>({
|
|
55
|
+
resolver: standardSchemaResolver(enqueueQueueTaskSchema),
|
|
56
|
+
defaultValues: {
|
|
57
|
+
message: "",
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<CommandFormDialog>
|
|
63
|
+
{children}
|
|
64
|
+
|
|
65
|
+
<Form {...form}>
|
|
66
|
+
<CommandFormDialogContent variables={variables} mutation={mutation}>
|
|
67
|
+
<CommandFormDialogHeader>
|
|
68
|
+
<CommandFormDialogTitle>Enqueue task</CommandFormDialogTitle>
|
|
69
|
+
</CommandFormDialogHeader>
|
|
70
|
+
|
|
71
|
+
<CommandFormDialogFields>
|
|
72
|
+
<FormField
|
|
73
|
+
control={form.control}
|
|
74
|
+
name="message"
|
|
75
|
+
render={({ field }) => (
|
|
76
|
+
<FormItem>
|
|
77
|
+
<FormLabel>Message</FormLabel>
|
|
78
|
+
<FormControl>
|
|
79
|
+
<Input {...field} placeholder="Enter your message" />
|
|
80
|
+
</FormControl>
|
|
81
|
+
<FormMessage />
|
|
82
|
+
</FormItem>
|
|
83
|
+
)}
|
|
84
|
+
/>
|
|
85
|
+
</CommandFormDialogFields>
|
|
86
|
+
|
|
87
|
+
<CommandFormDialogFooter>
|
|
88
|
+
<CommandFormDialogCancel>Cancel</CommandFormDialogCancel>
|
|
89
|
+
<CommandFormDialogSubmit>Enqueue</CommandFormDialogSubmit>
|
|
90
|
+
</CommandFormDialogFooter>
|
|
91
|
+
</CommandFormDialogContent>
|
|
92
|
+
</Form>
|
|
93
|
+
</CommandFormDialog>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const EnqueueQueueTaskCommandTrigger = CommandFormDialogTrigger;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useSuspenseQuery } from "@tanstack/react-query";
|
|
4
|
+
import type { CommandFor } from "@tulip-systems/commands";
|
|
5
|
+
import type { TableColumnDef } from "@tulip-systems/data-tables";
|
|
6
|
+
import {
|
|
7
|
+
createTableConfig,
|
|
8
|
+
TableConfigProvider,
|
|
9
|
+
useLocalStrategy,
|
|
10
|
+
} from "@tulip-systems/data-tables/client";
|
|
11
|
+
import type { PropsWithChildren } from "react";
|
|
12
|
+
import { orpc } from "@/server/router/client";
|
|
13
|
+
import type { QueueExecution } from "../_lib/validators";
|
|
14
|
+
|
|
15
|
+
type QueueExecutionsTableProviderProps = PropsWithChildren<{
|
|
16
|
+
columns: TableColumnDef<QueueExecution>[];
|
|
17
|
+
commands?: CommandFor<QueueExecution[]>[];
|
|
18
|
+
}>;
|
|
19
|
+
|
|
20
|
+
export function QueueExecutionsTableProvider({
|
|
21
|
+
columns,
|
|
22
|
+
commands,
|
|
23
|
+
children,
|
|
24
|
+
}: QueueExecutionsTableProviderProps) {
|
|
25
|
+
const response = useSuspenseQuery(orpc.queue.listExecutions.queryOptions());
|
|
26
|
+
const strategy = useLocalStrategy({ total: response.data.length });
|
|
27
|
+
|
|
28
|
+
const config = createTableConfig<QueueExecution>({
|
|
29
|
+
queryData: response.data,
|
|
30
|
+
getRowId: (row) => row.id,
|
|
31
|
+
columns,
|
|
32
|
+
strategy,
|
|
33
|
+
commands,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return <TableConfigProvider config={config}>{children}</TableConfigProvider>;
|
|
37
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { TableToolbar, TableToolbarList } from "@tulip-systems/data-tables";
|
|
4
|
+
import type { PropsWithChildren } from "react";
|
|
5
|
+
|
|
6
|
+
export function QueueExecutionsTableToolbar({ children }: PropsWithChildren) {
|
|
7
|
+
return (
|
|
8
|
+
<TableToolbar>
|
|
9
|
+
<TableToolbarList>
|
|
10
|
+
<h1 className="font-semibold text-lg tracking-tight">Queue executions</h1>
|
|
11
|
+
</TableToolbarList>
|
|
12
|
+
|
|
13
|
+
<TableToolbarList>{children}</TableToolbarList>
|
|
14
|
+
</TableToolbar>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { formatDateTime } from "@tulip-systems/core";
|
|
4
|
+
import { TableCell, type TableColumnDef, TableColumnHeader } from "@tulip-systems/data-tables";
|
|
5
|
+
import { createTableSelectCell } from "@tulip-systems/data-tables/client";
|
|
6
|
+
import type { QueueExecution } from "../_lib/validators";
|
|
7
|
+
|
|
8
|
+
export const queueExecutionColumns: TableColumnDef<QueueExecution>[] = [
|
|
9
|
+
createTableSelectCell(),
|
|
10
|
+
{
|
|
11
|
+
accessorKey: "message",
|
|
12
|
+
header: ({ column }) => <TableColumnHeader column={column} title="Message" />,
|
|
13
|
+
cell: ({ row }) => (
|
|
14
|
+
<TableCell className="max-w-[360px] truncate">{row.original.message}</TableCell>
|
|
15
|
+
),
|
|
16
|
+
enableSorting: false,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
accessorKey: "task",
|
|
20
|
+
header: ({ column }) => <TableColumnHeader column={column} title="Task" />,
|
|
21
|
+
cell: ({ row }) => <TableCell>{row.original.task}</TableCell>,
|
|
22
|
+
enableSorting: false,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
accessorKey: "jobId",
|
|
26
|
+
header: ({ column }) => <TableColumnHeader column={column} title="Job" />,
|
|
27
|
+
cell: ({ row }) => (
|
|
28
|
+
<TableCell className="max-w-[280px] truncate">{row.original.jobId}</TableCell>
|
|
29
|
+
),
|
|
30
|
+
enableSorting: false,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
accessorKey: "enqueuedAt",
|
|
34
|
+
header: ({ column }) => <TableColumnHeader column={column} title="Enqueued" />,
|
|
35
|
+
cell: ({ row }) => <TableCell>{formatDateTime(row.original.enqueuedAt)}</TableCell>,
|
|
36
|
+
enableSorting: false,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
accessorKey: "executedAt",
|
|
40
|
+
header: ({ column }) => <TableColumnHeader column={column} title="Executed" />,
|
|
41
|
+
cell: ({ row }) => <TableCell>{formatDateTime(row.original.executedAt)}</TableCell>,
|
|
42
|
+
enableSorting: false,
|
|
43
|
+
},
|
|
44
|
+
];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { commandBuilder } from "@tulip-systems/commands";
|
|
4
|
+
import { CommandLabel, DeleteCommand } from "@tulip-systems/commands/client";
|
|
5
|
+
import { toast } from "@tulip-systems/ui/sonner";
|
|
6
|
+
import { ListPlusIcon } from "lucide-react";
|
|
7
|
+
import { orpc } from "@/server/router/client";
|
|
8
|
+
import {
|
|
9
|
+
EnqueueQueueTaskCommand,
|
|
10
|
+
EnqueueQueueTaskCommandTrigger,
|
|
11
|
+
} from "../_components/create-command";
|
|
12
|
+
import type { QueueExecution } from "../_lib/validators";
|
|
13
|
+
|
|
14
|
+
export const queueEnqueueCommand = commandBuilder.render(() => (
|
|
15
|
+
<EnqueueQueueTaskCommand
|
|
16
|
+
variables={(values) => values}
|
|
17
|
+
mutation={orpc.queue.enqueueTask.mutationOptions({
|
|
18
|
+
meta: { invalidates: [orpc.queue.listExecutions.key()] },
|
|
19
|
+
onSuccess: () => toast.success("Task enqueued"),
|
|
20
|
+
onError: () => toast.error("Could not enqueue task"),
|
|
21
|
+
})}
|
|
22
|
+
>
|
|
23
|
+
<EnqueueQueueTaskCommandTrigger label="Enqueue task" hotkey="mod+i">
|
|
24
|
+
<ListPlusIcon className="size-4" />
|
|
25
|
+
<CommandLabel />
|
|
26
|
+
</EnqueueQueueTaskCommandTrigger>
|
|
27
|
+
</EnqueueQueueTaskCommand>
|
|
28
|
+
));
|
|
29
|
+
|
|
30
|
+
export const queueDeleteCommand = commandBuilder.$type<QueueExecution[]>().render(({ data }) => (
|
|
31
|
+
<DeleteCommand
|
|
32
|
+
variables={{ ids: data.map((item) => item.id) }}
|
|
33
|
+
mutation={orpc.queue.deleteExecutions.mutationOptions({
|
|
34
|
+
meta: { invalidates: [orpc.queue.listExecutions.key()] },
|
|
35
|
+
onSuccess: () => toast.success("Queue executions deleted"),
|
|
36
|
+
onError: () => toast.error("Could not delete queue executions"),
|
|
37
|
+
})}
|
|
38
|
+
/>
|
|
39
|
+
));
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import "server-cli-only";
|
|
2
|
+
|
|
3
|
+
import { defineTask } from "@tulip-systems/queue";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { db } from "@/server/db/init";
|
|
6
|
+
import { queueExecutions } from "./schema";
|
|
7
|
+
|
|
8
|
+
export const queueLogTask = defineTask("queue.log", {
|
|
9
|
+
schema: z.object({
|
|
10
|
+
message: z.string().min(1),
|
|
11
|
+
requestedAt: z.string().min(1),
|
|
12
|
+
}),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const queueLogTaskHandler = queueLogTask.handle(
|
|
16
|
+
"record-queue-execution",
|
|
17
|
+
async ({ payload, job }) => {
|
|
18
|
+
await db.insert(queueExecutions).values({
|
|
19
|
+
jobId: job.id,
|
|
20
|
+
task: job.task,
|
|
21
|
+
message: payload.message,
|
|
22
|
+
enqueuedAt: new Date(job.enqueuedAt),
|
|
23
|
+
requestedAt: new Date(payload.requestedAt),
|
|
24
|
+
executedAt: new Date(),
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import "server-cli-only";
|
|
2
|
+
|
|
3
|
+
import { desc, inArray } from "drizzle-orm";
|
|
4
|
+
import { queue } from "@/server/queue/init";
|
|
5
|
+
import { protectedProcedure } from "@/server/router/procedures";
|
|
6
|
+
import { queueLogTask } from "./queue";
|
|
7
|
+
import { queueExecutions } from "./schema";
|
|
8
|
+
import { deleteQueueExecutionsSchema, enqueueQueueTaskSchema } from "./validators";
|
|
9
|
+
|
|
10
|
+
export const queueRouter = {
|
|
11
|
+
listExecutions: protectedProcedure.handler(async ({ context }) =>
|
|
12
|
+
context.db.query.queueExecutions.findMany({
|
|
13
|
+
orderBy: [desc(queueExecutions.executedAt)],
|
|
14
|
+
limit: 100,
|
|
15
|
+
}),
|
|
16
|
+
),
|
|
17
|
+
enqueueTask: protectedProcedure.input(enqueueQueueTaskSchema).handler(async ({ input }) => {
|
|
18
|
+
const result = await queue.enqueue(queueLogTask, {
|
|
19
|
+
message: input.message,
|
|
20
|
+
requestedAt: new Date().toISOString(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
adapter: queue.adapter.key,
|
|
25
|
+
enqueued: result,
|
|
26
|
+
};
|
|
27
|
+
}),
|
|
28
|
+
deleteExecutions: protectedProcedure
|
|
29
|
+
.input(deleteQueueExecutionsSchema)
|
|
30
|
+
.handler(async ({ context, input }) => {
|
|
31
|
+
await context.db.delete(queueExecutions).where(inArray(queueExecutions.id, input.ids));
|
|
32
|
+
}),
|
|
33
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { pgTable } from "drizzle-orm/pg-core";
|
|
2
|
+
import { baseColumns } from "@/server/db/helpers";
|
|
3
|
+
|
|
4
|
+
export const queueExecutions = pgTable("queue_executions", (t) => ({
|
|
5
|
+
...baseColumns,
|
|
6
|
+
jobId: t.varchar().notNull(),
|
|
7
|
+
task: t.varchar().notNull(),
|
|
8
|
+
message: t.text().notNull(),
|
|
9
|
+
enqueuedAt: t.timestamp().notNull(),
|
|
10
|
+
requestedAt: t.timestamp().notNull(),
|
|
11
|
+
executedAt: t.timestamp().notNull(),
|
|
12
|
+
}));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { queueExecutions } from "./schema";
|
|
3
|
+
|
|
4
|
+
export type QueueExecution = typeof queueExecutions.$inferSelect;
|
|
5
|
+
|
|
6
|
+
export const enqueueQueueTaskSchema = z.object({
|
|
7
|
+
message: z.string().trim().min(1),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const deleteQueueExecutionsSchema = z.object({
|
|
11
|
+
ids: z.array(z.string()),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export type EnqueueQueueTaskInput = z.input<typeof enqueueQueueTaskSchema>;
|
|
15
|
+
export type EnqueueQueueTaskSchema = z.infer<typeof enqueueQueueTaskSchema>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Header, Topbar, TopbarTitle, TopbarTools } from "@tulip-systems/app/components/client";
|
|
2
|
+
import { InlineCommandMenu, InlineCommandMenuLoading } from "@tulip-systems/commands/client";
|
|
3
|
+
import { type PropsWithChildren, Suspense } from "react";
|
|
4
|
+
import { quotationCreateCommand } from "../_config/commands";
|
|
5
|
+
|
|
6
|
+
export default function QuotationsLayout({ children }: PropsWithChildren) {
|
|
7
|
+
return (
|
|
8
|
+
<>
|
|
9
|
+
<Header>
|
|
10
|
+
<Topbar>
|
|
11
|
+
<TopbarTitle breadcrumbs={[{ label: "Offertes" }]} />
|
|
12
|
+
|
|
13
|
+
<TopbarTools>
|
|
14
|
+
<Suspense fallback={<InlineCommandMenuLoading />}>
|
|
15
|
+
<InlineCommandMenu data={{}} commands={[quotationCreateCommand]} />
|
|
16
|
+
</Suspense>
|
|
17
|
+
</TopbarTools>
|
|
18
|
+
</Topbar>
|
|
19
|
+
</Header>
|
|
20
|
+
|
|
21
|
+
{children}
|
|
22
|
+
</>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Allowed } from "@tulip-systems/auth/permissions/client";
|
|
2
|
+
import { TableLayout } from "@tulip-systems/data-tables";
|
|
3
|
+
import { DataTable } from "@tulip-systems/data-tables/client";
|
|
4
|
+
import { QuotationPaginationTableProvider } from "../_components/tables";
|
|
5
|
+
import { QuotationTableToolbar } from "../_components/toolbars";
|
|
6
|
+
import { quotationColumns } from "../_config/columns-data";
|
|
7
|
+
import { quotationDeleteCommand } from "../_config/commands";
|
|
8
|
+
|
|
9
|
+
export default function QuotationsPage() {
|
|
10
|
+
return (
|
|
11
|
+
<Allowed permission={{ quotation: ["view"] }}>
|
|
12
|
+
<TableLayout className="p-content">
|
|
13
|
+
<QuotationPaginationTableProvider
|
|
14
|
+
columns={quotationColumns}
|
|
15
|
+
commands={[quotationDeleteCommand]}
|
|
16
|
+
>
|
|
17
|
+
<QuotationTableToolbar />
|
|
18
|
+
<DataTable />
|
|
19
|
+
</QuotationPaginationTableProvider>
|
|
20
|
+
</TableLayout>
|
|
21
|
+
</Allowed>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useSuspenseQuery } from "@tanstack/react-query";
|
|
4
|
+
import { Header, Topbar, TopbarTitle, TopbarTools } from "@tulip-systems/app/components/client";
|
|
5
|
+
import { InlineCommandMenu, InlineCommandMenuLoading } from "@tulip-systems/commands/client";
|
|
6
|
+
import { notFound } from "next/navigation";
|
|
7
|
+
import { Suspense, use } from "react";
|
|
8
|
+
import { orpc } from "@/server/router/client";
|
|
9
|
+
import { quotationDeleteCommand, quotationPreviewCommand } from "../../_config/commands";
|
|
10
|
+
|
|
11
|
+
export default function QuotationsLayout(props: LayoutProps<"/admin/quotations/[id]">) {
|
|
12
|
+
const params = use(props.params);
|
|
13
|
+
const id = params.id?.toString();
|
|
14
|
+
|
|
15
|
+
const { data } = useSuspenseQuery(orpc.quotations.getById.queryOptions({ input: { id } }));
|
|
16
|
+
if (!data) notFound();
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
<Header>
|
|
21
|
+
<Topbar>
|
|
22
|
+
<TopbarTitle
|
|
23
|
+
breadcrumbs={[{ label: "Offertes", href: "/admin/quotations" }, { label: data.title }]}
|
|
24
|
+
/>
|
|
25
|
+
|
|
26
|
+
<TopbarTools>
|
|
27
|
+
<Suspense fallback={<InlineCommandMenuLoading />}>
|
|
28
|
+
<InlineCommandMenu
|
|
29
|
+
data={data}
|
|
30
|
+
commands={[quotationPreviewCommand, quotationDeleteCommand]}
|
|
31
|
+
/>
|
|
32
|
+
</Suspense>
|
|
33
|
+
</TopbarTools>
|
|
34
|
+
</Topbar>
|
|
35
|
+
</Header>
|
|
36
|
+
|
|
37
|
+
{props.children}
|
|
38
|
+
</>
|
|
39
|
+
);
|
|
40
|
+
}
|