@hed-hog/operations 0.0.2 → 0.0.285

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.
Files changed (108) hide show
  1. package/README.md +122 -0
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/operations-data.controller.d.ts +139 -0
  7. package/dist/operations-data.controller.d.ts.map +1 -0
  8. package/dist/operations-data.controller.js +113 -0
  9. package/dist/operations-data.controller.js.map +1 -0
  10. package/dist/operations-growth.controller.d.ts +48 -0
  11. package/dist/operations-growth.controller.d.ts.map +1 -0
  12. package/dist/operations-growth.controller.js +90 -0
  13. package/dist/operations-growth.controller.js.map +1 -0
  14. package/dist/operations.module.d.ts.map +1 -1
  15. package/dist/operations.module.js +10 -4
  16. package/dist/operations.module.js.map +1 -1
  17. package/dist/operations.service.d.ts +178 -0
  18. package/dist/operations.service.d.ts.map +1 -0
  19. package/dist/operations.service.js +134 -0
  20. package/dist/operations.service.js.map +1 -0
  21. package/hedhog/data/menu.yaml +251 -132
  22. package/hedhog/data/operations_career_level.yaml +102 -0
  23. package/hedhog/data/operations_career_track.yaml +8 -0
  24. package/hedhog/data/operations_certification.yaml +38 -0
  25. package/hedhog/data/operations_evaluation_cycle.yaml +18 -0
  26. package/hedhog/data/operations_performance_criterion.yaml +48 -0
  27. package/hedhog/data/role.yaml +14 -7
  28. package/hedhog/data/route.yaml +143 -80
  29. package/hedhog/frontend/app/_components/allocation-calendar.tsx.ejs +56 -56
  30. package/hedhog/frontend/app/_components/kanban-board.tsx.ejs +83 -83
  31. package/hedhog/frontend/app/_components/operations-header.tsx.ejs +29 -29
  32. package/hedhog/frontend/app/_components/section-card.tsx.ejs +32 -32
  33. package/hedhog/frontend/app/_components/status-badge.tsx.ejs +15 -15
  34. package/hedhog/frontend/app/_components/timesheet-entry-dialog.tsx.ejs +142 -142
  35. package/hedhog/frontend/app/_lib/hooks/use-operations-data.ts.ejs +41 -41
  36. package/hedhog/frontend/app/_lib/hooks/use-operations-growth-data.ts.ejs +63 -0
  37. package/hedhog/frontend/app/_lib/mocks/allocations.mock.ts.ejs +74 -74
  38. package/hedhog/frontend/app/_lib/mocks/contracts.mock.ts.ejs +74 -74
  39. package/hedhog/frontend/app/_lib/mocks/operations-growth.mock.ts.ejs +824 -0
  40. package/hedhog/frontend/app/_lib/mocks/projects.mock.ts.ejs +60 -60
  41. package/hedhog/frontend/app/_lib/mocks/tasks.mock.ts.ejs +88 -88
  42. package/hedhog/frontend/app/_lib/mocks/timesheets.mock.ts.ejs +84 -84
  43. package/hedhog/frontend/app/_lib/mocks/users.mock.ts.ejs +67 -67
  44. package/hedhog/frontend/app/_lib/services/contracts.service.ts.ejs +10 -10
  45. package/hedhog/frontend/app/_lib/services/operations-growth.service.ts.ejs +31 -0
  46. package/hedhog/frontend/app/_lib/services/projects.service.ts.ejs +10 -10
  47. package/hedhog/frontend/app/_lib/services/tasks.service.ts.ejs +10 -10
  48. package/hedhog/frontend/app/_lib/services/timesheets.service.ts.ejs +10 -10
  49. package/hedhog/frontend/app/_lib/types/operations-growth.ts.ejs +209 -0
  50. package/hedhog/frontend/app/_lib/types/operations.ts.ejs +95 -95
  51. package/hedhog/frontend/app/_lib/utils/format.ts.ejs +25 -25
  52. package/hedhog/frontend/app/_lib/utils/growth.ts.ejs +62 -0
  53. package/hedhog/frontend/app/_lib/utils/metrics.ts.ejs +103 -103
  54. package/hedhog/frontend/app/_lib/utils/status.ts.ejs +80 -80
  55. package/hedhog/frontend/app/allocations/page.tsx.ejs +154 -99
  56. package/hedhog/frontend/app/approvals/page.tsx.ejs +147 -147
  57. package/hedhog/frontend/app/career/page.tsx.ejs +143 -0
  58. package/hedhog/frontend/app/certifications/page.tsx.ejs +201 -0
  59. package/hedhog/frontend/app/contracts/[id]/page.tsx.ejs +108 -108
  60. package/hedhog/frontend/app/contracts/page.tsx.ejs +180 -124
  61. package/hedhog/frontend/app/evaluations/page.tsx.ejs +277 -0
  62. package/hedhog/frontend/app/goals/page.tsx.ejs +170 -0
  63. package/hedhog/frontend/app/growth/page.tsx.ejs +288 -0
  64. package/hedhog/frontend/app/layout.tsx.ejs +9 -9
  65. package/hedhog/frontend/app/manager/page.tsx.ejs +175 -0
  66. package/hedhog/frontend/app/page.tsx.ejs +177 -177
  67. package/hedhog/frontend/app/projects/[id]/page.tsx.ejs +186 -186
  68. package/hedhog/frontend/app/projects/page.tsx.ejs +111 -111
  69. package/hedhog/frontend/app/rewards/page.tsx.ejs +195 -0
  70. package/hedhog/frontend/app/tasks/page.tsx.ejs +47 -47
  71. package/hedhog/frontend/app/timesheets/page.tsx.ejs +126 -126
  72. package/hedhog/frontend/messages/en.json +152 -142
  73. package/hedhog/frontend/messages/pt.json +152 -142
  74. package/hedhog/table/operations_allocation.yaml +52 -0
  75. package/hedhog/table/operations_calibration_item.yaml +61 -0
  76. package/hedhog/table/operations_calibration_session.yaml +25 -0
  77. package/hedhog/table/operations_career_level.yaml +75 -0
  78. package/hedhog/table/operations_career_track.yaml +21 -0
  79. package/hedhog/table/operations_certification.yaml +48 -0
  80. package/hedhog/table/operations_contract.yaml +57 -0
  81. package/hedhog/table/operations_employee.yaml +64 -0
  82. package/hedhog/table/operations_employee_certification.yaml +43 -0
  83. package/hedhog/table/operations_employee_connect.yaml +61 -0
  84. package/hedhog/table/operations_employee_evaluation.yaml +113 -0
  85. package/hedhog/table/operations_employee_evaluation_item.yaml +39 -0
  86. package/hedhog/table/operations_employee_profile.yaml +80 -0
  87. package/hedhog/table/operations_employee_skill_matrix.yaml +30 -0
  88. package/hedhog/table/operations_evaluation_cycle.yaml +31 -0
  89. package/hedhog/table/operations_goal.yaml +67 -0
  90. package/hedhog/table/operations_goal_progress.yaml +31 -0
  91. package/hedhog/table/operations_performance_criterion.yaml +29 -0
  92. package/hedhog/table/operations_project.yaml +66 -0
  93. package/hedhog/table/operations_promotion_readiness.yaml +49 -0
  94. package/hedhog/table/operations_promotion_recommendation.yaml +63 -0
  95. package/hedhog/table/operations_public_recognition.yaml +46 -0
  96. package/hedhog/table/operations_reward.yaml +100 -0
  97. package/hedhog/table/operations_score_event.yaml +81 -0
  98. package/hedhog/table/operations_task.yaml +60 -0
  99. package/hedhog/table/operations_timesheet.yaml +49 -0
  100. package/hedhog/table/operations_timesheet_entry.yaml +51 -0
  101. package/package.json +3 -3
  102. package/src/index.ts +2 -1
  103. package/src/language/en.json +8 -8
  104. package/src/language/pt.json +8 -8
  105. package/src/operations-data.controller.ts +54 -0
  106. package/src/operations-growth.controller.ts +44 -0
  107. package/src/operations.module.ts +21 -15
  108. package/src/operations.service.ts +137 -0
@@ -1,108 +1,108 @@
1
- 'use client';
2
-
3
- import { Page } from '@/components/entity-list';
4
- import { useParams } from 'next/navigation';
5
- import { OperationsHeader } from '../../_components/operations-header';
6
- import { SectionCard } from '../../_components/section-card';
7
- import { StatusBadge } from '../../_components/status-badge';
8
- import { useOperationsData } from '../../_lib/hooks/use-operations-data';
9
- import { formatCurrency, formatDate } from '../../_lib/utils/format';
10
- import {
11
- getContractBadgeClasses,
12
- getContractTypeLabel,
13
- } from '../../_lib/utils/status';
14
-
15
- export default function ContractDetailsPage() {
16
- const params = useParams<{ id: string }>();
17
- const { contracts, projects } = useOperationsData();
18
- const contract = contracts.find((item) => item.id === params.id);
19
-
20
- if (!contract) {
21
- return <Page>Contract not found.</Page>;
22
- }
23
-
24
- const linkedProjects = projects.filter((project) =>
25
- contract.linkedProjectIds.includes(project.id)
26
- );
27
-
28
- return (
29
- <Page>
30
- <OperationsHeader
31
- title={contract.name}
32
- description="Static mock detail page with production-ready structure."
33
- current="Contract Details"
34
- />
35
-
36
- <div className="grid gap-4 xl:grid-cols-2">
37
- <SectionCard title="Contract Info">
38
- <div className="grid gap-3 text-sm sm:grid-cols-2">
39
- <p>
40
- <span className="font-medium">Client:</span> {contract.client}
41
- </p>
42
- <p>
43
- <span className="font-medium">Type:</span>{' '}
44
- {getContractTypeLabel(contract.type)}
45
- </p>
46
- <p>
47
- <span className="font-medium">Start:</span>{' '}
48
- {formatDate(contract.startDate)}
49
- </p>
50
- <p>
51
- <span className="font-medium">End:</span>{' '}
52
- {formatDate(contract.endDate)}
53
- </p>
54
- <p>
55
- <span className="font-medium">Hour Rate:</span>{' '}
56
- {formatCurrency(contract.hourlyRate)}
57
- </p>
58
- <p>
59
- <span className="font-medium">Hour Limit:</span>{' '}
60
- {contract.hourLimit}h
61
- </p>
62
- <div className="sm:col-span-2">
63
- <StatusBadge
64
- label={contract.status}
65
- className={getContractBadgeClasses(contract.status)}
66
- />
67
- </div>
68
- </div>
69
- </SectionCard>
70
-
71
- <SectionCard title="Linked Projects">
72
- <div className="space-y-3">
73
- {linkedProjects.map((project) => (
74
- <div key={project.id} className="rounded-lg border p-3">
75
- <p className="font-medium">{project.name}</p>
76
- <p className="text-sm text-muted-foreground">{project.client}</p>
77
- </div>
78
- ))}
79
- </div>
80
- </SectionCard>
81
-
82
- <SectionCard title="Billing Rules">
83
- <ul className="space-y-2 text-sm">
84
- {contract.billingRules.map((rule) => (
85
- <li key={rule} className="rounded-md bg-muted/40 p-3">
86
- {rule}
87
- </li>
88
- ))}
89
- </ul>
90
- </SectionCard>
91
-
92
- <SectionCard title="SLA">
93
- <p className="text-sm text-muted-foreground">{contract.sla}</p>
94
- </SectionCard>
95
-
96
- <SectionCard title="Adjustments / Revisions" className="xl:col-span-2">
97
- <ul className="space-y-2 text-sm">
98
- {contract.revisions.map((revision) => (
99
- <li key={revision} className="rounded-md border p-3">
100
- {revision}
101
- </li>
102
- ))}
103
- </ul>
104
- </SectionCard>
105
- </div>
106
- </Page>
107
- );
108
- }
1
+ 'use client';
2
+
3
+ import { Page } from '@/components/entity-list';
4
+ import { useParams } from 'next/navigation';
5
+ import { OperationsHeader } from '../../_components/operations-header';
6
+ import { SectionCard } from '../../_components/section-card';
7
+ import { StatusBadge } from '../../_components/status-badge';
8
+ import { useOperationsData } from '../../_lib/hooks/use-operations-data';
9
+ import { formatCurrency, formatDate } from '../../_lib/utils/format';
10
+ import {
11
+ getContractBadgeClasses,
12
+ getContractTypeLabel,
13
+ } from '../../_lib/utils/status';
14
+
15
+ export default function ContractDetailsPage() {
16
+ const params = useParams<{ id: string }>();
17
+ const { contracts, projects } = useOperationsData();
18
+ const contract = contracts.find((item) => item.id === params.id);
19
+
20
+ if (!contract) {
21
+ return <Page>Contract not found.</Page>;
22
+ }
23
+
24
+ const linkedProjects = projects.filter((project) =>
25
+ contract.linkedProjectIds.includes(project.id)
26
+ );
27
+
28
+ return (
29
+ <Page>
30
+ <OperationsHeader
31
+ title={contract.name}
32
+ description="Static mock detail page with production-ready structure."
33
+ current="Contract Details"
34
+ />
35
+
36
+ <div className="grid gap-4 xl:grid-cols-2">
37
+ <SectionCard title="Contract Info">
38
+ <div className="grid gap-3 text-sm sm:grid-cols-2">
39
+ <p>
40
+ <span className="font-medium">Client:</span> {contract.client}
41
+ </p>
42
+ <p>
43
+ <span className="font-medium">Type:</span>{' '}
44
+ {getContractTypeLabel(contract.type)}
45
+ </p>
46
+ <p>
47
+ <span className="font-medium">Start:</span>{' '}
48
+ {formatDate(contract.startDate)}
49
+ </p>
50
+ <p>
51
+ <span className="font-medium">End:</span>{' '}
52
+ {formatDate(contract.endDate)}
53
+ </p>
54
+ <p>
55
+ <span className="font-medium">Hour Rate:</span>{' '}
56
+ {formatCurrency(contract.hourlyRate)}
57
+ </p>
58
+ <p>
59
+ <span className="font-medium">Hour Limit:</span>{' '}
60
+ {contract.hourLimit}h
61
+ </p>
62
+ <div className="sm:col-span-2">
63
+ <StatusBadge
64
+ label={contract.status}
65
+ className={getContractBadgeClasses(contract.status)}
66
+ />
67
+ </div>
68
+ </div>
69
+ </SectionCard>
70
+
71
+ <SectionCard title="Linked Projects">
72
+ <div className="space-y-3">
73
+ {linkedProjects.map((project) => (
74
+ <div key={project.id} className="rounded-lg border p-3">
75
+ <p className="font-medium">{project.name}</p>
76
+ <p className="text-sm text-muted-foreground">{project.client}</p>
77
+ </div>
78
+ ))}
79
+ </div>
80
+ </SectionCard>
81
+
82
+ <SectionCard title="Billing Rules">
83
+ <ul className="space-y-2 text-sm">
84
+ {contract.billingRules.map((rule) => (
85
+ <li key={rule} className="rounded-md bg-muted/40 p-3">
86
+ {rule}
87
+ </li>
88
+ ))}
89
+ </ul>
90
+ </SectionCard>
91
+
92
+ <SectionCard title="SLA">
93
+ <p className="text-sm text-muted-foreground">{contract.sla}</p>
94
+ </SectionCard>
95
+
96
+ <SectionCard title="Adjustments / Revisions" className="xl:col-span-2">
97
+ <ul className="space-y-2 text-sm">
98
+ {contract.revisions.map((revision) => (
99
+ <li key={revision} className="rounded-md border p-3">
100
+ {revision}
101
+ </li>
102
+ ))}
103
+ </ul>
104
+ </SectionCard>
105
+ </div>
106
+ </Page>
107
+ );
108
+ }
@@ -1,124 +1,180 @@
1
- 'use client';
2
-
3
- import { Page } from '@/components/entity-list';
4
- import { Input } from '@/components/ui/input';
5
- import {
6
- Select,
7
- SelectContent,
8
- SelectItem,
9
- SelectTrigger,
10
- SelectValue,
11
- } from '@/components/ui/select';
12
- import {
13
- Table,
14
- TableBody,
15
- TableCell,
16
- TableHead,
17
- TableHeader,
18
- TableRow,
19
- } from '@/components/ui/table';
20
- import { useTranslations } from 'next-intl';
21
- import Link from 'next/link';
22
- import { useMemo, useState } from 'react';
23
- import { OperationsHeader } from '../_components/operations-header';
24
- import { SectionCard } from '../_components/section-card';
25
- import { StatusBadge } from '../_components/status-badge';
26
- import { useOperationsData } from '../_lib/hooks/use-operations-data';
27
- import { formatCurrency, formatDate } from '../_lib/utils/format';
28
- import {
29
- getContractBadgeClasses,
30
- getContractTypeLabel,
31
- } from '../_lib/utils/status';
32
-
33
- export default function ContractsPage() {
34
- const t = useTranslations('operations.ContractsPage');
35
- const { contracts } = useOperationsData();
36
- const [search, setSearch] = useState('');
37
- const [status, setStatus] = useState('all');
38
-
39
- const filteredContracts = useMemo(
40
- () =>
41
- contracts.filter((contract) => {
42
- const matchesSearch =
43
- `${contract.name} ${contract.client}`
44
- .toLowerCase()
45
- .includes(search.toLowerCase());
46
- const matchesStatus = status === 'all' || contract.status === status;
47
- return matchesSearch && matchesStatus;
48
- }),
49
- [contracts, search, status]
50
- );
51
-
52
- return (
53
- <Page>
54
- <OperationsHeader
55
- title={t('title')}
56
- description={t('description')}
57
- current={t('breadcrumb')}
58
- />
59
-
60
- <SectionCard title={t('tableTitle')} description={t('tableDescription')}>
61
- <div className="mb-4 flex flex-col gap-3 lg:flex-row">
62
- <Input
63
- value={search}
64
- onChange={(event) => setSearch(event.target.value)}
65
- placeholder={t('searchPlaceholder')}
66
- />
67
- <Select value={status} onValueChange={setStatus}>
68
- <SelectTrigger className="w-full lg:w-[220px]">
69
- <SelectValue />
70
- </SelectTrigger>
71
- <SelectContent>
72
- <SelectItem value="all">{t('filters.all')}</SelectItem>
73
- <SelectItem value="active">{t('filters.active')}</SelectItem>
74
- <SelectItem value="renewal">{t('filters.renewal')}</SelectItem>
75
- <SelectItem value="expired">{t('filters.expired')}</SelectItem>
76
- <SelectItem value="draft">{t('filters.draft')}</SelectItem>
77
- </SelectContent>
78
- </Select>
79
- </div>
80
-
81
- <Table>
82
- <TableHeader>
83
- <TableRow>
84
- <TableHead>{t('columns.name')}</TableHead>
85
- <TableHead>{t('columns.client')}</TableHead>
86
- <TableHead>{t('columns.type')}</TableHead>
87
- <TableHead>{t('columns.startDate')}</TableHead>
88
- <TableHead>{t('columns.endDate')}</TableHead>
89
- <TableHead>{t('columns.hourRate')}</TableHead>
90
- <TableHead>{t('columns.hourLimit')}</TableHead>
91
- <TableHead>{t('columns.status')}</TableHead>
92
- </TableRow>
93
- </TableHeader>
94
- <TableBody>
95
- {filteredContracts.map((contract) => (
96
- <TableRow key={contract.id}>
97
- <TableCell>
98
- <Link
99
- className="font-medium text-primary hover:underline"
100
- href={`/operations/contracts/${contract.id}`}
101
- >
102
- {contract.name}
103
- </Link>
104
- </TableCell>
105
- <TableCell>{contract.client}</TableCell>
106
- <TableCell>{getContractTypeLabel(contract.type)}</TableCell>
107
- <TableCell>{formatDate(contract.startDate)}</TableCell>
108
- <TableCell>{formatDate(contract.endDate)}</TableCell>
109
- <TableCell>{formatCurrency(contract.hourlyRate)}</TableCell>
110
- <TableCell>{contract.hourLimit}h</TableCell>
111
- <TableCell>
112
- <StatusBadge
113
- label={contract.status}
114
- className={getContractBadgeClasses(contract.status)}
115
- />
116
- </TableCell>
117
- </TableRow>
118
- ))}
119
- </TableBody>
120
- </Table>
121
- </SectionCard>
122
- </Page>
123
- );
124
- }
1
+ 'use client';
2
+
3
+ import {
4
+ EmptyState,
5
+ Page,
6
+ PaginationFooter,
7
+ SearchBar,
8
+ } from '@/components/entity-list';
9
+ import { Input } from '@/components/ui/input';
10
+ import {
11
+ Select,
12
+ SelectContent,
13
+ SelectItem,
14
+ SelectTrigger,
15
+ SelectValue,
16
+ } from '@/components/ui/select';
17
+ import {
18
+ Table,
19
+ TableBody,
20
+ TableCell,
21
+ TableHead,
22
+ TableHeader,
23
+ TableRow,
24
+ } from '@/components/ui/table';
25
+ import { useTranslations } from 'next-intl';
26
+ import Link from 'next/link';
27
+ import { useMemo, useState } from 'react';
28
+ import { Layers } from 'lucide-react';
29
+ import { OperationsHeader } from '../_components/operations-header';
30
+ import { SectionCard } from '../_components/section-card';
31
+ import { StatusBadge } from '../_components/status-badge';
32
+ import { useOperationsData } from '../_lib/hooks/use-operations-data';
33
+ import { formatCurrency, formatDate } from '../_lib/utils/format';
34
+ import {
35
+ getContractBadgeClasses,
36
+ getContractTypeLabel,
37
+ } from '../_lib/utils/status';
38
+
39
+ const PAGE_SIZE_OPTIONS = [10, 20, 30, 50];
40
+
41
+ export default function ContractsPage() {
42
+ const t = useTranslations('operations.ContractsPage');
43
+ const { contracts } = useOperationsData();
44
+ const [searchInput, setSearchInput] = useState('');
45
+ const [search, setSearch] = useState('');
46
+ const [status, setStatus] = useState('all');
47
+ const [currentPage, setCurrentPage] = useState(1);
48
+ const [pageSize, setPageSize] = useState(PAGE_SIZE_OPTIONS[0]);
49
+
50
+ const filteredContracts = useMemo(
51
+ () =>
52
+ contracts.filter((contract) => {
53
+ const matchesSearch =
54
+ `${contract.name} ${contract.client}`
55
+ .toLowerCase()
56
+ .includes(search.toLowerCase());
57
+ const matchesStatus = status === 'all' || contract.status === status;
58
+ return matchesSearch && matchesStatus;
59
+ }),
60
+ [contracts, search, status]
61
+ );
62
+
63
+ const totalPages = Math.max(1, Math.ceil(filteredContracts.length / pageSize));
64
+ const safePage = Math.min(Math.max(currentPage, 1), totalPages);
65
+ const paginatedContracts = useMemo(() => {
66
+ const start = (safePage - 1) * pageSize;
67
+ return filteredContracts.slice(start, start + pageSize);
68
+ }, [filteredContracts, safePage, pageSize]);
69
+
70
+ return (
71
+ <Page>
72
+ <OperationsHeader
73
+ title={t('title')}
74
+ description={t('description')}
75
+ current={t('breadcrumb')}
76
+ />
77
+
78
+ <SearchBar
79
+ className="mb-6"
80
+ searchQuery={searchInput}
81
+ onSearchChange={setSearchInput}
82
+ placeholder={t('searchPlaceholder')}
83
+ onSearch={() => {
84
+ setSearch(searchInput);
85
+ setCurrentPage(1);
86
+ }}
87
+ controls={[
88
+ {
89
+ id: 'status-filter',
90
+ type: 'select',
91
+ value: status,
92
+ onChange: (value) => {
93
+ setStatus(value);
94
+ setCurrentPage(1);
95
+ },
96
+ placeholder: t('filters.all'),
97
+ options: [
98
+ { value: 'all', label: t('filters.all') },
99
+ { value: 'active', label: t('filters.active') },
100
+ { value: 'renewal', label: t('filters.renewal') },
101
+ { value: 'expired', label: t('filters.expired') },
102
+ { value: 'draft', label: t('filters.draft') },
103
+ ],
104
+ },
105
+ ]}
106
+ />
107
+
108
+ <SectionCard title={t('tableTitle')} description={t('tableDescription')}>
109
+ {filteredContracts.length === 0 ? (
110
+ <EmptyState
111
+ icon={<Layers className="h-12 w-12" />}
112
+ title={t('emptyState.title')}
113
+ description={t('emptyState.description')}
114
+ actionLabel={t('emptyState.action')}
115
+ onAction={() => {
116
+ setSearch('');
117
+ setSearchInput('');
118
+ setStatus('all');
119
+ setCurrentPage(1);
120
+ }}
121
+ />
122
+ ) : (
123
+ <div className="space-y-4">
124
+ <Table>
125
+ <TableHeader>
126
+ <TableRow>
127
+ <TableHead>{t('columns.name')}</TableHead>
128
+ <TableHead>{t('columns.client')}</TableHead>
129
+ <TableHead>{t('columns.type')}</TableHead>
130
+ <TableHead>{t('columns.startDate')}</TableHead>
131
+ <TableHead>{t('columns.endDate')}</TableHead>
132
+ <TableHead>{t('columns.hourRate')}</TableHead>
133
+ <TableHead>{t('columns.hourLimit')}</TableHead>
134
+ <TableHead>{t('columns.status')}</TableHead>
135
+ </TableRow>
136
+ </TableHeader>
137
+ <TableBody>
138
+ {paginatedContracts.map((contract) => (
139
+ <TableRow key={contract.id}>
140
+ <TableCell>
141
+ <Link
142
+ className="font-medium text-primary hover:underline"
143
+ href={`/operations/contracts/${contract.id}`}
144
+ >
145
+ {contract.name}
146
+ </Link>
147
+ </TableCell>
148
+ <TableCell>{contract.client}</TableCell>
149
+ <TableCell>{getContractTypeLabel(contract.type)}</TableCell>
150
+ <TableCell>{formatDate(contract.startDate)}</TableCell>
151
+ <TableCell>{formatDate(contract.endDate)}</TableCell>
152
+ <TableCell>{formatCurrency(contract.hourlyRate)}</TableCell>
153
+ <TableCell>{contract.hourLimit}h</TableCell>
154
+ <TableCell>
155
+ <StatusBadge
156
+ label={contract.status}
157
+ className={getContractBadgeClasses(contract.status)}
158
+ />
159
+ </TableCell>
160
+ </TableRow>
161
+ ))}
162
+ </TableBody>
163
+ </Table>
164
+ <PaginationFooter
165
+ currentPage={safePage}
166
+ pageSize={pageSize}
167
+ totalItems={filteredContracts.length}
168
+ onPageChange={setCurrentPage}
169
+ onPageSizeChange={(nextSize) => {
170
+ setPageSize(nextSize);
171
+ setCurrentPage(1);
172
+ }}
173
+ pageSizeOptions={PAGE_SIZE_OPTIONS}
174
+ />
175
+ </div>
176
+ )}
177
+ </SectionCard>
178
+ </Page>
179
+ );
180
+ }