@hed-hog/operations 0.0.302 → 0.0.303

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.
@@ -722,111 +722,108 @@ export default function OperationsContractsPage() {
722
722
  })}
723
723
  </div>
724
724
  ) : (
725
- <Card className="overflow-hidden border-border/60 py-0 shadow-sm">
726
- <CardContent className="p-0">
727
- <Table className="table-fixed">
728
- <TableHeader>
729
- <TableRow>
730
- <TableHead className="w-[34%]">
731
- {t('columns.title')}
732
- </TableHead>
733
- <TableHead>{commonT('labels.status')}</TableHead>
734
- <TableHead className="hidden md:table-cell">
735
- {t('columns.signatureStatus')}
736
- </TableHead>
737
- <TableHead className="hidden lg:table-cell">
738
- {t('columns.type')}
739
- </TableHead>
740
- <TableHead className="hidden xl:table-cell">
741
- {commonT('labels.client')}
742
- </TableHead>
743
- <TableHead className="hidden 2xl:table-cell">
744
- {t('columns.financials')}
745
- </TableHead>
746
- <TableHead className="w-30 text-right sm:w-42.5">
747
- {commonT('labels.actions')}
748
- </TableHead>
749
- </TableRow>
750
- </TableHeader>
751
- <TableBody>
752
- {filteredRows.map((contract) => {
753
- const financialTotal =
754
- (contract.valueAmount ?? 0) +
755
- (contract.revenueAmount ?? 0);
756
-
757
- return (
758
- <TableRow key={contract.id} className="hover:bg-muted/30">
759
- <TableCell>
760
- <div className="min-w-0">
761
- <div className="truncate font-medium">
762
- {contract.name ||
763
- contract.code ||
764
- commonT('labels.notAvailable')}
765
- </div>
766
- <div className="truncate text-xs text-muted-foreground">
767
- {[
768
- contract.code,
769
- contract.contractTemplateName,
770
- contract.mainRelatedPartyName,
771
- getContractOptionLabel(
772
- 'originTypes',
773
- contract.originType
774
- ),
775
- ]
776
- .filter(Boolean)
777
- .join(' • ') || commonT('labels.notAvailable')}
778
- </div>
779
- </div>
780
- </TableCell>
781
- <TableCell>
782
- <StatusBadge
783
- label={getContractOptionLabel(
784
- 'statuses',
785
- contract.status
786
- )}
787
- className={getStatusBadgeClass(contract.status)}
788
- />
789
- </TableCell>
790
- <TableCell className="hidden md:table-cell">
791
- <StatusBadge
792
- label={getContractOptionLabel(
793
- 'signatureStatuses',
794
- contract.signatureStatus
795
- )}
796
- className={getStatusBadgeClass(
797
- contract.signatureStatus
798
- )}
799
- />
800
- </TableCell>
801
- <TableCell className="hidden lg:table-cell">
802
- <div className="truncate">
803
- {getContractOptionLabel(
804
- 'contractTypes',
805
- contract.contractType
806
- )}
807
- </div>
808
- </TableCell>
809
- <TableCell className="hidden xl:table-cell">
810
- <div className="truncate">
811
- {contract.clientName ||
725
+ <div className="overflow-x-auto rounded-md border">
726
+ <Table className="table-fixed">
727
+ <TableHeader>
728
+ <TableRow>
729
+ <TableHead className="w-[34%]">
730
+ {t('columns.title')}
731
+ </TableHead>
732
+ <TableHead>{commonT('labels.status')}</TableHead>
733
+ <TableHead className="hidden md:table-cell">
734
+ {t('columns.signatureStatus')}
735
+ </TableHead>
736
+ <TableHead className="hidden lg:table-cell">
737
+ {t('columns.type')}
738
+ </TableHead>
739
+ <TableHead className="hidden xl:table-cell">
740
+ {commonT('labels.client')}
741
+ </TableHead>
742
+ <TableHead className="hidden 2xl:table-cell">
743
+ {t('columns.financials')}
744
+ </TableHead>
745
+ <TableHead className="w-30 text-right sm:w-42.5">
746
+ {commonT('labels.actions')}
747
+ </TableHead>
748
+ </TableRow>
749
+ </TableHeader>
750
+ <TableBody>
751
+ {filteredRows.map((contract) => {
752
+ const financialTotal =
753
+ (contract.valueAmount ?? 0) + (contract.revenueAmount ?? 0);
754
+
755
+ return (
756
+ <TableRow key={contract.id} className="hover:bg-muted/30">
757
+ <TableCell>
758
+ <div className="min-w-0">
759
+ <div className="truncate font-medium">
760
+ {contract.name ||
761
+ contract.code ||
812
762
  commonT('labels.notAvailable')}
813
763
  </div>
814
- </TableCell>
815
- <TableCell className="hidden 2xl:table-cell">
816
- {formatCurrency(financialTotal)}
817
- </TableCell>
818
- <TableCell>
819
- <div className="flex justify-end">
820
- {renderContractActions(contract, 'end')}
764
+ <div className="truncate text-xs text-muted-foreground">
765
+ {[
766
+ contract.code,
767
+ contract.contractTemplateName,
768
+ contract.mainRelatedPartyName,
769
+ getContractOptionLabel(
770
+ 'originTypes',
771
+ contract.originType
772
+ ),
773
+ ]
774
+ .filter(Boolean)
775
+ .join(' • ') || commonT('labels.notAvailable')}
821
776
  </div>
822
- </TableCell>
823
- </TableRow>
824
- );
825
- })}
826
- </TableBody>
827
- </Table>
828
- </CardContent>
829
- </Card>
777
+ </div>
778
+ </TableCell>
779
+ <TableCell>
780
+ <StatusBadge
781
+ label={getContractOptionLabel(
782
+ 'statuses',
783
+ contract.status
784
+ )}
785
+ className={getStatusBadgeClass(contract.status)}
786
+ />
787
+ </TableCell>
788
+ <TableCell className="hidden md:table-cell">
789
+ <StatusBadge
790
+ label={getContractOptionLabel(
791
+ 'signatureStatuses',
792
+ contract.signatureStatus
793
+ )}
794
+ className={getStatusBadgeClass(
795
+ contract.signatureStatus
796
+ )}
797
+ />
798
+ </TableCell>
799
+ <TableCell className="hidden lg:table-cell">
800
+ <div className="truncate">
801
+ {getContractOptionLabel(
802
+ 'contractTypes',
803
+ contract.contractType
804
+ )}
805
+ </div>
806
+ </TableCell>
807
+ <TableCell className="hidden xl:table-cell">
808
+ <div className="truncate">
809
+ {contract.clientName ||
810
+ commonT('labels.notAvailable')}
811
+ </div>
812
+ </TableCell>
813
+ <TableCell className="hidden 2xl:table-cell">
814
+ {formatCurrency(financialTotal)}
815
+ </TableCell>
816
+ <TableCell>
817
+ <div className="flex justify-end">
818
+ {renderContractActions(contract, 'end')}
819
+ </div>
820
+ </TableCell>
821
+ </TableRow>
822
+ );
823
+ })}
824
+ </TableBody>
825
+ </Table>
826
+ </div>
830
827
  )
831
828
  ) : (
832
829
  <EmptyState
@@ -2,7 +2,6 @@
2
2
 
3
3
  import { EmptyState, Page, SearchBar } from '@/components/entity-list';
4
4
  import { Button } from '@/components/ui/button';
5
- import { Card, CardContent } from '@/components/ui/card';
6
5
  import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
7
6
  import {
8
7
  Sheet,
@@ -256,99 +255,94 @@ export default function OperationsContractTemplatesPage() {
256
255
  />
257
256
 
258
257
  {isLoading ? (
259
- <Card className="py-0">
260
- <CardContent className="p-6 text-sm text-muted-foreground">
261
- {commonT('actions.refresh')}...
262
- </CardContent>
263
- </Card>
258
+ <div className="rounded-md border px-4 py-6 text-sm text-muted-foreground">
259
+ {commonT('actions.refresh')}...
260
+ </div>
264
261
  ) : filteredTemplates.length ? (
265
- <Card className="py-0">
266
- <CardContent className="p-0">
267
- <div className="overflow-x-auto rounded-md border">
268
- <Table>
269
- <TableHeader>
270
- <TableRow>
271
- <TableHead>{t('columns.name')}</TableHead>
272
- <TableHead>{t('columns.code')}</TableHead>
273
- <TableHead>{t('columns.type')}</TableHead>
274
- <TableHead>{t('columns.usageCount')}</TableHead>
275
- <TableHead>{commonT('labels.billingModel')}</TableHead>
276
- <TableHead>{t('columns.updatedAt')}</TableHead>
277
- <TableHead>{commonT('labels.status')}</TableHead>
278
- <TableHead className="text-right">
279
- {commonT('labels.actions')}
280
- </TableHead>
281
- </TableRow>
282
- </TableHeader>
283
- <TableBody>
284
- {filteredTemplates.map((template) => (
285
- <TableRow key={template.id}>
286
- <TableCell>
287
- <div className="space-y-1">
288
- <div className="font-medium">{template.name}</div>
289
- <div className="max-w-md text-xs text-muted-foreground">
290
- {template.description ||
291
- commonT('labels.notAvailable')}
292
- </div>
293
- </div>
294
- </TableCell>
295
- <TableCell>{template.code || '—'}</TableCell>
296
- <TableCell>
297
- {formatEnumLabel(template.contractType)}
298
- </TableCell>
299
- <TableCell>{template.usageCount ?? 0}</TableCell>
300
- <TableCell>
301
- {formatEnumLabel(template.billingModel)}
302
- </TableCell>
303
- <TableCell>{formatDate(template.updatedAt)}</TableCell>
304
- <TableCell>
305
- <StatusBadge
306
- label={formatEnumLabel(template.status)}
307
- className={getStatusBadgeClass(template.status)}
308
- />
309
- </TableCell>
310
- <TableCell>
311
- <div className="flex justify-end gap-2">
312
- <Button
313
- type="button"
314
- variant="outline"
315
- size="sm"
316
- className="cursor-pointer"
317
- asChild
318
- >
319
- <Link href={`/operations/contracts?template=${template.id}`}>
320
- {commonT('actions.useTemplate')}
321
- </Link>
322
- </Button>
323
- <Button
324
- type="button"
325
- variant="outline"
326
- size="icon"
327
- className="cursor-pointer"
328
- onClick={() => openEditSheet(template)}
329
- >
330
- <Pencil className="size-4" />
331
- </Button>
332
- <Button
333
- type="button"
334
- variant="outline"
335
- size="sm"
336
- className="cursor-pointer"
337
- onClick={() => void toggleTemplateStatus(template)}
338
- >
339
- {template.status === 'active'
340
- ? commonT('actions.deactivate')
341
- : commonT('actions.activate')}
342
- </Button>
343
- </div>
344
- </TableCell>
345
- </TableRow>
346
- ))}
347
- </TableBody>
348
- </Table>
349
- </div>
350
- </CardContent>
351
- </Card>
262
+ <div className="overflow-x-auto rounded-md border">
263
+ <Table>
264
+ <TableHeader>
265
+ <TableRow>
266
+ <TableHead>{t('columns.name')}</TableHead>
267
+ <TableHead>{t('columns.code')}</TableHead>
268
+ <TableHead>{t('columns.type')}</TableHead>
269
+ <TableHead>{t('columns.usageCount')}</TableHead>
270
+ <TableHead>{commonT('labels.billingModel')}</TableHead>
271
+ <TableHead>{t('columns.updatedAt')}</TableHead>
272
+ <TableHead>{commonT('labels.status')}</TableHead>
273
+ <TableHead className="text-right">
274
+ {commonT('labels.actions')}
275
+ </TableHead>
276
+ </TableRow>
277
+ </TableHeader>
278
+ <TableBody>
279
+ {filteredTemplates.map((template) => (
280
+ <TableRow key={template.id}>
281
+ <TableCell>
282
+ <div className="space-y-1">
283
+ <div className="font-medium">{template.name}</div>
284
+ <div className="max-w-md text-xs text-muted-foreground">
285
+ {template.description || commonT('labels.notAvailable')}
286
+ </div>
287
+ </div>
288
+ </TableCell>
289
+ <TableCell>{template.code || '—'}</TableCell>
290
+ <TableCell>
291
+ {formatEnumLabel(template.contractType)}
292
+ </TableCell>
293
+ <TableCell>{template.usageCount ?? 0}</TableCell>
294
+ <TableCell>
295
+ {formatEnumLabel(template.billingModel)}
296
+ </TableCell>
297
+ <TableCell>{formatDate(template.updatedAt)}</TableCell>
298
+ <TableCell>
299
+ <StatusBadge
300
+ label={formatEnumLabel(template.status)}
301
+ className={getStatusBadgeClass(template.status)}
302
+ />
303
+ </TableCell>
304
+ <TableCell>
305
+ <div className="flex justify-end gap-2">
306
+ <Button
307
+ type="button"
308
+ variant="outline"
309
+ size="sm"
310
+ className="cursor-pointer"
311
+ asChild
312
+ >
313
+ <Link
314
+ href={`/operations/contracts?template=${template.id}`}
315
+ >
316
+ {commonT('actions.useTemplate')}
317
+ </Link>
318
+ </Button>
319
+ <Button
320
+ type="button"
321
+ variant="outline"
322
+ size="icon"
323
+ className="cursor-pointer"
324
+ onClick={() => openEditSheet(template)}
325
+ >
326
+ <Pencil className="size-4" />
327
+ </Button>
328
+ <Button
329
+ type="button"
330
+ variant="outline"
331
+ size="sm"
332
+ className="cursor-pointer"
333
+ onClick={() => void toggleTemplateStatus(template)}
334
+ >
335
+ {template.status === 'active'
336
+ ? commonT('actions.deactivate')
337
+ : commonT('actions.activate')}
338
+ </Button>
339
+ </div>
340
+ </TableCell>
341
+ </TableRow>
342
+ ))}
343
+ </TableBody>
344
+ </Table>
345
+ </div>
352
346
  ) : (
353
347
  <EmptyState
354
348
  icon={<FileStack className="size-12" />}
@@ -2,7 +2,6 @@
2
2
 
3
3
  import { EmptyState, Page, SearchBar } from '@/components/entity-list';
4
4
  import { Button } from '@/components/ui/button';
5
- import { Card, CardContent } from '@/components/ui/card';
6
5
  import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
7
6
  import {
8
7
  Sheet,
@@ -51,10 +50,13 @@ export default function OperationsDepartmentsPage() {
51
50
  const { request, showToastHandler, currentLocaleCode } = useApp();
52
51
  const access = useOperationsAccess();
53
52
  const [search, setSearch] = useState('');
54
- const [statusFilter, setStatusFilter] = useState<'all' | 'active' | 'inactive'>('all');
53
+ const [statusFilter, setStatusFilter] = useState<
54
+ 'all' | 'active' | 'inactive'
55
+ >('all');
55
56
  const [isSheetOpen, setIsSheetOpen] = useState(false);
56
57
  const [isSaving, setIsSaving] = useState(false);
57
- const [editingDepartment, setEditingDepartment] = useState<OperationsDepartment | null>(null);
58
+ const [editingDepartment, setEditingDepartment] =
59
+ useState<OperationsDepartment | null>(null);
58
60
  const [form, setForm] = useState<DepartmentFormState>(EMPTY_FORM_STATE);
59
61
 
60
62
  const {
@@ -65,7 +67,10 @@ export default function OperationsDepartmentsPage() {
65
67
  queryKey: ['operations-departments-list', currentLocaleCode],
66
68
  enabled: access.isDirector,
67
69
  queryFn: () =>
68
- fetchOperations<OperationsDepartment[]>(request, '/operations/departments'),
70
+ fetchOperations<OperationsDepartment[]>(
71
+ request,
72
+ '/operations/departments'
73
+ ),
69
74
  });
70
75
 
71
76
  const filteredDepartments = useMemo(
@@ -210,7 +215,9 @@ export default function OperationsDepartmentsPage() {
210
215
  current={t('breadcrumb')}
211
216
  actions={
212
217
  <Button variant="outline" size="sm" asChild>
213
- <Link href="/operations/collaborators">{commonT('actions.back')}</Link>
218
+ <Link href="/operations/collaborators">
219
+ {commonT('actions.back')}
220
+ </Link>
214
221
  </Button>
215
222
  }
216
223
  />
@@ -237,7 +244,9 @@ export default function OperationsDepartmentsPage() {
237
244
  actions={
238
245
  <div className="flex flex-wrap gap-2">
239
246
  <Button variant="outline" size="sm" asChild>
240
- <Link href="/operations/collaborators">{commonT('actions.back')}</Link>
247
+ <Link href="/operations/collaborators">
248
+ {commonT('actions.back')}
249
+ </Link>
241
250
  </Button>
242
251
  <Button size="sm" onClick={openCreateSheet}>
243
252
  {commonT('actions.create')}
@@ -260,7 +269,9 @@ export default function OperationsDepartmentsPage() {
260
269
  type: 'select',
261
270
  value: statusFilter,
262
271
  onChange: (value) =>
263
- setStatusFilter((value as 'all' | 'active' | 'inactive') ?? 'all'),
272
+ setStatusFilter(
273
+ (value as 'all' | 'active' | 'inactive') ?? 'all'
274
+ ),
264
275
  placeholder: commonT('labels.status'),
265
276
  options: [
266
277
  { value: 'all', label: commonT('filters.allStatuses') },
@@ -273,70 +284,70 @@ export default function OperationsDepartmentsPage() {
273
284
  </div>
274
285
 
275
286
  {isLoading ? (
276
- <Card className="py-0">
277
- <CardContent className="p-6 text-sm text-muted-foreground">
278
- {commonT('actions.refresh')}...
279
- </CardContent>
280
- </Card>
287
+ <div className="rounded-md border px-4 py-6 text-sm text-muted-foreground">
288
+ {commonT('actions.refresh')}...
289
+ </div>
281
290
  ) : filteredDepartments.length > 0 ? (
282
- <Card className="py-0">
283
- <CardContent className="p-0">
284
- <div className="overflow-x-auto rounded-md border">
285
- <Table>
286
- <TableHeader>
287
- <TableRow>
288
- <TableHead>{t('columns.name')}</TableHead>
289
- <TableHead>{t('columns.code')}</TableHead>
290
- <TableHead>{t('columns.description')}</TableHead>
291
- <TableHead>{t('columns.collaborators')}</TableHead>
292
- <TableHead>{commonT('labels.status')}</TableHead>
293
- <TableHead className="text-right">{commonT('labels.actions')}</TableHead>
294
- </TableRow>
295
- </TableHeader>
296
- <TableBody>
297
- {filteredDepartments.map((department) => (
298
- <TableRow key={department.id}>
299
- <TableCell className="font-medium">{department.name}</TableCell>
300
- <TableCell>{department.code || '—'}</TableCell>
301
- <TableCell className="max-w-md text-sm text-muted-foreground">
302
- {department.description || commonT('labels.notAvailable')}
303
- </TableCell>
304
- <TableCell>{Number(department.collaboratorCount ?? 0)}</TableCell>
305
- <TableCell>
306
- <StatusBadge
307
- label={formatEnumLabel(department.status)}
308
- className={getStatusBadgeClass(department.status)}
309
- />
310
- </TableCell>
311
- <TableCell>
312
- <div className="flex justify-end gap-2">
313
- <Button
314
- variant="outline"
315
- size="icon"
316
- className="cursor-pointer"
317
- onClick={() => openEditSheet(department)}
318
- >
319
- <Pencil className="size-4" />
320
- </Button>
321
- <Button
322
- variant="outline"
323
- size="sm"
324
- className="cursor-pointer"
325
- onClick={() => void toggleStatus(department)}
326
- >
327
- {department.status === 'inactive'
328
- ? commonT('actions.activate')
329
- : commonT('actions.deactivate')}
330
- </Button>
331
- </div>
332
- </TableCell>
333
- </TableRow>
334
- ))}
335
- </TableBody>
336
- </Table>
337
- </div>
338
- </CardContent>
339
- </Card>
291
+ <div className="overflow-x-auto rounded-md border">
292
+ <Table>
293
+ <TableHeader>
294
+ <TableRow>
295
+ <TableHead>{t('columns.name')}</TableHead>
296
+ <TableHead>{t('columns.code')}</TableHead>
297
+ <TableHead>{t('columns.description')}</TableHead>
298
+ <TableHead>{t('columns.collaborators')}</TableHead>
299
+ <TableHead>{commonT('labels.status')}</TableHead>
300
+ <TableHead className="text-right">
301
+ {commonT('labels.actions')}
302
+ </TableHead>
303
+ </TableRow>
304
+ </TableHeader>
305
+ <TableBody>
306
+ {filteredDepartments.map((department) => (
307
+ <TableRow key={department.id}>
308
+ <TableCell className="font-medium">
309
+ {department.name}
310
+ </TableCell>
311
+ <TableCell>{department.code || ''}</TableCell>
312
+ <TableCell className="max-w-md text-sm text-muted-foreground">
313
+ {department.description || commonT('labels.notAvailable')}
314
+ </TableCell>
315
+ <TableCell>
316
+ {Number(department.collaboratorCount ?? 0)}
317
+ </TableCell>
318
+ <TableCell>
319
+ <StatusBadge
320
+ label={formatEnumLabel(department.status)}
321
+ className={getStatusBadgeClass(department.status)}
322
+ />
323
+ </TableCell>
324
+ <TableCell>
325
+ <div className="flex justify-end gap-2">
326
+ <Button
327
+ variant="outline"
328
+ size="icon"
329
+ className="cursor-pointer"
330
+ onClick={() => openEditSheet(department)}
331
+ >
332
+ <Pencil className="size-4" />
333
+ </Button>
334
+ <Button
335
+ variant="outline"
336
+ size="sm"
337
+ className="cursor-pointer"
338
+ onClick={() => void toggleStatus(department)}
339
+ >
340
+ {department.status === 'inactive'
341
+ ? commonT('actions.activate')
342
+ : commonT('actions.deactivate')}
343
+ </Button>
344
+ </div>
345
+ </TableCell>
346
+ </TableRow>
347
+ ))}
348
+ </TableBody>
349
+ </Table>
350
+ </div>
340
351
  ) : (
341
352
  <EmptyState
342
353
  icon={<Building2 className="size-12" />}
@@ -351,7 +362,9 @@ export default function OperationsDepartmentsPage() {
351
362
  <SheetContent className="w-full overflow-y-auto sm:max-w-xl">
352
363
  <SheetHeader>
353
364
  <SheetTitle>
354
- {editingDepartment ? t('sheet.editTitle') : t('sheet.createTitle')}
365
+ {editingDepartment
366
+ ? t('sheet.editTitle')
367
+ : t('sheet.createTitle')}
355
368
  </SheetTitle>
356
369
  <SheetDescription>{t('sheet.description')}</SheetDescription>
357
370
  </SheetHeader>
@@ -390,20 +403,28 @@ export default function OperationsDepartmentsPage() {
390
403
  </div>
391
404
 
392
405
  <div className="space-y-2">
393
- <label className="text-sm font-medium" htmlFor="department-description">
406
+ <label
407
+ className="text-sm font-medium"
408
+ htmlFor="department-description"
409
+ >
394
410
  {t('form.description')}
395
411
  </label>
396
412
  <textarea
397
413
  id="department-description"
398
414
  value={form.description}
399
- onChange={(event) => updateForm('description', event.target.value)}
415
+ onChange={(event) =>
416
+ updateForm('description', event.target.value)
417
+ }
400
418
  className="min-h-24 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
401
419
  placeholder={t('form.description')}
402
420
  />
403
421
  </div>
404
422
 
405
423
  <div className="space-y-2">
406
- <label className="text-sm font-medium" htmlFor="department-status">
424
+ <label
425
+ className="text-sm font-medium"
426
+ htmlFor="department-status"
427
+ >
407
428
  {t('form.status')}
408
429
  </label>
409
430
  <select
@@ -2,7 +2,6 @@
2
2
 
3
3
  import { EmptyState, Page, SearchBar } from '@/components/entity-list';
4
4
  import { Button } from '@/components/ui/button';
5
- import { Card, CardContent } from '@/components/ui/card';
6
5
  import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
7
6
  import {
8
7
  Sheet,
@@ -65,7 +64,10 @@ export default function OperationsProjectsPage() {
65
64
  const editProjectId = parseEditProjectId(searchParams.get('edit'));
66
65
  const isSheetOpen = createParam === '1' || editProjectId !== null;
67
66
 
68
- const updateSheetQuery = (next: { create?: boolean; editId?: number | null }) => {
67
+ const updateSheetQuery = (next: {
68
+ create?: boolean;
69
+ editId?: number | null;
70
+ }) => {
69
71
  const params = new URLSearchParams(searchParams.toString());
70
72
 
71
73
  params.delete('create');
@@ -117,7 +119,9 @@ export default function OperationsProjectsPage() {
117
119
  ]
118
120
  .filter(Boolean)
119
121
  .some((value) =>
120
- String(value).toLowerCase().includes(search.trim().toLowerCase())
122
+ String(value)
123
+ .toLowerCase()
124
+ .includes(search.trim().toLowerCase())
121
125
  );
122
126
 
123
127
  const matchesStatus =
@@ -220,139 +224,141 @@ export default function OperationsProjectsPage() {
220
224
  />
221
225
 
222
226
  {filteredRows.length > 0 ? (
223
- <Card className="overflow-hidden border-border/60 py-0 shadow-sm">
224
- <CardContent className="p-0">
225
- <Table className="table-fixed">
226
- <TableHeader>
227
- <TableRow>
228
- <TableHead className="w-[30%]">
229
- {commonT('labels.project')}
230
- </TableHead>
231
- <TableHead>{commonT('labels.client')}</TableHead>
232
- <TableHead>{commonT('labels.status')}</TableHead>
233
- <TableHead className="hidden lg:table-cell">
234
- {commonT('labels.manager')}
235
- </TableHead>
236
- <TableHead className="hidden md:table-cell">
237
- {commonT('labels.teamSize')}
238
- </TableHead>
239
- <TableHead className="hidden xl:table-cell">
240
- {commonT('labels.startDate')}
241
- </TableHead>
242
- <TableHead className="hidden xl:table-cell">
243
- {commonT('labels.endDate')}
244
- </TableHead>
245
- <TableHead className="hidden 2xl:table-cell">
246
- {commonT('labels.contractStatus')}
247
- </TableHead>
248
- <TableHead className="w-30 text-right sm:w-42.5">
249
- {commonT('labels.actions')}
250
- </TableHead>
251
- </TableRow>
252
- </TableHeader>
253
- <TableBody>
254
- {filteredRows.map((project) => (
255
- <TableRow key={project.id} className="hover:bg-muted/30">
256
- <TableCell>
257
- <div className="min-w-0">
258
- <div className="truncate font-medium">{project.name}</div>
259
- <div className="truncate text-xs text-muted-foreground">
260
- {[project.code, project.myRoleLabel, project.contractName]
261
- .filter(Boolean)
262
- .join(' • ') || commonT('labels.notAvailable')}
263
- </div>
264
- </div>
265
- </TableCell>
266
- <TableCell>
267
- <div className="truncate">
268
- {project.clientName || commonT('labels.notAvailable')}
227
+ <div className="overflow-x-auto rounded-md border">
228
+ <Table className="table-fixed">
229
+ <TableHeader>
230
+ <TableRow>
231
+ <TableHead className="w-[30%]">
232
+ {commonT('labels.project')}
233
+ </TableHead>
234
+ <TableHead>{commonT('labels.client')}</TableHead>
235
+ <TableHead>{commonT('labels.status')}</TableHead>
236
+ <TableHead className="hidden lg:table-cell">
237
+ {commonT('labels.manager')}
238
+ </TableHead>
239
+ <TableHead className="hidden md:table-cell">
240
+ {commonT('labels.teamSize')}
241
+ </TableHead>
242
+ <TableHead className="hidden xl:table-cell">
243
+ {commonT('labels.startDate')}
244
+ </TableHead>
245
+ <TableHead className="hidden xl:table-cell">
246
+ {commonT('labels.endDate')}
247
+ </TableHead>
248
+ <TableHead className="hidden 2xl:table-cell">
249
+ {commonT('labels.contractStatus')}
250
+ </TableHead>
251
+ <TableHead className="w-30 text-right sm:w-42.5">
252
+ {commonT('labels.actions')}
253
+ </TableHead>
254
+ </TableRow>
255
+ </TableHeader>
256
+ <TableBody>
257
+ {filteredRows.map((project) => (
258
+ <TableRow key={project.id} className="hover:bg-muted/30">
259
+ <TableCell>
260
+ <div className="min-w-0">
261
+ <div className="truncate font-medium">{project.name}</div>
262
+ <div className="truncate text-xs text-muted-foreground">
263
+ {[
264
+ project.code,
265
+ project.myRoleLabel,
266
+ project.contractName,
267
+ ]
268
+ .filter(Boolean)
269
+ .join(' • ') || commonT('labels.notAvailable')}
269
270
  </div>
270
- </TableCell>
271
- <TableCell>
271
+ </div>
272
+ </TableCell>
273
+ <TableCell>
274
+ <div className="truncate">
275
+ {project.clientName || commonT('labels.notAvailable')}
276
+ </div>
277
+ </TableCell>
278
+ <TableCell>
279
+ <StatusBadge
280
+ label={formatEnumLabel(project.status)}
281
+ className={getStatusBadgeClass(project.status)}
282
+ />
283
+ </TableCell>
284
+ <TableCell className="hidden lg:table-cell">
285
+ <div className="truncate">
286
+ {project.managerName || commonT('labels.notAssigned')}
287
+ </div>
288
+ </TableCell>
289
+ <TableCell className="hidden md:table-cell">
290
+ {project.teamSize ?? 0}
291
+ </TableCell>
292
+ <TableCell className="hidden xl:table-cell">
293
+ {formatDate(project.startDate)}
294
+ </TableCell>
295
+ <TableCell className="hidden xl:table-cell">
296
+ {formatDate(project.endDate)}
297
+ </TableCell>
298
+ <TableCell className="hidden 2xl:table-cell">
299
+ {project.contractStatus ? (
272
300
  <StatusBadge
273
- label={formatEnumLabel(project.status)}
274
- className={getStatusBadgeClass(project.status)}
301
+ label={formatEnumLabel(project.contractStatus)}
302
+ className={getStatusBadgeClass(project.contractStatus)}
275
303
  />
276
- </TableCell>
277
- <TableCell className="hidden lg:table-cell">
278
- <div className="truncate">
279
- {project.managerName || commonT('labels.notAssigned')}
280
- </div>
281
- </TableCell>
282
- <TableCell className="hidden md:table-cell">
283
- {project.teamSize ?? 0}
284
- </TableCell>
285
- <TableCell className="hidden xl:table-cell">
286
- {formatDate(project.startDate)}
287
- </TableCell>
288
- <TableCell className="hidden xl:table-cell">
289
- {formatDate(project.endDate)}
290
- </TableCell>
291
- <TableCell className="hidden 2xl:table-cell">
292
- {project.contractStatus ? (
293
- <StatusBadge
294
- label={formatEnumLabel(project.contractStatus)}
295
- className={getStatusBadgeClass(project.contractStatus)}
296
- />
297
- ) : (
298
- commonT('labels.notAssigned')
299
- )}
300
- </TableCell>
301
- <TableCell>
302
- <div className="flex flex-wrap justify-end gap-1.5 sm:gap-2">
303
- <Button variant="outline" size="icon" asChild>
304
- <Link href={`/operations/projects/${project.id}`}>
305
- <Eye className="size-4" />
306
- </Link>
307
- </Button>
308
- {access.isDirector ? (
309
- <Button
310
- variant="outline"
311
- size="icon"
312
- className="cursor-pointer"
313
- onClick={() => openEditSheet(project.id)}
314
- >
315
- <Pencil className="size-4" />
316
- </Button>
317
- ) : null}
304
+ ) : (
305
+ commonT('labels.notAssigned')
306
+ )}
307
+ </TableCell>
308
+ <TableCell>
309
+ <div className="flex flex-wrap justify-end gap-1.5 sm:gap-2">
310
+ <Button variant="outline" size="icon" asChild>
311
+ <Link href={`/operations/projects/${project.id}`}>
312
+ <Eye className="size-4" />
313
+ </Link>
314
+ </Button>
315
+ {access.isDirector ? (
318
316
  <Button
319
317
  variant="outline"
320
318
  size="icon"
321
- asChild={Boolean(project.contractId)}
322
- disabled={!project.contractId}
319
+ className="cursor-pointer"
320
+ onClick={() => openEditSheet(project.id)}
323
321
  >
324
- {project.contractId ? (
325
- <Link
326
- href={`/operations/contracts?edit=${project.contractId}`}
327
- >
328
- <FileText className="size-4" />
329
- </Link>
330
- ) : (
331
- <span>
332
- <FileText className="size-4" />
333
- </span>
334
- )}
322
+ <Pencil className="size-4" />
335
323
  </Button>
336
- {access.isDirector ? (
337
- <Button
338
- variant="outline"
339
- size="sm"
340
- className="cursor-pointer"
341
- onClick={() => void toggleArchived(project)}
324
+ ) : null}
325
+ <Button
326
+ variant="outline"
327
+ size="icon"
328
+ asChild={Boolean(project.contractId)}
329
+ disabled={!project.contractId}
330
+ >
331
+ {project.contractId ? (
332
+ <Link
333
+ href={`/operations/contracts?edit=${project.contractId}`}
342
334
  >
343
- {project.status === 'archived'
344
- ? commonT('actions.activate')
345
- : t('actions.archive')}
346
- </Button>
347
- ) : null}
348
- </div>
349
- </TableCell>
350
- </TableRow>
351
- ))}
352
- </TableBody>
353
- </Table>
354
- </CardContent>
355
- </Card>
335
+ <FileText className="size-4" />
336
+ </Link>
337
+ ) : (
338
+ <span>
339
+ <FileText className="size-4" />
340
+ </span>
341
+ )}
342
+ </Button>
343
+ {access.isDirector ? (
344
+ <Button
345
+ variant="outline"
346
+ size="sm"
347
+ className="cursor-pointer"
348
+ onClick={() => void toggleArchived(project)}
349
+ >
350
+ {project.status === 'archived'
351
+ ? commonT('actions.activate')
352
+ : t('actions.archive')}
353
+ </Button>
354
+ ) : null}
355
+ </div>
356
+ </TableCell>
357
+ </TableRow>
358
+ ))}
359
+ </TableBody>
360
+ </Table>
361
+ </div>
356
362
  ) : (
357
363
  <EmptyState
358
364
  icon={<FolderKanban className="size-12" />}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/operations",
3
- "version": "0.0.302",
3
+ "version": "0.0.303",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -11,11 +11,11 @@
11
11
  "@nestjs/mapped-types": "*",
12
12
  "@hed-hog/api-pagination": "0.0.7",
13
13
  "@hed-hog/api-prisma": "0.0.6",
14
+ "@hed-hog/api-types": "0.0.1",
14
15
  "@hed-hog/api-locale": "0.0.14",
15
16
  "@hed-hog/api": "0.0.6",
16
- "@hed-hog/core": "0.0.302",
17
- "@hed-hog/contact": "0.0.302",
18
- "@hed-hog/api-types": "0.0.1"
17
+ "@hed-hog/core": "0.0.303",
18
+ "@hed-hog/contact": "0.0.303"
19
19
  },
20
20
  "exports": {
21
21
  ".": {