@nuxt-customer-portal/timesheets 0.3.0

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 (141) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +5 -0
  4. package/app/assets/timesheets.css +368 -0
  5. package/app/components/TimesheetsActivityForm.vue +61 -0
  6. package/app/components/TimesheetsAdminActivities.vue +323 -0
  7. package/app/components/TimesheetsAdminApprovals.vue +449 -0
  8. package/app/components/TimesheetsAdminEmptyState.vue +28 -0
  9. package/app/components/TimesheetsAdminListToolbar.vue +19 -0
  10. package/app/components/TimesheetsAdminPaginatedList.vue +95 -0
  11. package/app/components/TimesheetsAdminProjects.vue +234 -0
  12. package/app/components/TimesheetsAdminRates.vue +83 -0
  13. package/app/components/TimesheetsAdminReports.vue +123 -0
  14. package/app/components/TimesheetsAdminSectionPage.vue +135 -0
  15. package/app/components/TimesheetsAdminSettings.vue +15 -0
  16. package/app/components/TimesheetsAdministrationOrganizationPanel.vue +26 -0
  17. package/app/components/TimesheetsClientSettingsPanel.vue +52 -0
  18. package/app/components/TimesheetsClientTimesheetBrowser.vue +439 -0
  19. package/app/components/TimesheetsDashboardCard.vue +260 -0
  20. package/app/components/TimesheetsDashboardClientApprovals.vue +1 -0
  21. package/app/components/TimesheetsDashboardInternalApprovals.vue +1 -0
  22. package/app/components/TimesheetsDashboardMyTimesheets.vue +1 -0
  23. package/app/components/TimesheetsDashboardMyWeek.vue +1 -0
  24. package/app/components/TimesheetsDashboardSupplierTimesheets.vue +47 -0
  25. package/app/components/TimesheetsOrganizationFeaturesCard.vue +66 -0
  26. package/app/components/TimesheetsPageShell.vue +48 -0
  27. package/app/components/TimesheetsProjectDetail.vue +338 -0
  28. package/app/components/TimesheetsProjectForm.vue +215 -0
  29. package/app/components/TimesheetsSetupChecklist.vue +48 -0
  30. package/app/components/TimesheetsSubmissionMessages.vue +30 -0
  31. package/app/components/TimesheetsSubmissionTimeline.vue +89 -0
  32. package/app/components/TimesheetsWorkspaceSettingsForm.vue +80 -0
  33. package/app/composables/useTimesheetMenu.ts +69 -0
  34. package/app/composables/useTimesheetMutationError.ts +35 -0
  35. package/app/composables/useTimesheets.ts +248 -0
  36. package/app/composables/useTimesheetsAdminList.ts +165 -0
  37. package/app/composables/useTimesheetsDashboard.ts +8 -0
  38. package/app/pages/admin/timesheets/activities.vue +3 -0
  39. package/app/pages/admin/timesheets/approvals.vue +3 -0
  40. package/app/pages/admin/timesheets/index.vue +7 -0
  41. package/app/pages/admin/timesheets/internal-approvals.vue +131 -0
  42. package/app/pages/admin/timesheets/projects/[id].vue +57 -0
  43. package/app/pages/admin/timesheets/projects.vue +8 -0
  44. package/app/pages/admin/timesheets/rates.vue +3 -0
  45. package/app/pages/admin/timesheets/reports.vue +3 -0
  46. package/app/pages/admin/timesheets/settings.vue +3 -0
  47. package/app/pages/timesheets/approvals/reviewers.vue +96 -0
  48. package/app/pages/timesheets/approvals.vue +11 -0
  49. package/app/pages/timesheets/client-approvals.vue +108 -0
  50. package/app/pages/timesheets/client.vue +17 -0
  51. package/app/pages/timesheets/index.vue +1458 -0
  52. package/app/pages/timesheets/internal-approvals.vue +100 -0
  53. package/app/pages/timesheets/suppliers.vue +23 -0
  54. package/app/plugins/timesheets-feature.ts +122 -0
  55. package/i18n/locales/en.json +455 -0
  56. package/i18n/locales/nl.json +455 -0
  57. package/migrations/0000_baseline.sql +409 -0
  58. package/migrations/0001_remove_unused_legacy_invoice_schema.sql +31 -0
  59. package/migrations/0002_submission_batches.sql +47 -0
  60. package/migrations/0003_sync_provider_clients.sql +26 -0
  61. package/migrations/0004_client_reply.sql +1 -0
  62. package/migrations/0005_timer_rounding.sql +1 -0
  63. package/migrations/meta/0000_snapshot.json +3178 -0
  64. package/migrations/meta/_journal.json +13 -0
  65. package/nuxt.config.ts +20 -0
  66. package/package.json +61 -0
  67. package/portal.manifest.mjs +10 -0
  68. package/server/api/timesheets/admin/activities/[id]/deletion.get.ts +18 -0
  69. package/server/api/timesheets/admin/activities/[id].delete.ts +20 -0
  70. package/server/api/timesheets/admin/activities/[id].patch.ts +19 -0
  71. package/server/api/timesheets/admin/activities/index.get.ts +19 -0
  72. package/server/api/timesheets/admin/activities/index.post.ts +19 -0
  73. package/server/api/timesheets/admin/approvals/[id].post.ts +19 -0
  74. package/server/api/timesheets/admin/bootstrap.get.ts +73 -0
  75. package/server/api/timesheets/admin/client-settings/[clientId]/index.get.ts +25 -0
  76. package/server/api/timesheets/admin/client-settings/[clientId]/index.put.ts +28 -0
  77. package/server/api/timesheets/admin/clients/[id]/deletion.get.ts +18 -0
  78. package/server/api/timesheets/admin/clients/[id].delete.ts +20 -0
  79. package/server/api/timesheets/admin/clients/[id].patch.ts +14 -0
  80. package/server/api/timesheets/admin/clients/index.get.ts +18 -0
  81. package/server/api/timesheets/admin/clients/index.post.ts +25 -0
  82. package/server/api/timesheets/admin/internal-approvals/[userId].put.ts +11 -0
  83. package/server/api/timesheets/admin/internal-approvals/index.get.ts +8 -0
  84. package/server/api/timesheets/admin/internal-approvals/index.patch.ts +11 -0
  85. package/server/api/timesheets/admin/organization-capabilities/[organizationId].get.ts +19 -0
  86. package/server/api/timesheets/admin/organization-capabilities/[organizationId].patch.ts +23 -0
  87. package/server/api/timesheets/admin/projects/[id]/deletion.get.ts +18 -0
  88. package/server/api/timesheets/admin/projects/[id].delete.ts +20 -0
  89. package/server/api/timesheets/admin/projects/[id].get.ts +22 -0
  90. package/server/api/timesheets/admin/projects/[id].patch.ts +19 -0
  91. package/server/api/timesheets/admin/projects/index.get.ts +18 -0
  92. package/server/api/timesheets/admin/projects/index.post.ts +19 -0
  93. package/server/api/timesheets/admin/report.get.ts +26 -0
  94. package/server/api/timesheets/admin/settings.patch.ts +19 -0
  95. package/server/api/timesheets/admin/tariffs/index.put.ts +20 -0
  96. package/server/api/timesheets/admin/team/[userId].put.ts +24 -0
  97. package/server/api/timesheets/bootstrap.get.ts +24 -0
  98. package/server/api/timesheets/capabilities.get.ts +58 -0
  99. package/server/api/timesheets/client/[workspaceClientId]/index.get.ts +12 -0
  100. package/server/api/timesheets/client/[workspaceClientId]/reviewers.get.ts +10 -0
  101. package/server/api/timesheets/client/[workspaceClientId]/reviewers.put.ts +18 -0
  102. package/server/api/timesheets/client/[workspaceClientId]/reviews/[submissionId].post.ts +18 -0
  103. package/server/api/timesheets/client/approval-members.get.ts +11 -0
  104. package/server/api/timesheets/client/approval-suppliers.get.ts +8 -0
  105. package/server/api/timesheets/client/approvals.get.ts +14 -0
  106. package/server/api/timesheets/client/reviewer-suppliers.get.ts +10 -0
  107. package/server/api/timesheets/client/supplier-options.get.ts +14 -0
  108. package/server/api/timesheets/client/supplier-timesheets.get.ts +17 -0
  109. package/server/api/timesheets/client/workspaces.get.ts +11 -0
  110. package/server/api/timesheets/client-approvals.get.ts +18 -0
  111. package/server/api/timesheets/dashboard.get.ts +191 -0
  112. package/server/api/timesheets/entries/[id].delete.ts +18 -0
  113. package/server/api/timesheets/entries/[id].patch.ts +23 -0
  114. package/server/api/timesheets/entries/index.post.ts +22 -0
  115. package/server/api/timesheets/internal-approvals/[id].post.ts +9 -0
  116. package/server/api/timesheets/internal-approvals/index.get.ts +37 -0
  117. package/server/api/timesheets/internal-approvals/list.get.ts +12 -0
  118. package/server/api/timesheets/setup-status.get.ts +21 -0
  119. package/server/api/timesheets/submissions/[id]/client-reply.post.ts +20 -0
  120. package/server/api/timesheets/submissions/[id]/request-client-approval.post.ts +21 -0
  121. package/server/api/timesheets/submissions/[id]/resubmit.post.ts +10 -0
  122. package/server/api/timesheets/timer/index.delete.ts +18 -0
  123. package/server/api/timesheets/timer/index.post.ts +23 -0
  124. package/server/api/timesheets/weeks/[id]/submissions.post.ts +19 -0
  125. package/server/db/schema/timesheets.ts +383 -0
  126. package/server/plugins/client-sync.ts +30 -0
  127. package/server/plugins/openapi-contracts.ts +57 -0
  128. package/server/utils/timesheet-admin-listing.ts +281 -0
  129. package/server/utils/timesheet-email.ts +221 -0
  130. package/server/utils/timesheet-repository.ts +3096 -0
  131. package/server/utils/timesheet-validation.ts +231 -0
  132. package/shared/client-approval-dashboard.ts +17 -0
  133. package/shared/emails.ts +403 -0
  134. package/shared/feature.ts +184 -0
  135. package/shared/period-lock.ts +8 -0
  136. package/shared/submission-reply.ts +3 -0
  137. package/shared/timer-rounding.ts +2 -0
  138. package/shared/timesheet-dates.ts +23 -0
  139. package/shared/timesheet-errors.ts +13 -0
  140. package/shared/types/timesheet.ts +424 -0
  141. package/tokens.css +25 -0
@@ -0,0 +1,184 @@
1
+ import { timesheetEmails } from './emails'
2
+ import type { PortalFeatureDefinition } from '@nuxt-customer-portal/core/shared/types/feature'
3
+
4
+ export const timesheetActions = ['read', 'create', 'update', 'submit', 'report', 'approve', 'manage'] as const
5
+
6
+ export type TimesheetAction = (typeof timesheetActions)[number]
7
+
8
+ export const timesheetsFeature: PortalFeatureDefinition<TimesheetAction> = {
9
+ id: 'timesheets',
10
+ emails: timesheetEmails,
11
+ navigation: [
12
+ {
13
+ id: 'timesheets',
14
+ labelKey: 'features.timesheets.navigation.myTimesheet',
15
+ icon: 'i-lucide-clock-3',
16
+ to: '/timesheets',
17
+ audiences: ['authenticated'],
18
+ order: 30
19
+ },
20
+ {
21
+ id: 'timesheets-approvals',
22
+ labelKey: 'features.timesheets.internalApprovals.title',
23
+ icon: 'i-lucide-stamp',
24
+ to: '/timesheets/internal-approvals',
25
+ audiences: ['authenticated'],
26
+ order: 31
27
+ },
28
+ {
29
+ id: 'timesheets-suppliers',
30
+ labelKey: 'features.timesheets.suppliers.title',
31
+ icon: 'i-lucide-building-2',
32
+ to: '/timesheets/suppliers',
33
+ audiences: ['authenticated'],
34
+ order: 32
35
+ },
36
+ {
37
+ id: 'timesheets-admin',
38
+ labelKey: 'features.timesheets.navigation.manage',
39
+ icon: 'i-lucide-chart-no-axes-combined',
40
+ to: '/admin/timesheets/internal-approvals',
41
+ audiences: ['providerAdmin'],
42
+ location: 'admin',
43
+ order: 130
44
+ }
45
+ ],
46
+ modules: [
47
+ {
48
+ id: 'timesheets',
49
+ labelKey: 'features.timesheets.navigation.myTimesheet',
50
+ icon: 'i-lucide-clock-3',
51
+ to: '/timesheets',
52
+ routePrefixes: ['/timesheets', '/admin/timesheets'],
53
+ audiences: ['authenticated'],
54
+ order: 30,
55
+ menuItems: [
56
+ {
57
+ id: 'my-timesheet',
58
+ labelKey: 'features.timesheets.navigation.myTimesheet',
59
+ icon: 'i-lucide-clock-3',
60
+ to: '/timesheets',
61
+ audiences: ['authenticated']
62
+ },
63
+ {
64
+ id: 'client-approvals',
65
+ labelKey: 'features.timesheets.approvals.title',
66
+ icon: 'i-lucide-stamp',
67
+ to: '/timesheets/approvals',
68
+ exact: true,
69
+ audiences: ['authenticated']
70
+ },
71
+ {
72
+ id: 'approval-reviewers',
73
+ labelKey: 'features.timesheets.approvals.reviewersTitle',
74
+ icon: 'i-lucide-users-round',
75
+ to: '/timesheets/approvals/reviewers',
76
+ audiences: ['clientAdmin']
77
+ },
78
+ {
79
+ id: 'supplier-timesheets',
80
+ labelKey: 'features.timesheets.suppliers.title',
81
+ icon: 'i-lucide-building-2',
82
+ to: '/timesheets/suppliers',
83
+ audiences: ['authenticated']
84
+ },
85
+ {
86
+ id: 'timesheet-approvals',
87
+ labelKey: 'features.timesheets.internalApprovals.title',
88
+ icon: 'i-lucide-stamp',
89
+ to: '/timesheets/internal-approvals',
90
+ audiences: ['authenticated']
91
+ },
92
+ {
93
+ id: 'provider-client-approvals',
94
+ labelKey: 'features.timesheets.admin.clientApprovals',
95
+ icon: 'i-lucide-building-2',
96
+ to: '/timesheets/client-approvals',
97
+ audiences: ['providerAdmin']
98
+ },
99
+ {
100
+ id: 'internal-approval-settings',
101
+ labelKey: 'features.timesheets.internalApprovals.manageTitle',
102
+ icon: 'i-lucide-user-round-check',
103
+ to: '/admin/timesheets/internal-approvals',
104
+ audiences: ['providerAdmin']
105
+ },
106
+ {
107
+ id: 'timesheet-projects',
108
+ labelKey: 'features.timesheets.admin.projects',
109
+ icon: 'i-lucide-folder-kanban',
110
+ to: '/admin/timesheets/projects',
111
+ audiences: ['providerAdmin']
112
+ },
113
+ {
114
+ id: 'timesheet-activities',
115
+ labelKey: 'features.timesheets.admin.activities',
116
+ icon: 'i-lucide-tags',
117
+ to: '/admin/timesheets/activities',
118
+ audiences: ['providerAdmin']
119
+ },
120
+ {
121
+ id: 'timesheet-rates',
122
+ labelKey: 'features.timesheets.admin.teamRates',
123
+ icon: 'i-lucide-badge-euro',
124
+ to: '/admin/timesheets/rates',
125
+ audiences: ['providerAdmin']
126
+ },
127
+ {
128
+ id: 'timesheet-settings',
129
+ labelKey: 'features.timesheets.admin.workspaceSettings',
130
+ icon: 'i-lucide-settings-2',
131
+ to: '/admin/timesheets/settings',
132
+ audiences: ['providerAdmin']
133
+ },
134
+ {
135
+ id: 'timesheet-reports',
136
+ labelKey: 'features.timesheets.admin.reports',
137
+ icon: 'i-lucide-chart-no-axes-combined',
138
+ to: '/admin/timesheets/reports',
139
+ audiences: ['providerAdmin']
140
+ }
141
+ ]
142
+ }
143
+ ],
144
+ dashboardWidgets: [
145
+ {
146
+ id: 'timesheets-my-timesheets',
147
+ component: 'TimesheetsDashboardMyTimesheets',
148
+ area: 'main',
149
+ size: 'half',
150
+ order: 21
151
+ },
152
+ { id: 'timesheets-my-week', component: 'TimesheetsDashboardMyWeek', area: 'main', size: 'half', order: 20 },
153
+ {
154
+ id: 'timesheets-internal-approvals',
155
+ component: 'TimesheetsDashboardInternalApprovals',
156
+ area: 'attention',
157
+ size: 'half',
158
+ order: 10
159
+ },
160
+ {
161
+ id: 'timesheets-client-approvals',
162
+ component: 'TimesheetsDashboardClientApprovals',
163
+ area: 'attention',
164
+ size: 'half',
165
+ order: 20
166
+ },
167
+ {
168
+ id: 'timesheets-supplier-timesheets',
169
+ component: 'TimesheetsDashboardSupplierTimesheets',
170
+ area: 'main',
171
+ size: 'half',
172
+ order: 25
173
+ }
174
+ ],
175
+ clientIntegration: {
176
+ moduleId: 'timesheets',
177
+ labelKey: 'features.timesheets.navigation.myTimesheet',
178
+ detailComponent: 'TimesheetsClientSettingsPanel'
179
+ },
180
+ policy: {
181
+ PROVIDER: { owner: timesheetActions, admin: timesheetActions, member: ['read', 'create', 'update', 'submit'] },
182
+ CLIENT: { owner: ['read', 'approve', 'manage'], admin: ['read', 'approve', 'manage'], member: ['read'] }
183
+ }
184
+ }
@@ -0,0 +1,8 @@
1
+ export const isTimesheetDateLocked = (
2
+ date: string,
3
+ periods: readonly { status: string; periodStartsOn: string; periodEndsOn: string }[]
4
+ ) =>
5
+ periods.some(
6
+ (period) =>
7
+ ['SUBMITTED', 'APPROVED'].includes(period.status) && period.periodStartsOn <= date && date <= period.periodEndsOn
8
+ )
@@ -0,0 +1,3 @@
1
+ import { z } from 'zod'
2
+
3
+ export const submissionReplySchema = z.object({ reply: z.string().trim().max(5000).optional() })
@@ -0,0 +1,2 @@
1
+ export const roundTimerMinutes = (elapsedMs: number, increment: number) =>
2
+ Math.max(1, Math.ceil(elapsedMs / (increment * 60_000))) * increment
@@ -0,0 +1,23 @@
1
+ export const mondayFor = (dateString?: string) => {
2
+ const date = dateString ? new Date(`${dateString}T12:00:00Z`) : new Date()
3
+ const day = date.getUTCDay()
4
+ const offset = day === 0 ? -6 : 1 - day
5
+ date.setUTCDate(date.getUTCDate() + offset)
6
+ return date.toISOString().slice(0, 10)
7
+ }
8
+
9
+ export const addIsoDays = (dateString: string, days: number) => {
10
+ const date = new Date(`${dateString}T12:00:00Z`)
11
+ date.setUTCDate(date.getUTCDate() + days)
12
+ return date.toISOString().slice(0, 10)
13
+ }
14
+
15
+ /** Format a calendar-date period identically in the UI and server-rendered emails. */
16
+ export const formatTimesheetPeriod = (from: string, to: string, locale: string) =>
17
+ new Intl.DateTimeFormat(locale, {
18
+ weekday: 'short',
19
+ day: 'numeric',
20
+ month: 'short',
21
+ year: 'numeric',
22
+ timeZone: 'UTC'
23
+ }).formatRange(new Date(`${from}T12:00:00Z`), new Date(`${to}T12:00:00Z`))
@@ -0,0 +1,13 @@
1
+ export const TIMESHEET_ERROR_CODES = {
2
+ tariffRequired: 'TIMESHEET_TARIFF_REQUIRED',
3
+ entryDisabled: 'TIMESHEET_ENTRY_DISABLED',
4
+ runningTimer: 'TIMESHEET_RUNNING_TIMER',
5
+ internalApproverRequired: 'TIMESHEET_INTERNAL_APPROVER_REQUIRED',
6
+ internalApprovalUnauthorized: 'TIMESHEET_INTERNAL_APPROVAL_UNAUTHORIZED',
7
+ internalApprovalStale: 'TIMESHEET_INTERNAL_APPROVAL_STALE',
8
+ internalApprovalMemberInvalid: 'TIMESHEET_INTERNAL_APPROVAL_MEMBER_INVALID',
9
+ internalApprovalSelfAssignment: 'TIMESHEET_INTERNAL_APPROVAL_SELF_ASSIGNMENT',
10
+ internalApprovalDuplicateAssignment: 'TIMESHEET_INTERNAL_APPROVAL_DUPLICATE_ASSIGNMENT'
11
+ } as const
12
+
13
+ export type TimesheetErrorCode = (typeof TIMESHEET_ERROR_CODES)[keyof typeof TIMESHEET_ERROR_CODES]
@@ -0,0 +1,424 @@
1
+ export type TimesheetStatus = 'DRAFT' | 'SUBMITTED' | 'APPROVED' | 'REJECTED'
2
+ export type ProjectStatus = 'ACTIVE' | 'ARCHIVED'
3
+ export type ClientAccessMode = 'DISABLED' | 'VIEW' | 'REVIEW'
4
+ export type ClientReviewStatus = 'PENDING' | 'APPROVED' | 'DISPUTED'
5
+
6
+ export interface TimesheetsListPagination {
7
+ total: number
8
+ page: number
9
+ pageSize: number
10
+ pageCount: number
11
+ }
12
+ export interface TimesheetsListResponse<T> {
13
+ items: T[]
14
+ pagination: TimesheetsListPagination
15
+ }
16
+
17
+ export interface TimesheetSettingsDto {
18
+ timerRoundingMinutes?: number
19
+ currency: string
20
+ timezone: string
21
+ weekStartsOn: number
22
+ internalApprovalsEnabled: boolean
23
+ }
24
+
25
+ export interface TeamMemberDto {
26
+ id: string
27
+ name: string
28
+ email: string
29
+ image: string | null
30
+ organizationRole: string
31
+ defaultHourlyRateMinor: number | null
32
+ canEnterTime: boolean
33
+ internalApprovalRequired: boolean
34
+ approverUserIds: string[]
35
+ }
36
+
37
+ export interface InternalApprovalConfigurationDto {
38
+ enabled: boolean
39
+ members: TeamMemberDto[]
40
+ }
41
+
42
+ export interface InternalApprovalQueueDto {
43
+ teamMembers: Array<{ id: string; name: string }>
44
+ settings: TimesheetSettingsDto
45
+ approvals: ApprovalQueueItemDto[]
46
+ clients: ClientDto[]
47
+ projects: ProjectDto[]
48
+ activities: ActivityTypeDto[]
49
+ }
50
+
51
+ export interface TimesheetsSetupStatusDto {
52
+ complete: boolean
53
+ hasClient: boolean
54
+ hasActiveActivity: boolean
55
+ hasConfiguredProject: boolean
56
+ billableWorkExists: boolean
57
+ enabledMemberCount: number
58
+ missingDefaultTariffCount: number
59
+ }
60
+
61
+ export interface ClientDto {
62
+ id: string
63
+ organizationId: string
64
+ name: string
65
+ officialName: string | null
66
+ slug: string
67
+ logo: string | null
68
+ accessMode: ClientAccessMode
69
+ }
70
+
71
+ export interface ClientWorkspaceDto {
72
+ id: string
73
+ workspaceOrganizationId: string
74
+ workspaceName: string
75
+ accessMode: ClientAccessMode
76
+ canReview: boolean
77
+ canManageReviewers: boolean
78
+ }
79
+ export interface ClientReviewerDto {
80
+ id: string
81
+ name: string
82
+ email: string
83
+ role: string
84
+ assigned: boolean
85
+ fixedAccess: boolean
86
+ }
87
+ export interface ClientTimesheetEntryDto {
88
+ id: string
89
+ date: string
90
+ project: string
91
+ person: string
92
+ activity: string
93
+ minutes: number
94
+ note: string | null
95
+ }
96
+ export interface ClientTimesheetHistoryDto {
97
+ id: string
98
+ action: 'SUBMITTED' | 'APPROVED_INTERNAL' | 'REOPENED' | 'APPROVED_CLIENT' | 'DISPUTED_CLIENT'
99
+ actorName: string
100
+ comment: string | null
101
+ createdAt: string
102
+ }
103
+ export interface ClientTimesheetSliceDto {
104
+ weeklyTimesheetId: string
105
+ submissionId: string
106
+ weekStartsOn: string
107
+ periodStartsOn: string
108
+ periodEndsOn: string
109
+ person: string
110
+ status: ClientReviewStatus
111
+ version: number
112
+ comment: string | null
113
+ reviewedAt: string | null
114
+ entries: ClientTimesheetEntryDto[]
115
+ history: ClientTimesheetHistoryDto[]
116
+ }
117
+ export interface ClientTimesheetsDto {
118
+ workspace: ClientWorkspaceDto
119
+ slices: ClientTimesheetSliceDto[]
120
+ }
121
+ export interface ClientApprovalItemDto {
122
+ timeline?: SubmissionHistoryEventDto[]
123
+ messages?: SubmissionMessageDto[]
124
+ userId: string
125
+ id: string
126
+ workspaceClientId: string
127
+ supplierName: string
128
+ weeklyTimesheetId: string
129
+ submissionId: string
130
+ weekStartsOn: string
131
+ periodStartsOn: string
132
+ periodEndsOn: string
133
+ person: string
134
+ totalMinutes: number
135
+ status: ClientReviewStatus
136
+ version: number
137
+ comment: string | null
138
+ reviewedAt: string | null
139
+ reviewerName: string | null
140
+ reviewerUserId: string | null
141
+ hasReviewers: boolean
142
+ canAct: boolean
143
+ canManageReviewers: boolean
144
+ entries: ClientTimesheetEntryDto[]
145
+ history: ClientTimesheetHistoryDto[]
146
+ }
147
+ export interface ClientApprovalsDto {
148
+ isAdmin: boolean
149
+ pendingCount: number
150
+ items: ClientApprovalItemDto[]
151
+ }
152
+ export interface ClientSupplierTimesheetItemDto extends ClientTimesheetSliceDto {
153
+ id: string
154
+ workspaceClientId: string
155
+ supplierName: string
156
+ totalMinutes: number
157
+ }
158
+ export interface ClientReviewerSupplierDto extends ClientWorkspaceDto {
159
+ pendingCount: number
160
+ reviewerCount: number
161
+ }
162
+ export interface ClientApprovalSupplierOptionDto {
163
+ id: string
164
+ name: string
165
+ }
166
+
167
+ export interface ClientOrganizationOptionDto {
168
+ id: string
169
+ name: string
170
+ slug: string
171
+ logo: string | null
172
+ }
173
+
174
+ export interface ActivityTypeDto {
175
+ id: string
176
+ name: string
177
+ billable: boolean
178
+ active: boolean
179
+ }
180
+
181
+ export interface ProjectDto {
182
+ id: string
183
+ clientOrganizationId: string
184
+ clientName: string
185
+ internal: boolean
186
+ name: string
187
+ code: string | null
188
+ status: ProjectStatus
189
+ startsOn: string | null
190
+ endsOn: string | null
191
+ budgetMinutes: number | null
192
+ budgetMinor: number | null
193
+ activityTypeIds: string[]
194
+ personRates: Record<string, number>
195
+ }
196
+
197
+ export interface TimeEntryDto {
198
+ id: string
199
+ projectId: string
200
+ activityTypeId: string
201
+ entryDate: string
202
+ durationMinutes: number
203
+ note: string | null
204
+ billable: boolean
205
+ hourlyRateMinor: number
206
+ currency: string
207
+ timerStartedAt: string | null
208
+ submissionId: string | null
209
+ submissionStatus: TimesheetStatus | null
210
+ }
211
+
212
+ export interface TimesheetSubmissionDto {
213
+ history?: SubmissionHistoryEventDto[]
214
+ clientThreads?: ClientSubmissionThreadDto[]
215
+ messages?: SubmissionMessageDto[]
216
+ id: string
217
+ periodStartsOn: string
218
+ periodEndsOn: string
219
+ status: TimesheetStatus
220
+ submittedAt: string | null
221
+ reviewedAt: string | null
222
+ reviewedById: string | null
223
+ reviewerName: string | null
224
+ reviewerImage: string | null
225
+ rejectionComment: string | null
226
+ version: number
227
+ entryIds: string[]
228
+ }
229
+
230
+ export interface WeekDto {
231
+ id: string
232
+ userId: string
233
+ weekStartsOn: string
234
+ status: TimesheetStatus
235
+ submittedAt: string | null
236
+ reviewedAt: string | null
237
+ reviewedById: string | null
238
+ rejectionComment: string | null
239
+ entries: TimeEntryDto[]
240
+ submissions: TimesheetSubmissionDto[]
241
+ }
242
+
243
+ export interface TimesheetBootstrapDto {
244
+ runningTimer: TimeEntryDto | null
245
+ canStartTimer: boolean
246
+ settings: TimesheetSettingsDto
247
+ clients: ClientDto[]
248
+ projects: ProjectDto[]
249
+ activities: ActivityTypeDto[]
250
+ team: TeamMemberDto[]
251
+ week: WeekDto
252
+ canEnterTime: boolean
253
+ setupStatus: TimesheetsSetupStatusDto
254
+ }
255
+
256
+ export interface TimesheetCapabilitiesDto {
257
+ canEnterTime: boolean
258
+ canApproveInternalTimesheets: boolean
259
+ hasInternalApprovalAssignments: boolean
260
+ canManageTimesheets: boolean
261
+ canReviewClientTimesheets: boolean
262
+ canViewSupplierTime: boolean
263
+ canAccessApprovals: boolean
264
+ canManageClientReviewers: boolean
265
+ pendingInternalApprovalCount: number
266
+ pendingClientApprovalCount: number
267
+ unassignedClientReviewerSupplierCount: number
268
+ }
269
+
270
+ export interface TimesheetsDashboardDto {
271
+ myWeek?: {
272
+ projects: Array<{ id: string; name: string; clientName: string; totalMinutes: number }>
273
+ previousSubmissions: Array<{
274
+ id: string
275
+ weekStartsOn: string
276
+ periodStartsOn: string
277
+ periodEndsOn: string
278
+ status: TimesheetStatus
279
+ totalMinutes: number
280
+ }>
281
+ weekStartsOn: string
282
+ status: TimesheetStatus
283
+ totalMinutes: number
284
+ rejectionComment: string | null
285
+ hasRunningTimer: boolean
286
+ batches: Array<{
287
+ id: string
288
+ status: TimesheetStatus
289
+ totalMinutes: number
290
+ periodStartsOn: string
291
+ periodEndsOn: string
292
+ }>
293
+ unsubmitted: {
294
+ totalMinutes: number
295
+ periodStartsOn: string
296
+ periodEndsOn: string
297
+ } | null
298
+ }
299
+ internalApprovals?: {
300
+ pendingCount: number
301
+ items: Array<
302
+ Pick<
303
+ ApprovalQueueItemDto,
304
+ | 'id'
305
+ | 'userName'
306
+ | 'weekStartsOn'
307
+ | 'periodStartsOn'
308
+ | 'periodEndsOn'
309
+ | 'totalMinutes'
310
+ | 'submittedAt'
311
+ | 'status'
312
+ >
313
+ >
314
+ }
315
+ clientApprovals?: {
316
+ pendingCount: number
317
+ hasHistory: boolean
318
+ unassignedSupplierCount: number
319
+ items: Array<
320
+ Pick<
321
+ ClientApprovalItemDto,
322
+ | 'id'
323
+ | 'supplierName'
324
+ | 'person'
325
+ | 'weekStartsOn'
326
+ | 'periodStartsOn'
327
+ | 'periodEndsOn'
328
+ | 'totalMinutes'
329
+ | 'status'
330
+ >
331
+ >
332
+ }
333
+ supplierTimesheets?: {
334
+ items: Array<
335
+ Pick<
336
+ ClientSupplierTimesheetItemDto,
337
+ 'id' | 'supplierName' | 'person' | 'weekStartsOn' | 'periodStartsOn' | 'periodEndsOn' | 'totalMinutes'
338
+ >
339
+ >
340
+ }
341
+ }
342
+
343
+ export interface ApprovalQueueItemDto {
344
+ history?: SubmissionHistoryEventDto[]
345
+ messages?: SubmissionMessageDto[]
346
+ id: string
347
+ userId: string
348
+ userName: string
349
+ weekStartsOn: string
350
+ periodStartsOn: string
351
+ periodEndsOn: string
352
+ status: TimesheetStatus
353
+ totalMinutes: number
354
+ billableMinutes: number
355
+ billableAmountMinor: number
356
+ currency: string
357
+ submittedAt: string | null
358
+ entries: TimeEntryDto[]
359
+ clientReviews: Array<{
360
+ clientOrganizationId: string
361
+ status: ClientReviewStatus | 'AUTO_APPROVED'
362
+ version: number
363
+ canReply: boolean
364
+ createdAt: string
365
+ comment: string | null
366
+ }>
367
+ }
368
+
369
+ export interface ReportRowDto {
370
+ entryId: string
371
+ date: string
372
+ client: string
373
+ project: string
374
+ person: string
375
+ activity: string
376
+ minutes: number
377
+ billable: boolean
378
+ hourlyRateMinor: number
379
+ amountMinor: number
380
+ currency: string
381
+ status: TimesheetStatus
382
+ note: string | null
383
+ }
384
+
385
+ export interface TimesheetReportDto {
386
+ rows: ReportRowDto[]
387
+ totals: {
388
+ minutes: number
389
+ billableMinutes: number
390
+ nonBillableMinutes: number
391
+ billableAmountMinor: number
392
+ currency: string
393
+ }
394
+ }
395
+
396
+ export interface SubmissionMessageDto {
397
+ id: string
398
+ authorUserId: string | null
399
+ authorName: string | null
400
+ authorImage: string | null
401
+ role: 'submitter' | 'reviewer'
402
+ comment: string
403
+ createdAt: string
404
+ }
405
+
406
+ export interface ClientSubmissionThreadDto {
407
+ clientOrganizationId: string
408
+ clientName: string
409
+ status: ClientReviewStatus
410
+ version: number
411
+ messages: SubmissionMessageDto[]
412
+ }
413
+
414
+ export interface SubmissionHistoryEventDto {
415
+ clientOrganizationId?: string | null
416
+ actorUserId: string
417
+ actorImage: string | null
418
+ comment: string | null
419
+ id: string
420
+ action: string
421
+ actorName: string | null
422
+ clientName: string | null
423
+ createdAt: string
424
+ }
package/tokens.css ADDED
@@ -0,0 +1,25 @@
1
+ :root {
2
+ --timesheets-color-running: var(--color-success-600);
3
+ --timesheets-color-running-soft: var(--color-success-50);
4
+ --timesheets-color-weekend: var(--color-neutral-50);
5
+ --timesheets-rule: var(--ui-border);
6
+ --timesheets-radius: var(--ui-radius);
7
+ --timesheets-space-cell: 0.75rem;
8
+ --timesheets-duration-fast: 120ms;
9
+ --timesheets-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
10
+ }
11
+
12
+ .dark {
13
+ --timesheets-color-running: var(--color-success-400);
14
+ --timesheets-color-running-soft: var(--color-neutral-800);
15
+ --timesheets-color-weekend: var(--color-neutral-900);
16
+ }
17
+
18
+ html[data-portal-theme='brutal'] {
19
+ --timesheets-color-weekend: var(--color-paper-2);
20
+ }
21
+
22
+ html.dark[data-portal-theme='brutal'] {
23
+ --timesheets-color-running-soft: var(--color-paper-2);
24
+ --timesheets-color-weekend: var(--color-paper-2);
25
+ }