@hed-hog/operations 0.0.306 → 0.0.310
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 +31 -4
- package/dist/controllers/operations-timesheets.controller.d.ts.map +1 -1
- package/dist/controllers/operations-timesheets.controller.js +16 -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/operations.service.d.ts +340 -271
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +1007 -1043
- package/dist/operations.service.js.map +1 -1
- package/dist/operations.service.spec.js +0 -22
- package/dist/operations.service.spec.js.map +1 -1
- package/hedhog/data/menu.yaml +0 -36
- package/hedhog/data/route.yaml +42 -73
- 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/_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 +842 -150
- package/hedhog/frontend/app/collaborator-types/page.tsx.ejs +445 -153
- 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 +412 -147
- package/hedhog/frontend/app/time-off/page.tsx.ejs +400 -123
- package/hedhog/frontend/app/timesheets/page.tsx.ejs +460 -365
- package/hedhog/frontend/messages/en.json +143 -14
- package/hedhog/frontend/messages/pt.json +192 -54
- package/hedhog/table/operations_contract.yaml +0 -9
- 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 +17 -8
- package/src/dto/list-approvals.dto.ts +12 -0
- package/src/dto/list-collaborator-types.dto.ts +7 -2
- package/src/dto/list-collaborators.dto.ts +4 -0
- 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/operations.service.spec.ts +0 -30
- package/src/operations.service.ts +1557 -1806
- 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,66 +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();
|
|
190
|
+
const sensors = useSensors(
|
|
191
|
+
useSensor(PointerSensor, { activationConstraint: { distance: 6 } })
|
|
192
|
+
);
|
|
56
193
|
const [search, setSearch] = useState('');
|
|
57
194
|
const [statusFilter, setStatusFilter] = useState<
|
|
58
195
|
'all' | 'active' | 'inactive'
|
|
59
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);
|
|
208
|
+
const [isReordering, setIsReordering] = useState(false);
|
|
62
209
|
const [editingType, setEditingType] =
|
|
63
210
|
useState<OperationsCollaboratorType | null>(null);
|
|
64
211
|
const [form, setForm] = useState<CollaboratorTypeFormState>(EMPTY_FORM_STATE);
|
|
212
|
+
const [orderedCollaboratorTypes, setOrderedCollaboratorTypes] = useState<
|
|
213
|
+
OperationsCollaboratorType[]
|
|
214
|
+
>([]);
|
|
65
215
|
|
|
66
216
|
const {
|
|
67
|
-
data:
|
|
217
|
+
data: collaboratorTypesResponse,
|
|
68
218
|
isLoading,
|
|
69
219
|
refetch,
|
|
70
|
-
} = useQuery<OperationsCollaboratorType
|
|
71
|
-
queryKey: [
|
|
220
|
+
} = useQuery<PaginatedResponse<OperationsCollaboratorType>>({
|
|
221
|
+
queryKey: [
|
|
222
|
+
'operations-collaborator-types-page',
|
|
223
|
+
currentLocaleCode,
|
|
224
|
+
search,
|
|
225
|
+
statusFilter,
|
|
226
|
+
page,
|
|
227
|
+
pageSize,
|
|
228
|
+
],
|
|
72
229
|
enabled: access.isDirector,
|
|
73
|
-
queryFn: () =>
|
|
74
|
-
|
|
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>>(
|
|
75
240
|
request,
|
|
76
|
-
|
|
77
|
-
)
|
|
241
|
+
`/operations/collaborator-types?${params.toString()}`
|
|
242
|
+
);
|
|
243
|
+
},
|
|
244
|
+
placeholderData: (previous) => previous,
|
|
78
245
|
});
|
|
79
246
|
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
String(value)
|
|
94
|
-
.toLowerCase()
|
|
95
|
-
.includes(search.trim().toLowerCase())
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
const matchesStatus =
|
|
99
|
-
statusFilter === 'all'
|
|
100
|
-
? true
|
|
101
|
-
: collaboratorType.status === statusFilter;
|
|
102
|
-
|
|
103
|
-
return matchesSearch && matchesStatus;
|
|
104
|
-
}),
|
|
105
|
-
[collaboratorTypes, search, statusFilter]
|
|
106
|
-
);
|
|
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]);
|
|
107
260
|
|
|
108
261
|
const statsCards = useMemo(
|
|
109
262
|
() => [
|
|
@@ -140,6 +293,17 @@ export default function OperationsCollaboratorTypesPage() {
|
|
|
140
293
|
[collaboratorTypes, t]
|
|
141
294
|
);
|
|
142
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
|
+
|
|
143
307
|
const updateForm = <K extends keyof CollaboratorTypeFormState>(
|
|
144
308
|
field: K,
|
|
145
309
|
value: CollaboratorTypeFormState[K]
|
|
@@ -163,7 +327,6 @@ export default function OperationsCollaboratorTypesPage() {
|
|
|
163
327
|
slug: collaboratorType.slug ?? '',
|
|
164
328
|
description: collaboratorType.description ?? '',
|
|
165
329
|
category: collaboratorType.category ?? '',
|
|
166
|
-
sortOrder: String(collaboratorType.sortOrder ?? 0),
|
|
167
330
|
status: collaboratorType.status ?? 'active',
|
|
168
331
|
});
|
|
169
332
|
setIsSheetOpen(true);
|
|
@@ -189,7 +352,6 @@ export default function OperationsCollaboratorTypesPage() {
|
|
|
189
352
|
slug: form.slug.trim() || null,
|
|
190
353
|
description: form.description.trim() || null,
|
|
191
354
|
category: form.category.trim() || null,
|
|
192
|
-
sortOrder: Number(form.sortOrder || '0'),
|
|
193
355
|
status: form.status,
|
|
194
356
|
isActive: form.status === 'active',
|
|
195
357
|
}
|
|
@@ -225,6 +387,51 @@ export default function OperationsCollaboratorTypesPage() {
|
|
|
225
387
|
}
|
|
226
388
|
};
|
|
227
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
|
+
|
|
228
435
|
if (!access.isLoading && !access.isDirector) {
|
|
229
436
|
return (
|
|
230
437
|
<Page>
|
|
@@ -276,107 +483,196 @@ export default function OperationsCollaboratorTypesPage() {
|
|
|
276
483
|
|
|
277
484
|
<KpiCardsGrid items={statsCards} columns={4} />
|
|
278
485
|
|
|
279
|
-
<div className="flex flex-col gap-4">
|
|
280
|
-
<
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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')}
|
|
303
553
|
</div>
|
|
304
554
|
|
|
305
555
|
{isLoading ? (
|
|
306
556
|
<div className="rounded-md border px-4 py-6 text-sm text-muted-foreground">
|
|
307
557
|
{commonT('actions.refresh')}...
|
|
308
558
|
</div>
|
|
309
|
-
) :
|
|
310
|
-
|
|
311
|
-
<
|
|
312
|
-
|
|
313
|
-
<
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
<
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
{collaboratorType.name}
|
|
331
|
-
</TableCell>
|
|
332
|
-
<TableCell>{collaboratorType.slug}</TableCell>
|
|
333
|
-
<TableCell>
|
|
334
|
-
{collaboratorType.category ||
|
|
335
|
-
commonT('labels.notAvailable')}
|
|
336
|
-
</TableCell>
|
|
337
|
-
<TableCell className="max-w-md text-sm text-muted-foreground">
|
|
338
|
-
{collaboratorType.description ||
|
|
339
|
-
commonT('labels.notAvailable')}
|
|
340
|
-
</TableCell>
|
|
341
|
-
<TableCell>
|
|
342
|
-
{Number(collaboratorType.sortOrder ?? 0)}
|
|
343
|
-
</TableCell>
|
|
344
|
-
<TableCell>
|
|
345
|
-
{Number(collaboratorType.collaboratorCount ?? 0)}
|
|
346
|
-
</TableCell>
|
|
347
|
-
<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>
|
|
348
580
|
<StatusBadge
|
|
349
|
-
label={
|
|
581
|
+
label={getStatusLabel(collaboratorType.status)}
|
|
350
582
|
className={getStatusBadgeClass(collaboratorType.status)}
|
|
351
583
|
/>
|
|
352
|
-
</
|
|
353
|
-
<
|
|
354
|
-
|
|
355
|
-
<
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
+
)
|
|
380
676
|
) : (
|
|
381
677
|
<EmptyState
|
|
382
678
|
icon={<Briefcase className="size-12" />}
|
|
@@ -387,6 +683,18 @@ export default function OperationsCollaboratorTypesPage() {
|
|
|
387
683
|
/>
|
|
388
684
|
)}
|
|
389
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
|
+
|
|
390
698
|
<Sheet open={isSheetOpen} onOpenChange={setIsSheetOpen}>
|
|
391
699
|
<SheetContent className="w-full overflow-y-auto sm:max-w-xl">
|
|
392
700
|
<SheetHeader>
|
|
@@ -397,7 +705,7 @@ export default function OperationsCollaboratorTypesPage() {
|
|
|
397
705
|
</SheetHeader>
|
|
398
706
|
|
|
399
707
|
<form
|
|
400
|
-
className="mt-6
|
|
708
|
+
className="mt-6 space-y-4 px-6"
|
|
401
709
|
onSubmit={(event) => {
|
|
402
710
|
event.preventDefault();
|
|
403
711
|
void saveCollaboratorType();
|
|
@@ -457,22 +765,6 @@ export default function OperationsCollaboratorTypesPage() {
|
|
|
457
765
|
/>
|
|
458
766
|
</div>
|
|
459
767
|
|
|
460
|
-
<div className="space-y-2">
|
|
461
|
-
<label className="text-sm font-medium" htmlFor="type-sort-order">
|
|
462
|
-
{t('form.sortOrder')}
|
|
463
|
-
</label>
|
|
464
|
-
<input
|
|
465
|
-
id="type-sort-order"
|
|
466
|
-
type="number"
|
|
467
|
-
value={form.sortOrder}
|
|
468
|
-
onChange={(event) =>
|
|
469
|
-
updateForm('sortOrder', event.target.value)
|
|
470
|
-
}
|
|
471
|
-
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
|
|
472
|
-
placeholder="0"
|
|
473
|
-
/>
|
|
474
|
-
</div>
|
|
475
|
-
|
|
476
768
|
<div className="space-y-2">
|
|
477
769
|
<label className="text-sm font-medium" htmlFor="type-status">
|
|
478
770
|
{t('form.status')}
|
|
@@ -488,8 +780,8 @@ export default function OperationsCollaboratorTypesPage() {
|
|
|
488
780
|
}
|
|
489
781
|
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
|
|
490
782
|
>
|
|
491
|
-
<option value="active">{
|
|
492
|
-
<option value="inactive">{
|
|
783
|
+
<option value="active">{getStatusLabel('active')}</option>
|
|
784
|
+
<option value="inactive">{getStatusLabel('inactive')}</option>
|
|
493
785
|
</select>
|
|
494
786
|
</div>
|
|
495
787
|
|