@hed-hog/operations 0.0.325 → 0.0.327

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 (32) hide show
  1. package/dist/controllers/operations-collaborators.controller.d.ts +5 -0
  2. package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
  3. package/dist/operations.service.d.ts +9 -1
  4. package/dist/operations.service.d.ts.map +1 -1
  5. package/dist/operations.service.js +140 -26
  6. package/dist/operations.service.js.map +1 -1
  7. package/hedhog/data/integration_event_catalog.yaml +313 -0
  8. package/hedhog/data/setting_group.yaml +21 -0
  9. package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +410 -23
  10. package/hedhog/frontend/app/_components/my-project-summary-screen.tsx.ejs +504 -375
  11. package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +258 -230
  12. package/hedhog/frontend/app/_components/task-detail-sheet.tsx.ejs +225 -162
  13. package/hedhog/frontend/app/_components/task-form-sheet.tsx.ejs +484 -230
  14. package/hedhog/frontend/app/_lib/api.ts.ejs +13 -4
  15. package/hedhog/frontend/app/_lib/hooks/use-mention-items.ts.ejs +28 -0
  16. package/hedhog/frontend/app/_lib/types.ts.ejs +30 -29
  17. package/hedhog/frontend/app/my-tasks/page.tsx.ejs +347 -236
  18. package/hedhog/frontend/app/reports/projects/page.tsx.ejs +31 -7
  19. package/hedhog/frontend/messages/en.json +38 -55
  20. package/hedhog/frontend/messages/en.json.ejs +21 -4
  21. package/hedhog/frontend/messages/pt.json +36 -55
  22. package/hedhog/frontend/messages/pt.json.ejs +14 -3
  23. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/types.d.ts +1 -0
  24. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/types.d.ts.map +1 -1
  25. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/types.ts +1 -0
  26. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/types.d.ts +1 -0
  27. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/types.d.ts.map +1 -1
  28. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/types.ts +1 -0
  29. package/hedhog/table/operations_collaborator.yaml +5 -0
  30. package/hedhog/table/operations_collaborator_compensation_history.yaml +4 -0
  31. package/package.json +5 -5
  32. package/src/operations.service.ts +202 -26
@@ -18,11 +18,13 @@ import {
18
18
  SheetHeader,
19
19
  SheetTitle,
20
20
  } from '@/components/ui/sheet';
21
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
21
22
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
22
23
  import { Archive, Loader2, Paperclip, Plus } from 'lucide-react';
23
24
  import { useTranslations } from 'next-intl';
24
25
  import { useEffect, useState } from 'react';
25
26
  import { fetchOperations, mutateOperations } from '../_lib/api';
27
+ import { useMentionItems } from '../_lib/hooks/use-mention-items';
26
28
  import type {
27
29
  OperationsProjectDetails,
28
30
  OperationsProjectOption,
@@ -30,6 +32,7 @@ import type {
30
32
  PaginatedResponse,
31
33
  } from '../_lib/types';
32
34
  import { ProjectFormScreen } from './project-form-screen';
35
+ import { TaskCommentsSection } from './task-detail-sheet';
33
36
  import { TaskFileAttachments } from './task-file-attachments';
34
37
 
35
38
  type TaskColumnId = 'todo' | 'doing' | 'review' | 'done';
@@ -92,6 +95,8 @@ export type TaskFormSheetProps = {
92
95
  projectRequired?: boolean;
93
96
  /** When false the project selector is hidden (e.g. when the project is already known from context). Default: true. */
94
97
  showProject?: boolean;
98
+ /** Default active tab when opening in edit mode. Default: 'info'. */
99
+ defaultTab?: 'info' | 'comments';
95
100
  onSaved: () => void;
96
101
  };
97
102
 
@@ -103,17 +108,21 @@ export function TaskFormSheet({
103
108
  defaultAssigneeCollaboratorId,
104
109
  projectRequired = false,
105
110
  showProject = true,
111
+ defaultTab = 'info',
106
112
  onSaved,
107
113
  }: TaskFormSheetProps) {
108
- const { currentLocaleCode } = useApp();
114
+ const { currentLocaleCode, request: appRequest } = useApp();
109
115
  const commonT = useTranslations('operations.Common');
110
116
  const taskT = useTranslations('operations.ProjectDetailsPage');
111
117
  const projectsT = useTranslations('operations.ProjectsPage');
112
118
 
119
+ const mentionItems = useMentionItems(appRequest);
120
+
113
121
  const [formData, setFormData] = useState<TaskFormState>(EMPTY_TASK_FORM);
114
122
  const [loading, setLoading] = useState(false);
115
123
  const [archivingId, setArchivingId] = useState<number | null>(null);
116
124
  const [projectFormOpen, setProjectFormOpen] = useState(false);
125
+ const [activeTab, setActiveTab] = useState<'info' | 'comments'>(defaultTab);
117
126
  const [localProjectOptions, setLocalProjectOptions] = useState<
118
127
  OperationsProjectOption[]
119
128
  >([]);
@@ -141,6 +150,7 @@ export function TaskFormSheet({
141
150
  // Sync form state when editingTask or open changes.
142
151
  useEffect(() => {
143
152
  if (!open) return;
153
+ setActiveTab(defaultTab);
144
154
  if (editingTask) {
145
155
  setFormData({
146
156
  projectId: String(editingTask.projectId ?? ''),
@@ -248,7 +258,7 @@ export function TaskFormSheet({
248
258
  }}
249
259
  >
250
260
  <SheetContent className="flex w-full flex-col overflow-hidden sm:max-w-xl">
251
- <SheetHeader>
261
+ <SheetHeader className="shrink-0">
252
262
  <SheetTitle>
253
263
  {editingTask
254
264
  ? taskT('taskForm.titleEdit')
@@ -256,243 +266,487 @@ export function TaskFormSheet({
256
266
  </SheetTitle>
257
267
  </SheetHeader>
258
268
 
259
- <div className="flex-1 space-y-4 overflow-y-auto px-4 py-2">
260
- {/* Project selector — hidden when showProject=false (project is known from context) */}
261
- {showProject ? (
262
- <div className="space-y-1.5">
263
- <Label htmlFor="task-form-project">
264
- {commonT('labels.project')}
265
- {!editingTask && projectRequired ? ' *' : ''}
266
- </Label>
267
- <div className="flex gap-2">
268
- <Select
269
- value={formData.projectId}
270
- onValueChange={(v) =>
271
- setFormData((prev) => ({ ...prev, projectId: v }))
272
- }
273
- >
274
- <SelectTrigger className="w-full" id="task-form-project">
275
- <SelectValue placeholder={commonT('labels.project')} />
276
- </SelectTrigger>
277
- <SelectContent>
278
- {!projectRequired && !editingTask ? (
279
- <SelectItem value="none">
280
- {commonT('labels.notAssigned')}
281
- </SelectItem>
269
+ {editingTask ? (
270
+ <Tabs
271
+ value={activeTab}
272
+ onValueChange={(v) => setActiveTab(v as 'info' | 'comments')}
273
+ className="flex min-h-0 flex-1 flex-col"
274
+ >
275
+ <TabsList className="mx-4 shrink-0">
276
+ <TabsTrigger value="info">Informações</TabsTrigger>
277
+ <TabsTrigger value="comments">Comentários</TabsTrigger>
278
+ </TabsList>
279
+
280
+ <TabsContent
281
+ value="info"
282
+ className="flex min-h-0 flex-1 flex-col data-[state=inactive]:hidden"
283
+ >
284
+ <div className="flex-1 space-y-4 overflow-y-auto px-4 py-2">
285
+ {/* Project selector — hidden when showProject=false (project is known from context) */}
286
+ {showProject ? (
287
+ <div className="space-y-1.5">
288
+ <Label htmlFor="task-form-project">
289
+ {commonT('labels.project')}
290
+ {!editingTask && projectRequired ? ' *' : ''}
291
+ </Label>
292
+ <div className="flex gap-2">
293
+ <Select
294
+ value={formData.projectId}
295
+ onValueChange={(v) =>
296
+ setFormData((prev) => ({ ...prev, projectId: v }))
297
+ }
298
+ >
299
+ <SelectTrigger
300
+ className="w-full"
301
+ id="task-form-project"
302
+ >
303
+ <SelectValue
304
+ placeholder={commonT('labels.project')}
305
+ />
306
+ </SelectTrigger>
307
+ <SelectContent>
308
+ {!projectRequired && !editingTask ? (
309
+ <SelectItem value="none">
310
+ {commonT('labels.notAssigned')}
311
+ </SelectItem>
312
+ ) : null}
313
+ {editingTask?.projectId &&
314
+ !projectOptions.some(
315
+ (p) =>
316
+ String(p.id) === String(editingTask.projectId)
317
+ ) ? (
318
+ <SelectItem
319
+ key={`fallback-${editingTask.projectId}`}
320
+ value={String(editingTask.projectId)}
321
+ >
322
+ {[
323
+ editingTask.projectCode,
324
+ editingTask.projectName,
325
+ ]
326
+ .filter(Boolean)
327
+ .join(' • ') || String(editingTask.projectId)}
328
+ </SelectItem>
329
+ ) : null}
330
+ {projectOptions.map((p) => (
331
+ <SelectItem key={p.id} value={String(p.id)}>
332
+ {p.label}
333
+ </SelectItem>
334
+ ))}
335
+ </SelectContent>
336
+ </Select>
337
+ <Button
338
+ type="button"
339
+ variant="outline"
340
+ size="icon"
341
+ className="shrink-0"
342
+ title={projectsT('sheet.createTitle')}
343
+ onClick={() => setProjectFormOpen(true)}
344
+ >
345
+ <Plus className="size-4" />
346
+ </Button>
347
+ </div>
348
+ </div>
349
+ ) : null}
350
+
351
+ <div className="space-y-1.5">
352
+ <Label htmlFor="task-form-name">
353
+ {taskT('taskForm.nameLabel')} *
354
+ </Label>
355
+ <Input
356
+ id="task-form-name"
357
+ placeholder={taskT('taskForm.namePlaceholder')}
358
+ value={formData.name}
359
+ onChange={(e) =>
360
+ setFormData((prev) => ({
361
+ ...prev,
362
+ name: e.target.value,
363
+ }))
364
+ }
365
+ />
366
+ </div>
367
+
368
+ <div className="space-y-1.5">
369
+ <Label htmlFor="task-form-description">
370
+ {taskT('taskForm.descriptionLabel')}
371
+ </Label>
372
+ <RichTextEditor
373
+ value={formData.description}
374
+ onChange={(val) =>
375
+ setFormData((prev) => ({ ...prev, description: val }))
376
+ }
377
+ mentions={mentionItems}
378
+ />
379
+ </div>
380
+
381
+ <div className="grid grid-cols-2 gap-3">
382
+ <div className="space-y-1.5">
383
+ <Label>{taskT('taskForm.priorityLabel')}</Label>
384
+ <Select
385
+ value={formData.priority}
386
+ onValueChange={(v) =>
387
+ setFormData((prev) => ({
388
+ ...prev,
389
+ priority: v as TaskFormState['priority'],
390
+ }))
391
+ }
392
+ >
393
+ <SelectTrigger className="w-full">
394
+ <SelectValue />
395
+ </SelectTrigger>
396
+ <SelectContent>
397
+ <SelectItem value="low">
398
+ {getTaskPriorityLabel('low')}
399
+ </SelectItem>
400
+ <SelectItem value="medium">
401
+ {getTaskPriorityLabel('medium')}
402
+ </SelectItem>
403
+ <SelectItem value="high">
404
+ {getTaskPriorityLabel('high')}
405
+ </SelectItem>
406
+ </SelectContent>
407
+ </Select>
408
+ </div>
409
+
410
+ <div className="space-y-1.5">
411
+ <Label>{taskT('taskForm.columnLabel')}</Label>
412
+ <Select
413
+ value={formData.status}
414
+ onValueChange={(v) =>
415
+ setFormData((prev) => ({
416
+ ...prev,
417
+ status: v as TaskColumnId,
418
+ }))
419
+ }
420
+ >
421
+ <SelectTrigger className="w-full">
422
+ <SelectValue />
423
+ </SelectTrigger>
424
+ <SelectContent>
425
+ {KANBAN_COLUMNS.map((col) => (
426
+ <SelectItem key={col.id} value={col.id}>
427
+ {col.label}
428
+ </SelectItem>
429
+ ))}
430
+ </SelectContent>
431
+ </Select>
432
+ </div>
433
+ </div>
434
+
435
+ <div className="grid grid-cols-2 gap-3">
436
+ <div className="space-y-1.5">
437
+ <Label htmlFor="task-form-due-date">
438
+ {taskT('taskForm.deadlineLabel')}
439
+ </Label>
440
+ <Input
441
+ id="task-form-due-date"
442
+ type="date"
443
+ value={formData.dueDate}
444
+ onChange={(e) =>
445
+ setFormData((prev) => ({
446
+ ...prev,
447
+ dueDate: e.target.value,
448
+ }))
449
+ }
450
+ />
451
+ </div>
452
+
453
+ <div className="space-y-1.5">
454
+ <Label htmlFor="task-form-estimate">
455
+ {taskT('taskForm.estimateLabel')}
456
+ </Label>
457
+ <Input
458
+ id="task-form-estimate"
459
+ type="number"
460
+ min="0"
461
+ step="0.5"
462
+ placeholder="0"
463
+ value={formData.estimateHours}
464
+ onChange={(e) =>
465
+ setFormData((prev) => ({
466
+ ...prev,
467
+ estimateHours: e.target.value,
468
+ }))
469
+ }
470
+ />
471
+ </div>
472
+ </div>
473
+
474
+ <div className="space-y-1.5">
475
+ <Label htmlFor="task-form-tags">
476
+ {taskT('taskForm.tagsLabel')}
477
+ </Label>
478
+ <Input
479
+ id="task-form-tags"
480
+ placeholder={taskT('taskForm.tagsPlaceholder')}
481
+ value={formData.tags}
482
+ onChange={(e) =>
483
+ setFormData((prev) => ({
484
+ ...prev,
485
+ tags: e.target.value,
486
+ }))
487
+ }
488
+ />
489
+ </div>
490
+
491
+ <div className="space-y-1.5">
492
+ <Label className="flex items-center gap-1.5">
493
+ <Paperclip className="size-3.5" />
494
+ {taskT('taskForm.attachmentsLabel')}
495
+ </Label>
496
+ <TaskFileAttachments taskId={editingTask.id} />
497
+ </div>
498
+ </div>
499
+
500
+ <div className="mt-4 flex flex-wrap items-center justify-between gap-2 border-t px-4 pb-4 pt-4">
501
+ <div className="flex gap-2">
502
+ <Button
503
+ type="button"
504
+ variant="outline"
505
+ disabled={loading || archivingId === editingTask.id}
506
+ onClick={() => void handleArchive()}
507
+ >
508
+ {archivingId === editingTask.id ? (
509
+ <Loader2 className="mr-2 size-4 animate-spin" />
510
+ ) : (
511
+ <Archive className="mr-2 size-4" />
512
+ )}
513
+ {commonT('actions.archive')}
514
+ </Button>
515
+ </div>
516
+ <div className="flex gap-2">
517
+ <Button
518
+ variant="outline"
519
+ onClick={close}
520
+ disabled={loading}
521
+ >
522
+ {commonT('actions.cancel')}
523
+ </Button>
524
+ <Button
525
+ onClick={() => void handleSubmit()}
526
+ disabled={loading || isSubmitDisabled()}
527
+ >
528
+ {loading ? (
529
+ <Loader2 className="mr-2 size-4 animate-spin" />
282
530
  ) : null}
283
- {editingTask?.projectId &&
284
- !projectOptions.some(
285
- (p) => String(p.id) === String(editingTask.projectId)
286
- ) ? (
287
- <SelectItem
288
- key={`fallback-${editingTask.projectId}`}
289
- value={String(editingTask.projectId)}
531
+ {commonT('actions.save')}
532
+ </Button>
533
+ </div>
534
+ </div>
535
+ </TabsContent>
536
+
537
+ <TabsContent
538
+ value="comments"
539
+ className="min-h-0 flex-1 overflow-y-auto px-4 py-2 data-[state=inactive]:hidden"
540
+ >
541
+ <TaskCommentsSection taskId={editingTask.id} />
542
+ </TabsContent>
543
+ </Tabs>
544
+ ) : (
545
+ <>
546
+ <div className="flex-1 space-y-4 overflow-y-auto px-4 py-2">
547
+ {/* Project selector — hidden when showProject=false (project is known from context) */}
548
+ {showProject ? (
549
+ <div className="space-y-1.5">
550
+ <Label htmlFor="task-form-project">
551
+ {commonT('labels.project')}
552
+ {projectRequired ? ' *' : ''}
553
+ </Label>
554
+ <div className="flex gap-2">
555
+ <Select
556
+ value={formData.projectId}
557
+ onValueChange={(v) =>
558
+ setFormData((prev) => ({ ...prev, projectId: v }))
559
+ }
560
+ >
561
+ <SelectTrigger
562
+ className="w-full"
563
+ id="task-form-project"
290
564
  >
291
- {[editingTask.projectCode, editingTask.projectName]
292
- .filter(Boolean)
293
- .join(' • ') || String(editingTask.projectId)}
565
+ <SelectValue
566
+ placeholder={commonT('labels.project')}
567
+ />
568
+ </SelectTrigger>
569
+ <SelectContent>
570
+ {!projectRequired ? (
571
+ <SelectItem value="none">
572
+ {commonT('labels.notAssigned')}
573
+ </SelectItem>
574
+ ) : null}
575
+ {projectOptions.map((p) => (
576
+ <SelectItem key={p.id} value={String(p.id)}>
577
+ {p.label}
578
+ </SelectItem>
579
+ ))}
580
+ </SelectContent>
581
+ </Select>
582
+ <Button
583
+ type="button"
584
+ variant="outline"
585
+ size="icon"
586
+ className="shrink-0"
587
+ title={projectsT('sheet.createTitle')}
588
+ onClick={() => setProjectFormOpen(true)}
589
+ >
590
+ <Plus className="size-4" />
591
+ </Button>
592
+ </div>
593
+ </div>
594
+ ) : null}
595
+
596
+ <div className="space-y-1.5">
597
+ <Label htmlFor="task-form-name">
598
+ {taskT('taskForm.nameLabel')} *
599
+ </Label>
600
+ <Input
601
+ id="task-form-name"
602
+ placeholder={taskT('taskForm.namePlaceholder')}
603
+ value={formData.name}
604
+ onChange={(e) =>
605
+ setFormData((prev) => ({ ...prev, name: e.target.value }))
606
+ }
607
+ />
608
+ </div>
609
+
610
+ <div className="space-y-1.5">
611
+ <Label htmlFor="task-form-description">
612
+ {taskT('taskForm.descriptionLabel')}
613
+ </Label>
614
+ <RichTextEditor
615
+ value={formData.description}
616
+ onChange={(val) =>
617
+ setFormData((prev) => ({ ...prev, description: val }))
618
+ }
619
+ mentions={mentionItems}
620
+ />
621
+ </div>
622
+
623
+ <div className="grid grid-cols-2 gap-3">
624
+ <div className="space-y-1.5">
625
+ <Label>{taskT('taskForm.priorityLabel')}</Label>
626
+ <Select
627
+ value={formData.priority}
628
+ onValueChange={(v) =>
629
+ setFormData((prev) => ({
630
+ ...prev,
631
+ priority: v as TaskFormState['priority'],
632
+ }))
633
+ }
634
+ >
635
+ <SelectTrigger className="w-full">
636
+ <SelectValue />
637
+ </SelectTrigger>
638
+ <SelectContent>
639
+ <SelectItem value="low">
640
+ {getTaskPriorityLabel('low')}
294
641
  </SelectItem>
295
- ) : null}
296
- {projectOptions.map((p) => (
297
- <SelectItem key={p.id} value={String(p.id)}>
298
- {p.label}
642
+ <SelectItem value="medium">
643
+ {getTaskPriorityLabel('medium')}
299
644
  </SelectItem>
300
- ))}
301
- </SelectContent>
302
- </Select>
303
- <Button
304
- type="button"
305
- variant="outline"
306
- size="icon"
307
- className="shrink-0"
308
- title={projectsT('sheet.createTitle')}
309
- onClick={() => setProjectFormOpen(true)}
310
- >
311
- <Plus className="size-4" />
312
- </Button>
645
+ <SelectItem value="high">
646
+ {getTaskPriorityLabel('high')}
647
+ </SelectItem>
648
+ </SelectContent>
649
+ </Select>
650
+ </div>
651
+
652
+ <div className="space-y-1.5">
653
+ <Label>{taskT('taskForm.columnLabel')}</Label>
654
+ <Select
655
+ value={formData.status}
656
+ onValueChange={(v) =>
657
+ setFormData((prev) => ({
658
+ ...prev,
659
+ status: v as TaskColumnId,
660
+ }))
661
+ }
662
+ >
663
+ <SelectTrigger className="w-full">
664
+ <SelectValue />
665
+ </SelectTrigger>
666
+ <SelectContent>
667
+ {KANBAN_COLUMNS.map((col) => (
668
+ <SelectItem key={col.id} value={col.id}>
669
+ {col.label}
670
+ </SelectItem>
671
+ ))}
672
+ </SelectContent>
673
+ </Select>
674
+ </div>
313
675
  </div>
314
- </div>
315
- ) : null}
316
-
317
- <div className="space-y-1.5">
318
- <Label htmlFor="task-form-name">
319
- {taskT('taskForm.nameLabel')} *
320
- </Label>
321
- <Input
322
- id="task-form-name"
323
- placeholder={taskT('taskForm.namePlaceholder')}
324
- value={formData.name}
325
- onChange={(e) =>
326
- setFormData((prev) => ({ ...prev, name: e.target.value }))
327
- }
328
- />
329
- </div>
330
-
331
- <div className="space-y-1.5">
332
- <Label htmlFor="task-form-description">
333
- {taskT('taskForm.descriptionLabel')}
334
- </Label>
335
- <RichTextEditor
336
- value={formData.description}
337
- onChange={(val) =>
338
- setFormData((prev) => ({ ...prev, description: val }))
339
- }
340
- />
341
- </div>
342
-
343
- <div className="grid grid-cols-2 gap-3">
344
- <div className="space-y-1.5">
345
- <Label>{taskT('taskForm.priorityLabel')}</Label>
346
- <Select
347
- value={formData.priority}
348
- onValueChange={(v) =>
349
- setFormData((prev) => ({
350
- ...prev,
351
- priority: v as TaskFormState['priority'],
352
- }))
353
- }
354
- >
355
- <SelectTrigger className="w-full">
356
- <SelectValue />
357
- </SelectTrigger>
358
- <SelectContent>
359
- <SelectItem value="low">
360
- {getTaskPriorityLabel('low')}
361
- </SelectItem>
362
- <SelectItem value="medium">
363
- {getTaskPriorityLabel('medium')}
364
- </SelectItem>
365
- <SelectItem value="high">
366
- {getTaskPriorityLabel('high')}
367
- </SelectItem>
368
- </SelectContent>
369
- </Select>
370
- </div>
371
676
 
372
- <div className="space-y-1.5">
373
- <Label>{taskT('taskForm.columnLabel')}</Label>
374
- <Select
375
- value={formData.status}
376
- onValueChange={(v) =>
377
- setFormData((prev) => ({
378
- ...prev,
379
- status: v as TaskColumnId,
380
- }))
381
- }
382
- >
383
- <SelectTrigger className="w-full">
384
- <SelectValue />
385
- </SelectTrigger>
386
- <SelectContent>
387
- {KANBAN_COLUMNS.map((col) => (
388
- <SelectItem key={col.id} value={col.id}>
389
- {col.label}
390
- </SelectItem>
391
- ))}
392
- </SelectContent>
393
- </Select>
394
- </div>
395
- </div>
396
-
397
- <div className="grid grid-cols-2 gap-3">
398
- <div className="space-y-1.5">
399
- <Label htmlFor="task-form-due-date">
400
- {taskT('taskForm.deadlineLabel')}
401
- </Label>
402
- <Input
403
- id="task-form-due-date"
404
- type="date"
405
- value={formData.dueDate}
406
- onChange={(e) =>
407
- setFormData((prev) => ({
408
- ...prev,
409
- dueDate: e.target.value,
410
- }))
411
- }
412
- />
413
- </div>
677
+ <div className="grid grid-cols-2 gap-3">
678
+ <div className="space-y-1.5">
679
+ <Label htmlFor="task-form-due-date">
680
+ {taskT('taskForm.deadlineLabel')}
681
+ </Label>
682
+ <Input
683
+ id="task-form-due-date"
684
+ type="date"
685
+ value={formData.dueDate}
686
+ onChange={(e) =>
687
+ setFormData((prev) => ({
688
+ ...prev,
689
+ dueDate: e.target.value,
690
+ }))
691
+ }
692
+ />
693
+ </div>
414
694
 
415
- <div className="space-y-1.5">
416
- <Label htmlFor="task-form-estimate">
417
- {taskT('taskForm.estimateLabel')}
418
- </Label>
419
- <Input
420
- id="task-form-estimate"
421
- type="number"
422
- min="0"
423
- step="0.5"
424
- placeholder="0"
425
- value={formData.estimateHours}
426
- onChange={(e) =>
427
- setFormData((prev) => ({
428
- ...prev,
429
- estimateHours: e.target.value,
430
- }))
431
- }
432
- />
695
+ <div className="space-y-1.5">
696
+ <Label htmlFor="task-form-estimate">
697
+ {taskT('taskForm.estimateLabel')}
698
+ </Label>
699
+ <Input
700
+ id="task-form-estimate"
701
+ type="number"
702
+ min="0"
703
+ step="0.5"
704
+ placeholder="0"
705
+ value={formData.estimateHours}
706
+ onChange={(e) =>
707
+ setFormData((prev) => ({
708
+ ...prev,
709
+ estimateHours: e.target.value,
710
+ }))
711
+ }
712
+ />
713
+ </div>
714
+ </div>
715
+
716
+ <div className="space-y-1.5">
717
+ <Label htmlFor="task-form-tags">
718
+ {taskT('taskForm.tagsLabel')}
719
+ </Label>
720
+ <Input
721
+ id="task-form-tags"
722
+ placeholder={taskT('taskForm.tagsPlaceholder')}
723
+ value={formData.tags}
724
+ onChange={(e) =>
725
+ setFormData((prev) => ({ ...prev, tags: e.target.value }))
726
+ }
727
+ />
728
+ </div>
433
729
  </div>
434
- </div>
435
-
436
- <div className="space-y-1.5">
437
- <Label htmlFor="task-form-tags">
438
- {taskT('taskForm.tagsLabel')}
439
- </Label>
440
- <Input
441
- id="task-form-tags"
442
- placeholder={taskT('taskForm.tagsPlaceholder')}
443
- value={formData.tags}
444
- onChange={(e) =>
445
- setFormData((prev) => ({ ...prev, tags: e.target.value }))
446
- }
447
- />
448
- </div>
449
-
450
- {editingTask ? (
451
- <div className="space-y-1.5">
452
- <Label className="flex items-center gap-1.5">
453
- <Paperclip className="size-3.5" />
454
- {taskT('taskForm.attachmentsLabel')}
455
- </Label>
456
- <TaskFileAttachments taskId={editingTask.id} />
730
+
731
+ <div className="mt-4 flex flex-wrap items-center justify-between gap-2 border-t px-4 pb-4 pt-4">
732
+ <div className="flex gap-2" />
733
+ <div className="flex gap-2">
734
+ <Button variant="outline" onClick={close} disabled={loading}>
735
+ {commonT('actions.cancel')}
736
+ </Button>
737
+ <Button
738
+ onClick={() => void handleSubmit()}
739
+ disabled={loading || isSubmitDisabled()}
740
+ >
741
+ {loading ? (
742
+ <Loader2 className="mr-2 size-4 animate-spin" />
743
+ ) : null}
744
+ {commonT('actions.create')}
745
+ </Button>
746
+ </div>
457
747
  </div>
458
- ) : null}
459
- </div>
460
-
461
- <div className="mt-4 flex flex-wrap items-center justify-between gap-2 border-t px-4 pb-4 pt-4">
462
- <div className="flex gap-2">
463
- {editingTask ? (
464
- <Button
465
- type="button"
466
- variant="outline"
467
- disabled={loading || archivingId === editingTask.id}
468
- onClick={() => void handleArchive()}
469
- >
470
- {archivingId === editingTask.id ? (
471
- <Loader2 className="mr-2 size-4 animate-spin" />
472
- ) : (
473
- <Archive className="mr-2 size-4" />
474
- )}
475
- {commonT('actions.archive')}
476
- </Button>
477
- ) : null}
478
- </div>
479
- <div className="flex gap-2">
480
- <Button variant="outline" onClick={close} disabled={loading}>
481
- {commonT('actions.cancel')}
482
- </Button>
483
- <Button
484
- onClick={() => void handleSubmit()}
485
- disabled={loading || isSubmitDisabled()}
486
- >
487
- {loading ? (
488
- <Loader2 className="mr-2 size-4 animate-spin" />
489
- ) : null}
490
- {editingTask
491
- ? commonT('actions.save')
492
- : commonT('actions.create')}
493
- </Button>
494
- </div>
495
- </div>
748
+ </>
749
+ )}
496
750
  </SheetContent>
497
751
  </Sheet>
498
752