@hed-hog/operations 0.0.304 → 0.0.305

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.
Files changed (52) hide show
  1. package/dist/controllers/operations-projects.controller.d.ts +15 -0
  2. package/dist/controllers/operations-projects.controller.d.ts.map +1 -1
  3. package/dist/controllers/operations-tasks.controller.d.ts +41 -10
  4. package/dist/controllers/operations-tasks.controller.d.ts.map +1 -1
  5. package/dist/controllers/operations-tasks.controller.js +11 -0
  6. package/dist/controllers/operations-tasks.controller.js.map +1 -1
  7. package/dist/dto/create-task.dto.d.ts +7 -1
  8. package/dist/dto/create-task.dto.d.ts.map +1 -1
  9. package/dist/dto/create-task.dto.js +38 -5
  10. package/dist/dto/create-task.dto.js.map +1 -1
  11. package/dist/dto/list-tasks.dto.d.ts +1 -1
  12. package/dist/dto/list-tasks.dto.d.ts.map +1 -1
  13. package/dist/dto/list-tasks.dto.js +2 -2
  14. package/dist/dto/list-tasks.dto.js.map +1 -1
  15. package/dist/dto/update-task.dto.d.ts +7 -1
  16. package/dist/dto/update-task.dto.d.ts.map +1 -1
  17. package/dist/dto/update-task.dto.js +38 -5
  18. package/dist/dto/update-task.dto.js.map +1 -1
  19. package/dist/operations.service.d.ts +68 -12
  20. package/dist/operations.service.d.ts.map +1 -1
  21. package/dist/operations.service.js +380 -101
  22. package/dist/operations.service.js.map +1 -1
  23. package/hedhog/data/route.yaml +13 -0
  24. package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +44 -44
  25. package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +168 -213
  26. package/hedhog/frontend/app/_components/collaborator-select-with-create.tsx.ejs +256 -256
  27. package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +7 -7
  28. package/hedhog/frontend/app/_components/contract-template-form-screen.tsx.ejs +306 -306
  29. package/hedhog/frontend/app/_components/contract-template-select-with-create.tsx.ejs +247 -247
  30. package/hedhog/frontend/app/_components/contract-wizard-sheet.tsx.ejs +3520 -3520
  31. package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +1504 -52
  32. package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +528 -403
  33. package/hedhog/frontend/app/_components/section-card.tsx.ejs +25 -18
  34. package/hedhog/frontend/app/_components/system-user-select-with-create.tsx.ejs +609 -0
  35. package/hedhog/frontend/app/_lib/types.ts.ejs +5 -0
  36. package/hedhog/frontend/app/_lib/utils/format.ts.ejs +7 -7
  37. package/hedhog/frontend/app/_lib/utils/forms.ts.ejs +48 -1
  38. package/hedhog/frontend/app/collaborator-types/page.tsx.ejs +502 -502
  39. package/hedhog/frontend/app/collaborators/page.tsx.ejs +10 -7
  40. package/hedhog/frontend/app/contracts/page.tsx.ejs +938 -938
  41. package/hedhog/frontend/app/projects/[id]/edit/page.tsx.ejs +1 -1
  42. package/hedhog/frontend/app/projects/page.tsx.ejs +360 -133
  43. package/hedhog/frontend/messages/en.json +27 -4
  44. package/hedhog/frontend/messages/pt.json +27 -4
  45. package/hedhog/table/operations_project.yaml +9 -0
  46. package/hedhog/table/operations_task.yaml +43 -4
  47. package/package.json +5 -5
  48. package/src/controllers/operations-tasks.controller.ts +11 -0
  49. package/src/dto/create-task.dto.ts +47 -7
  50. package/src/dto/list-tasks.dto.ts +3 -3
  51. package/src/dto/update-task.dto.ts +47 -7
  52. package/src/operations.service.ts +556 -88
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
 
3
- import { EmptyState, Page } from '@/components/entity-list';
4
- import { Button } from '@/components/ui/button';
5
- import { FormActions } from '@/components/ui/form-actions';
3
+ import { EmptyState, Page } from '@/components/entity-list';
4
+ import { Button } from '@/components/ui/button';
5
+ import { FormActions } from '@/components/ui/form-actions';
6
6
  import { Input } from '@/components/ui/input';
7
7
  import { Label } from '@/components/ui/label';
8
8
  import {
@@ -21,12 +21,12 @@ import Link from 'next/link';
21
21
  import { useRouter } from 'next/navigation';
22
22
  import { useEffect, useState } from 'react';
23
23
  import { fetchOperations, mutateOperations } from '../_lib/api';
24
- import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
25
- import type { OperationsContractTemplate } from '../_lib/types';
26
- import { formatEnumLabel } from '../_lib/utils/format';
27
- import { trimToNull } from '../_lib/utils/forms';
28
- import { ContractContentEditor } from './contract-content-editor';
29
- import { OperationsHeader } from './operations-header';
24
+ import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
25
+ import type { OperationsContractTemplate } from '../_lib/types';
26
+ import { formatEnumLabel } from '../_lib/utils/format';
27
+ import { trimToNull } from '../_lib/utils/forms';
28
+ import { ContractContentEditor } from './contract-content-editor';
29
+ import { OperationsHeader } from './operations-header';
30
30
 
31
31
  type ContractTemplateFormState = {
32
32
  code: string;
@@ -86,15 +86,15 @@ export function ContractTemplateFormScreen({
86
86
  }: ContractTemplateFormScreenProps) {
87
87
  const t = useTranslations('operations.ContractTemplateFormPage');
88
88
  const commonT = useTranslations('operations.Common');
89
- const { request, showToastHandler, currentLocaleCode } = useApp();
90
- const access = useOperationsAccess();
91
- const router = useRouter();
92
- const [form, setForm] = useState<ContractTemplateFormState>(buildEmptyForm());
93
- const isSheetMode = Boolean(onCancel);
94
- const [sheetStep, setSheetStep] = useState(0);
95
- const nextLabel = currentLocaleCode?.toLowerCase().startsWith('pt')
96
- ? 'Proximo'
97
- : 'Next';
89
+ const { request, showToastHandler, currentLocaleCode } = useApp();
90
+ const access = useOperationsAccess();
91
+ const router = useRouter();
92
+ const [form, setForm] = useState<ContractTemplateFormState>(buildEmptyForm());
93
+ const isSheetMode = Boolean(onCancel);
94
+ const [sheetStep, setSheetStep] = useState(0);
95
+ const nextLabel = currentLocaleCode?.toLowerCase().startsWith('pt')
96
+ ? 'Proximo'
97
+ : 'Next';
98
98
 
99
99
  const { data: template, isLoading } = useQuery<OperationsContractTemplate>({
100
100
  queryKey: [
@@ -110,31 +110,31 @@ export function ContractTemplateFormScreen({
110
110
  ),
111
111
  });
112
112
 
113
- useEffect(() => {
114
- if (template) {
115
- // eslint-disable-next-line react-hooks/set-state-in-effect
116
- setForm(toFormState(template));
117
- return;
113
+ useEffect(() => {
114
+ if (template) {
115
+ // eslint-disable-next-line react-hooks/set-state-in-effect
116
+ setForm(toFormState(template));
117
+ return;
118
118
  }
119
119
 
120
120
  if (!templateId) {
121
- setForm(buildEmptyForm());
122
- }
123
- }, [template, templateId]);
124
-
125
- useEffect(() => {
126
- if (!isSheetMode) return;
127
- setSheetStep(0);
128
- }, [isSheetMode, templateId]);
129
-
130
- const validateOverviewStep = () => {
131
- if (!form.name.trim()) {
132
- showToastHandler?.('error', t('messages.requiredFields'));
133
- return false;
134
- }
135
-
136
- return true;
137
- };
121
+ setForm(buildEmptyForm());
122
+ }
123
+ }, [template, templateId]);
124
+
125
+ useEffect(() => {
126
+ if (!isSheetMode) return;
127
+ setSheetStep(0);
128
+ }, [isSheetMode, templateId]);
129
+
130
+ const validateOverviewStep = () => {
131
+ if (!form.name.trim()) {
132
+ showToastHandler?.('error', t('messages.requiredFields'));
133
+ return false;
134
+ }
135
+
136
+ return true;
137
+ };
138
138
 
139
139
  const onSubmit = async () => {
140
140
  if (!form.name.trim()) {
@@ -224,272 +224,272 @@ export function ContractTemplateFormScreen({
224
224
  );
225
225
  }
226
226
 
227
- const overviewSection = (
228
- <section className="space-y-6">
229
- <div className="space-y-1">
230
- <h2 className="text-xl font-semibold">{t('sections.overview')}</h2>
231
- <p className="text-sm text-muted-foreground">
232
- {t('sections.overviewDescription')}
233
- </p>
234
- </div>
235
-
236
- <div className="grid gap-4 md:grid-cols-12">
237
- <div className="space-y-2 md:col-span-3">
238
- <Label htmlFor="contract-template-code">{t('fields.code')}</Label>
239
- <Input
240
- id="contract-template-code"
241
- value={form.code}
242
- placeholder={t('placeholders.code')}
243
- onChange={(event) =>
244
- setForm((current) => ({ ...current, code: event.target.value }))
245
- }
246
- />
247
- </div>
248
- <div className="space-y-2 md:col-span-6">
249
- <Label htmlFor="contract-template-name">{t('fields.name')}</Label>
250
- <Input
251
- id="contract-template-name"
252
- value={form.name}
253
- placeholder={t('placeholders.name')}
254
- onChange={(event) =>
255
- setForm((current) => ({ ...current, name: event.target.value }))
256
- }
257
- />
258
- </div>
259
- <div className="space-y-2 md:col-span-3">
260
- <Label>{commonT('labels.status')}</Label>
261
- <Select
262
- value={form.status}
263
- onValueChange={(value) =>
264
- setForm((current) => ({ ...current, status: value }))
265
- }
266
- >
267
- <SelectTrigger className="w-full">
268
- <SelectValue />
269
- </SelectTrigger>
270
- <SelectContent>
271
- {['active', 'draft', 'inactive', 'archived'].map((value) => (
272
- <SelectItem key={value} value={value}>
273
- {formatEnumLabel(value)}
274
- </SelectItem>
275
- ))}
276
- </SelectContent>
277
- </Select>
278
- </div>
279
-
280
- <div className="space-y-2 md:col-span-3">
281
- <Label>{t('fields.contractCategory')}</Label>
282
- <Select
283
- value={form.contractCategory}
284
- onValueChange={(value) =>
285
- setForm((current) => ({ ...current, contractCategory: value }))
286
- }
287
- >
288
- <SelectTrigger className="w-full">
289
- <SelectValue />
290
- </SelectTrigger>
291
- <SelectContent>
292
- {[
293
- 'employee',
294
- 'contractor',
295
- 'client',
296
- 'supplier',
297
- 'vendor',
298
- 'partner',
299
- 'internal',
300
- 'other',
301
- ].map((value) => (
302
- <SelectItem key={value} value={value}>
303
- {formatEnumLabel(value)}
304
- </SelectItem>
305
- ))}
306
- </SelectContent>
307
- </Select>
308
- </div>
309
- <div className="space-y-2 md:col-span-3">
310
- <Label>{t('fields.contractType')}</Label>
311
- <Select
312
- value={form.contractType}
313
- onValueChange={(value) =>
314
- setForm((current) => ({ ...current, contractType: value }))
315
- }
316
- >
317
- <SelectTrigger className="w-full">
318
- <SelectValue />
319
- </SelectTrigger>
320
- <SelectContent>
321
- {[
322
- 'clt',
323
- 'pj',
324
- 'freelancer_agreement',
325
- 'service_agreement',
326
- 'fixed_term',
327
- 'recurring_service',
328
- 'nda',
329
- 'amendment',
330
- 'addendum',
331
- 'other',
332
- ].map((value) => (
333
- <SelectItem key={value} value={value}>
334
- {formatEnumLabel(value)}
335
- </SelectItem>
336
- ))}
337
- </SelectContent>
338
- </Select>
339
- </div>
340
- <div className="space-y-2 md:col-span-3">
341
- <Label>{commonT('labels.billingModel')}</Label>
342
- <Select
343
- value={form.billingModel}
344
- onValueChange={(value) =>
345
- setForm((current) => ({ ...current, billingModel: value }))
346
- }
347
- >
348
- <SelectTrigger className="w-full">
349
- <SelectValue />
350
- </SelectTrigger>
351
- <SelectContent>
352
- {['time_and_material', 'monthly_retainer', 'fixed_price'].map(
353
- (value) => (
354
- <SelectItem key={value} value={value}>
355
- {formatEnumLabel(value)}
356
- </SelectItem>
357
- )
358
- )}
359
- </SelectContent>
360
- </Select>
361
- </div>
362
- <div className="space-y-2 md:col-span-3">
363
- <Label>{t('fields.signatureStatus')}</Label>
364
- <Select
365
- value={form.signatureStatus}
366
- onValueChange={(value) =>
367
- setForm((current) => ({ ...current, signatureStatus: value }))
368
- }
369
- >
370
- <SelectTrigger className="w-full">
371
- <SelectValue />
372
- </SelectTrigger>
373
- <SelectContent>
374
- {[
375
- 'not_started',
376
- 'pending',
377
- 'partially_signed',
378
- 'signed',
379
- 'expired',
380
- ].map((value) => (
381
- <SelectItem key={value} value={value}>
382
- {formatEnumLabel(value)}
383
- </SelectItem>
384
- ))}
385
- </SelectContent>
386
- </Select>
387
- </div>
388
-
389
- <div className="space-y-2 md:col-span-12">
390
- <Label htmlFor="contract-template-description">
391
- {t('fields.description')}
392
- </Label>
393
- <Textarea
394
- id="contract-template-description"
395
- rows={3}
396
- value={form.description}
397
- placeholder={t('placeholders.description')}
398
- onChange={(event) =>
399
- setForm((current) => ({
400
- ...current,
401
- description: event.target.value,
402
- }))
403
- }
404
- />
405
- </div>
406
-
407
- <div className="flex items-center justify-between rounded-lg border px-4 py-4 md:col-span-12">
408
- <div className="space-y-1">
409
- <div className="text-sm font-medium">{t('fields.isActive')}</div>
410
- <div className="text-xs text-muted-foreground">
411
- {t('fields.isActiveDescription')}
412
- </div>
413
- </div>
414
- <Switch
415
- checked={form.isActive}
416
- onCheckedChange={(checked) =>
417
- setForm((current) => ({ ...current, isActive: checked }))
418
- }
419
- />
420
- </div>
421
- </div>
422
- </section>
423
- );
424
-
425
- const contentSection = (
426
- <section className="space-y-6">
427
- <ContractContentEditor
428
- value={form.contentHtml}
429
- onChange={(value) =>
430
- setForm((current) => ({ ...current, contentHtml: value }))
431
- }
432
- editorTitle={t('sections.editor')}
433
- editorDescription={t('sections.editorDescription')}
434
- previewTitle={t('sections.preview')}
435
- previewDescription={t('sections.previewDescription')}
436
- promptContext={{
437
- name: form.name,
438
- code: form.code,
439
- description: form.description,
440
- contract_type: form.contractType,
441
- billing_model: form.billingModel,
442
- }}
443
- showPreview={false}
444
- chrome="plain"
445
- />
446
- </section>
447
- );
448
-
449
- const formBody = isSheetMode ? (
450
- <div className="space-y-6">
451
- {sheetStep === 0 ? overviewSection : contentSection}
452
- {templateId && isLoading ? (
453
- <div className="text-sm text-muted-foreground">{t('loading')}</div>
454
- ) : null}
455
- </div>
456
- ) : (
457
- <div className="space-y-8">
458
- {overviewSection}
459
- {contentSection}
460
- {templateId && isLoading ? (
461
- <div className="text-sm text-muted-foreground">{t('loading')}</div>
462
- ) : null}
463
- </div>
464
- );
465
-
466
- if (isSheetMode) {
467
- return (
468
- <div className="mt-4 space-y-6 pb-6">
469
- {formBody}
470
-
471
- <FormActions
472
- sheet
473
- cancelLabel={
474
- sheetStep === 0 ? commonT('actions.cancel') : commonT('actions.back')
475
- }
476
- onCancel={
477
- sheetStep === 0 ? onCancel : () => setSheetStep((current) => current - 1)
478
- }
479
- onSubmit={() => {
480
- if (sheetStep === 0) {
481
- if (!validateOverviewStep()) return;
482
- setSheetStep(1);
483
- return;
484
- }
485
-
486
- void onSubmit();
487
- }}
488
- submitIcon={sheetStep === 0 ? undefined : <Save className="size-4" />}
489
- submitLabel={sheetStep === 0 ? nextLabel : commonT('actions.save')}
490
- submitSize="lg"
491
- />
492
- </div>
227
+ const overviewSection = (
228
+ <section className="space-y-6">
229
+ <div className="space-y-1">
230
+ <h2 className="text-xl font-semibold">{t('sections.overview')}</h2>
231
+ <p className="text-sm text-muted-foreground">
232
+ {t('sections.overviewDescription')}
233
+ </p>
234
+ </div>
235
+
236
+ <div className="grid gap-4 md:grid-cols-12">
237
+ <div className="space-y-2 md:col-span-3">
238
+ <Label htmlFor="contract-template-code">{t('fields.code')}</Label>
239
+ <Input
240
+ id="contract-template-code"
241
+ value={form.code}
242
+ placeholder={t('placeholders.code')}
243
+ onChange={(event) =>
244
+ setForm((current) => ({ ...current, code: event.target.value }))
245
+ }
246
+ />
247
+ </div>
248
+ <div className="space-y-2 md:col-span-6">
249
+ <Label htmlFor="contract-template-name">{t('fields.name')}</Label>
250
+ <Input
251
+ id="contract-template-name"
252
+ value={form.name}
253
+ placeholder={t('placeholders.name')}
254
+ onChange={(event) =>
255
+ setForm((current) => ({ ...current, name: event.target.value }))
256
+ }
257
+ />
258
+ </div>
259
+ <div className="space-y-2 md:col-span-3">
260
+ <Label>{commonT('labels.status')}</Label>
261
+ <Select
262
+ value={form.status}
263
+ onValueChange={(value) =>
264
+ setForm((current) => ({ ...current, status: value }))
265
+ }
266
+ >
267
+ <SelectTrigger className="w-full">
268
+ <SelectValue />
269
+ </SelectTrigger>
270
+ <SelectContent>
271
+ {['active', 'draft', 'inactive', 'archived'].map((value) => (
272
+ <SelectItem key={value} value={value}>
273
+ {formatEnumLabel(value)}
274
+ </SelectItem>
275
+ ))}
276
+ </SelectContent>
277
+ </Select>
278
+ </div>
279
+
280
+ <div className="space-y-2 md:col-span-3">
281
+ <Label>{t('fields.contractCategory')}</Label>
282
+ <Select
283
+ value={form.contractCategory}
284
+ onValueChange={(value) =>
285
+ setForm((current) => ({ ...current, contractCategory: value }))
286
+ }
287
+ >
288
+ <SelectTrigger className="w-full">
289
+ <SelectValue />
290
+ </SelectTrigger>
291
+ <SelectContent>
292
+ {[
293
+ 'employee',
294
+ 'contractor',
295
+ 'client',
296
+ 'supplier',
297
+ 'vendor',
298
+ 'partner',
299
+ 'internal',
300
+ 'other',
301
+ ].map((value) => (
302
+ <SelectItem key={value} value={value}>
303
+ {formatEnumLabel(value)}
304
+ </SelectItem>
305
+ ))}
306
+ </SelectContent>
307
+ </Select>
308
+ </div>
309
+ <div className="space-y-2 md:col-span-3">
310
+ <Label>{t('fields.contractType')}</Label>
311
+ <Select
312
+ value={form.contractType}
313
+ onValueChange={(value) =>
314
+ setForm((current) => ({ ...current, contractType: value }))
315
+ }
316
+ >
317
+ <SelectTrigger className="w-full">
318
+ <SelectValue />
319
+ </SelectTrigger>
320
+ <SelectContent>
321
+ {[
322
+ 'clt',
323
+ 'pj',
324
+ 'freelancer_agreement',
325
+ 'service_agreement',
326
+ 'fixed_term',
327
+ 'recurring_service',
328
+ 'nda',
329
+ 'amendment',
330
+ 'addendum',
331
+ 'other',
332
+ ].map((value) => (
333
+ <SelectItem key={value} value={value}>
334
+ {formatEnumLabel(value)}
335
+ </SelectItem>
336
+ ))}
337
+ </SelectContent>
338
+ </Select>
339
+ </div>
340
+ <div className="space-y-2 md:col-span-3">
341
+ <Label>{commonT('labels.billingModel')}</Label>
342
+ <Select
343
+ value={form.billingModel}
344
+ onValueChange={(value) =>
345
+ setForm((current) => ({ ...current, billingModel: value }))
346
+ }
347
+ >
348
+ <SelectTrigger className="w-full">
349
+ <SelectValue />
350
+ </SelectTrigger>
351
+ <SelectContent>
352
+ {['time_and_material', 'monthly_retainer', 'fixed_price'].map(
353
+ (value) => (
354
+ <SelectItem key={value} value={value}>
355
+ {formatEnumLabel(value)}
356
+ </SelectItem>
357
+ )
358
+ )}
359
+ </SelectContent>
360
+ </Select>
361
+ </div>
362
+ <div className="space-y-2 md:col-span-3">
363
+ <Label>{t('fields.signatureStatus')}</Label>
364
+ <Select
365
+ value={form.signatureStatus}
366
+ onValueChange={(value) =>
367
+ setForm((current) => ({ ...current, signatureStatus: value }))
368
+ }
369
+ >
370
+ <SelectTrigger className="w-full">
371
+ <SelectValue />
372
+ </SelectTrigger>
373
+ <SelectContent>
374
+ {[
375
+ 'not_started',
376
+ 'pending',
377
+ 'partially_signed',
378
+ 'signed',
379
+ 'expired',
380
+ ].map((value) => (
381
+ <SelectItem key={value} value={value}>
382
+ {formatEnumLabel(value)}
383
+ </SelectItem>
384
+ ))}
385
+ </SelectContent>
386
+ </Select>
387
+ </div>
388
+
389
+ <div className="space-y-2 md:col-span-12">
390
+ <Label htmlFor="contract-template-description">
391
+ {t('fields.description')}
392
+ </Label>
393
+ <Textarea
394
+ id="contract-template-description"
395
+ rows={3}
396
+ value={form.description}
397
+ placeholder={t('placeholders.description')}
398
+ onChange={(event) =>
399
+ setForm((current) => ({
400
+ ...current,
401
+ description: event.target.value,
402
+ }))
403
+ }
404
+ />
405
+ </div>
406
+
407
+ <div className="flex items-center justify-between rounded-lg border px-4 py-4 md:col-span-12">
408
+ <div className="space-y-1">
409
+ <div className="text-sm font-medium">{t('fields.isActive')}</div>
410
+ <div className="text-xs text-muted-foreground">
411
+ {t('fields.isActiveDescription')}
412
+ </div>
413
+ </div>
414
+ <Switch
415
+ checked={form.isActive}
416
+ onCheckedChange={(checked) =>
417
+ setForm((current) => ({ ...current, isActive: checked }))
418
+ }
419
+ />
420
+ </div>
421
+ </div>
422
+ </section>
423
+ );
424
+
425
+ const contentSection = (
426
+ <section className="space-y-6">
427
+ <ContractContentEditor
428
+ value={form.contentHtml}
429
+ onChange={(value) =>
430
+ setForm((current) => ({ ...current, contentHtml: value }))
431
+ }
432
+ editorTitle={t('sections.editor')}
433
+ editorDescription={t('sections.editorDescription')}
434
+ previewTitle={t('sections.preview')}
435
+ previewDescription={t('sections.previewDescription')}
436
+ promptContext={{
437
+ name: form.name,
438
+ code: form.code,
439
+ description: form.description,
440
+ contract_type: form.contractType,
441
+ billing_model: form.billingModel,
442
+ }}
443
+ showPreview={false}
444
+ chrome="plain"
445
+ />
446
+ </section>
447
+ );
448
+
449
+ const formBody = isSheetMode ? (
450
+ <div className="space-y-6">
451
+ {sheetStep === 0 ? overviewSection : contentSection}
452
+ {templateId && isLoading ? (
453
+ <div className="text-sm text-muted-foreground">{t('loading')}</div>
454
+ ) : null}
455
+ </div>
456
+ ) : (
457
+ <div className="space-y-8">
458
+ {overviewSection}
459
+ {contentSection}
460
+ {templateId && isLoading ? (
461
+ <div className="text-sm text-muted-foreground">{t('loading')}</div>
462
+ ) : null}
463
+ </div>
464
+ );
465
+
466
+ if (isSheetMode) {
467
+ return (
468
+ <div className="mt-4 space-y-6 pb-6">
469
+ {formBody}
470
+
471
+ <FormActions
472
+ sheet
473
+ cancelLabel={
474
+ sheetStep === 0 ? commonT('actions.cancel') : commonT('actions.back')
475
+ }
476
+ onCancel={
477
+ sheetStep === 0 ? onCancel : () => setSheetStep((current) => current - 1)
478
+ }
479
+ onSubmit={() => {
480
+ if (sheetStep === 0) {
481
+ if (!validateOverviewStep()) return;
482
+ setSheetStep(1);
483
+ return;
484
+ }
485
+
486
+ void onSubmit();
487
+ }}
488
+ submitIcon={sheetStep === 0 ? undefined : <Save className="size-4" />}
489
+ submitLabel={sheetStep === 0 ? nextLabel : commonT('actions.save')}
490
+ submitSize="lg"
491
+ />
492
+ </div>
493
493
  );
494
494
  }
495
495