@happyvertical/smrt-projects 0.51.39 → 0.53.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 (56) hide show
  1. package/AGENTS.md +20 -9
  2. package/README.md +13 -1
  3. package/SERVICE_TIME_MIGRATION.md +45 -3
  4. package/dist/index.js +154 -588
  5. package/dist/index.js.map +1 -1
  6. package/dist/manifest.json +3 -1484
  7. package/dist/models/Repository.d.ts +9 -0
  8. package/dist/models/Repository.d.ts.map +1 -1
  9. package/dist/models/service-evidence.d.ts +13 -92
  10. package/dist/models/service-evidence.d.ts.map +1 -1
  11. package/dist/playground.js +2 -2
  12. package/dist/playground.js.map +1 -1
  13. package/dist/services/service-evidence-service.d.ts +6 -55
  14. package/dist/services/service-evidence-service.d.ts.map +1 -1
  15. package/dist/services/subscription-commercial-resolver.d.ts +1 -1
  16. package/dist/services/subscription-commercial-resolver.d.ts.map +1 -1
  17. package/dist/smrt-knowledge.json +11 -1069
  18. package/dist/svelte/components/ApprovalActions.svelte +1 -1
  19. package/dist/svelte/components/ApprovalActions.svelte.d.ts +1 -1
  20. package/dist/svelte/components/ApprovalActions.svelte.d.ts.map +1 -1
  21. package/dist/svelte/components/__tests__/TimesheetsReexports.test.js +18 -0
  22. package/dist/svelte/i18n.d.ts +0 -9
  23. package/dist/svelte/i18n.d.ts.map +1 -1
  24. package/dist/svelte/i18n.js +0 -9
  25. package/dist/svelte/index.d.ts +2 -8
  26. package/dist/svelte/index.d.ts.map +1 -1
  27. package/dist/svelte/index.js +3 -4
  28. package/dist/svelte/playground.d.ts +7 -2
  29. package/dist/svelte/playground.d.ts.map +1 -1
  30. package/dist/svelte/playground.js +7 -2
  31. package/dist/svelte/utils.d.ts +4 -48
  32. package/dist/svelte/utils.d.ts.map +1 -1
  33. package/dist/svelte/utils.js +4 -56
  34. package/dist/svelte/utils.test.js +1 -1
  35. package/dist/types.d.ts +1 -10
  36. package/dist/types.d.ts.map +1 -1
  37. package/package.json +19 -19
  38. package/dist/svelte/components/DurationDisplay.svelte +0 -81
  39. package/dist/svelte/components/DurationDisplay.svelte.d.ts +0 -20
  40. package/dist/svelte/components/DurationDisplay.svelte.d.ts.map +0 -1
  41. package/dist/svelte/components/TimeEntryCard.svelte +0 -304
  42. package/dist/svelte/components/TimeEntryCard.svelte.d.ts +0 -25
  43. package/dist/svelte/components/TimeEntryCard.svelte.d.ts.map +0 -1
  44. package/dist/svelte/components/TimeEntryList.svelte +0 -360
  45. package/dist/svelte/components/TimeEntryList.svelte.d.ts +0 -24
  46. package/dist/svelte/components/TimeEntryList.svelte.d.ts.map +0 -1
  47. package/dist/svelte/components/TimeSummary.svelte +0 -176
  48. package/dist/svelte/components/TimeSummary.svelte.d.ts +0 -30
  49. package/dist/svelte/components/TimeSummary.svelte.d.ts.map +0 -1
  50. package/dist/svelte/components/__tests__/DurationDisplay.test.js +0 -57
  51. package/dist/svelte/components/__tests__/TimeEntryCard.test.js +0 -45
  52. package/dist/svelte/components/__tests__/TimeEntryList.test.js +0 -50
  53. package/dist/svelte/components/__tests__/TimeSummary.test.js +0 -39
  54. package/dist/svelte/components/utils.d.ts +0 -51
  55. package/dist/svelte/components/utils.d.ts.map +0 -1
  56. package/dist/svelte/components/utils.js +0 -58
@@ -1,304 +0,0 @@
1
- <script lang="ts">
2
- /**
3
- * TimeEntryCard - Card component for displaying time entries
4
- * Supports selection mode for bulk operations
5
- */
6
-
7
- import { useI18n } from '@happyvertical/smrt-ui/i18n';
8
- import type { Snippet } from 'svelte';
9
- import { M } from '../i18n.js';
10
- import {
11
- type Currency,
12
- formatCurrency,
13
- formatDate,
14
- formatHours,
15
- statusColors,
16
- type TimeEntry,
17
- } from './utils.js';
18
-
19
- /** Props for TimeEntryCard component */
20
- export interface Props {
21
- /** Time entry data to display. */
22
- entry: TimeEntry;
23
- /** URL to navigate to when the card is clicked. */
24
- href?: string;
25
- /** Invoked when the card content is clicked. */
26
- onclick?: () => void;
27
- /** Shows a checkbox for selecting the entry. */
28
- selectable?: boolean;
29
- /** Indicates whether the entry is selected. */
30
- selected?: boolean;
31
- /** Invoked when the selection checkbox is toggled. */
32
- onselect?: (id: string, selected: boolean) => void;
33
- /** Currency code for formatting hourly rates and amounts. */
34
- currency?: Currency;
35
- /** Renders action buttons on the right side of the card. */
36
- actions?: Snippet;
37
- }
38
-
39
- let {
40
- entry,
41
- href,
42
- onclick,
43
- selectable = false,
44
- selected = false,
45
- onselect,
46
- currency = 'CAD',
47
- actions,
48
- }: Props = $props();
49
-
50
- const { t } = useI18n();
51
-
52
- function handleCheckboxChange(event: Event) {
53
- const target = event.target as HTMLInputElement;
54
- onselect?.(entry.id, target.checked);
55
- }
56
-
57
- function handleCardClick(event: MouseEvent) {
58
- // If there's an onclick handler, use it
59
- if (onclick) {
60
- event.preventDefault();
61
- onclick();
62
- }
63
- // If href is provided, let the anchor handle navigation naturally
64
- }
65
-
66
- function handleKeydown(event: KeyboardEvent) {
67
- if (event.key === 'Enter' || event.key === ' ') {
68
- if (onclick) {
69
- event.preventDefault();
70
- onclick();
71
- }
72
- // For href, let the anchor handle it naturally
73
- }
74
- }
75
- </script>
76
-
77
- <div
78
- class="time-entry-card"
79
- class:selectable
80
- class:selected
81
- class:clickable={!!href || !!onclick}
82
- >
83
- {#if selectable}
84
- <div class="checkbox-wrapper">
85
- <!-- raw-primitive-allow: native checkbox; no Provider-free checkbox primitive, kept native to match the indeterminate select-all in TimeEntryList -->
86
- <input
87
- type="checkbox"
88
- checked={selected}
89
- onchange={handleCheckboxChange}
90
- aria-label={t(M['projects.time_entry_card.select_entry'], { description: entry.description })}
91
- />
92
- </div>
93
- {/if}
94
-
95
- {#if href}
96
- <a
97
- {href}
98
- class="card-content"
99
- onclick={handleCardClick}
100
- onkeydown={handleKeydown}
101
- >
102
- {@render cardBody()}
103
- </a>
104
- {:else if onclick}
105
- <!-- raw-primitive-allow: pressable selection card wrapping rich content; structural, not a standard action button -->
106
- <button
107
- type="button"
108
- class="card-content"
109
- onclick={() => onclick?.()}
110
- >
111
- {@render cardBody()}
112
- </button>
113
- {:else}
114
- <div class="card-content">
115
- {@render cardBody()}
116
- </div>
117
- {/if}
118
-
119
- {#if actions}
120
- <div class="card-actions">
121
- {@render actions()}
122
- </div>
123
- {/if}
124
- </div>
125
-
126
- {#snippet cardBody()}
127
- <div class="entry-header">
128
- <span class="date">{formatDate(entry.date)}</span>
129
- <span class="status-badge" style="--status-color: {statusColors[entry.status]}">
130
- {entry.status.toUpperCase()}
131
- </span>
132
- </div>
133
-
134
- <div class="entry-body">
135
- <p class="description">{entry.description}</p>
136
- {#if entry.workerName}
137
- <p class="worker">{entry.workerName}</p>
138
- {/if}
139
- </div>
140
-
141
- <div class="entry-footer">
142
- <div class="hours">
143
- <span class="hours-value">{formatHours(entry.hours)}</span>
144
- {#if entry.hourlyRate}
145
- <span class="rate">@ {formatCurrency(entry.hourlyRate, currency)}/hr</span>
146
- {/if}
147
- </div>
148
- {#if entry.amount !== undefined}
149
- <span class="amount">{formatCurrency(entry.amount, currency)}</span>
150
- {/if}
151
- </div>
152
-
153
- {#if entry.mileage && entry.mileage > 0}
154
- <div class="mileage">
155
- {t(M['projects.time_entry_card.mileage'], { mileage: entry.mileage })}
156
- </div>
157
- {/if}
158
- {/snippet}
159
-
160
- <style>
161
- .time-entry-card {
162
- display: flex;
163
- align-items: stretch;
164
- background: var(--smrt-color-surface);
165
- border: 1px solid var(--smrt-color-outline-variant);
166
- border-radius: var(--smrt-radius-medium, 12px);
167
- overflow: hidden;
168
- transition: all 0.2s var(--smrt-easing-standard);
169
- }
170
-
171
- .time-entry-card:hover {
172
- border-color: var(--smrt-color-outline);
173
- }
174
-
175
- .time-entry-card.selected {
176
- border-color: var(--smrt-color-primary);
177
- background: var(--smrt-color-primary-container);
178
- }
179
-
180
- .time-entry-card.clickable .card-content {
181
- cursor: pointer;
182
- }
183
-
184
- .time-entry-card.clickable:hover {
185
- box-shadow: var(--smrt-elevation-1);
186
- }
187
-
188
- .checkbox-wrapper {
189
- display: flex;
190
- align-items: center;
191
- padding: 1rem;
192
- background: var(--smrt-color-surface-container-low);
193
- border-right: 1px solid var(--smrt-color-outline-variant);
194
- }
195
-
196
- .checkbox-wrapper input[type='checkbox'] {
197
- width: 1.25rem;
198
- height: 1.25rem;
199
- cursor: pointer;
200
- accent-color: var(--smrt-color-primary);
201
- }
202
-
203
- .card-content {
204
- flex: 1;
205
- padding: 1rem;
206
- min-width: 0;
207
- text-decoration: none;
208
- color: inherit;
209
- background: transparent;
210
- border: none;
211
- text-align: left;
212
- font: inherit;
213
- display: block;
214
- width: 100%;
215
- }
216
-
217
- a.card-content:focus,
218
- button.card-content:focus {
219
- outline: 2px solid var(--smrt-color-primary);
220
- outline-offset: -2px;
221
- border-radius: var(--smrt-radius-small, 8px);
222
- }
223
-
224
- .entry-header {
225
- display: flex;
226
- justify-content: space-between;
227
- align-items: center;
228
- margin-bottom: 0.5rem;
229
- }
230
-
231
- .date {
232
- font-size: var(--smrt-typography-body-medium-size, 0.875rem);
233
- color: var(--smrt-color-on-surface-variant);
234
- }
235
-
236
- .status-badge {
237
- font-size: var(--smrt-typography-label-small-size, 0.625rem);
238
- font-weight: var(--smrt-typography-label-small-weight, 500);
239
- padding: 0.25rem 0.5rem;
240
- border-radius: var(--smrt-radius-small, 8px);
241
- background: var(--status-color);
242
- color: var(--smrt-color-on-primary);
243
- letter-spacing: var(--smrt-typography-label-small-tracking, 0.5px);
244
- }
245
-
246
- .entry-body {
247
- margin-bottom: 0.75rem;
248
- }
249
-
250
- .description {
251
- margin: 0;
252
- font-size: var(--smrt-typography-body-large-size, 0.9375rem);
253
- color: var(--smrt-color-on-surface);
254
- line-height: var(--smrt-typography-body-large-line-height, 1.5);
255
- }
256
-
257
- .worker {
258
- margin: 0.25rem 0 0;
259
- font-size: var(--smrt-typography-body-small-size, 0.8125rem);
260
- color: var(--smrt-color-on-surface-variant);
261
- }
262
-
263
- .entry-footer {
264
- display: flex;
265
- justify-content: space-between;
266
- align-items: baseline;
267
- }
268
-
269
- .hours {
270
- display: flex;
271
- align-items: baseline;
272
- gap: 0.5rem;
273
- }
274
-
275
- .hours-value {
276
- font-size: var(--smrt-typography-body-large-size, 1rem);
277
- font-weight: var(--smrt-typography-title-medium-weight, 500);
278
- color: var(--smrt-color-primary);
279
- }
280
-
281
- .rate {
282
- font-size: var(--smrt-typography-body-small-size, 0.75rem);
283
- color: var(--smrt-color-on-surface-variant);
284
- }
285
-
286
- .amount {
287
- font-size: var(--smrt-typography-body-large-size, 1rem);
288
- font-weight: var(--smrt-typography-title-medium-weight, 500);
289
- color: var(--smrt-color-on-surface);
290
- }
291
-
292
- .mileage {
293
- margin-top: 0.5rem;
294
- font-size: var(--smrt-typography-body-small-size, 0.75rem);
295
- color: var(--smrt-color-on-surface-variant);
296
- }
297
-
298
- .card-actions {
299
- display: flex;
300
- align-items: center;
301
- padding: 0.5rem;
302
- border-left: 1px solid var(--smrt-color-outline-variant);
303
- }
304
- </style>
@@ -1,25 +0,0 @@
1
- import type { Snippet } from 'svelte';
2
- import { type Currency, type TimeEntry } from './utils.js';
3
- /** Props for TimeEntryCard component */
4
- export interface Props {
5
- /** Time entry data to display. */
6
- entry: TimeEntry;
7
- /** URL to navigate to when the card is clicked. */
8
- href?: string;
9
- /** Invoked when the card content is clicked. */
10
- onclick?: () => void;
11
- /** Shows a checkbox for selecting the entry. */
12
- selectable?: boolean;
13
- /** Indicates whether the entry is selected. */
14
- selected?: boolean;
15
- /** Invoked when the selection checkbox is toggled. */
16
- onselect?: (id: string, selected: boolean) => void;
17
- /** Currency code for formatting hourly rates and amounts. */
18
- currency?: Currency;
19
- /** Renders action buttons on the right side of the card. */
20
- actions?: Snippet;
21
- }
22
- declare const TimeEntryCard: import("svelte").Component<Props, {}, "">;
23
- type TimeEntryCard = ReturnType<typeof TimeEntryCard>;
24
- export default TimeEntryCard;
25
- //# sourceMappingURL=TimeEntryCard.svelte.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"TimeEntryCard.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/TimeEntryCard.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,EACL,KAAK,QAAQ,EAKb,KAAK,SAAS,EACf,MAAM,YAAY,CAAC;AAGpB,wCAAwC;AACxC,MAAM,WAAW,KAAK;IACpB,kCAAkC;IAClC,KAAK,EAAE,SAAS,CAAC;IACjB,mDAAmD;IACnD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,gDAAgD;IAChD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sDAAsD;IACtD,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IACnD,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAoHD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -1,360 +0,0 @@
1
- <script lang="ts">
2
- /**
3
- * TimeEntryList - List of time entries with optional selection
4
- * Supports bulk selection and grouping
5
- */
6
-
7
- import { useI18n } from '@happyvertical/smrt-ui/i18n';
8
- import { M } from '../i18n.js';
9
- import {
10
- type Currency,
11
- formatCurrency,
12
- formatDate,
13
- formatHours,
14
- statusColors,
15
- type TimeEntry,
16
- } from './utils.js';
17
-
18
- const { t } = useI18n();
19
-
20
- /** Props for TimeEntryList component */
21
- export interface Props {
22
- /** Time entries to display in the list. */
23
- entries: TimeEntry[];
24
- /** Shows checkboxes for selecting entries. */
25
- selectable?: boolean;
26
- /** Identifiers of currently selected entries. */
27
- selectedIds?: string[];
28
- /** Invoked when the selection changes. */
29
- onselectionchange?: (ids: string[]) => void;
30
- /** Message shown when the entries list is empty. */
31
- emptyMessage?: string;
32
- /** Base URL to construct entry detail links. */
33
- baseHref?: string;
34
- /** Currency code for formatting amounts. */
35
- currency?: Currency;
36
- /** Filter function to determine which entries can be selected */
37
- canSelect?: (entry: TimeEntry) => boolean;
38
- }
39
-
40
- let {
41
- entries,
42
- selectable = false,
43
- selectedIds = [],
44
- onselectionchange,
45
- emptyMessage = 'No time entries',
46
- baseHref,
47
- currency = 'CAD',
48
- canSelect = () => true,
49
- }: Props = $props();
50
-
51
- // Filter entries that can actually be selected
52
- const selectableEntries = $derived(selectable ? entries.filter(canSelect) : []);
53
-
54
- const allSelected = $derived(
55
- selectableEntries.length > 0 &&
56
- selectableEntries.every((e) => selectedIds.includes(e.id)),
57
- );
58
-
59
- const someSelected = $derived(selectedIds.length > 0 && !allSelected);
60
-
61
- function handleSelectAll(event: Event) {
62
- const target = event.target as HTMLInputElement;
63
- if (target.checked) {
64
- onselectionchange?.(selectableEntries.map((e) => e.id));
65
- } else {
66
- onselectionchange?.([]);
67
- }
68
- }
69
-
70
- function handleSelect(id: string, selected: boolean, event: Event) {
71
- event.preventDefault();
72
- event.stopPropagation();
73
- if (selected) {
74
- onselectionchange?.([...selectedIds, id]);
75
- } else {
76
- onselectionchange?.(selectedIds.filter((i) => i !== id));
77
- }
78
- }
79
-
80
- function getEntryHref(entry: TimeEntry): string | undefined {
81
- if (!baseHref) return undefined;
82
- return `${baseHref}/${entry.id}`;
83
- }
84
- </script>
85
-
86
- {#if entries.length === 0}
87
- <div class="empty-state">
88
- <p>{emptyMessage}</p>
89
- </div>
90
- {:else}
91
- <div class="time-entry-list">
92
- {#if selectable && selectableEntries.length > 0}
93
- <div class="list-header">
94
- <label class="select-all">
95
- <!-- raw-primitive-allow: select-all checkbox needs the indeterminate state; must stay native -->
96
- <input
97
- type="checkbox"
98
- checked={allSelected}
99
- indeterminate={someSelected}
100
- onchange={handleSelectAll}
101
- aria-label={t(M['projects.time_entry_list.select_all_aria'])}
102
- />
103
- <span>{t(M['projects.time_entry_list.select_all'])}</span>
104
- </label>
105
- <span class="selection-count">
106
- {t(M['projects.time_entry_list.selection_count'], { selected: selectedIds.length, total: selectableEntries.length })}
107
- </span>
108
- </div>
109
- {/if}
110
-
111
- <div class="entries">
112
- {#each entries as entry (entry.id)}
113
- {@const isSelected = selectedIds.includes(entry.id)}
114
- {@const isSelectable = selectable && canSelect(entry)}
115
- {@const entryHref = getEntryHref(entry)}
116
- <div
117
- class="entry-row"
118
- class:selected={isSelected}
119
- class:selectable={isSelectable}
120
- class:has-checkbox={selectable && selectableEntries.length > 0}
121
- >
122
- {#if selectable && selectableEntries.length > 0}
123
- <div class="checkbox-cell">
124
- {#if isSelectable}
125
- <!-- raw-primitive-allow: native row checkbox; no Provider-free checkbox primitive, kept native to match the indeterminate select-all -->
126
- <input
127
- type="checkbox"
128
- checked={isSelected}
129
- onchange={(e) => handleSelect(entry.id, (e.target as HTMLInputElement).checked, e)}
130
- aria-label={t(M['projects.time_entry_list.select_entry_aria'], { description: entry.description })}
131
- />
132
- {/if}
133
- </div>
134
- {/if}
135
-
136
- {#if entryHref}
137
- <a href={entryHref} class="entry-content">
138
- {@render entryDetails(entry)}
139
- </a>
140
- {:else}
141
- <div class="entry-content">
142
- {@render entryDetails(entry)}
143
- </div>
144
- {/if}
145
- </div>
146
- {/each}
147
- </div>
148
- </div>
149
- {/if}
150
-
151
- {#snippet entryDetails(entry: TimeEntry)}
152
- <div class="date-cell">
153
- {formatDate(entry.date)}
154
- </div>
155
-
156
- <div class="description-cell">
157
- <span class="description">{entry.description}</span>
158
- {#if entry.workerName}
159
- <span class="worker">{entry.workerName}</span>
160
- {/if}
161
- </div>
162
-
163
- <div class="hours-cell">
164
- {formatHours(entry.hours)}
165
- </div>
166
-
167
- <div class="status-cell">
168
- <span class="status-badge" style="--status-color: {statusColors[entry.status]}">
169
- {entry.status}
170
- </span>
171
- </div>
172
-
173
- {#if entry.amount !== undefined}
174
- <div class="amount-cell">
175
- {formatCurrency(entry.amount, currency)}
176
- </div>
177
- {/if}
178
- {/snippet}
179
-
180
- <style>
181
- .empty-state {
182
- padding: 3rem 1rem;
183
- text-align: center;
184
- color: var(--smrt-color-on-surface-variant);
185
- }
186
-
187
- .time-entry-list {
188
- background: var(--smrt-color-surface);
189
- border-radius: var(--smrt-radius-large, 16px);
190
- overflow: hidden;
191
- border: 1px solid var(--smrt-color-outline-variant);
192
- }
193
-
194
- .list-header {
195
- display: flex;
196
- justify-content: space-between;
197
- align-items: center;
198
- padding: 0.75rem 1rem;
199
- background: var(--smrt-color-surface-container-low);
200
- border-bottom: 1px solid var(--smrt-color-outline-variant);
201
- }
202
-
203
- .select-all {
204
- display: flex;
205
- align-items: center;
206
- gap: 0.5rem;
207
- cursor: pointer;
208
- font-size: var(--smrt-typography-body-medium-size, 0.875rem);
209
- font-weight: var(--smrt-typography-label-large-weight, 500);
210
- }
211
-
212
- .select-all input[type='checkbox'] {
213
- width: 1rem;
214
- height: 1rem;
215
- cursor: pointer;
216
- accent-color: var(--smrt-color-primary);
217
- }
218
-
219
- .selection-count {
220
- font-size: var(--smrt-typography-body-medium-size, 0.875rem);
221
- color: var(--smrt-color-on-surface-variant);
222
- }
223
-
224
- .entries {
225
- display: flex;
226
- flex-direction: column;
227
- }
228
-
229
- .entry-row {
230
- display: flex;
231
- align-items: center;
232
- border-bottom: 1px solid var(--smrt-color-outline-variant);
233
- transition: background 0.15s var(--smrt-easing-standard);
234
- }
235
-
236
- .entry-row:last-child {
237
- border-bottom: none;
238
- }
239
-
240
- .entry-row:hover {
241
- background: var(--smrt-color-surface-container-lowest);
242
- }
243
-
244
- .entry-row.selected {
245
- background: var(--smrt-color-primary-container);
246
- }
247
-
248
- .checkbox-cell {
249
- width: 2rem;
250
- flex-shrink: 0;
251
- padding-left: 1rem;
252
- }
253
-
254
- .checkbox-cell input[type='checkbox'] {
255
- width: 1rem;
256
- height: 1rem;
257
- cursor: pointer;
258
- accent-color: var(--smrt-color-primary);
259
- }
260
-
261
- .entry-content {
262
- display: flex;
263
- align-items: center;
264
- flex: 1;
265
- padding: 0.875rem 1rem;
266
- text-decoration: none;
267
- color: inherit;
268
- min-width: 0;
269
- }
270
-
271
- a.entry-content:focus {
272
- outline: 2px solid var(--smrt-color-primary);
273
- outline-offset: -2px;
274
- }
275
-
276
- .date-cell {
277
- width: 4.5rem;
278
- flex-shrink: 0;
279
- font-size: var(--smrt-typography-body-medium-size, 0.875rem);
280
- color: var(--smrt-color-on-surface-variant);
281
- }
282
-
283
- .description-cell {
284
- flex: 1;
285
- min-width: 0;
286
- padding-right: 1rem;
287
- }
288
-
289
- .description {
290
- display: block;
291
- font-size: var(--smrt-typography-body-large-size, 0.9375rem);
292
- color: var(--smrt-color-on-surface);
293
- white-space: nowrap;
294
- overflow: hidden;
295
- text-overflow: ellipsis;
296
- }
297
-
298
- .worker {
299
- display: block;
300
- font-size: var(--smrt-typography-body-small-size, 0.75rem);
301
- color: var(--smrt-color-on-surface-variant);
302
- margin-top: 0.125rem;
303
- }
304
-
305
- .hours-cell {
306
- width: 4rem;
307
- flex-shrink: 0;
308
- font-size: var(--smrt-typography-body-large-size, 0.9375rem);
309
- font-weight: var(--smrt-typography-title-medium-weight, 500);
310
- color: var(--smrt-color-primary);
311
- text-align: right;
312
- padding-right: 1rem;
313
- }
314
-
315
- .status-cell {
316
- width: 6rem;
317
- flex-shrink: 0;
318
- text-align: center;
319
- }
320
-
321
- .status-badge {
322
- display: inline-block;
323
- font-size: var(--smrt-typography-label-small-size, 0.625rem);
324
- font-weight: var(--smrt-typography-label-small-weight, 500);
325
- padding: 0.25rem 0.5rem;
326
- border-radius: var(--smrt-radius-small, 8px);
327
- background: var(--status-color);
328
- color: var(--smrt-color-on-primary);
329
- text-transform: uppercase;
330
- letter-spacing: var(--smrt-typography-label-small-tracking, 0.5px);
331
- }
332
-
333
- .amount-cell {
334
- width: 6rem;
335
- flex-shrink: 0;
336
- font-size: var(--smrt-typography-body-large-size, 0.9375rem);
337
- font-weight: var(--smrt-typography-title-medium-weight, 500);
338
- text-align: right;
339
- }
340
-
341
- @media (max-width: 640px) {
342
- .entry-content {
343
- flex-wrap: wrap;
344
- gap: 0.5rem;
345
- }
346
-
347
- .description-cell {
348
- order: 1;
349
- width: 100%;
350
- padding-right: 0;
351
- }
352
-
353
- .date-cell,
354
- .hours-cell,
355
- .status-cell,
356
- .amount-cell {
357
- flex: none;
358
- }
359
- }
360
- </style>
@@ -1,24 +0,0 @@
1
- import { type Currency, type TimeEntry } from './utils.js';
2
- /** Props for TimeEntryList component */
3
- export interface Props {
4
- /** Time entries to display in the list. */
5
- entries: TimeEntry[];
6
- /** Shows checkboxes for selecting entries. */
7
- selectable?: boolean;
8
- /** Identifiers of currently selected entries. */
9
- selectedIds?: string[];
10
- /** Invoked when the selection changes. */
11
- onselectionchange?: (ids: string[]) => void;
12
- /** Message shown when the entries list is empty. */
13
- emptyMessage?: string;
14
- /** Base URL to construct entry detail links. */
15
- baseHref?: string;
16
- /** Currency code for formatting amounts. */
17
- currency?: Currency;
18
- /** Filter function to determine which entries can be selected */
19
- canSelect?: (entry: TimeEntry) => boolean;
20
- }
21
- declare const TimeEntryList: import("svelte").Component<Props, {}, "">;
22
- type TimeEntryList = ReturnType<typeof TimeEntryList>;
23
- export default TimeEntryList;
24
- //# sourceMappingURL=TimeEntryList.svelte.d.ts.map