@hed-hog/operations 0.0.331 → 0.0.332

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 (62) hide show
  1. package/dist/controllers/operations-collaborators.controller.d.ts +54 -0
  2. package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
  3. package/dist/controllers/operations-collaborators.controller.js +100 -0
  4. package/dist/controllers/operations-collaborators.controller.js.map +1 -1
  5. package/dist/dto/create-collaborator-invoice.dto.d.ts +11 -0
  6. package/dist/dto/create-collaborator-invoice.dto.d.ts.map +1 -0
  7. package/dist/dto/create-collaborator-invoice.dto.js +55 -0
  8. package/dist/dto/create-collaborator-invoice.dto.js.map +1 -0
  9. package/dist/dto/create-collaborator-payment.dto.d.ts +10 -0
  10. package/dist/dto/create-collaborator-payment.dto.d.ts.map +1 -0
  11. package/dist/dto/create-collaborator-payment.dto.js +50 -0
  12. package/dist/dto/create-collaborator-payment.dto.js.map +1 -0
  13. package/dist/dto/list-collaborator-invoice.dto.d.ts +4 -0
  14. package/dist/dto/list-collaborator-invoice.dto.d.ts.map +1 -0
  15. package/dist/dto/list-collaborator-invoice.dto.js +8 -0
  16. package/dist/dto/list-collaborator-invoice.dto.js.map +1 -0
  17. package/dist/dto/list-collaborator-payment.dto.d.ts +4 -0
  18. package/dist/dto/list-collaborator-payment.dto.d.ts.map +1 -0
  19. package/dist/dto/list-collaborator-payment.dto.js +8 -0
  20. package/dist/dto/list-collaborator-payment.dto.js.map +1 -0
  21. package/dist/dto/update-collaborator-invoice.dto.d.ts +6 -0
  22. package/dist/dto/update-collaborator-invoice.dto.d.ts.map +1 -0
  23. package/dist/dto/update-collaborator-invoice.dto.js +9 -0
  24. package/dist/dto/update-collaborator-invoice.dto.js.map +1 -0
  25. package/dist/dto/update-collaborator-payment.dto.d.ts +6 -0
  26. package/dist/dto/update-collaborator-payment.dto.d.ts.map +1 -0
  27. package/dist/dto/update-collaborator-payment.dto.js +9 -0
  28. package/dist/dto/update-collaborator-payment.dto.js.map +1 -0
  29. package/dist/operations.service.d.ts +76 -0
  30. package/dist/operations.service.d.ts.map +1 -1
  31. package/dist/operations.service.js +235 -5
  32. package/dist/operations.service.js.map +1 -1
  33. package/hedhog/data/menu.yaml +27 -8
  34. package/hedhog/data/route.yaml +72 -0
  35. package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +39 -3
  36. package/hedhog/frontend/app/_components/collaborator-invoices-tab.tsx.ejs +443 -0
  37. package/hedhog/frontend/app/_components/collaborator-payment-history-tab.tsx.ejs +429 -0
  38. package/hedhog/frontend/app/_components/my-project-summary-screen.tsx.ejs +86 -87
  39. package/hedhog/frontend/app/_components/project-assignments-tab.tsx.ejs +218 -10
  40. package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +710 -26
  41. package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +158 -38
  42. package/hedhog/frontend/app/_components/task-detail-sheet.tsx.ejs +807 -803
  43. package/hedhog/frontend/app/_lib/api.ts.ejs +631 -480
  44. package/hedhog/frontend/app/_lib/types.ts.ejs +6 -5
  45. package/hedhog/frontend/app/_lib/utils/task-ui.ts.ejs +18 -0
  46. package/hedhog/frontend/app/my-projects/page.tsx.ejs +16 -2
  47. package/hedhog/frontend/app/my-tasks/page.tsx.ejs +95 -157
  48. package/hedhog/frontend/app/projects/page.tsx.ejs +42 -6
  49. package/hedhog/frontend/app/tasks-gantt/page.tsx.ejs +953 -0
  50. package/hedhog/frontend/messages/en.json +96 -2
  51. package/hedhog/frontend/messages/pt.json +96 -2
  52. package/hedhog/table/operations_collaborator_invoice.yaml +35 -0
  53. package/hedhog/table/operations_collaborator_payment.yaml +32 -0
  54. package/package.json +5 -5
  55. package/src/controllers/operations-collaborators.controller.ts +117 -8
  56. package/src/dto/create-collaborator-invoice.dto.ts +39 -0
  57. package/src/dto/create-collaborator-payment.dto.ts +35 -0
  58. package/src/dto/list-collaborator-invoice.dto.ts +3 -0
  59. package/src/dto/list-collaborator-payment.dto.ts +3 -0
  60. package/src/dto/update-collaborator-invoice.dto.ts +6 -0
  61. package/src/dto/update-collaborator-payment.dto.ts +6 -0
  62. package/src/operations.service.ts +328 -5
@@ -0,0 +1,429 @@
1
+ 'use client';
2
+
3
+ import { Button } from '@/components/ui/button';
4
+ import { Input } from '@/components/ui/input';
5
+ import { Label } from '@/components/ui/label';
6
+ import {
7
+ Select,
8
+ SelectContent,
9
+ SelectItem,
10
+ SelectTrigger,
11
+ SelectValue,
12
+ } from '@/components/ui/select';
13
+ import {
14
+ Table,
15
+ TableBody,
16
+ TableCell,
17
+ TableHead,
18
+ TableHeader,
19
+ TableRow,
20
+ } from '@/components/ui/table';
21
+ import { Textarea } from '@/components/ui/textarea';
22
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
23
+ import { BanknoteIcon, Pencil, Plus, Trash2, X } from 'lucide-react';
24
+ import { useTranslations } from 'next-intl';
25
+ import { useState } from 'react';
26
+ import {
27
+ type CollaboratorPayment,
28
+ createCollaboratorPayment,
29
+ deleteCollaboratorPayment,
30
+ fetchCollaboratorPayments,
31
+ updateCollaboratorPayment,
32
+ } from '../_lib/api';
33
+ import { formatCurrency, formatDate } from '../_lib/utils/format';
34
+
35
+ const PAYMENT_METHODS = ['bank_transfer', 'pix', 'check', 'other'] as const;
36
+ type PaymentMethod = (typeof PAYMENT_METHODS)[number];
37
+
38
+ type FormState = {
39
+ amount: string;
40
+ paymentDate: string;
41
+ referenceMonth: string;
42
+ paymentMethod: PaymentMethod;
43
+ notes: string;
44
+ };
45
+
46
+ function emptyForm(): FormState {
47
+ return {
48
+ amount: '',
49
+ paymentDate: '',
50
+ referenceMonth: '',
51
+ paymentMethod: 'pix',
52
+ notes: '',
53
+ };
54
+ }
55
+
56
+ function paymentToForm(p: CollaboratorPayment): FormState {
57
+ return {
58
+ amount: p.amount ? Number(p.amount).toFixed(2).replace('.', ',') : '',
59
+ paymentDate: p.paymentDate ? p.paymentDate.slice(0, 10) : '',
60
+ referenceMonth: p.referenceMonth
61
+ ? p.referenceMonth.split('-').reverse().join('/')
62
+ : '',
63
+ paymentMethod: (p.paymentMethod as PaymentMethod) ?? 'pix',
64
+ notes: p.notes ?? '',
65
+ };
66
+ }
67
+
68
+ type Props = {
69
+ collaboratorId: number;
70
+ disabled?: boolean;
71
+ };
72
+
73
+ export function CollaboratorPaymentHistoryTab({
74
+ collaboratorId,
75
+ disabled = false,
76
+ }: Props) {
77
+ const t = useTranslations('operations.CollaboratorFormPage');
78
+ const commonT = useTranslations('operations.Common');
79
+ const { request, showToastHandler, getSettingValue, currentLocaleCode } =
80
+ useApp();
81
+
82
+ const [isAdding, setIsAdding] = useState(false);
83
+ const [editingId, setEditingId] = useState<number | null>(null);
84
+ const [confirmDeleteId, setConfirmDeleteId] = useState<number | null>(null);
85
+ const [form, setForm] = useState<FormState>(emptyForm());
86
+ const [saving, setSaving] = useState(false);
87
+
88
+ const {
89
+ data: payments = [],
90
+ isLoading,
91
+ refetch,
92
+ } = useQuery<CollaboratorPayment[]>({
93
+ queryKey: [
94
+ 'operations-collaborator-payment-history',
95
+ currentLocaleCode,
96
+ collaboratorId,
97
+ ],
98
+ staleTime: 0,
99
+ refetchOnMount: 'always',
100
+ queryFn: () => fetchCollaboratorPayments(request, collaboratorId),
101
+ });
102
+
103
+ function openAdd() {
104
+ setEditingId(null);
105
+ setForm(emptyForm());
106
+ setIsAdding(true);
107
+ }
108
+
109
+ function openEdit(p: CollaboratorPayment) {
110
+ setIsAdding(false);
111
+ setForm(paymentToForm(p));
112
+ setEditingId(p.id);
113
+ }
114
+
115
+ function cancelForm() {
116
+ setIsAdding(false);
117
+ setEditingId(null);
118
+ setForm(emptyForm());
119
+ }
120
+
121
+ async function handleSave() {
122
+ const amount = parseFloat(form.amount.replace(',', '.'));
123
+ if (!form.paymentDate || isNaN(amount) || amount <= 0) return;
124
+
125
+ setSaving(true);
126
+ try {
127
+ const rawMonth = form.referenceMonth;
128
+ const referenceMonth =
129
+ rawMonth && rawMonth.length === 7
130
+ ? rawMonth.split('/').reverse().join('-')
131
+ : rawMonth || null;
132
+
133
+ const data = {
134
+ amount,
135
+ paymentDate: form.paymentDate,
136
+ referenceMonth,
137
+ paymentMethod: form.paymentMethod,
138
+ notes: form.notes || null,
139
+ };
140
+
141
+ if (editingId !== null) {
142
+ await updateCollaboratorPayment(
143
+ request,
144
+ collaboratorId,
145
+ editingId,
146
+ data
147
+ );
148
+ } else {
149
+ await createCollaboratorPayment(request, collaboratorId, data);
150
+ }
151
+
152
+ showToastHandler?.('success', t('messages.saveSuccess'));
153
+ cancelForm();
154
+ await refetch();
155
+ } catch {
156
+ showToastHandler?.('error', t('messages.saveError'));
157
+ } finally {
158
+ setSaving(false);
159
+ }
160
+ }
161
+
162
+ async function handleDelete(paymentId: number) {
163
+ setSaving(true);
164
+ try {
165
+ await deleteCollaboratorPayment(request, collaboratorId, paymentId);
166
+ showToastHandler?.('success', t('messages.deleteSuccess'));
167
+ setConfirmDeleteId(null);
168
+ await refetch();
169
+ } catch {
170
+ showToastHandler?.('error', t('messages.deleteError'));
171
+ } finally {
172
+ setSaving(false);
173
+ }
174
+ }
175
+
176
+ const methodLabel = (m: string) => {
177
+ const key =
178
+ `paymentHistory.method${m.charAt(0).toUpperCase() + m.slice(1).replace(/_([a-z])/g, (_, c: string) => c.toUpperCase())}` as Parameters<
179
+ typeof t
180
+ >[0];
181
+ try {
182
+ return t(key);
183
+ } catch {
184
+ return m;
185
+ }
186
+ };
187
+
188
+ const showForm = isAdding || editingId !== null;
189
+
190
+ return (
191
+ <div className="space-y-4">
192
+ {!disabled && (
193
+ <div className="flex justify-end">
194
+ <Button
195
+ size="sm"
196
+ variant="outline"
197
+ onClick={openAdd}
198
+ disabled={showForm}
199
+ >
200
+ <Plus className="mr-1 size-4" />
201
+ {t('paymentHistory.add')}
202
+ </Button>
203
+ </div>
204
+ )}
205
+
206
+ {showForm && (
207
+ <div className="rounded-md border bg-muted/30 p-4">
208
+ <p className="mb-3 text-sm font-medium">
209
+ {editingId !== null
210
+ ? t('paymentHistory.editTitle')
211
+ : t('paymentHistory.add')}
212
+ </p>
213
+ <div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
214
+ <div className="space-y-1">
215
+ <Label className="text-xs">{t('paymentHistory.amount')} *</Label>
216
+ <Input
217
+ type="text"
218
+ inputMode="numeric"
219
+ placeholder="0,00"
220
+ value={form.amount}
221
+ onChange={(e) => {
222
+ const digits = e.target.value.replace(/\D/g, '');
223
+ if (!digits) {
224
+ setForm((f) => ({ ...f, amount: '' }));
225
+ return;
226
+ }
227
+ const cents = parseInt(digits, 10);
228
+ const formatted = (cents / 100).toFixed(2).replace('.', ',');
229
+ setForm((f) => ({ ...f, amount: formatted }));
230
+ }}
231
+ />
232
+ </div>
233
+ <div className="space-y-1">
234
+ <Label className="text-xs">
235
+ {t('paymentHistory.paymentDate')} *
236
+ </Label>
237
+ <Input
238
+ type="date"
239
+ value={form.paymentDate}
240
+ onChange={(e) =>
241
+ setForm((f) => ({ ...f, paymentDate: e.target.value }))
242
+ }
243
+ />
244
+ </div>
245
+ <div className="space-y-1">
246
+ <Label className="text-xs">
247
+ {t('paymentHistory.referenceMonth')}
248
+ </Label>
249
+ <Input
250
+ type="text"
251
+ inputMode="numeric"
252
+ placeholder="MM/AAAA"
253
+ maxLength={7}
254
+ value={form.referenceMonth}
255
+ onChange={(e) => {
256
+ const digits = e.target.value.replace(/\D/g, '');
257
+ const masked =
258
+ digits.length > 2
259
+ ? digits.slice(0, 2) + '/' + digits.slice(2, 6)
260
+ : digits;
261
+ setForm((f) => ({ ...f, referenceMonth: masked }));
262
+ }}
263
+ />
264
+ </div>
265
+ <div className="space-y-1">
266
+ <Label className="text-xs">
267
+ {t('paymentHistory.paymentMethod')}
268
+ </Label>
269
+ <Select
270
+ value={form.paymentMethod}
271
+ onValueChange={(v) =>
272
+ setForm((f) => ({
273
+ ...f,
274
+ paymentMethod: v as PaymentMethod,
275
+ }))
276
+ }
277
+ >
278
+ <SelectTrigger className="w-full cursor-pointer">
279
+ <SelectValue />
280
+ </SelectTrigger>
281
+ <SelectContent>
282
+ {PAYMENT_METHODS.map((m) => (
283
+ <SelectItem key={m} value={m} className="cursor-pointer">
284
+ {methodLabel(m)}
285
+ </SelectItem>
286
+ ))}
287
+ </SelectContent>
288
+ </Select>
289
+ </div>
290
+ <div className="space-y-1 sm:col-span-2">
291
+ <Label className="text-xs">{t('paymentHistory.notes')}</Label>
292
+ <Textarea
293
+ rows={2}
294
+ value={form.notes}
295
+ onChange={(e) =>
296
+ setForm((f) => ({ ...f, notes: e.target.value }))
297
+ }
298
+ />
299
+ </div>
300
+ </div>
301
+ <div className="mt-3 flex justify-end gap-2">
302
+ <Button
303
+ size="sm"
304
+ variant="ghost"
305
+ onClick={cancelForm}
306
+ disabled={saving}
307
+ >
308
+ <X className="mr-1 size-4" />
309
+ {commonT('actions.cancel')}
310
+ </Button>
311
+ <Button size="sm" onClick={handleSave} disabled={saving}>
312
+ {commonT('actions.save')}
313
+ </Button>
314
+ </div>
315
+ </div>
316
+ )}
317
+
318
+ {isLoading ? (
319
+ <p className="text-sm text-muted-foreground">{t('loading')}</p>
320
+ ) : payments.length === 0 && !showForm ? (
321
+ <div className="flex flex-col items-center gap-3 py-10 text-center">
322
+ <BanknoteIcon className="size-10 text-muted-foreground/40" />
323
+ <p className="text-sm text-muted-foreground">
324
+ {t('tabs.paymentHistoryEmpty')}
325
+ </p>
326
+ </div>
327
+ ) : payments.length > 0 ? (
328
+ <div className="overflow-x-auto rounded-md border">
329
+ <Table>
330
+ <TableHeader>
331
+ <TableRow>
332
+ <TableHead>{t('paymentHistory.amount')}</TableHead>
333
+ <TableHead>{t('paymentHistory.paymentDate')}</TableHead>
334
+ <TableHead>{t('paymentHistory.referenceMonth')}</TableHead>
335
+ <TableHead>{t('paymentHistory.paymentMethod')}</TableHead>
336
+ <TableHead>{t('paymentHistory.notes')}</TableHead>
337
+ {!disabled && (
338
+ <TableHead className="w-20 text-right"></TableHead>
339
+ )}
340
+ </TableRow>
341
+ </TableHeader>
342
+ <TableBody>
343
+ {payments.map((entry) => (
344
+ <TableRow key={entry.id}>
345
+ <TableCell className="font-medium">
346
+ {formatCurrency(
347
+ Number(entry.amount),
348
+ getSettingValue,
349
+ currentLocaleCode
350
+ )}
351
+ </TableCell>
352
+ <TableCell>
353
+ {entry.paymentDate
354
+ ? formatDate(
355
+ entry.paymentDate,
356
+ getSettingValue,
357
+ currentLocaleCode
358
+ )
359
+ : '—'}
360
+ </TableCell>
361
+ <TableCell>
362
+ {entry.referenceMonth
363
+ ? entry.referenceMonth.split('-').reverse().join('/')
364
+ : '—'}
365
+ </TableCell>
366
+ <TableCell>
367
+ {entry.paymentMethod
368
+ ? methodLabel(entry.paymentMethod)
369
+ : '—'}
370
+ </TableCell>
371
+ <TableCell className="text-muted-foreground">
372
+ {entry.notes ?? '—'}
373
+ </TableCell>
374
+ {!disabled && (
375
+ <TableCell className="text-right">
376
+ {confirmDeleteId === entry.id ? (
377
+ <div className="flex items-center justify-end gap-1">
378
+ <span className="text-xs text-muted-foreground">
379
+ {t('paymentHistory.deleteConfirm')}
380
+ </span>
381
+ <Button
382
+ size="sm"
383
+ variant="destructive"
384
+ className="h-7 px-2 text-xs cursor-pointer"
385
+ disabled={saving}
386
+ onClick={() => handleDelete(entry.id)}
387
+ >
388
+ {commonT('actions.delete')}
389
+ </Button>
390
+ <Button
391
+ size="sm"
392
+ variant="ghost"
393
+ className="h-7 px-2 text-xs cursor-pointer"
394
+ onClick={() => setConfirmDeleteId(null)}
395
+ >
396
+ <X className="size-3" />
397
+ </Button>
398
+ </div>
399
+ ) : (
400
+ <div className="flex items-center justify-end gap-1">
401
+ <Button
402
+ size="icon"
403
+ variant="ghost"
404
+ className="size-7 cursor-pointer"
405
+ onClick={() => openEdit(entry)}
406
+ >
407
+ <Pencil className="size-3.5" />
408
+ </Button>
409
+ <Button
410
+ size="icon"
411
+ variant="ghost"
412
+ className="size-7 cursor-pointer text-destructive hover:text-destructive"
413
+ onClick={() => setConfirmDeleteId(entry.id)}
414
+ >
415
+ <Trash2 className="size-3.5" />
416
+ </Button>
417
+ </div>
418
+ )}
419
+ </TableCell>
420
+ )}
421
+ </TableRow>
422
+ ))}
423
+ </TableBody>
424
+ </Table>
425
+ </div>
426
+ ) : null}
427
+ </div>
428
+ );
429
+ }
@@ -1,42 +1,42 @@
1
1
  'use client';
2
2
 
3
3
  import { EmptyState, Page } from '@/components/entity-list';
4
+ import { RichTextEditor } from '@/components/rich-text-editor';
4
5
  import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
5
6
  import { Button } from '@/components/ui/button';
6
- import {
7
- Dialog,
8
- DialogContent,
9
- DialogDescription,
7
+ import {
8
+ Dialog,
9
+ DialogContent,
10
+ DialogDescription,
10
11
  DialogFooter,
11
12
  DialogHeader,
12
- DialogTitle,
13
- } from '@/components/ui/dialog';
14
- import { Input } from '@/components/ui/input';
15
- import { Label } from '@/components/ui/label';
16
- import { Progress } from '@/components/ui/progress';
17
- import {
18
- Select,
19
- SelectContent,
20
- SelectItem,
21
- SelectTrigger,
22
- SelectValue,
23
- } from '@/components/ui/select';
24
- import {
25
- Sheet,
26
- SheetContent,
27
- SheetHeader,
28
- SheetTitle,
29
- } from '@/components/ui/sheet';
30
- import {
31
- Table,
32
- TableBody,
33
- TableCell,
13
+ DialogTitle,
14
+ } from '@/components/ui/dialog';
15
+ import { Input } from '@/components/ui/input';
16
+ import { Label } from '@/components/ui/label';
17
+ import { Progress } from '@/components/ui/progress';
18
+ import {
19
+ Select,
20
+ SelectContent,
21
+ SelectItem,
22
+ SelectTrigger,
23
+ SelectValue,
24
+ } from '@/components/ui/select';
25
+ import {
26
+ Sheet,
27
+ SheetContent,
28
+ SheetHeader,
29
+ SheetTitle,
30
+ } from '@/components/ui/sheet';
31
+ import {
32
+ Table,
33
+ TableBody,
34
+ TableCell,
34
35
  TableHead,
35
36
  TableHeader,
36
- TableRow,
37
- } from '@/components/ui/table';
38
- import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
39
- import { RichTextEditor } from '@/components/rich-text-editor';
37
+ TableRow,
38
+ } from '@/components/ui/table';
39
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
40
40
  import {
41
41
  closestCenter,
42
42
  DndContext,
@@ -67,13 +67,13 @@ import {
67
67
  Timer,
68
68
  Trash2,
69
69
  } from 'lucide-react';
70
- import { useTranslations } from 'next-intl';
71
- import { useCallback, useMemo, useState } from 'react';
72
- import { fetchOperations, mutateOperations } from '../_lib/api';
73
- import { useMentionItems } from '../_lib/hooks/use-mention-items';
74
- import type {
75
- OperationsMyProjectSummary,
76
- OperationsTaskOption,
70
+ import { useTranslations } from 'next-intl';
71
+ import { useCallback, useMemo, useState } from 'react';
72
+ import { fetchOperations, mutateOperations } from '../_lib/api';
73
+ import { useMentionItems } from '../_lib/hooks/use-mention-items';
74
+ import type {
75
+ OperationsMyProjectSummary,
76
+ OperationsTaskOption,
77
77
  PaginatedResponse,
78
78
  } from '../_lib/types';
79
79
  import {
@@ -84,13 +84,13 @@ import {
84
84
  import { OperationsHeader } from './operations-header';
85
85
  import { SectionCard } from './section-card';
86
86
  import { StatusBadge } from './status-badge';
87
- import {
88
- TaskDetailSheet,
89
- TaskCommentsSection,
90
- type TaskDetailSheetData,
91
- } from './task-detail-sheet';
92
- import { TaskFileAttachments } from './task-file-attachments';
93
- import { TimesheetEntryCreateSheet } from './timesheet-entry-create-sheet';
87
+ import {
88
+ TaskCommentsSection,
89
+ TaskDetailSheet,
90
+ type TaskDetailSheetData,
91
+ } from './task-detail-sheet';
92
+ import { TaskFileAttachments } from './task-file-attachments';
93
+ import { TimesheetEntryCreateSheet } from './timesheet-entry-create-sheet';
94
94
 
95
95
  type BoardColumnId = 'todo' | 'doing' | 'review' | 'done';
96
96
 
@@ -106,12 +106,12 @@ type BoardTask = {
106
106
  assigneeCollaboratorId: number | null;
107
107
  assigneeName: string | null;
108
108
  assigneeUserPhotoId: number | null;
109
- assigneePersonAvatarId: number | null;
110
- commentCount: number;
111
- fileCount: number;
112
- doingStartedAt: string | null;
113
- totalDoingMinutes: number;
114
- };
109
+ assigneePersonAvatarId: number | null;
110
+ commentCount: number;
111
+ fileCount: number;
112
+ doingStartedAt: string | null;
113
+ totalDoingMinutes: number;
114
+ };
115
115
 
116
116
  type TaskFormState = {
117
117
  name: string;
@@ -171,26 +171,26 @@ function parseTaskId(value: UniqueIdentifier | null | undefined) {
171
171
  return match ? Number(match[1]) : null;
172
172
  }
173
173
 
174
- function parseColumnId(
175
- value: UniqueIdentifier | null | undefined
176
- ): BoardColumnId | null {
177
- if (!value) return null;
178
- const match = String(value).match(/^col-(.+)$/);
179
- const id = match?.[1];
180
- return KANBAN_COLUMNS.some((c) => c.id === id) ? (id as BoardColumnId) : null;
181
- }
182
-
183
- function normalizeDateInputValue(value?: string | null) {
184
- if (!value) return '';
185
- const match = String(value)
186
- .trim()
187
- .match(/^\d{4}-\d{2}-\d{2}/);
188
- return match?.[0] ?? '';
189
- }
190
-
191
- function apiTaskToBoardTask(
192
- row: OperationsMyProjectSummary['tasks'][number] | OperationsTaskOption
193
- ): BoardTask {
174
+ function parseColumnId(
175
+ value: UniqueIdentifier | null | undefined
176
+ ): BoardColumnId | null {
177
+ if (!value) return null;
178
+ const match = String(value).match(/^col-(.+)$/);
179
+ const id = match?.[1];
180
+ return KANBAN_COLUMNS.some((c) => c.id === id) ? (id as BoardColumnId) : null;
181
+ }
182
+
183
+ function normalizeDateInputValue(value?: string | null) {
184
+ if (!value) return '';
185
+ const match = String(value)
186
+ .trim()
187
+ .match(/^\d{4}-\d{2}-\d{2}/);
188
+ return match?.[0] ?? '';
189
+ }
190
+
191
+ function apiTaskToBoardTask(
192
+ row: OperationsMyProjectSummary['tasks'][number] | OperationsTaskOption
193
+ ): BoardTask {
194
194
  const status = KANBAN_COLUMNS.some((c) => c.id === row.status)
195
195
  ? (row.status as BoardColumnId)
196
196
  : 'todo';
@@ -377,12 +377,12 @@ function DroppableColumn({
377
377
 
378
378
  export function MyProjectSummaryScreen({ projectId }: { projectId: number }) {
379
379
  const t = useTranslations('operations.ProjectDetailsPage');
380
- const commonT = useTranslations('operations.Common');
381
- const formT = useTranslations('operations.ProjectFormPage');
382
- const { request, currentLocaleCode, getSettingValue } = useApp();
383
- const mentionItems = useMentionItems(request);
384
-
385
- const getProjectStatusLabel = (value?: string | null) => {
380
+ const commonT = useTranslations('operations.Common');
381
+ const formT = useTranslations('operations.ProjectFormPage');
382
+ const { request, currentLocaleCode, getSettingValue } = useApp();
383
+ const mentionItems = useMentionItems(request);
384
+
385
+ const getProjectStatusLabel = (value?: string | null) => {
386
386
  if (!value) return commonT('labels.notAvailable');
387
387
  try {
388
388
  return formT(`options.statuses.${value}`);
@@ -569,10 +569,10 @@ export function MyProjectSummaryScreen({ projectId }: { projectId: number }) {
569
569
  setActiveDragTask(task ?? null);
570
570
  };
571
571
 
572
- const openCreateTaskForm = useCallback(
573
- (defaultStatus: BoardColumnId = 'todo') => {
574
- setEditingTaskId(null);
575
- setTaskFormData({
572
+ const openCreateTaskForm = useCallback(
573
+ (defaultStatus: BoardColumnId = 'todo') => {
574
+ setEditingTaskId(null);
575
+ setTaskFormData({
576
576
  ...EMPTY_TASK_FORM,
577
577
  status: defaultStatus,
578
578
  assigneeCollaboratorId:
@@ -834,7 +834,7 @@ export function MyProjectSummaryScreen({ projectId }: { projectId: number }) {
834
834
  {(isOver) => (
835
835
  <div
836
836
  className={[
837
- 'flex min-h-128 flex-col overflow-hidden rounded-3xl border bg-linear-to-b p-3 transition-all',
837
+ 'flex min-h-48 max-h-160 flex-col rounded-3xl border bg-linear-to-b p-3 transition-all',
838
838
  getColumnClassName(column.id),
839
839
  isOver
840
840
  ? 'border-primary shadow-lg ring-2 ring-primary/15'
@@ -870,7 +870,7 @@ export function MyProjectSummaryScreen({ projectId }: { projectId: number }) {
870
870
  </div>
871
871
  </div>
872
872
 
873
- <div className="flex flex-1 flex-col gap-2">
873
+ <div className="flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto pb-1 pr-0.5">
874
874
  <AnimatePresence initial={false}>
875
875
  {taskColumns[column.id].map((task) => {
876
876
  const tags = getTaskTags(task);
@@ -1309,11 +1309,10 @@ export function MyProjectSummaryScreen({ projectId }: { projectId: number }) {
1309
1309
  }}
1310
1310
  />
1311
1311
 
1312
-
1313
- <Sheet
1314
- open={taskFormOpen}
1315
- onOpenChange={(open) => {
1316
- if (!open) {
1312
+ <Sheet
1313
+ open={taskFormOpen}
1314
+ onOpenChange={(open) => {
1315
+ if (!open) {
1317
1316
  setTaskFormOpen(false);
1318
1317
  setEditingTaskId(null);
1319
1318
  }