@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
|
-
<
|
|
726
|
-
<
|
|
727
|
-
<
|
|
728
|
-
<
|
|
729
|
-
<
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
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
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
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
|
-
</
|
|
823
|
-
</
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
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
|
-
<
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
<
|
|
266
|
-
<
|
|
267
|
-
<
|
|
268
|
-
<
|
|
269
|
-
<
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
<
|
|
284
|
-
|
|
285
|
-
<
|
|
286
|
-
<
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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<
|
|
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] =
|
|
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[]>(
|
|
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">
|
|
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">
|
|
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(
|
|
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
|
-
<
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
<
|
|
283
|
-
<
|
|
284
|
-
<
|
|
285
|
-
<
|
|
286
|
-
<
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
|
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
|
|
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) =>
|
|
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
|
|
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: {
|
|
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)
|
|
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
|
-
<
|
|
224
|
-
<
|
|
225
|
-
<
|
|
226
|
-
<
|
|
227
|
-
<
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
{
|
|
255
|
-
<
|
|
256
|
-
<
|
|
257
|
-
<div className="
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
</
|
|
271
|
-
|
|
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.
|
|
274
|
-
className={getStatusBadgeClass(project.
|
|
301
|
+
label={formatEnumLabel(project.contractStatus)}
|
|
302
|
+
className={getStatusBadgeClass(project.contractStatus)}
|
|
275
303
|
/>
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
322
|
-
|
|
319
|
+
className="cursor-pointer"
|
|
320
|
+
onClick={() => openEditSheet(project.id)}
|
|
323
321
|
>
|
|
324
|
-
|
|
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
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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.
|
|
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.
|
|
17
|
-
"@hed-hog/contact": "0.0.
|
|
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
|
".": {
|