@hed-hog/operations 0.0.305 → 0.0.309
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-approvals.controller.d.ts +114 -1
- package/dist/controllers/operations-approvals.controller.d.ts.map +1 -1
- package/dist/controllers/operations-approvals.controller.js +16 -3
- package/dist/controllers/operations-approvals.controller.js.map +1 -1
- package/dist/controllers/operations-collaborators.controller.d.ts +16 -1
- package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
- package/dist/controllers/operations-collaborators.controller.js +16 -3
- package/dist/controllers/operations-collaborators.controller.js.map +1 -1
- package/dist/controllers/operations-contracts.controller.d.ts +14 -453
- package/dist/controllers/operations-contracts.controller.d.ts.map +1 -1
- package/dist/controllers/operations-contracts.controller.js +11 -112
- package/dist/controllers/operations-contracts.controller.js.map +1 -1
- package/dist/controllers/operations-org-structure.controller.d.ts +65 -2
- package/dist/controllers/operations-org-structure.controller.d.ts.map +1 -1
- package/dist/controllers/operations-org-structure.controller.js +18 -5
- package/dist/controllers/operations-org-structure.controller.js.map +1 -1
- package/dist/controllers/operations-projects.controller.d.ts +28 -4
- package/dist/controllers/operations-projects.controller.d.ts.map +1 -1
- package/dist/controllers/operations-projects.controller.js +17 -5
- package/dist/controllers/operations-projects.controller.js.map +1 -1
- package/dist/controllers/operations-timesheets.controller.d.ts +52 -4
- package/dist/controllers/operations-timesheets.controller.d.ts.map +1 -1
- package/dist/controllers/operations-timesheets.controller.js +28 -11
- package/dist/controllers/operations-timesheets.controller.js.map +1 -1
- package/dist/dto/list-approvals.dto.d.ts +6 -0
- package/dist/dto/list-approvals.dto.d.ts.map +1 -0
- package/dist/dto/list-approvals.dto.js +28 -0
- package/dist/dto/list-approvals.dto.js.map +1 -0
- package/dist/dto/list-collaborator-types.dto.d.ts +3 -1
- package/dist/dto/list-collaborator-types.dto.d.ts.map +1 -1
- package/dist/dto/list-collaborator-types.dto.js +7 -1
- package/dist/dto/list-collaborator-types.dto.js.map +1 -1
- package/dist/dto/list-collaborators.dto.d.ts +1 -0
- package/dist/dto/list-collaborators.dto.d.ts.map +1 -1
- package/dist/dto/list-collaborators.dto.js +5 -0
- package/dist/dto/list-collaborators.dto.js.map +1 -1
- package/dist/dto/list-contracts.dto.d.ts +8 -0
- package/dist/dto/list-contracts.dto.d.ts.map +1 -0
- package/dist/dto/list-contracts.dto.js +38 -0
- package/dist/dto/list-contracts.dto.js.map +1 -0
- package/dist/dto/list-departments.dto.d.ts +5 -0
- package/dist/dto/list-departments.dto.d.ts.map +1 -0
- package/dist/dto/list-departments.dto.js +23 -0
- package/dist/dto/list-departments.dto.js.map +1 -0
- package/dist/dto/list-projects.dto.d.ts +5 -0
- package/dist/dto/list-projects.dto.d.ts.map +1 -0
- package/dist/dto/list-projects.dto.js +23 -0
- package/dist/dto/list-projects.dto.js.map +1 -0
- package/dist/dto/list-schedule-adjustments.dto.d.ts +5 -0
- package/dist/dto/list-schedule-adjustments.dto.d.ts.map +1 -0
- package/dist/dto/list-schedule-adjustments.dto.js +23 -0
- package/dist/dto/list-schedule-adjustments.dto.js.map +1 -0
- package/dist/dto/list-time-off-requests.dto.d.ts +5 -0
- package/dist/dto/list-time-off-requests.dto.d.ts.map +1 -0
- package/dist/dto/list-time-off-requests.dto.js +23 -0
- package/dist/dto/list-time-off-requests.dto.js.map +1 -0
- package/dist/dto/list-timesheets.dto.d.ts +5 -0
- package/dist/dto/list-timesheets.dto.d.ts.map +1 -0
- package/dist/dto/list-timesheets.dto.js +23 -0
- package/dist/dto/list-timesheets.dto.js.map +1 -0
- package/dist/dto/reorder-collaborator-types.dto.d.ts +4 -0
- package/dist/dto/reorder-collaborator-types.dto.d.ts.map +1 -0
- package/dist/dto/reorder-collaborator-types.dto.js +25 -0
- package/dist/dto/reorder-collaborator-types.dto.js.map +1 -0
- package/dist/dto/update-collaborator-type.dto.d.ts +3 -1
- package/dist/dto/update-collaborator-type.dto.d.ts.map +1 -1
- package/dist/dto/update-collaborator-type.dto.js +2 -1
- package/dist/dto/update-collaborator-type.dto.js.map +1 -1
- package/dist/operations.service.d.ts +362 -271
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +1195 -1098
- package/dist/operations.service.js.map +1 -1
- package/dist/operations.service.spec.js +73 -22
- package/dist/operations.service.spec.js.map +1 -1
- package/hedhog/data/menu.yaml +19 -55
- package/hedhog/data/operations_collaborator_type.yaml +76 -76
- package/hedhog/data/route.yaml +52 -70
- package/hedhog/frontend/app/_components/async-options-combobox.tsx.ejs +5 -3
- package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +8 -1
- package/hedhog/frontend/app/_components/collaborator-select-with-create.tsx.ejs +15 -10
- package/hedhog/frontend/app/_components/contract-details-screen.tsx.ejs +108 -213
- package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +251 -2039
- package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +167 -60
- package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +70 -301
- package/hedhog/frontend/app/_components/system-user-select-with-create.tsx.ejs +102 -51
- package/hedhog/frontend/app/_components/timesheet-task-create-sheet.tsx.ejs +1 -0
- package/hedhog/frontend/app/_lib/types.ts.ejs +19 -24
- package/hedhog/frontend/app/_lib/utils/format.ts.ejs +14 -9
- package/hedhog/frontend/app/approvals/page.tsx.ejs +843 -151
- package/hedhog/frontend/app/collaborator-types/page.tsx.ejs +457 -154
- package/hedhog/frontend/app/collaborators/page.tsx.ejs +118 -49
- package/hedhog/frontend/app/contracts/[id]/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/contracts/page.tsx.ejs +215 -617
- package/hedhog/frontend/app/departments/page.tsx.ejs +257 -113
- package/hedhog/frontend/app/projects/page.tsx.ejs +90 -51
- package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +546 -118
- package/hedhog/frontend/app/time-off/page.tsx.ejs +400 -123
- package/hedhog/frontend/app/timesheets/page.tsx.ejs +647 -342
- package/hedhog/frontend/messages/en.json +148 -14
- package/hedhog/frontend/messages/pt.json +199 -56
- package/hedhog/table/operations_collaborator.yaml +18 -18
- package/hedhog/table/operations_collaborator_equity_participation.yaml +43 -43
- package/hedhog/table/operations_collaborator_type.yaml +33 -33
- package/hedhog/table/operations_contract.yaml +0 -9
- package/hedhog/table/operations_contract_document.yaml +33 -33
- package/package.json +4 -4
- package/src/controllers/operations-approvals.controller.ts +9 -3
- package/src/controllers/operations-collaborators.controller.ts +15 -2
- package/src/controllers/operations-contracts.controller.ts +8 -92
- package/src/controllers/operations-org-structure.controller.ts +17 -4
- package/src/controllers/operations-projects.controller.ts +10 -4
- package/src/controllers/operations-timesheets.controller.ts +30 -8
- package/src/dto/create-collaborator-type.dto.ts +43 -43
- package/src/dto/create-collaborator.dto.ts +223 -223
- package/src/dto/list-approvals.dto.ts +12 -0
- package/src/dto/list-collaborator-types.dto.ts +20 -15
- package/src/dto/list-collaborators.dto.ts +34 -30
- package/src/dto/list-contracts.dto.ts +20 -0
- package/src/dto/list-departments.dto.ts +8 -0
- package/src/dto/list-projects.dto.ts +8 -0
- package/src/dto/list-schedule-adjustments.dto.ts +8 -0
- package/src/dto/list-time-off-requests.dto.ts +8 -0
- package/src/dto/list-timesheets.dto.ts +8 -0
- package/src/dto/reorder-collaborator-types.dto.ts +10 -0
- package/src/dto/update-collaborator-type.dto.ts +4 -3
- package/src/dto/update-collaborator.dto.ts +3 -3
- package/src/operations.service.spec.ts +96 -30
- package/src/operations.service.ts +1738 -1777
- package/hedhog/frontend/app/_components/contract-creation-wizard.tsx.ejs +0 -631
- package/hedhog/frontend/app/_components/contract-template-form-screen.tsx.ejs +0 -526
- package/hedhog/frontend/app/_components/contract-template-select-with-create.tsx.ejs +0 -247
- package/hedhog/frontend/app/_components/contract-wizard-sheet.tsx.ejs +0 -3520
- package/hedhog/frontend/app/contracts/templates/page.tsx.ejs +0 -380
- package/hedhog/frontend/app/team/page.tsx.ejs +0 -352
- package/hedhog/table/operations_contract_financial_term.yaml +0 -40
- package/hedhog/table/operations_contract_revision.yaml +0 -38
- package/hedhog/table/operations_contract_signature.yaml +0 -38
- package/hedhog/table/operations_contract_template.yaml +0 -58
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
EmptyState,
|
|
5
|
+
Page,
|
|
6
|
+
PaginationFooter,
|
|
7
|
+
SearchBar,
|
|
8
|
+
} from '@/components/entity-list';
|
|
4
9
|
import { Button } from '@/components/ui/button';
|
|
10
|
+
import { Card, CardContent } from '@/components/ui/card';
|
|
5
11
|
import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
|
|
6
12
|
import {
|
|
7
13
|
Sheet,
|
|
@@ -18,16 +24,42 @@ import {
|
|
|
18
24
|
TableHeader,
|
|
19
25
|
TableRow,
|
|
20
26
|
} from '@/components/ui/table';
|
|
27
|
+
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
|
28
|
+
import {
|
|
29
|
+
closestCenter,
|
|
30
|
+
DndContext,
|
|
31
|
+
type DragEndEvent,
|
|
32
|
+
PointerSensor,
|
|
33
|
+
useSensor,
|
|
34
|
+
useSensors,
|
|
35
|
+
} from '@dnd-kit/core';
|
|
36
|
+
import {
|
|
37
|
+
arrayMove,
|
|
38
|
+
SortableContext,
|
|
39
|
+
useSortable,
|
|
40
|
+
verticalListSortingStrategy,
|
|
41
|
+
} from '@dnd-kit/sortable';
|
|
42
|
+
import { CSS } from '@dnd-kit/utilities';
|
|
21
43
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
22
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
Briefcase,
|
|
46
|
+
GripVertical,
|
|
47
|
+
LayoutGrid,
|
|
48
|
+
List,
|
|
49
|
+
Pencil,
|
|
50
|
+
Users,
|
|
51
|
+
} from 'lucide-react';
|
|
23
52
|
import { useTranslations } from 'next-intl';
|
|
24
53
|
import Link from 'next/link';
|
|
25
|
-
import { useMemo, useState } from 'react';
|
|
54
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
26
55
|
import { OperationsHeader } from '../_components/operations-header';
|
|
27
56
|
import { StatusBadge } from '../_components/status-badge';
|
|
28
57
|
import { fetchOperations, mutateOperations } from '../_lib/api';
|
|
29
58
|
import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
|
|
30
|
-
import type {
|
|
59
|
+
import type {
|
|
60
|
+
OperationsCollaboratorType,
|
|
61
|
+
PaginatedResponse,
|
|
62
|
+
} from '../_lib/types';
|
|
31
63
|
import { formatEnumLabel, getStatusBadgeClass } from '../_lib/utils/format';
|
|
32
64
|
|
|
33
65
|
type CollaboratorTypeFormState = {
|
|
@@ -35,7 +67,6 @@ type CollaboratorTypeFormState = {
|
|
|
35
67
|
slug: string;
|
|
36
68
|
description: string;
|
|
37
69
|
category: string;
|
|
38
|
-
sortOrder: string;
|
|
39
70
|
status: 'active' | 'inactive';
|
|
40
71
|
};
|
|
41
72
|
|
|
@@ -44,65 +75,188 @@ const EMPTY_FORM_STATE: CollaboratorTypeFormState = {
|
|
|
44
75
|
slug: '',
|
|
45
76
|
description: '',
|
|
46
77
|
category: '',
|
|
47
|
-
sortOrder: '0',
|
|
48
78
|
status: 'active',
|
|
49
79
|
};
|
|
80
|
+
const EMPTY_COLLABORATOR_TYPES: OperationsCollaboratorType[] = [];
|
|
81
|
+
|
|
82
|
+
function SortableCollaboratorTypeRow({
|
|
83
|
+
collaboratorType,
|
|
84
|
+
position,
|
|
85
|
+
commonT,
|
|
86
|
+
t,
|
|
87
|
+
onEdit,
|
|
88
|
+
onToggleStatus,
|
|
89
|
+
reorderEnabled,
|
|
90
|
+
getStatusLabel,
|
|
91
|
+
}: {
|
|
92
|
+
collaboratorType: OperationsCollaboratorType;
|
|
93
|
+
position: number;
|
|
94
|
+
commonT: ReturnType<typeof useTranslations>;
|
|
95
|
+
t: ReturnType<typeof useTranslations>;
|
|
96
|
+
onEdit: (collaboratorType: OperationsCollaboratorType) => void;
|
|
97
|
+
onToggleStatus: (collaboratorType: OperationsCollaboratorType) => void;
|
|
98
|
+
reorderEnabled: boolean;
|
|
99
|
+
getStatusLabel: (value?: string | null) => string;
|
|
100
|
+
}) {
|
|
101
|
+
const {
|
|
102
|
+
attributes,
|
|
103
|
+
listeners,
|
|
104
|
+
setActivatorNodeRef,
|
|
105
|
+
setNodeRef,
|
|
106
|
+
transform,
|
|
107
|
+
transition,
|
|
108
|
+
isDragging,
|
|
109
|
+
} = useSortable({
|
|
110
|
+
id: collaboratorType.id,
|
|
111
|
+
disabled: !reorderEnabled,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<TableRow
|
|
116
|
+
ref={setNodeRef}
|
|
117
|
+
style={{
|
|
118
|
+
transform: CSS.Transform.toString(transform),
|
|
119
|
+
transition,
|
|
120
|
+
}}
|
|
121
|
+
className={isDragging ? 'bg-muted/40' : undefined}
|
|
122
|
+
>
|
|
123
|
+
<TableCell className="w-14">
|
|
124
|
+
<button
|
|
125
|
+
type="button"
|
|
126
|
+
ref={setActivatorNodeRef}
|
|
127
|
+
{...listeners}
|
|
128
|
+
{...attributes}
|
|
129
|
+
disabled={!reorderEnabled}
|
|
130
|
+
className="inline-flex h-8 w-8 items-center justify-center rounded-md border border-input bg-background text-muted-foreground transition hover:text-foreground disabled:cursor-not-allowed disabled:opacity-40"
|
|
131
|
+
aria-label={t('columns.drag')}
|
|
132
|
+
>
|
|
133
|
+
<GripVertical className="size-4" />
|
|
134
|
+
</button>
|
|
135
|
+
</TableCell>
|
|
136
|
+
<TableCell className="font-medium">{position}</TableCell>
|
|
137
|
+
<TableCell className="font-medium">{collaboratorType.name}</TableCell>
|
|
138
|
+
<TableCell>{collaboratorType.slug}</TableCell>
|
|
139
|
+
<TableCell>
|
|
140
|
+
{collaboratorType.category || commonT('labels.notAvailable')}
|
|
141
|
+
</TableCell>
|
|
142
|
+
<TableCell className="max-w-md text-sm text-muted-foreground">
|
|
143
|
+
{collaboratorType.description || commonT('labels.notAvailable')}
|
|
144
|
+
</TableCell>
|
|
145
|
+
<TableCell>{Number(collaboratorType.collaboratorCount ?? 0)}</TableCell>
|
|
146
|
+
<TableCell>
|
|
147
|
+
<StatusBadge
|
|
148
|
+
label={getStatusLabel(collaboratorType.status)}
|
|
149
|
+
className={getStatusBadgeClass(collaboratorType.status)}
|
|
150
|
+
/>
|
|
151
|
+
</TableCell>
|
|
152
|
+
<TableCell>
|
|
153
|
+
<div className="flex justify-end gap-2">
|
|
154
|
+
<Button
|
|
155
|
+
variant="outline"
|
|
156
|
+
size="icon"
|
|
157
|
+
className="cursor-pointer"
|
|
158
|
+
onClick={() => onEdit(collaboratorType)}
|
|
159
|
+
>
|
|
160
|
+
<Pencil className="size-4" />
|
|
161
|
+
</Button>
|
|
162
|
+
<Button
|
|
163
|
+
variant="outline"
|
|
164
|
+
size="sm"
|
|
165
|
+
className="cursor-pointer"
|
|
166
|
+
onClick={() => onToggleStatus(collaboratorType)}
|
|
167
|
+
>
|
|
168
|
+
{collaboratorType.status === 'inactive'
|
|
169
|
+
? commonT('actions.activate')
|
|
170
|
+
: commonT('actions.deactivate')}
|
|
171
|
+
</Button>
|
|
172
|
+
</div>
|
|
173
|
+
</TableCell>
|
|
174
|
+
</TableRow>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
50
177
|
|
|
51
178
|
export default function OperationsCollaboratorTypesPage() {
|
|
52
179
|
const t = useTranslations('operations.CollaboratorTypesPage');
|
|
53
180
|
const commonT = useTranslations('operations.Common');
|
|
181
|
+
const collaboratorFormT = useTranslations('operations.CollaboratorFormPage');
|
|
54
182
|
const { request, showToastHandler, currentLocaleCode } = useApp();
|
|
183
|
+
const getStatusLabel = (value?: string | null) =>
|
|
184
|
+
value
|
|
185
|
+
? collaboratorFormT.has(`options.statuses.${value}`)
|
|
186
|
+
? collaboratorFormT(`options.statuses.${value}`)
|
|
187
|
+
: formatEnumLabel(value)
|
|
188
|
+
: '-';
|
|
55
189
|
const access = useOperationsAccess();
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
'all'
|
|
190
|
+
const sensors = useSensors(
|
|
191
|
+
useSensor(PointerSensor, { activationConstraint: { distance: 6 } })
|
|
59
192
|
);
|
|
193
|
+
const [search, setSearch] = useState('');
|
|
194
|
+
const [statusFilter, setStatusFilter] = useState<
|
|
195
|
+
'all' | 'active' | 'inactive'
|
|
196
|
+
>('all');
|
|
197
|
+
const [page, setPage] = useState(1);
|
|
198
|
+
const [pageSize, setPageSize] = useState(100);
|
|
199
|
+
const [viewMode, setViewMode] = useState<'table' | 'cards'>(() => {
|
|
200
|
+
if (typeof window === 'undefined') return 'table';
|
|
201
|
+
const saved = window.localStorage.getItem(
|
|
202
|
+
'operations-collaborator-types-view-mode'
|
|
203
|
+
);
|
|
204
|
+
return saved === 'cards' ? 'cards' : 'table';
|
|
205
|
+
});
|
|
60
206
|
const [isSheetOpen, setIsSheetOpen] = useState(false);
|
|
61
207
|
const [isSaving, setIsSaving] = useState(false);
|
|
62
|
-
const [
|
|
63
|
-
|
|
64
|
-
|
|
208
|
+
const [isReordering, setIsReordering] = useState(false);
|
|
209
|
+
const [editingType, setEditingType] =
|
|
210
|
+
useState<OperationsCollaboratorType | null>(null);
|
|
65
211
|
const [form, setForm] = useState<CollaboratorTypeFormState>(EMPTY_FORM_STATE);
|
|
212
|
+
const [orderedCollaboratorTypes, setOrderedCollaboratorTypes] = useState<
|
|
213
|
+
OperationsCollaboratorType[]
|
|
214
|
+
>([]);
|
|
66
215
|
|
|
67
216
|
const {
|
|
68
|
-
data:
|
|
217
|
+
data: collaboratorTypesResponse,
|
|
69
218
|
isLoading,
|
|
70
219
|
refetch,
|
|
71
|
-
} = useQuery<OperationsCollaboratorType
|
|
72
|
-
queryKey: [
|
|
220
|
+
} = useQuery<PaginatedResponse<OperationsCollaboratorType>>({
|
|
221
|
+
queryKey: [
|
|
222
|
+
'operations-collaborator-types-page',
|
|
223
|
+
currentLocaleCode,
|
|
224
|
+
search,
|
|
225
|
+
statusFilter,
|
|
226
|
+
page,
|
|
227
|
+
pageSize,
|
|
228
|
+
],
|
|
73
229
|
enabled: access.isDirector,
|
|
74
|
-
queryFn: () =>
|
|
75
|
-
|
|
230
|
+
queryFn: () => {
|
|
231
|
+
const params = new URLSearchParams({
|
|
232
|
+
page: String(page),
|
|
233
|
+
pageSize: String(pageSize),
|
|
234
|
+
sortField: 'sortOrder',
|
|
235
|
+
sortOrder: 'asc',
|
|
236
|
+
});
|
|
237
|
+
if (search.trim()) params.set('search', search.trim());
|
|
238
|
+
if (statusFilter !== 'all') params.set('status', statusFilter);
|
|
239
|
+
return fetchOperations<PaginatedResponse<OperationsCollaboratorType>>(
|
|
76
240
|
request,
|
|
77
|
-
|
|
78
|
-
)
|
|
241
|
+
`/operations/collaborator-types?${params.toString()}`
|
|
242
|
+
);
|
|
243
|
+
},
|
|
244
|
+
placeholderData: (previous) => previous,
|
|
79
245
|
});
|
|
80
246
|
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
String(value)
|
|
95
|
-
.toLowerCase()
|
|
96
|
-
.includes(search.trim().toLowerCase())
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
const matchesStatus =
|
|
100
|
-
statusFilter === 'all' ? true : collaboratorType.status === statusFilter;
|
|
101
|
-
|
|
102
|
-
return matchesSearch && matchesStatus;
|
|
103
|
-
}),
|
|
104
|
-
[collaboratorTypes, search, statusFilter]
|
|
105
|
-
);
|
|
247
|
+
const collaboratorTypes =
|
|
248
|
+
collaboratorTypesResponse?.data ?? EMPTY_COLLABORATOR_TYPES;
|
|
249
|
+
const totalItems = collaboratorTypesResponse?.total ?? 0;
|
|
250
|
+
const reorderEnabled =
|
|
251
|
+
viewMode === 'table' &&
|
|
252
|
+
!search.trim() &&
|
|
253
|
+
statusFilter === 'all' &&
|
|
254
|
+
page === 1 &&
|
|
255
|
+
totalItems <= pageSize;
|
|
256
|
+
|
|
257
|
+
useEffect(() => {
|
|
258
|
+
setOrderedCollaboratorTypes(collaboratorTypes);
|
|
259
|
+
}, [collaboratorTypes]);
|
|
106
260
|
|
|
107
261
|
const statsCards = useMemo(
|
|
108
262
|
() => [
|
|
@@ -115,7 +269,8 @@ export default function OperationsCollaboratorTypesPage() {
|
|
|
115
269
|
{
|
|
116
270
|
key: 'active',
|
|
117
271
|
title: t('cards.active'),
|
|
118
|
-
value: collaboratorTypes.filter((item) => item.status === 'active')
|
|
272
|
+
value: collaboratorTypes.filter((item) => item.status === 'active')
|
|
273
|
+
.length,
|
|
119
274
|
icon: Briefcase,
|
|
120
275
|
},
|
|
121
276
|
{
|
|
@@ -130,13 +285,25 @@ export default function OperationsCollaboratorTypesPage() {
|
|
|
130
285
|
{
|
|
131
286
|
key: 'inactive',
|
|
132
287
|
title: t('cards.inactive'),
|
|
133
|
-
value: collaboratorTypes.filter((item) => item.status === 'inactive')
|
|
288
|
+
value: collaboratorTypes.filter((item) => item.status === 'inactive')
|
|
289
|
+
.length,
|
|
134
290
|
icon: Briefcase,
|
|
135
291
|
},
|
|
136
292
|
],
|
|
137
293
|
[collaboratorTypes, t]
|
|
138
294
|
);
|
|
139
295
|
|
|
296
|
+
const handleViewModeChange = (value: string) => {
|
|
297
|
+
if (value !== 'table' && value !== 'cards') return;
|
|
298
|
+
setViewMode(value);
|
|
299
|
+
if (typeof window !== 'undefined') {
|
|
300
|
+
window.localStorage.setItem(
|
|
301
|
+
'operations-collaborator-types-view-mode',
|
|
302
|
+
value
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
|
|
140
307
|
const updateForm = <K extends keyof CollaboratorTypeFormState>(
|
|
141
308
|
field: K,
|
|
142
309
|
value: CollaboratorTypeFormState[K]
|
|
@@ -160,7 +327,6 @@ export default function OperationsCollaboratorTypesPage() {
|
|
|
160
327
|
slug: collaboratorType.slug ?? '',
|
|
161
328
|
description: collaboratorType.description ?? '',
|
|
162
329
|
category: collaboratorType.category ?? '',
|
|
163
|
-
sortOrder: String(collaboratorType.sortOrder ?? 0),
|
|
164
330
|
status: collaboratorType.status ?? 'active',
|
|
165
331
|
});
|
|
166
332
|
setIsSheetOpen(true);
|
|
@@ -186,7 +352,6 @@ export default function OperationsCollaboratorTypesPage() {
|
|
|
186
352
|
slug: form.slug.trim() || null,
|
|
187
353
|
description: form.description.trim() || null,
|
|
188
354
|
category: form.category.trim() || null,
|
|
189
|
-
sortOrder: Number(form.sortOrder || '0'),
|
|
190
355
|
status: form.status,
|
|
191
356
|
isActive: form.status === 'active',
|
|
192
357
|
}
|
|
@@ -222,6 +387,51 @@ export default function OperationsCollaboratorTypesPage() {
|
|
|
222
387
|
}
|
|
223
388
|
};
|
|
224
389
|
|
|
390
|
+
const handleDragEnd = async (event: DragEndEvent) => {
|
|
391
|
+
if (!reorderEnabled || isReordering) return;
|
|
392
|
+
|
|
393
|
+
const { active, over } = event;
|
|
394
|
+
|
|
395
|
+
if (!over || active.id === over.id) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const oldIndex = orderedCollaboratorTypes.findIndex(
|
|
400
|
+
(item) => item.id === Number(active.id)
|
|
401
|
+
);
|
|
402
|
+
const newIndex = orderedCollaboratorTypes.findIndex(
|
|
403
|
+
(item) => item.id === Number(over.id)
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
if (oldIndex < 0 || newIndex < 0) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const nextOrder = arrayMove(orderedCollaboratorTypes, oldIndex, newIndex);
|
|
411
|
+
const previousOrder = orderedCollaboratorTypes;
|
|
412
|
+
|
|
413
|
+
setOrderedCollaboratorTypes(nextOrder);
|
|
414
|
+
setIsReordering(true);
|
|
415
|
+
|
|
416
|
+
try {
|
|
417
|
+
await mutateOperations(
|
|
418
|
+
request,
|
|
419
|
+
'/operations/collaborator-types/reorder',
|
|
420
|
+
'PATCH',
|
|
421
|
+
{
|
|
422
|
+
ids: nextOrder.map((item) => item.id),
|
|
423
|
+
}
|
|
424
|
+
);
|
|
425
|
+
showToastHandler?.('success', t('messages.reorderSuccess'));
|
|
426
|
+
await refetch();
|
|
427
|
+
} catch {
|
|
428
|
+
setOrderedCollaboratorTypes(previousOrder);
|
|
429
|
+
showToastHandler?.('error', t('messages.reorderError'));
|
|
430
|
+
} finally {
|
|
431
|
+
setIsReordering(false);
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
|
|
225
435
|
if (!access.isLoading && !access.isDirector) {
|
|
226
436
|
return (
|
|
227
437
|
<Page>
|
|
@@ -273,103 +483,196 @@ export default function OperationsCollaboratorTypesPage() {
|
|
|
273
483
|
|
|
274
484
|
<KpiCardsGrid items={statsCards} columns={4} />
|
|
275
485
|
|
|
276
|
-
<div className="flex flex-col gap-4">
|
|
277
|
-
<
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
486
|
+
<div className="flex min-w-0 flex-col gap-4 xl:flex-row xl:items-center">
|
|
487
|
+
<div className="flex-1">
|
|
488
|
+
<SearchBar
|
|
489
|
+
searchQuery={search}
|
|
490
|
+
onSearchChange={(value) => {
|
|
491
|
+
setSearch(value);
|
|
492
|
+
setPage(1);
|
|
493
|
+
}}
|
|
494
|
+
showSearchButton={false}
|
|
495
|
+
debounceMs={500}
|
|
496
|
+
placeholder={t('searchPlaceholder')}
|
|
497
|
+
controls={[
|
|
498
|
+
{
|
|
499
|
+
id: 'status',
|
|
500
|
+
type: 'select',
|
|
501
|
+
value: statusFilter,
|
|
502
|
+
onChange: (value) => {
|
|
503
|
+
setStatusFilter(
|
|
504
|
+
(value as 'all' | 'active' | 'inactive') ?? 'all'
|
|
505
|
+
);
|
|
506
|
+
setPage(1);
|
|
507
|
+
},
|
|
508
|
+
placeholder: commonT('labels.status'),
|
|
509
|
+
options: [
|
|
510
|
+
{ value: 'all', label: commonT('filters.allStatuses') },
|
|
511
|
+
{ value: 'active', label: getStatusLabel('active') },
|
|
512
|
+
{ value: 'inactive', label: getStatusLabel('inactive') },
|
|
513
|
+
],
|
|
514
|
+
},
|
|
515
|
+
]}
|
|
516
|
+
/>
|
|
517
|
+
</div>
|
|
518
|
+
|
|
519
|
+
<div className="flex items-center justify-between gap-3 xl:justify-end">
|
|
520
|
+
<span className="text-xs font-medium text-muted-foreground">
|
|
521
|
+
{t('viewMode')}
|
|
522
|
+
</span>
|
|
523
|
+
<ToggleGroup
|
|
524
|
+
type="single"
|
|
525
|
+
value={viewMode}
|
|
526
|
+
onValueChange={handleViewModeChange}
|
|
527
|
+
variant="outline"
|
|
528
|
+
size="sm"
|
|
529
|
+
aria-label={t('viewMode')}
|
|
530
|
+
>
|
|
531
|
+
<ToggleGroupItem
|
|
532
|
+
value="table"
|
|
533
|
+
className="gap-1.5 px-2.5"
|
|
534
|
+
aria-label={t('viewModeTable')}
|
|
535
|
+
>
|
|
536
|
+
<List className="h-4 w-4" />
|
|
537
|
+
<span className="hidden sm:inline">{t('viewModeTable')}</span>
|
|
538
|
+
</ToggleGroupItem>
|
|
539
|
+
<ToggleGroupItem
|
|
540
|
+
value="cards"
|
|
541
|
+
className="gap-1.5 px-2.5"
|
|
542
|
+
aria-label={t('viewModeCards')}
|
|
543
|
+
>
|
|
544
|
+
<LayoutGrid className="h-4 w-4" />
|
|
545
|
+
<span className="hidden sm:inline">{t('viewModeCards')}</span>
|
|
546
|
+
</ToggleGroupItem>
|
|
547
|
+
</ToggleGroup>
|
|
548
|
+
</div>
|
|
549
|
+
</div>
|
|
550
|
+
|
|
551
|
+
<div className="rounded-md border border-dashed bg-muted/20 px-4 py-3 text-sm text-muted-foreground">
|
|
552
|
+
{reorderEnabled ? t('reorder.description') : t('reorder.filtered')}
|
|
300
553
|
</div>
|
|
301
554
|
|
|
302
555
|
{isLoading ? (
|
|
303
556
|
<div className="rounded-md border px-4 py-6 text-sm text-muted-foreground">
|
|
304
557
|
{commonT('actions.refresh')}...
|
|
305
558
|
</div>
|
|
306
|
-
) :
|
|
307
|
-
|
|
308
|
-
<
|
|
309
|
-
|
|
310
|
-
<
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
<
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
{collaboratorType.name}
|
|
328
|
-
</TableCell>
|
|
329
|
-
<TableCell>{collaboratorType.slug}</TableCell>
|
|
330
|
-
<TableCell>
|
|
331
|
-
{collaboratorType.category || commonT('labels.notAvailable')}
|
|
332
|
-
</TableCell>
|
|
333
|
-
<TableCell className="max-w-md text-sm text-muted-foreground">
|
|
334
|
-
{collaboratorType.description || commonT('labels.notAvailable')}
|
|
335
|
-
</TableCell>
|
|
336
|
-
<TableCell>{Number(collaboratorType.sortOrder ?? 0)}</TableCell>
|
|
337
|
-
<TableCell>
|
|
338
|
-
{Number(collaboratorType.collaboratorCount ?? 0)}
|
|
339
|
-
</TableCell>
|
|
340
|
-
<TableCell>
|
|
559
|
+
) : collaboratorTypes.length > 0 ? (
|
|
560
|
+
viewMode === 'cards' ? (
|
|
561
|
+
<div className="grid gap-4 md:grid-cols-2 2xl:grid-cols-3">
|
|
562
|
+
{orderedCollaboratorTypes.map((collaboratorType) => (
|
|
563
|
+
<Card
|
|
564
|
+
key={collaboratorType.id}
|
|
565
|
+
className="cursor-pointer overflow-hidden border-border/60 py-0 shadow-sm transition-all hover:-translate-y-0.5 hover:shadow-md"
|
|
566
|
+
onClick={() => openEditSheet(collaboratorType)}
|
|
567
|
+
>
|
|
568
|
+
<CardContent className="space-y-3 p-4">
|
|
569
|
+
<div className="flex items-start justify-between gap-3">
|
|
570
|
+
<div className="min-w-0">
|
|
571
|
+
<div className="truncate font-semibold">
|
|
572
|
+
{collaboratorType.name}
|
|
573
|
+
</div>
|
|
574
|
+
{collaboratorType.slug ? (
|
|
575
|
+
<div className="truncate text-xs text-muted-foreground">
|
|
576
|
+
{collaboratorType.slug}
|
|
577
|
+
</div>
|
|
578
|
+
) : null}
|
|
579
|
+
</div>
|
|
341
580
|
<StatusBadge
|
|
342
|
-
label={
|
|
581
|
+
label={getStatusLabel(collaboratorType.status)}
|
|
343
582
|
className={getStatusBadgeClass(collaboratorType.status)}
|
|
344
583
|
/>
|
|
345
|
-
</
|
|
346
|
-
<
|
|
347
|
-
|
|
348
|
-
<
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
584
|
+
</div>
|
|
585
|
+
<div className="flex flex-wrap gap-2">
|
|
586
|
+
{collaboratorType.category ? (
|
|
587
|
+
<span className="inline-flex items-center rounded-full bg-primary/10 px-2.5 py-1 text-xs font-medium text-primary">
|
|
588
|
+
{collaboratorType.category}
|
|
589
|
+
</span>
|
|
590
|
+
) : null}
|
|
591
|
+
<span className="inline-flex items-center rounded-full bg-muted px-2.5 py-1 text-xs font-medium text-muted-foreground">
|
|
592
|
+
{Number(collaboratorType.collaboratorCount ?? 0)}{' '}
|
|
593
|
+
{commonT('labels.collaborators')}
|
|
594
|
+
</span>
|
|
595
|
+
</div>
|
|
596
|
+
{collaboratorType.description ? (
|
|
597
|
+
<p className="line-clamp-2 text-sm text-muted-foreground">
|
|
598
|
+
{collaboratorType.description}
|
|
599
|
+
</p>
|
|
600
|
+
) : null}
|
|
601
|
+
<div className="flex justify-end gap-2 border-t border-border/60 pt-3">
|
|
602
|
+
<Button
|
|
603
|
+
variant="outline"
|
|
604
|
+
size="icon"
|
|
605
|
+
onClick={(e) => {
|
|
606
|
+
e.stopPropagation();
|
|
607
|
+
openEditSheet(collaboratorType);
|
|
608
|
+
}}
|
|
609
|
+
>
|
|
610
|
+
<Pencil className="size-4" />
|
|
611
|
+
</Button>
|
|
612
|
+
<Button
|
|
613
|
+
variant="outline"
|
|
614
|
+
size="sm"
|
|
615
|
+
onClick={(e) => {
|
|
616
|
+
e.stopPropagation();
|
|
617
|
+
void toggleStatus(collaboratorType);
|
|
618
|
+
}}
|
|
619
|
+
>
|
|
620
|
+
{collaboratorType.status === 'inactive'
|
|
621
|
+
? commonT('actions.activate')
|
|
622
|
+
: commonT('actions.deactivate')}
|
|
623
|
+
</Button>
|
|
624
|
+
</div>
|
|
625
|
+
</CardContent>
|
|
626
|
+
</Card>
|
|
627
|
+
))}
|
|
628
|
+
</div>
|
|
629
|
+
) : (
|
|
630
|
+
<div className="overflow-x-auto rounded-md border">
|
|
631
|
+
<DndContext
|
|
632
|
+
sensors={sensors}
|
|
633
|
+
collisionDetection={closestCenter}
|
|
634
|
+
onDragEnd={(event) => void handleDragEnd(event)}
|
|
635
|
+
>
|
|
636
|
+
<Table>
|
|
637
|
+
<TableHeader>
|
|
638
|
+
<TableRow>
|
|
639
|
+
<TableHead className="w-14">{t('columns.drag')}</TableHead>
|
|
640
|
+
<TableHead>{t('columns.sortOrder')}</TableHead>
|
|
641
|
+
<TableHead>{t('columns.name')}</TableHead>
|
|
642
|
+
<TableHead>{t('columns.slug')}</TableHead>
|
|
643
|
+
<TableHead>{t('columns.category')}</TableHead>
|
|
644
|
+
<TableHead>{t('columns.description')}</TableHead>
|
|
645
|
+
<TableHead>{t('columns.collaborators')}</TableHead>
|
|
646
|
+
<TableHead>{commonT('labels.status')}</TableHead>
|
|
647
|
+
<TableHead className="text-right">
|
|
648
|
+
{commonT('labels.actions')}
|
|
649
|
+
</TableHead>
|
|
650
|
+
</TableRow>
|
|
651
|
+
</TableHeader>
|
|
652
|
+
<TableBody>
|
|
653
|
+
<SortableContext
|
|
654
|
+
items={orderedCollaboratorTypes.map((item) => item.id)}
|
|
655
|
+
strategy={verticalListSortingStrategy}
|
|
656
|
+
>
|
|
657
|
+
{orderedCollaboratorTypes.map((collaboratorType, index) => (
|
|
658
|
+
<SortableCollaboratorTypeRow
|
|
659
|
+
key={collaboratorType.id}
|
|
660
|
+
collaboratorType={collaboratorType}
|
|
661
|
+
position={index + 1}
|
|
662
|
+
commonT={commonT}
|
|
663
|
+
t={t}
|
|
664
|
+
onEdit={openEditSheet}
|
|
665
|
+
onToggleStatus={(item) => void toggleStatus(item)}
|
|
666
|
+
reorderEnabled={reorderEnabled && !isReordering}
|
|
667
|
+
getStatusLabel={getStatusLabel}
|
|
668
|
+
/>
|
|
669
|
+
))}
|
|
670
|
+
</SortableContext>
|
|
671
|
+
</TableBody>
|
|
672
|
+
</Table>
|
|
673
|
+
</DndContext>
|
|
674
|
+
</div>
|
|
675
|
+
)
|
|
373
676
|
) : (
|
|
374
677
|
<EmptyState
|
|
375
678
|
icon={<Briefcase className="size-12" />}
|
|
@@ -380,6 +683,18 @@ export default function OperationsCollaboratorTypesPage() {
|
|
|
380
683
|
/>
|
|
381
684
|
)}
|
|
382
685
|
|
|
686
|
+
<PaginationFooter
|
|
687
|
+
currentPage={page}
|
|
688
|
+
pageSize={pageSize}
|
|
689
|
+
totalItems={totalItems}
|
|
690
|
+
pageSizeOptions={[25, 50, 100, 200]}
|
|
691
|
+
onPageChange={setPage}
|
|
692
|
+
onPageSizeChange={(size) => {
|
|
693
|
+
setPageSize(size);
|
|
694
|
+
setPage(1);
|
|
695
|
+
}}
|
|
696
|
+
/>
|
|
697
|
+
|
|
383
698
|
<Sheet open={isSheetOpen} onOpenChange={setIsSheetOpen}>
|
|
384
699
|
<SheetContent className="w-full overflow-y-auto sm:max-w-xl">
|
|
385
700
|
<SheetHeader>
|
|
@@ -390,7 +705,7 @@ export default function OperationsCollaboratorTypesPage() {
|
|
|
390
705
|
</SheetHeader>
|
|
391
706
|
|
|
392
707
|
<form
|
|
393
|
-
className="mt-6 space-y-4"
|
|
708
|
+
className="mt-6 space-y-4 px-6"
|
|
394
709
|
onSubmit={(event) => {
|
|
395
710
|
event.preventDefault();
|
|
396
711
|
void saveCollaboratorType();
|
|
@@ -442,26 +757,14 @@ export default function OperationsCollaboratorTypesPage() {
|
|
|
442
757
|
<textarea
|
|
443
758
|
id="type-description"
|
|
444
759
|
value={form.description}
|
|
445
|
-
onChange={(event) =>
|
|
760
|
+
onChange={(event) =>
|
|
761
|
+
updateForm('description', event.target.value)
|
|
762
|
+
}
|
|
446
763
|
className="min-h-24 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
|
|
447
764
|
placeholder={t('form.description')}
|
|
448
765
|
/>
|
|
449
766
|
</div>
|
|
450
767
|
|
|
451
|
-
<div className="space-y-2">
|
|
452
|
-
<label className="text-sm font-medium" htmlFor="type-sort-order">
|
|
453
|
-
{t('form.sortOrder')}
|
|
454
|
-
</label>
|
|
455
|
-
<input
|
|
456
|
-
id="type-sort-order"
|
|
457
|
-
type="number"
|
|
458
|
-
value={form.sortOrder}
|
|
459
|
-
onChange={(event) => updateForm('sortOrder', event.target.value)}
|
|
460
|
-
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
|
|
461
|
-
placeholder="0"
|
|
462
|
-
/>
|
|
463
|
-
</div>
|
|
464
|
-
|
|
465
768
|
<div className="space-y-2">
|
|
466
769
|
<label className="text-sm font-medium" htmlFor="type-status">
|
|
467
770
|
{t('form.status')}
|
|
@@ -477,8 +780,8 @@ export default function OperationsCollaboratorTypesPage() {
|
|
|
477
780
|
}
|
|
478
781
|
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
|
|
479
782
|
>
|
|
480
|
-
<option value="active">{
|
|
481
|
-
<option value="inactive">{
|
|
783
|
+
<option value="active">{getStatusLabel('active')}</option>
|
|
784
|
+
<option value="inactive">{getStatusLabel('inactive')}</option>
|
|
482
785
|
</select>
|
|
483
786
|
</div>
|
|
484
787
|
|