@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.
- package/dist/controllers/operations-collaborators.controller.d.ts +5 -0
- package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
- package/dist/operations.service.d.ts +9 -1
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +140 -26
- package/dist/operations.service.js.map +1 -1
- package/hedhog/data/integration_event_catalog.yaml +313 -0
- package/hedhog/data/setting_group.yaml +21 -0
- package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +410 -23
- package/hedhog/frontend/app/_components/my-project-summary-screen.tsx.ejs +504 -375
- package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +258 -230
- package/hedhog/frontend/app/_components/task-detail-sheet.tsx.ejs +225 -162
- package/hedhog/frontend/app/_components/task-form-sheet.tsx.ejs +484 -230
- package/hedhog/frontend/app/_lib/api.ts.ejs +13 -4
- package/hedhog/frontend/app/_lib/hooks/use-mention-items.ts.ejs +28 -0
- package/hedhog/frontend/app/_lib/types.ts.ejs +30 -29
- package/hedhog/frontend/app/my-tasks/page.tsx.ejs +347 -236
- package/hedhog/frontend/app/reports/projects/page.tsx.ejs +31 -7
- package/hedhog/frontend/messages/en.json +38 -55
- package/hedhog/frontend/messages/en.json.ejs +21 -4
- package/hedhog/frontend/messages/pt.json +36 -55
- package/hedhog/frontend/messages/pt.json.ejs +14 -3
- package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/types.d.ts +1 -0
- package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/types.d.ts.map +1 -1
- package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/types.ts +1 -0
- package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/types.d.ts +1 -0
- package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/types.d.ts.map +1 -1
- package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/types.ts +1 -0
- package/hedhog/table/operations_collaborator.yaml +5 -0
- package/hedhog/table/operations_collaborator_compensation_history.yaml +4 -0
- package/package.json +5 -5
- 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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
</
|
|
267
|
-
<
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
<
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
{
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
292
|
-
.
|
|
293
|
-
|
|
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
|
-
|
|
296
|
-
|
|
297
|
-
<SelectItem key={p.id} value={String(p.id)}>
|
|
298
|
-
{p.label}
|
|
642
|
+
<SelectItem value="medium">
|
|
643
|
+
{getTaskPriorityLabel('medium')}
|
|
299
644
|
</SelectItem>
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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
|
-
|
|
459
|
-
|
|
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
|
|