@hed-hog/operations 0.0.325 → 0.0.326
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
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
import { Input } from '@/components/ui/input';
|
|
20
20
|
import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
|
|
21
21
|
import { Label } from '@/components/ui/label';
|
|
22
|
+
import { Progress } from '@/components/ui/progress';
|
|
22
23
|
import {
|
|
23
24
|
Select,
|
|
24
25
|
SelectContent,
|
|
@@ -26,6 +27,12 @@ import {
|
|
|
26
27
|
SelectTrigger,
|
|
27
28
|
SelectValue,
|
|
28
29
|
} from '@/components/ui/select';
|
|
30
|
+
import {
|
|
31
|
+
Sheet,
|
|
32
|
+
SheetContent,
|
|
33
|
+
SheetHeader,
|
|
34
|
+
SheetTitle,
|
|
35
|
+
} from '@/components/ui/sheet';
|
|
29
36
|
import {
|
|
30
37
|
Table,
|
|
31
38
|
TableBody,
|
|
@@ -34,7 +41,7 @@ import {
|
|
|
34
41
|
TableHeader,
|
|
35
42
|
TableRow,
|
|
36
43
|
} from '@/components/ui/table';
|
|
37
|
-
import {
|
|
44
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
38
45
|
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
|
39
46
|
import {
|
|
40
47
|
closestCenter,
|
|
@@ -66,7 +73,6 @@ import {
|
|
|
66
73
|
Pencil,
|
|
67
74
|
PlayCircle,
|
|
68
75
|
Plus,
|
|
69
|
-
Rows3,
|
|
70
76
|
Timer,
|
|
71
77
|
Trash2,
|
|
72
78
|
} from 'lucide-react';
|
|
@@ -76,10 +82,13 @@ import { useCallback, useMemo, useState } from 'react';
|
|
|
76
82
|
import { OperationsHeader } from '../_components/operations-header';
|
|
77
83
|
import { StatusBadge } from '../_components/status-badge';
|
|
78
84
|
import {
|
|
85
|
+
TaskCommentsSection,
|
|
79
86
|
TaskDetailSheet,
|
|
80
87
|
type TaskDetailSheetData,
|
|
81
88
|
} from '../_components/task-detail-sheet';
|
|
89
|
+
import { TaskFileAttachments } from '../_components/task-file-attachments';
|
|
82
90
|
import { fetchOperations, mutateOperations } from '../_lib/api';
|
|
91
|
+
import { useMentionItems } from '../_lib/hooks/use-mention-items';
|
|
83
92
|
import type {
|
|
84
93
|
OperationsProjectOption,
|
|
85
94
|
OperationsTaskOption,
|
|
@@ -191,6 +200,26 @@ function isPastDue(dueDate?: string | null) {
|
|
|
191
200
|
return new Date(dueDate) < new Date();
|
|
192
201
|
}
|
|
193
202
|
|
|
203
|
+
function getColumnClassName(columnId: BoardColumnId) {
|
|
204
|
+
const styles: Record<BoardColumnId, string> = {
|
|
205
|
+
todo: 'from-slate-500/20 via-slate-500/5 to-transparent',
|
|
206
|
+
doing: 'from-sky-500/20 via-cyan-500/5 to-transparent',
|
|
207
|
+
review: 'from-amber-500/20 via-yellow-500/5 to-transparent',
|
|
208
|
+
done: 'from-emerald-500/20 via-green-500/5 to-transparent',
|
|
209
|
+
};
|
|
210
|
+
return styles[columnId];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function getColumnDotClassName(columnId: BoardColumnId) {
|
|
214
|
+
const styles: Record<BoardColumnId, string> = {
|
|
215
|
+
todo: 'bg-slate-500',
|
|
216
|
+
doing: 'bg-sky-500',
|
|
217
|
+
review: 'bg-amber-500',
|
|
218
|
+
done: 'bg-emerald-500',
|
|
219
|
+
};
|
|
220
|
+
return styles[columnId];
|
|
221
|
+
}
|
|
222
|
+
|
|
194
223
|
function getInitials(value?: string | null) {
|
|
195
224
|
const parts = String(value ?? '')
|
|
196
225
|
.trim()
|
|
@@ -276,6 +305,7 @@ export default function OperationsMyTasksPage() {
|
|
|
276
305
|
const detailT = useTranslations('operations.ProjectDetailsPage');
|
|
277
306
|
|
|
278
307
|
const { request, currentLocaleCode, getSettingValue } = useApp();
|
|
308
|
+
const mentionItems = useMentionItems(request);
|
|
279
309
|
const [search, setSearch] = useState('');
|
|
280
310
|
const [statusFilter, setStatusFilter] = useState('all');
|
|
281
311
|
const [page, setPage] = useState(1);
|
|
@@ -303,6 +333,7 @@ export default function OperationsMyTasksPage() {
|
|
|
303
333
|
const [taskFormOpen, setTaskFormOpen] = useState(false);
|
|
304
334
|
const [editingTaskId, setEditingTaskId] = useState<number | null>(null);
|
|
305
335
|
const [taskFormLoading, setTaskFormLoading] = useState(false);
|
|
336
|
+
const [archivingTaskId, setArchivingTaskId] = useState<number | null>(null);
|
|
306
337
|
const [taskFormData, setTaskFormData] =
|
|
307
338
|
useState<TaskFormState>(EMPTY_TASK_FORM);
|
|
308
339
|
const [inlineCreateColumn, setInlineCreateColumn] =
|
|
@@ -486,12 +517,22 @@ export default function OperationsMyTasksPage() {
|
|
|
486
517
|
|
|
487
518
|
const handleArchiveTask = useCallback(
|
|
488
519
|
async (taskId: number) => {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
520
|
+
setArchivingTaskId(taskId);
|
|
521
|
+
try {
|
|
522
|
+
await mutateOperations(
|
|
523
|
+
request,
|
|
524
|
+
`/operations/tasks/${taskId}`,
|
|
525
|
+
'PATCH',
|
|
526
|
+
{ archived: true }
|
|
527
|
+
);
|
|
528
|
+
setSelectedTask(null);
|
|
529
|
+
setBoardOverride(null);
|
|
530
|
+
await refetchAll();
|
|
531
|
+
} catch {
|
|
532
|
+
// ignore
|
|
533
|
+
} finally {
|
|
534
|
+
setArchivingTaskId(null);
|
|
535
|
+
}
|
|
495
536
|
},
|
|
496
537
|
[request, refetchAll]
|
|
497
538
|
);
|
|
@@ -746,19 +787,31 @@ export default function OperationsMyTasksPage() {
|
|
|
746
787
|
{(isOver) => (
|
|
747
788
|
<div
|
|
748
789
|
className={[
|
|
749
|
-
'rounded-
|
|
790
|
+
'flex min-h-[32rem] flex-col overflow-hidden rounded-3xl border bg-linear-to-b p-3 transition-all',
|
|
791
|
+
getColumnClassName(column.id),
|
|
750
792
|
isOver
|
|
751
|
-
? 'border-primary
|
|
793
|
+
? 'border-primary shadow-lg ring-2 ring-primary/15'
|
|
752
794
|
: 'border-border',
|
|
753
795
|
].join(' ')}
|
|
754
796
|
>
|
|
755
|
-
<div className="mb-3 flex items-center justify-between">
|
|
756
|
-
<div className="
|
|
757
|
-
<
|
|
758
|
-
|
|
797
|
+
<div className="mb-3 flex items-center justify-between gap-3 rounded-2xl border bg-background/85 p-3 shadow-xs">
|
|
798
|
+
<div className="min-w-0">
|
|
799
|
+
<div className="flex items-center gap-2 text-sm font-semibold">
|
|
800
|
+
<span
|
|
801
|
+
className={[
|
|
802
|
+
'size-2.5 rounded-full',
|
|
803
|
+
getColumnDotClassName(column.id),
|
|
804
|
+
].join(' ')}
|
|
805
|
+
/>
|
|
806
|
+
{column.label}
|
|
807
|
+
</div>
|
|
808
|
+
<div className="mt-1 text-xs text-muted-foreground">
|
|
809
|
+
{boardColumns[column.id].length}{' '}
|
|
810
|
+
{detailT('kanban.items')}
|
|
811
|
+
</div>
|
|
759
812
|
</div>
|
|
760
813
|
<div className="flex items-center gap-1">
|
|
761
|
-
<span className="rounded-full bg-background px-2 py-0.5 text-xs text-muted-foreground">
|
|
814
|
+
<span className="rounded-full border bg-background px-2 py-0.5 text-xs font-medium text-muted-foreground">
|
|
762
815
|
{boardColumns[column.id].length}
|
|
763
816
|
</span>
|
|
764
817
|
<button
|
|
@@ -795,7 +848,7 @@ export default function OperationsMyTasksPage() {
|
|
|
795
848
|
role="button"
|
|
796
849
|
tabIndex={0}
|
|
797
850
|
onClick={() =>
|
|
798
|
-
!isDragging &&
|
|
851
|
+
!isDragging && openEditTaskForm(task)
|
|
799
852
|
}
|
|
800
853
|
onKeyDown={(event) => {
|
|
801
854
|
if (
|
|
@@ -803,7 +856,7 @@ export default function OperationsMyTasksPage() {
|
|
|
803
856
|
event.key === ' '
|
|
804
857
|
) {
|
|
805
858
|
event.preventDefault();
|
|
806
|
-
|
|
859
|
+
openEditTaskForm(task);
|
|
807
860
|
}
|
|
808
861
|
}}
|
|
809
862
|
className={[
|
|
@@ -833,31 +886,14 @@ export default function OperationsMyTasksPage() {
|
|
|
833
886
|
</p>
|
|
834
887
|
) : null}
|
|
835
888
|
</div>
|
|
836
|
-
<
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
</span>
|
|
845
|
-
<Button
|
|
846
|
-
type="button"
|
|
847
|
-
variant="ghost"
|
|
848
|
-
size="icon"
|
|
849
|
-
className="size-7 shrink-0 rounded-full opacity-0 transition group-hover:opacity-100"
|
|
850
|
-
onPointerDown={(event) =>
|
|
851
|
-
event.stopPropagation()
|
|
852
|
-
}
|
|
853
|
-
onClick={(event) => {
|
|
854
|
-
event.stopPropagation();
|
|
855
|
-
openEditTaskForm(task);
|
|
856
|
-
}}
|
|
857
|
-
>
|
|
858
|
-
<Pencil className="size-3.5" />
|
|
859
|
-
</Button>
|
|
860
|
-
</div>
|
|
889
|
+
<span
|
|
890
|
+
className={[
|
|
891
|
+
'shrink-0 rounded-full border px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide',
|
|
892
|
+
getPriorityClassName(task.priority),
|
|
893
|
+
].join(' ')}
|
|
894
|
+
>
|
|
895
|
+
{getTaskPriorityLabel(task.priority)}
|
|
896
|
+
</span>
|
|
861
897
|
</div>
|
|
862
898
|
|
|
863
899
|
{taskTags.length > 0 ? (
|
|
@@ -1199,6 +1235,7 @@ export default function OperationsMyTasksPage() {
|
|
|
1199
1235
|
<TaskDetailSheet
|
|
1200
1236
|
task={selectedTask}
|
|
1201
1237
|
open={selectedTask !== null}
|
|
1238
|
+
defaultTab="comments"
|
|
1202
1239
|
onOpenChange={(open) => {
|
|
1203
1240
|
if (!open) setSelectedTask(null);
|
|
1204
1241
|
}}
|
|
@@ -1291,7 +1328,7 @@ export default function OperationsMyTasksPage() {
|
|
|
1291
1328
|
</DialogContent>
|
|
1292
1329
|
</Dialog>
|
|
1293
1330
|
|
|
1294
|
-
<
|
|
1331
|
+
<Sheet
|
|
1295
1332
|
open={taskFormOpen}
|
|
1296
1333
|
onOpenChange={(open) => {
|
|
1297
1334
|
if (!taskFormLoading) {
|
|
@@ -1303,209 +1340,283 @@ export default function OperationsMyTasksPage() {
|
|
|
1303
1340
|
}
|
|
1304
1341
|
}}
|
|
1305
1342
|
>
|
|
1306
|
-
<
|
|
1307
|
-
<
|
|
1308
|
-
<
|
|
1343
|
+
<SheetContent className="flex w-full flex-col overflow-hidden sm:max-w-xl">
|
|
1344
|
+
<SheetHeader className="shrink-0">
|
|
1345
|
+
<SheetTitle>
|
|
1309
1346
|
{editingTaskId
|
|
1310
1347
|
? detailT('taskForm.titleEdit')
|
|
1311
1348
|
: detailT('taskForm.titleNew')}
|
|
1312
|
-
</
|
|
1313
|
-
</
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
<
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
<SelectTrigger className="w-full" id="task-project">
|
|
1327
|
-
<SelectValue
|
|
1328
|
-
placeholder={t('dialogs.createProjectPlaceholder')}
|
|
1329
|
-
/>
|
|
1330
|
-
</SelectTrigger>
|
|
1331
|
-
<SelectContent>
|
|
1332
|
-
{projectOptions.map((project) => (
|
|
1333
|
-
<SelectItem key={project.id} value={String(project.id)}>
|
|
1334
|
-
{project.label}
|
|
1335
|
-
</SelectItem>
|
|
1336
|
-
))}
|
|
1337
|
-
</SelectContent>
|
|
1338
|
-
</Select>
|
|
1339
|
-
</div>
|
|
1340
|
-
|
|
1341
|
-
<div className="space-y-2">
|
|
1342
|
-
<Label htmlFor="task-name">
|
|
1343
|
-
{detailT('taskForm.nameLabel')} *
|
|
1344
|
-
</Label>
|
|
1345
|
-
<Input
|
|
1346
|
-
id="task-name"
|
|
1347
|
-
value={taskFormData.name}
|
|
1348
|
-
onChange={(event) =>
|
|
1349
|
-
setTaskFormData((prev) => ({
|
|
1350
|
-
...prev,
|
|
1351
|
-
name: event.target.value,
|
|
1352
|
-
}))
|
|
1353
|
-
}
|
|
1354
|
-
placeholder={detailT('taskForm.namePlaceholder')}
|
|
1355
|
-
/>
|
|
1356
|
-
</div>
|
|
1357
|
-
|
|
1358
|
-
<div className="space-y-2">
|
|
1359
|
-
<Label htmlFor="task-description">
|
|
1360
|
-
{detailT('taskForm.descriptionLabel')}
|
|
1361
|
-
</Label>
|
|
1362
|
-
<RichTextEditor
|
|
1363
|
-
value={taskFormData.description}
|
|
1364
|
-
onChange={(val) =>
|
|
1365
|
-
setTaskFormData((prev) => ({
|
|
1366
|
-
...prev,
|
|
1367
|
-
description: val,
|
|
1368
|
-
}))
|
|
1369
|
-
}
|
|
1370
|
-
/>
|
|
1371
|
-
</div>
|
|
1349
|
+
</SheetTitle>
|
|
1350
|
+
</SheetHeader>
|
|
1351
|
+
|
|
1352
|
+
<Tabs defaultValue="info" className="flex min-h-0 flex-1 flex-col">
|
|
1353
|
+
<TabsList className="mx-4 grid w-[calc(100%-2rem)] shrink-0 grid-cols-2">
|
|
1354
|
+
<TabsTrigger value="info">
|
|
1355
|
+
{detailT('taskForm.tabInfo')}
|
|
1356
|
+
</TabsTrigger>
|
|
1357
|
+
{editingTaskId ? (
|
|
1358
|
+
<TabsTrigger value="comments">
|
|
1359
|
+
{detailT('taskForm.tabComments')}
|
|
1360
|
+
</TabsTrigger>
|
|
1361
|
+
) : null}
|
|
1362
|
+
</TabsList>
|
|
1372
1363
|
|
|
1373
|
-
<
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1364
|
+
<TabsContent
|
|
1365
|
+
value="info"
|
|
1366
|
+
className="flex min-h-0 flex-1 flex-col data-[state=inactive]:hidden"
|
|
1367
|
+
>
|
|
1368
|
+
<div className="flex-1 space-y-4 overflow-y-auto px-4 py-2">
|
|
1369
|
+
<div className="space-y-2">
|
|
1370
|
+
<Label htmlFor="task-project">
|
|
1371
|
+
{commonT('labels.project')}
|
|
1372
|
+
</Label>
|
|
1373
|
+
<Select
|
|
1374
|
+
value={taskFormData.projectId}
|
|
1375
|
+
onValueChange={(value) =>
|
|
1376
|
+
setTaskFormData((prev) => ({
|
|
1377
|
+
...prev,
|
|
1378
|
+
projectId: value,
|
|
1379
|
+
}))
|
|
1380
|
+
}
|
|
1381
|
+
>
|
|
1382
|
+
<SelectTrigger className="w-full" id="task-project">
|
|
1383
|
+
<SelectValue
|
|
1384
|
+
placeholder={t('dialogs.createProjectPlaceholder')}
|
|
1385
|
+
/>
|
|
1386
|
+
</SelectTrigger>
|
|
1387
|
+
<SelectContent>
|
|
1388
|
+
{projectOptions.map((project) => (
|
|
1389
|
+
<SelectItem key={project.id} value={String(project.id)}>
|
|
1390
|
+
{project.label}
|
|
1391
|
+
</SelectItem>
|
|
1392
|
+
))}
|
|
1393
|
+
</SelectContent>
|
|
1394
|
+
</Select>
|
|
1395
|
+
</div>
|
|
1396
|
+
|
|
1397
|
+
<div className="space-y-2">
|
|
1398
|
+
<Label htmlFor="task-name">
|
|
1399
|
+
{detailT('taskForm.nameLabel')} *
|
|
1400
|
+
</Label>
|
|
1401
|
+
<Input
|
|
1402
|
+
id="task-name"
|
|
1403
|
+
value={taskFormData.name}
|
|
1404
|
+
onChange={(event) =>
|
|
1405
|
+
setTaskFormData((prev) => ({
|
|
1406
|
+
...prev,
|
|
1407
|
+
name: event.target.value,
|
|
1408
|
+
}))
|
|
1409
|
+
}
|
|
1410
|
+
placeholder={detailT('taskForm.namePlaceholder')}
|
|
1411
|
+
/>
|
|
1412
|
+
</div>
|
|
1413
|
+
|
|
1414
|
+
<div className="space-y-2">
|
|
1415
|
+
<Label htmlFor="task-description">
|
|
1416
|
+
{detailT('taskForm.descriptionLabel')}
|
|
1417
|
+
</Label>
|
|
1418
|
+
<RichTextEditor
|
|
1419
|
+
value={taskFormData.description}
|
|
1420
|
+
onChange={(val) =>
|
|
1421
|
+
setTaskFormData((prev) => ({
|
|
1422
|
+
...prev,
|
|
1423
|
+
description: val,
|
|
1424
|
+
}))
|
|
1425
|
+
}
|
|
1426
|
+
mentions={mentionItems}
|
|
1427
|
+
/>
|
|
1428
|
+
</div>
|
|
1429
|
+
|
|
1430
|
+
<div className="grid grid-cols-2 gap-3">
|
|
1431
|
+
<div className="space-y-2">
|
|
1432
|
+
<Label>{detailT('taskForm.priorityLabel')}</Label>
|
|
1433
|
+
<Select
|
|
1434
|
+
value={taskFormData.priority}
|
|
1435
|
+
onValueChange={(value) =>
|
|
1436
|
+
setTaskFormData((prev) => ({
|
|
1437
|
+
...prev,
|
|
1438
|
+
priority: value as TaskFormState['priority'],
|
|
1439
|
+
}))
|
|
1440
|
+
}
|
|
1441
|
+
>
|
|
1442
|
+
<SelectTrigger className="w-full">
|
|
1443
|
+
<SelectValue />
|
|
1444
|
+
</SelectTrigger>
|
|
1445
|
+
<SelectContent>
|
|
1446
|
+
<SelectItem value="low">
|
|
1447
|
+
{getTaskPriorityLabel('low')}
|
|
1448
|
+
</SelectItem>
|
|
1449
|
+
<SelectItem value="medium">
|
|
1450
|
+
{getTaskPriorityLabel('medium')}
|
|
1451
|
+
</SelectItem>
|
|
1452
|
+
<SelectItem value="high">
|
|
1453
|
+
{getTaskPriorityLabel('high')}
|
|
1454
|
+
</SelectItem>
|
|
1455
|
+
</SelectContent>
|
|
1456
|
+
</Select>
|
|
1457
|
+
</div>
|
|
1401
1458
|
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1459
|
+
<div className="space-y-2">
|
|
1460
|
+
<Label>{detailT('taskForm.columnLabel')}</Label>
|
|
1461
|
+
<Select
|
|
1462
|
+
value={taskFormData.status}
|
|
1463
|
+
onValueChange={(value) =>
|
|
1464
|
+
setTaskFormData((prev) => ({
|
|
1465
|
+
...prev,
|
|
1466
|
+
status: value as BoardColumnId,
|
|
1467
|
+
}))
|
|
1468
|
+
}
|
|
1469
|
+
>
|
|
1470
|
+
<SelectTrigger className="w-full">
|
|
1471
|
+
<SelectValue />
|
|
1472
|
+
</SelectTrigger>
|
|
1473
|
+
<SelectContent>
|
|
1474
|
+
{KANBAN_COLUMNS.map((column) => (
|
|
1475
|
+
<SelectItem key={column.id} value={column.id}>
|
|
1476
|
+
{column.label}
|
|
1477
|
+
</SelectItem>
|
|
1478
|
+
))}
|
|
1479
|
+
</SelectContent>
|
|
1480
|
+
</Select>
|
|
1481
|
+
</div>
|
|
1482
|
+
</div>
|
|
1483
|
+
|
|
1484
|
+
<div className="grid grid-cols-2 gap-3">
|
|
1485
|
+
<div className="space-y-2">
|
|
1486
|
+
<Label htmlFor="task-due-date">
|
|
1487
|
+
{detailT('taskForm.deadlineLabel')}
|
|
1488
|
+
</Label>
|
|
1489
|
+
<Input
|
|
1490
|
+
id="task-due-date"
|
|
1491
|
+
type="date"
|
|
1492
|
+
value={taskFormData.dueDate}
|
|
1493
|
+
onChange={(event) =>
|
|
1494
|
+
setTaskFormData((prev) => ({
|
|
1495
|
+
...prev,
|
|
1496
|
+
dueDate: event.target.value,
|
|
1497
|
+
}))
|
|
1498
|
+
}
|
|
1499
|
+
/>
|
|
1500
|
+
</div>
|
|
1426
1501
|
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1502
|
+
<div className="space-y-2">
|
|
1503
|
+
<Label htmlFor="task-estimate">
|
|
1504
|
+
{detailT('taskForm.estimateLabel')}
|
|
1505
|
+
</Label>
|
|
1506
|
+
<Input
|
|
1507
|
+
id="task-estimate"
|
|
1508
|
+
type="number"
|
|
1509
|
+
min="0"
|
|
1510
|
+
step="0.5"
|
|
1511
|
+
placeholder="0"
|
|
1512
|
+
value={taskFormData.estimateHours}
|
|
1513
|
+
onChange={(event) =>
|
|
1514
|
+
setTaskFormData((prev) => ({
|
|
1515
|
+
...prev,
|
|
1516
|
+
estimateHours: event.target.value,
|
|
1517
|
+
}))
|
|
1518
|
+
}
|
|
1519
|
+
/>
|
|
1520
|
+
</div>
|
|
1521
|
+
</div>
|
|
1522
|
+
|
|
1523
|
+
<div className="space-y-2">
|
|
1524
|
+
<Label htmlFor="task-tags">
|
|
1525
|
+
{detailT('taskForm.tagsLabel')}
|
|
1526
|
+
</Label>
|
|
1527
|
+
<Input
|
|
1528
|
+
id="task-tags"
|
|
1529
|
+
value={taskFormData.tags}
|
|
1530
|
+
onChange={(event) =>
|
|
1531
|
+
setTaskFormData((prev) => ({
|
|
1532
|
+
...prev,
|
|
1533
|
+
tags: event.target.value,
|
|
1534
|
+
}))
|
|
1535
|
+
}
|
|
1536
|
+
placeholder={detailT('taskForm.tagsPlaceholder')}
|
|
1537
|
+
/>
|
|
1538
|
+
</div>
|
|
1539
|
+
|
|
1540
|
+
{editingTaskId ? (
|
|
1541
|
+
<div className="space-y-1.5">
|
|
1542
|
+
<Label className="flex items-center gap-1.5">
|
|
1543
|
+
<Paperclip className="size-3.5" />
|
|
1544
|
+
{detailT('taskForm.attachmentsLabel')}
|
|
1545
|
+
</Label>
|
|
1546
|
+
<TaskFileAttachments taskId={editingTaskId} />
|
|
1547
|
+
</div>
|
|
1548
|
+
) : null}
|
|
1443
1549
|
</div>
|
|
1444
1550
|
|
|
1445
|
-
<div className="
|
|
1446
|
-
<
|
|
1447
|
-
{
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1551
|
+
<div className="mt-4 flex flex-wrap items-center justify-between gap-2 border-t px-4 pb-4 pt-4">
|
|
1552
|
+
<div className="flex gap-2">
|
|
1553
|
+
{editingTaskId ? (
|
|
1554
|
+
<Button
|
|
1555
|
+
type="button"
|
|
1556
|
+
variant="outline"
|
|
1557
|
+
disabled={
|
|
1558
|
+
taskFormLoading || archivingTaskId === editingTaskId
|
|
1559
|
+
}
|
|
1560
|
+
onClick={() => {
|
|
1561
|
+
if (!editingTaskId) return;
|
|
1562
|
+
const id = editingTaskId;
|
|
1563
|
+
setTaskFormOpen(false);
|
|
1564
|
+
setEditingTaskId(null);
|
|
1565
|
+
setTaskFormData(EMPTY_TASK_FORM);
|
|
1566
|
+
void handleArchiveTask(id);
|
|
1567
|
+
}}
|
|
1568
|
+
>
|
|
1569
|
+
{archivingTaskId === editingTaskId ? (
|
|
1570
|
+
<Loader2 className="mr-2 size-4 animate-spin" />
|
|
1571
|
+
) : (
|
|
1572
|
+
<Archive className="mr-2 size-4" />
|
|
1573
|
+
)}
|
|
1574
|
+
{commonT('actions.archive')}
|
|
1575
|
+
</Button>
|
|
1576
|
+
) : null}
|
|
1577
|
+
</div>
|
|
1578
|
+
<div className="flex gap-2">
|
|
1579
|
+
<Button
|
|
1580
|
+
variant="ghost"
|
|
1581
|
+
onClick={() => {
|
|
1582
|
+
setTaskFormOpen(false);
|
|
1583
|
+
setEditingTaskId(null);
|
|
1584
|
+
setTaskFormData(EMPTY_TASK_FORM);
|
|
1585
|
+
}}
|
|
1586
|
+
disabled={taskFormLoading}
|
|
1587
|
+
>
|
|
1588
|
+
{commonT('actions.cancel')}
|
|
1589
|
+
</Button>
|
|
1590
|
+
<Button
|
|
1591
|
+
onClick={() => void handleTaskFormSubmit()}
|
|
1592
|
+
disabled={
|
|
1593
|
+
taskFormLoading ||
|
|
1594
|
+
!taskFormData.projectId ||
|
|
1595
|
+
!taskFormData.name.trim()
|
|
1596
|
+
}
|
|
1597
|
+
>
|
|
1598
|
+
{taskFormLoading ? (
|
|
1599
|
+
<Loader2 className="size-4 animate-spin" />
|
|
1600
|
+
) : null}
|
|
1601
|
+
{editingTaskId
|
|
1602
|
+
? commonT('actions.save')
|
|
1603
|
+
: t('actions.create')}
|
|
1604
|
+
</Button>
|
|
1605
|
+
</div>
|
|
1463
1606
|
</div>
|
|
1464
|
-
</
|
|
1607
|
+
</TabsContent>
|
|
1465
1608
|
|
|
1466
|
-
|
|
1467
|
-
<
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
placeholder={detailT('taskForm.tagsPlaceholder')}
|
|
1478
|
-
/>
|
|
1479
|
-
</div>
|
|
1480
|
-
</div>
|
|
1481
|
-
<DialogFooter className="gap-2 sm:justify-between">
|
|
1482
|
-
<Button
|
|
1483
|
-
variant="ghost"
|
|
1484
|
-
onClick={() => {
|
|
1485
|
-
setTaskFormOpen(false);
|
|
1486
|
-
setEditingTaskId(null);
|
|
1487
|
-
setTaskFormData(EMPTY_TASK_FORM);
|
|
1488
|
-
}}
|
|
1489
|
-
disabled={taskFormLoading}
|
|
1490
|
-
>
|
|
1491
|
-
{commonT('actions.cancel')}
|
|
1492
|
-
</Button>
|
|
1493
|
-
<Button
|
|
1494
|
-
onClick={() => void handleTaskFormSubmit()}
|
|
1495
|
-
disabled={
|
|
1496
|
-
taskFormLoading ||
|
|
1497
|
-
!taskFormData.projectId ||
|
|
1498
|
-
!taskFormData.name.trim()
|
|
1499
|
-
}
|
|
1500
|
-
>
|
|
1501
|
-
{taskFormLoading ? (
|
|
1502
|
-
<Loader2 className="size-4 animate-spin" />
|
|
1503
|
-
) : null}
|
|
1504
|
-
{editingTaskId ? commonT('actions.save') : t('actions.create')}
|
|
1505
|
-
</Button>
|
|
1506
|
-
</DialogFooter>
|
|
1507
|
-
</DialogContent>
|
|
1508
|
-
</Dialog>
|
|
1609
|
+
{editingTaskId ? (
|
|
1610
|
+
<TabsContent
|
|
1611
|
+
value="comments"
|
|
1612
|
+
className="min-h-0 flex-1 overflow-y-auto px-4 py-2 data-[state=inactive]:hidden"
|
|
1613
|
+
>
|
|
1614
|
+
<TaskCommentsSection taskId={editingTaskId} />
|
|
1615
|
+
</TabsContent>
|
|
1616
|
+
) : null}
|
|
1617
|
+
</Tabs>
|
|
1618
|
+
</SheetContent>
|
|
1619
|
+
</Sheet>
|
|
1509
1620
|
</Page>
|
|
1510
1621
|
);
|
|
1511
1622
|
}
|