@hed-hog/operations 0.0.303 → 0.0.305
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/README.md +200 -43
- package/dist/controllers/operations-approvals.controller.d.ts +9 -0
- package/dist/controllers/operations-approvals.controller.d.ts.map +1 -0
- package/dist/controllers/operations-approvals.controller.js +64 -0
- package/dist/controllers/operations-approvals.controller.js.map +1 -0
- package/dist/controllers/operations-collaborators.controller.d.ts +223 -0
- package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -0
- package/dist/controllers/operations-collaborators.controller.js +96 -0
- package/dist/controllers/operations-collaborators.controller.js.map +1 -0
- package/dist/controllers/operations-contracts.controller.d.ts +683 -0
- package/dist/controllers/operations-contracts.controller.d.ts.map +1 -0
- package/dist/controllers/operations-contracts.controller.js +198 -0
- package/dist/controllers/operations-contracts.controller.js.map +1 -0
- package/dist/controllers/operations-org-structure.controller.d.ts +108 -0
- package/dist/controllers/operations-org-structure.controller.d.ts.map +1 -0
- package/dist/controllers/operations-org-structure.controller.js +143 -0
- package/dist/controllers/operations-org-structure.controller.js.map +1 -0
- package/dist/controllers/operations-projects.controller.d.ts +184 -0
- package/dist/controllers/operations-projects.controller.d.ts.map +1 -0
- package/dist/controllers/operations-projects.controller.js +87 -0
- package/dist/controllers/operations-projects.controller.js.map +1 -0
- package/dist/controllers/operations-tasks.controller.d.ts +85 -0
- package/dist/controllers/operations-tasks.controller.d.ts.map +1 -0
- package/dist/controllers/operations-tasks.controller.js +90 -0
- package/dist/controllers/operations-tasks.controller.js.map +1 -0
- package/dist/controllers/operations-timesheets.controller.d.ts +99 -0
- package/dist/controllers/operations-timesheets.controller.d.ts.map +1 -0
- package/dist/controllers/operations-timesheets.controller.js +154 -0
- package/dist/controllers/operations-timesheets.controller.js.map +1 -0
- package/dist/dto/create-collaborator-type.dto.d.ts +10 -0
- package/dist/dto/create-collaborator-type.dto.d.ts.map +1 -0
- package/dist/dto/create-collaborator-type.dto.js +56 -0
- package/dist/dto/create-collaborator-type.dto.js.map +1 -0
- package/dist/dto/create-collaborator.dto.d.ts +42 -0
- package/dist/dto/create-collaborator.dto.d.ts.map +1 -0
- package/dist/dto/create-collaborator.dto.js +228 -0
- package/dist/dto/create-collaborator.dto.js.map +1 -0
- package/dist/dto/create-schedule-adjustment-request.dto.d.ts +17 -0
- package/dist/dto/create-schedule-adjustment-request.dto.d.ts.map +1 -0
- package/dist/dto/create-schedule-adjustment-request.dto.js +89 -0
- package/dist/dto/create-schedule-adjustment-request.dto.js.map +1 -0
- package/dist/dto/create-task.dto.d.ts +14 -0
- package/dist/dto/create-task.dto.d.ts.map +1 -0
- package/dist/dto/create-task.dto.js +83 -0
- package/dist/dto/create-task.dto.js.map +1 -0
- package/dist/dto/create-time-off-request.dto.d.ts +9 -0
- package/dist/dto/create-time-off-request.dto.d.ts.map +1 -0
- package/dist/dto/create-time-off-request.dto.js +54 -0
- package/dist/dto/create-time-off-request.dto.js.map +1 -0
- package/dist/dto/create-timesheet-entry.dto.d.ts +12 -0
- package/dist/dto/create-timesheet-entry.dto.d.ts.map +1 -0
- package/dist/dto/create-timesheet-entry.dto.js +75 -0
- package/dist/dto/create-timesheet-entry.dto.js.map +1 -0
- package/dist/dto/list-collaborator-types.dto.d.ts +4 -0
- package/dist/dto/list-collaborator-types.dto.d.ts.map +1 -0
- package/dist/dto/list-collaborator-types.dto.js +29 -0
- package/dist/dto/list-collaborator-types.dto.js.map +1 -0
- package/dist/dto/list-collaborators.dto.d.ts +8 -0
- package/dist/dto/list-collaborators.dto.d.ts.map +1 -0
- package/dist/dto/list-collaborators.dto.js +42 -0
- package/dist/dto/list-collaborators.dto.js.map +1 -0
- package/dist/dto/list-project-options.dto.d.ts +4 -0
- package/dist/dto/list-project-options.dto.d.ts.map +1 -0
- package/dist/dto/list-project-options.dto.js +8 -0
- package/dist/dto/list-project-options.dto.js.map +1 -0
- package/dist/dto/list-tasks.dto.d.ts +7 -0
- package/dist/dto/list-tasks.dto.d.ts.map +1 -0
- package/dist/dto/list-tasks.dto.js +38 -0
- package/dist/dto/list-tasks.dto.js.map +1 -0
- package/dist/dto/list-timesheet-entries.dto.d.ts +10 -0
- package/dist/dto/list-timesheet-entries.dto.d.ts.map +1 -0
- package/dist/dto/list-timesheet-entries.dto.js +54 -0
- package/dist/dto/list-timesheet-entries.dto.js.map +1 -0
- package/dist/dto/update-collaborator-type.dto.d.ts +4 -0
- package/dist/dto/update-collaborator-type.dto.d.ts.map +1 -0
- package/dist/dto/update-collaborator-type.dto.js +8 -0
- package/dist/dto/update-collaborator-type.dto.js.map +1 -0
- package/dist/dto/update-collaborator.dto.d.ts +4 -0
- package/dist/dto/update-collaborator.dto.d.ts.map +1 -0
- package/dist/dto/update-collaborator.dto.js +8 -0
- package/dist/dto/update-collaborator.dto.js.map +1 -0
- package/dist/dto/update-task.dto.d.ts +14 -0
- package/dist/dto/update-task.dto.d.ts.map +1 -0
- package/dist/dto/update-task.dto.js +84 -0
- package/dist/dto/update-task.dto.js.map +1 -0
- package/dist/operations.controller.d.ts +0 -1045
- package/dist/operations.controller.d.ts.map +1 -1
- package/dist/operations.controller.js +0 -429
- package/dist/operations.controller.js.map +1 -1
- package/dist/operations.module.d.ts.map +1 -1
- package/dist/operations.module.js +23 -2
- package/dist/operations.module.js.map +1 -1
- package/dist/operations.service.d.ts +429 -8
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +1931 -165
- package/dist/operations.service.js.map +1 -1
- package/dist/operations.service.spec.js +315 -1
- package/dist/operations.service.spec.js.map +1 -1
- package/dist/services/shared/operations-access.service.d.ts +16 -0
- package/dist/services/shared/operations-access.service.d.ts.map +1 -0
- package/dist/services/shared/operations-access.service.js +48 -0
- package/dist/services/shared/operations-access.service.js.map +1 -0
- package/hedhog/data/dashboard.yaml +20 -0
- package/hedhog/data/dashboard_component.yaml +274 -0
- package/hedhog/data/dashboard_component_role.yaml +174 -0
- package/hedhog/data/dashboard_item.yaml +299 -0
- package/hedhog/data/dashboard_role.yaml +20 -0
- package/hedhog/data/menu.yaml +30 -13
- package/hedhog/data/operations_collaborator_type.yaml +76 -0
- package/hedhog/data/route.yaml +196 -0
- package/hedhog/frontend/app/_components/async-options-combobox.tsx.ejs +231 -0
- package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +125 -40
- package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +740 -106
- package/hedhog/frontend/app/_components/collaborator-select-with-create.tsx.ejs +256 -256
- package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +7 -7
- package/hedhog/frontend/app/_components/contract-template-form-screen.tsx.ejs +306 -306
- package/hedhog/frontend/app/_components/contract-template-select-with-create.tsx.ejs +247 -247
- package/hedhog/frontend/app/_components/contract-wizard-sheet.tsx.ejs +3520 -3520
- package/hedhog/frontend/app/_components/department-select-with-create.tsx.ejs +38 -16
- package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +1504 -52
- package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +1017 -649
- package/hedhog/frontend/app/_components/section-card.tsx.ejs +25 -18
- package/hedhog/frontend/app/_components/system-user-select-with-create.tsx.ejs +609 -0
- package/hedhog/frontend/app/_components/timesheet-task-create-sheet.tsx.ejs +213 -0
- package/hedhog/frontend/app/_lib/api.ts.ejs +30 -1
- package/hedhog/frontend/app/_lib/types.ts.ejs +147 -39
- package/hedhog/frontend/app/_lib/utils/format.ts.ejs +40 -9
- package/hedhog/frontend/app/_lib/utils/forms.ts.ejs +48 -1
- package/hedhog/frontend/app/approvals/page.tsx.ejs +116 -98
- package/hedhog/frontend/app/collaborator-types/page.tsx.ejs +502 -0
- package/hedhog/frontend/app/collaborators/page.tsx.ejs +116 -72
- package/hedhog/frontend/app/contracts/page.tsx.ejs +938 -938
- package/hedhog/frontend/app/contracts/templates/page.tsx.ejs +11 -9
- package/hedhog/frontend/app/departments/page.tsx.ejs +1 -1
- package/hedhog/frontend/app/projects/[id]/edit/page.tsx.ejs +1 -1
- package/hedhog/frontend/app/projects/page.tsx.ejs +364 -133
- package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +244 -120
- package/hedhog/frontend/app/team/page.tsx.ejs +15 -2
- package/hedhog/frontend/app/time-off/page.tsx.ejs +158 -82
- package/hedhog/frontend/app/timesheets/page.tsx.ejs +814 -357
- package/hedhog/frontend/messages/en.json +268 -53
- package/hedhog/frontend/messages/pt.json +484 -271
- package/hedhog/table/operations_collaborator.yaml +26 -13
- package/hedhog/table/operations_collaborator_equity_participation.yaml +43 -0
- package/hedhog/table/operations_collaborator_type.yaml +33 -0
- package/hedhog/table/operations_job_title.yaml +24 -0
- package/hedhog/table/operations_project.yaml +9 -0
- package/hedhog/table/operations_project_assignment.yaml +9 -0
- package/hedhog/table/operations_project_role.yaml +39 -0
- package/hedhog/table/operations_task.yaml +69 -0
- package/hedhog/table/operations_timesheet_entry.yaml +12 -0
- package/package.json +6 -6
- package/src/controllers/operations-approvals.controller.ts +24 -0
- package/src/controllers/operations-collaborators.controller.ts +60 -0
- package/src/controllers/operations-contracts.controller.ts +138 -0
- package/src/controllers/operations-org-structure.controller.ts +92 -0
- package/src/controllers/operations-projects.controller.ts +50 -0
- package/src/controllers/operations-tasks.controller.ts +63 -0
- package/src/controllers/operations-timesheets.controller.ts +100 -0
- package/src/dto/create-collaborator-type.dto.ts +43 -0
- package/src/dto/create-collaborator.dto.ts +223 -0
- package/src/dto/create-schedule-adjustment-request.dto.ts +91 -0
- package/src/dto/create-task.dto.ts +75 -0
- package/src/dto/create-time-off-request.dto.ts +53 -0
- package/src/dto/create-timesheet-entry.dto.ts +67 -0
- package/src/dto/list-collaborator-types.dto.ts +15 -0
- package/src/dto/list-collaborators.dto.ts +30 -0
- package/src/dto/list-project-options.dto.ts +3 -0
- package/src/dto/list-tasks.dto.ts +25 -0
- package/src/dto/list-timesheet-entries.dto.ts +40 -0
- package/src/dto/update-collaborator-type.dto.ts +3 -0
- package/src/dto/update-collaborator.dto.ts +3 -0
- package/src/dto/update-task.dto.ts +76 -0
- package/src/operations.controller.ts +1 -278
- package/src/operations.module.ts +23 -2
- package/src/operations.service.spec.ts +450 -0
- package/src/operations.service.ts +4507 -1561
- package/src/services/shared/operations-access.service.ts +52 -0
|
@@ -362,15 +362,17 @@ export default function OperationsContractTemplatesPage() {
|
|
|
362
362
|
<SheetDescription>{t('sheet.description')}</SheetDescription>
|
|
363
363
|
</SheetHeader>
|
|
364
364
|
|
|
365
|
-
<
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
365
|
+
<div className="px-4">
|
|
366
|
+
<ContractTemplateFormScreen
|
|
367
|
+
templateId={editingTemplate?.id}
|
|
368
|
+
onCancel={() => setIsSheetOpen(false)}
|
|
369
|
+
onSaved={async () => {
|
|
370
|
+
setIsSheetOpen(false);
|
|
371
|
+
setEditingTemplate(null);
|
|
372
|
+
await refetch();
|
|
373
|
+
}}
|
|
374
|
+
/>
|
|
375
|
+
</div>
|
|
374
376
|
</SheetContent>
|
|
375
377
|
</Sheet>
|
|
376
378
|
</Page>
|
|
@@ -2,6 +2,7 @@
|
|
|
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';
|
|
5
6
|
import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
|
|
6
7
|
import {
|
|
7
8
|
Sheet,
|
|
@@ -18,12 +19,15 @@ import {
|
|
|
18
19
|
TableHeader,
|
|
19
20
|
TableRow,
|
|
20
21
|
} from '@/components/ui/table';
|
|
22
|
+
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
|
21
23
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
22
24
|
import {
|
|
23
25
|
CalendarDays,
|
|
24
26
|
Eye,
|
|
25
27
|
FileText,
|
|
26
28
|
FolderKanban,
|
|
29
|
+
LayoutGrid,
|
|
30
|
+
List,
|
|
27
31
|
Pencil,
|
|
28
32
|
PlayCircle,
|
|
29
33
|
ShieldAlert,
|
|
@@ -44,6 +48,10 @@ import {
|
|
|
44
48
|
getStatusBadgeClass,
|
|
45
49
|
} from '../_lib/utils/format';
|
|
46
50
|
|
|
51
|
+
const PROJECT_VIEW_STORAGE_KEY = 'operations-projects-view-mode';
|
|
52
|
+
|
|
53
|
+
type ProjectViewMode = 'table' | 'cards';
|
|
54
|
+
|
|
47
55
|
function parseEditProjectId(value: string | null) {
|
|
48
56
|
const parsed = Number(value);
|
|
49
57
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
|
|
@@ -52,13 +60,23 @@ function parseEditProjectId(value: string | null) {
|
|
|
52
60
|
export default function OperationsProjectsPage() {
|
|
53
61
|
const t = useTranslations('operations.ProjectsPage');
|
|
54
62
|
const commonT = useTranslations('operations.Common');
|
|
55
|
-
const { request, showToastHandler, currentLocaleCode } =
|
|
63
|
+
const { request, showToastHandler, currentLocaleCode, getSettingValue } =
|
|
64
|
+
useApp();
|
|
56
65
|
const access = useOperationsAccess();
|
|
57
66
|
const router = useRouter();
|
|
58
67
|
const pathname = usePathname();
|
|
59
68
|
const searchParams = useSearchParams();
|
|
60
69
|
const [search, setSearch] = useState('');
|
|
61
70
|
const [statusFilter, setStatusFilter] = useState('all');
|
|
71
|
+
const [viewMode, setViewMode] = useState<ProjectViewMode>(() => {
|
|
72
|
+
if (typeof window === 'undefined') {
|
|
73
|
+
return 'table';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const savedViewMode = window.localStorage.getItem(PROJECT_VIEW_STORAGE_KEY);
|
|
77
|
+
|
|
78
|
+
return savedViewMode === 'cards' ? 'cards' : 'table';
|
|
79
|
+
});
|
|
62
80
|
|
|
63
81
|
const createParam = searchParams.get('create');
|
|
64
82
|
const editProjectId = parseEditProjectId(searchParams.get('edit'));
|
|
@@ -137,31 +155,55 @@ export default function OperationsProjectsPage() {
|
|
|
137
155
|
{
|
|
138
156
|
key: 'total',
|
|
139
157
|
title: t('cards.total'),
|
|
158
|
+
description: t('cards.totalDescription'),
|
|
140
159
|
value: projects.length,
|
|
141
160
|
icon: FolderKanban,
|
|
161
|
+
accentClassName: 'from-slate-500/20 via-slate-400/10 to-transparent',
|
|
162
|
+
iconContainerClassName: 'bg-slate-100 text-slate-700',
|
|
142
163
|
},
|
|
143
164
|
{
|
|
144
165
|
key: 'active',
|
|
145
166
|
title: t('cards.active'),
|
|
167
|
+
description: t('cards.activeDescription'),
|
|
146
168
|
value: projects.filter((item) => item.status === 'active').length,
|
|
147
169
|
icon: PlayCircle,
|
|
170
|
+
accentClassName: 'from-green-500/20 via-emerald-500/10 to-transparent',
|
|
171
|
+
iconContainerClassName: 'bg-green-50 text-green-600',
|
|
148
172
|
},
|
|
149
173
|
{
|
|
150
174
|
key: 'atRisk',
|
|
151
175
|
title: t('cards.atRisk'),
|
|
176
|
+
description: t('cards.atRiskDescription'),
|
|
152
177
|
value: projects.filter((item) => item.status === 'at_risk').length,
|
|
153
178
|
icon: ShieldAlert,
|
|
179
|
+
accentClassName: 'from-amber-500/20 via-orange-500/10 to-transparent',
|
|
180
|
+
iconContainerClassName: 'bg-amber-50 text-amber-600',
|
|
154
181
|
},
|
|
155
182
|
{
|
|
156
183
|
key: 'upcomingDeliveries',
|
|
157
184
|
title: t('cards.upcomingDeliveries'),
|
|
185
|
+
description: t('cards.upcomingDeliveriesDescription'),
|
|
158
186
|
value: projects.filter((item) => Boolean(item.endDate)).length,
|
|
159
187
|
icon: CalendarDays,
|
|
188
|
+
accentClassName: 'from-blue-500/20 via-cyan-500/10 to-transparent',
|
|
189
|
+
iconContainerClassName: 'bg-blue-50 text-blue-600',
|
|
160
190
|
},
|
|
161
191
|
],
|
|
162
192
|
[projects, t]
|
|
163
193
|
);
|
|
164
194
|
|
|
195
|
+
const handleViewModeChange = (value: string) => {
|
|
196
|
+
if (value !== 'table' && value !== 'cards') {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
setViewMode(value);
|
|
201
|
+
|
|
202
|
+
if (typeof window !== 'undefined') {
|
|
203
|
+
window.localStorage.setItem(PROJECT_VIEW_STORAGE_KEY, value);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
165
207
|
const toggleArchived = async (project: OperationsProject) => {
|
|
166
208
|
const nextStatus = project.status === 'archived' ? 'active' : 'archived';
|
|
167
209
|
|
|
@@ -198,67 +240,81 @@ export default function OperationsProjectsPage() {
|
|
|
198
240
|
|
|
199
241
|
<KpiCardsGrid items={statsCards} columns={4} />
|
|
200
242
|
|
|
201
|
-
<
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
243
|
+
<div className="flex min-w-0 flex-col gap-4 xl:flex-row xl:items-center">
|
|
244
|
+
<div className="flex-1">
|
|
245
|
+
<SearchBar
|
|
246
|
+
className="w-full"
|
|
247
|
+
searchQuery={search}
|
|
248
|
+
onSearchChange={setSearch}
|
|
249
|
+
onSearch={() => undefined}
|
|
250
|
+
placeholder={t('searchPlaceholder')}
|
|
251
|
+
controls={[
|
|
252
|
+
{
|
|
253
|
+
id: 'status',
|
|
254
|
+
type: 'select',
|
|
255
|
+
value: statusFilter,
|
|
256
|
+
onChange: setStatusFilter,
|
|
257
|
+
placeholder: commonT('labels.status'),
|
|
258
|
+
options: [
|
|
259
|
+
{ value: 'all', label: commonT('filters.allStatuses') },
|
|
260
|
+
{ value: 'planning', label: formatEnumLabel('planning') },
|
|
261
|
+
{ value: 'active', label: formatEnumLabel('active') },
|
|
262
|
+
{ value: 'at_risk', label: formatEnumLabel('at_risk') },
|
|
263
|
+
{ value: 'paused', label: formatEnumLabel('paused') },
|
|
264
|
+
{ value: 'completed', label: formatEnumLabel('completed') },
|
|
265
|
+
{ value: 'archived', label: formatEnumLabel('archived') },
|
|
266
|
+
],
|
|
267
|
+
},
|
|
268
|
+
]}
|
|
269
|
+
/>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
<div className="flex items-center justify-between gap-3 xl:justify-end">
|
|
273
|
+
<span className="text-xs font-medium text-muted-foreground">
|
|
274
|
+
{t('viewMode')}
|
|
275
|
+
</span>
|
|
276
|
+
<ToggleGroup
|
|
277
|
+
type="single"
|
|
278
|
+
value={viewMode}
|
|
279
|
+
onValueChange={handleViewModeChange}
|
|
280
|
+
variant="outline"
|
|
281
|
+
size="sm"
|
|
282
|
+
aria-label={t('viewMode')}
|
|
283
|
+
>
|
|
284
|
+
<ToggleGroupItem
|
|
285
|
+
value="table"
|
|
286
|
+
className="gap-1.5 px-2.5"
|
|
287
|
+
aria-label={t('viewModeTable')}
|
|
288
|
+
>
|
|
289
|
+
<List className="h-4 w-4" />
|
|
290
|
+
<span className="hidden sm:inline">{t('viewModeTable')}</span>
|
|
291
|
+
</ToggleGroupItem>
|
|
292
|
+
<ToggleGroupItem
|
|
293
|
+
value="cards"
|
|
294
|
+
className="gap-1.5 px-2.5"
|
|
295
|
+
aria-label={t('viewModeCards')}
|
|
296
|
+
>
|
|
297
|
+
<LayoutGrid className="h-4 w-4" />
|
|
298
|
+
<span className="hidden sm:inline">{t('viewModeCards')}</span>
|
|
299
|
+
</ToggleGroupItem>
|
|
300
|
+
</ToggleGroup>
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
225
303
|
|
|
226
304
|
{filteredRows.length > 0 ? (
|
|
227
|
-
|
|
228
|
-
<
|
|
229
|
-
|
|
230
|
-
<
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
<
|
|
235
|
-
|
|
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>
|
|
305
|
+
viewMode === 'cards' ? (
|
|
306
|
+
<div className="grid gap-4 md:grid-cols-2 2xl:grid-cols-3">
|
|
307
|
+
{filteredRows.map((project) => (
|
|
308
|
+
<Card
|
|
309
|
+
key={project.id}
|
|
310
|
+
className="cursor-pointer overflow-hidden border-border/60 py-0 shadow-sm transition-all hover:-translate-y-0.5 hover:shadow-md"
|
|
311
|
+
>
|
|
312
|
+
<CardContent className="space-y-4 p-4">
|
|
313
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
|
260
314
|
<div className="min-w-0">
|
|
261
|
-
<div className="truncate font-
|
|
315
|
+
<div className="truncate font-semibold">
|
|
316
|
+
{project.name}
|
|
317
|
+
</div>
|
|
262
318
|
<div className="truncate text-xs text-muted-foreground">
|
|
263
319
|
{[
|
|
264
320
|
project.code,
|
|
@@ -269,96 +325,271 @@ export default function OperationsProjectsPage() {
|
|
|
269
325
|
.join(' • ') || commonT('labels.notAvailable')}
|
|
270
326
|
</div>
|
|
271
327
|
</div>
|
|
272
|
-
</TableCell>
|
|
273
|
-
<TableCell>
|
|
274
|
-
<div className="truncate">
|
|
275
|
-
{project.clientName || commonT('labels.notAvailable')}
|
|
276
|
-
</div>
|
|
277
|
-
</TableCell>
|
|
278
|
-
<TableCell>
|
|
279
328
|
<StatusBadge
|
|
280
329
|
label={formatEnumLabel(project.status)}
|
|
281
330
|
className={getStatusBadgeClass(project.status)}
|
|
282
331
|
/>
|
|
283
|
-
</
|
|
284
|
-
|
|
285
|
-
|
|
332
|
+
</div>
|
|
333
|
+
|
|
334
|
+
<div className="grid gap-2 text-sm text-muted-foreground lg:grid-cols-2">
|
|
335
|
+
<div>
|
|
336
|
+
<span className="font-medium text-foreground">
|
|
337
|
+
{commonT('labels.client')}:
|
|
338
|
+
</span>{' '}
|
|
339
|
+
{project.clientName || commonT('labels.notAvailable')}
|
|
340
|
+
</div>
|
|
341
|
+
<div>
|
|
342
|
+
<span className="font-medium text-foreground">
|
|
343
|
+
{commonT('labels.manager')}:
|
|
344
|
+
</span>{' '}
|
|
286
345
|
{project.managerName || commonT('labels.notAssigned')}
|
|
287
346
|
</div>
|
|
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
|
-
|
|
347
|
+
<div>
|
|
348
|
+
<span className="font-medium text-foreground">
|
|
349
|
+
{commonT('labels.teamSize')}:
|
|
350
|
+
</span>{' '}
|
|
351
|
+
{project.teamSize ?? 0}
|
|
352
|
+
</div>
|
|
353
|
+
<div>
|
|
354
|
+
<span className="font-medium text-foreground">
|
|
355
|
+
{commonT('labels.startDate')}:
|
|
356
|
+
</span>{' '}
|
|
357
|
+
{formatDate(
|
|
358
|
+
project.startDate,
|
|
359
|
+
getSettingValue,
|
|
360
|
+
currentLocaleCode
|
|
361
|
+
)}
|
|
362
|
+
</div>
|
|
363
|
+
<div>
|
|
364
|
+
<span className="font-medium text-foreground">
|
|
365
|
+
{commonT('labels.endDate')}:
|
|
366
|
+
</span>{' '}
|
|
367
|
+
{formatDate(
|
|
368
|
+
project.endDate,
|
|
369
|
+
getSettingValue,
|
|
370
|
+
currentLocaleCode
|
|
371
|
+
)}
|
|
372
|
+
</div>
|
|
373
|
+
<div className="flex items-center gap-2">
|
|
374
|
+
<span className="font-medium text-foreground">
|
|
375
|
+
{commonT('labels.contractStatus')}:
|
|
376
|
+
</span>
|
|
377
|
+
{project.contractStatus ? (
|
|
378
|
+
<StatusBadge
|
|
379
|
+
label={formatEnumLabel(project.contractStatus)}
|
|
380
|
+
className={getStatusBadgeClass(
|
|
381
|
+
project.contractStatus
|
|
382
|
+
)}
|
|
383
|
+
/>
|
|
384
|
+
) : (
|
|
385
|
+
<span>{commonT('labels.notAssigned')}</span>
|
|
386
|
+
)}
|
|
387
|
+
</div>
|
|
388
|
+
</div>
|
|
389
|
+
|
|
390
|
+
<div className="flex flex-wrap justify-end gap-2 border-t border-border/60 pt-3">
|
|
391
|
+
<Button variant="outline" size="icon" asChild>
|
|
392
|
+
<Link href={`/operations/projects/${project.id}`}>
|
|
393
|
+
<Eye className="size-4" />
|
|
394
|
+
</Link>
|
|
395
|
+
</Button>
|
|
396
|
+
{access.isDirector ? (
|
|
397
|
+
<Button
|
|
398
|
+
variant="outline"
|
|
399
|
+
size="icon"
|
|
400
|
+
className="cursor-pointer"
|
|
401
|
+
onClick={() => openEditSheet(project.id)}
|
|
402
|
+
>
|
|
403
|
+
<Pencil className="size-4" />
|
|
314
404
|
</Button>
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
405
|
+
) : null}
|
|
406
|
+
<Button
|
|
407
|
+
variant="outline"
|
|
408
|
+
size="icon"
|
|
409
|
+
asChild={Boolean(project.contractId)}
|
|
410
|
+
disabled={!project.contractId}
|
|
411
|
+
>
|
|
412
|
+
{project.contractId ? (
|
|
413
|
+
<Link
|
|
414
|
+
href={`/operations/contracts?edit=${project.contractId}`}
|
|
321
415
|
>
|
|
322
|
-
<
|
|
323
|
-
</
|
|
324
|
-
) :
|
|
416
|
+
<FileText className="size-4" />
|
|
417
|
+
</Link>
|
|
418
|
+
) : (
|
|
419
|
+
<span>
|
|
420
|
+
<FileText className="size-4" />
|
|
421
|
+
</span>
|
|
422
|
+
)}
|
|
423
|
+
</Button>
|
|
424
|
+
{access.isDirector ? (
|
|
325
425
|
<Button
|
|
326
426
|
variant="outline"
|
|
327
|
-
size="
|
|
328
|
-
|
|
329
|
-
|
|
427
|
+
size="sm"
|
|
428
|
+
className="cursor-pointer"
|
|
429
|
+
onClick={() => void toggleArchived(project)}
|
|
330
430
|
>
|
|
331
|
-
{project.
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
>
|
|
335
|
-
<FileText className="size-4" />
|
|
336
|
-
</Link>
|
|
337
|
-
) : (
|
|
338
|
-
<span>
|
|
339
|
-
<FileText className="size-4" />
|
|
340
|
-
</span>
|
|
341
|
-
)}
|
|
431
|
+
{project.status === 'archived'
|
|
432
|
+
? commonT('actions.activate')
|
|
433
|
+
: t('actions.archive')}
|
|
342
434
|
</Button>
|
|
343
|
-
|
|
435
|
+
) : null}
|
|
436
|
+
</div>
|
|
437
|
+
</CardContent>
|
|
438
|
+
</Card>
|
|
439
|
+
))}
|
|
440
|
+
</div>
|
|
441
|
+
) : (
|
|
442
|
+
<div className="overflow-x-auto rounded-md border">
|
|
443
|
+
<Table className="table-fixed">
|
|
444
|
+
<TableHeader>
|
|
445
|
+
<TableRow>
|
|
446
|
+
<TableHead className="w-[30%]">
|
|
447
|
+
{commonT('labels.project')}
|
|
448
|
+
</TableHead>
|
|
449
|
+
<TableHead>{commonT('labels.client')}</TableHead>
|
|
450
|
+
<TableHead>{commonT('labels.status')}</TableHead>
|
|
451
|
+
<TableHead className="hidden lg:table-cell">
|
|
452
|
+
{commonT('labels.manager')}
|
|
453
|
+
</TableHead>
|
|
454
|
+
<TableHead className="hidden md:table-cell">
|
|
455
|
+
{commonT('labels.teamSize')}
|
|
456
|
+
</TableHead>
|
|
457
|
+
<TableHead className="hidden xl:table-cell">
|
|
458
|
+
{commonT('labels.startDate')}
|
|
459
|
+
</TableHead>
|
|
460
|
+
<TableHead className="hidden xl:table-cell">
|
|
461
|
+
{commonT('labels.endDate')}
|
|
462
|
+
</TableHead>
|
|
463
|
+
<TableHead className="hidden 2xl:table-cell">
|
|
464
|
+
{commonT('labels.contractStatus')}
|
|
465
|
+
</TableHead>
|
|
466
|
+
<TableHead className="w-30 text-right sm:w-42.5">
|
|
467
|
+
{commonT('labels.actions')}
|
|
468
|
+
</TableHead>
|
|
469
|
+
</TableRow>
|
|
470
|
+
</TableHeader>
|
|
471
|
+
<TableBody>
|
|
472
|
+
{filteredRows.map((project) => (
|
|
473
|
+
<TableRow
|
|
474
|
+
key={project.id}
|
|
475
|
+
className="cursor-pointer hover:bg-muted/30"
|
|
476
|
+
>
|
|
477
|
+
<TableCell>
|
|
478
|
+
<div className="min-w-0">
|
|
479
|
+
<div className="truncate font-medium">
|
|
480
|
+
{project.name}
|
|
481
|
+
</div>
|
|
482
|
+
<div className="truncate text-xs text-muted-foreground">
|
|
483
|
+
{[
|
|
484
|
+
project.code,
|
|
485
|
+
project.myRoleLabel,
|
|
486
|
+
project.contractName,
|
|
487
|
+
]
|
|
488
|
+
.filter(Boolean)
|
|
489
|
+
.join(' • ') || commonT('labels.notAvailable')}
|
|
490
|
+
</div>
|
|
491
|
+
</div>
|
|
492
|
+
</TableCell>
|
|
493
|
+
<TableCell>
|
|
494
|
+
<div className="truncate">
|
|
495
|
+
{project.clientName || commonT('labels.notAvailable')}
|
|
496
|
+
</div>
|
|
497
|
+
</TableCell>
|
|
498
|
+
<TableCell>
|
|
499
|
+
<StatusBadge
|
|
500
|
+
label={formatEnumLabel(project.status)}
|
|
501
|
+
className={getStatusBadgeClass(project.status)}
|
|
502
|
+
/>
|
|
503
|
+
</TableCell>
|
|
504
|
+
<TableCell className="hidden lg:table-cell">
|
|
505
|
+
<div className="truncate">
|
|
506
|
+
{project.managerName || commonT('labels.notAssigned')}
|
|
507
|
+
</div>
|
|
508
|
+
</TableCell>
|
|
509
|
+
<TableCell className="hidden md:table-cell">
|
|
510
|
+
{project.teamSize ?? 0}
|
|
511
|
+
</TableCell>
|
|
512
|
+
<TableCell className="hidden xl:table-cell">
|
|
513
|
+
{formatDate(
|
|
514
|
+
project.startDate,
|
|
515
|
+
getSettingValue,
|
|
516
|
+
currentLocaleCode
|
|
517
|
+
)}
|
|
518
|
+
</TableCell>
|
|
519
|
+
<TableCell className="hidden xl:table-cell">
|
|
520
|
+
{formatDate(
|
|
521
|
+
project.endDate,
|
|
522
|
+
getSettingValue,
|
|
523
|
+
currentLocaleCode
|
|
524
|
+
)}
|
|
525
|
+
</TableCell>
|
|
526
|
+
<TableCell className="hidden 2xl:table-cell">
|
|
527
|
+
{project.contractStatus ? (
|
|
528
|
+
<StatusBadge
|
|
529
|
+
label={formatEnumLabel(project.contractStatus)}
|
|
530
|
+
className={getStatusBadgeClass(
|
|
531
|
+
project.contractStatus
|
|
532
|
+
)}
|
|
533
|
+
/>
|
|
534
|
+
) : (
|
|
535
|
+
commonT('labels.notAssigned')
|
|
536
|
+
)}
|
|
537
|
+
</TableCell>
|
|
538
|
+
<TableCell>
|
|
539
|
+
<div className="flex flex-wrap justify-end gap-1.5 sm:gap-2">
|
|
540
|
+
<Button variant="outline" size="icon" asChild>
|
|
541
|
+
<Link href={`/operations/projects/${project.id}`}>
|
|
542
|
+
<Eye className="size-4" />
|
|
543
|
+
</Link>
|
|
544
|
+
</Button>
|
|
545
|
+
{access.isDirector ? (
|
|
546
|
+
<Button
|
|
547
|
+
variant="outline"
|
|
548
|
+
size="icon"
|
|
549
|
+
className="cursor-pointer"
|
|
550
|
+
onClick={() => openEditSheet(project.id)}
|
|
551
|
+
>
|
|
552
|
+
<Pencil className="size-4" />
|
|
553
|
+
</Button>
|
|
554
|
+
) : null}
|
|
344
555
|
<Button
|
|
345
556
|
variant="outline"
|
|
346
|
-
size="
|
|
347
|
-
|
|
348
|
-
|
|
557
|
+
size="icon"
|
|
558
|
+
asChild={Boolean(project.contractId)}
|
|
559
|
+
disabled={!project.contractId}
|
|
349
560
|
>
|
|
350
|
-
{project.
|
|
351
|
-
|
|
352
|
-
|
|
561
|
+
{project.contractId ? (
|
|
562
|
+
<Link
|
|
563
|
+
href={`/operations/contracts?edit=${project.contractId}`}
|
|
564
|
+
>
|
|
565
|
+
<FileText className="size-4" />
|
|
566
|
+
</Link>
|
|
567
|
+
) : (
|
|
568
|
+
<span>
|
|
569
|
+
<FileText className="size-4" />
|
|
570
|
+
</span>
|
|
571
|
+
)}
|
|
353
572
|
</Button>
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
573
|
+
{access.isDirector ? (
|
|
574
|
+
<Button
|
|
575
|
+
variant="outline"
|
|
576
|
+
size="sm"
|
|
577
|
+
className="cursor-pointer"
|
|
578
|
+
onClick={() => void toggleArchived(project)}
|
|
579
|
+
>
|
|
580
|
+
{project.status === 'archived'
|
|
581
|
+
? commonT('actions.activate')
|
|
582
|
+
: t('actions.archive')}
|
|
583
|
+
</Button>
|
|
584
|
+
) : null}
|
|
585
|
+
</div>
|
|
586
|
+
</TableCell>
|
|
587
|
+
</TableRow>
|
|
588
|
+
))}
|
|
589
|
+
</TableBody>
|
|
590
|
+
</Table>
|
|
591
|
+
</div>
|
|
592
|
+
)
|
|
362
593
|
) : (
|
|
363
594
|
<EmptyState
|
|
364
595
|
icon={<FolderKanban className="size-12" />}
|