@happyvertical/smrt-timesheets 0.0.0-stage → 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.
- package/AGENTS.md +105 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +242 -2
- package/dist/__smrt-register__.d.ts +2 -0
- package/dist/__smrt-register__.d.ts.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +495 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.json +1412 -0
- package/dist/models/index.d.ts +3 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/models/json.d.ts +6 -0
- package/dist/models/json.d.ts.map +1 -0
- package/dist/models/service-snapshots.d.ts +66 -0
- package/dist/models/service-snapshots.d.ts.map +1 -0
- package/dist/models/service-time-entry.d.ts +66 -0
- package/dist/models/service-time-entry.d.ts.map +1 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/service-evidence-service.d.ts +67 -0
- package/dist/services/service-evidence-service.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +1016 -0
- package/dist/svelte/components/DurationDisplay.svelte +81 -0
- package/dist/svelte/components/DurationDisplay.svelte.d.ts +20 -0
- package/dist/svelte/components/DurationDisplay.svelte.d.ts.map +1 -0
- package/dist/svelte/components/TimeEntryApprovalQueue.svelte +253 -0
- package/dist/svelte/components/TimeEntryApprovalQueue.svelte.d.ts +15 -0
- package/dist/svelte/components/TimeEntryApprovalQueue.svelte.d.ts.map +1 -0
- package/dist/svelte/components/TimeEntryCard.svelte +304 -0
- package/dist/svelte/components/TimeEntryCard.svelte.d.ts +25 -0
- package/dist/svelte/components/TimeEntryCard.svelte.d.ts.map +1 -0
- package/dist/svelte/components/TimeEntryList.svelte +360 -0
- package/dist/svelte/components/TimeEntryList.svelte.d.ts +24 -0
- package/dist/svelte/components/TimeEntryList.svelte.d.ts.map +1 -0
- package/dist/svelte/components/TimeSummary.svelte +176 -0
- package/dist/svelte/components/TimeSummary.svelte.d.ts +30 -0
- package/dist/svelte/components/TimeSummary.svelte.d.ts.map +1 -0
- package/dist/svelte/components/__tests__/DurationDisplay.test.js +57 -0
- package/dist/svelte/components/__tests__/TimeEntryApprovalQueue.test.js +114 -0
- package/dist/svelte/components/__tests__/TimeEntryCard.test.js +45 -0
- package/dist/svelte/components/__tests__/TimeEntryList.test.js +50 -0
- package/dist/svelte/components/__tests__/TimeSummary.test.js +39 -0
- package/dist/svelte/components/utils.d.ts +51 -0
- package/dist/svelte/components/utils.d.ts.map +1 -0
- package/dist/svelte/components/utils.js +58 -0
- package/dist/svelte/i18n.d.ts +31 -0
- package/dist/svelte/i18n.d.ts.map +1 -0
- package/dist/svelte/i18n.js +31 -0
- package/dist/svelte/index.d.ts +25 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +27 -0
- package/dist/svelte/types.d.ts +41 -0
- package/dist/svelte/types.d.ts.map +1 -0
- package/dist/svelte/types.js +28 -0
- package/dist/types.d.ts +29 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/dist/ui.d.ts +17 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +68 -0
- package/dist/ui.js.map +1 -0
- package/package.json +85 -3
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
/**
|
|
3
|
+
* Component coverage for TimeEntryApprovalQueue via the shared S11 harness
|
|
4
|
+
* (#1416): rendering, the approve/reject callbacks (reject collects a
|
|
5
|
+
* reason first), and accessibility.
|
|
6
|
+
*/
|
|
7
|
+
import { expectNoA11yViolations, render, screen, userEvent, } from '@happyvertical/smrt-vitest/svelte';
|
|
8
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
9
|
+
import TimeEntryApprovalQueue from '../TimeEntryApprovalQueue.svelte';
|
|
10
|
+
function entryView(overrides = {}) {
|
|
11
|
+
return {
|
|
12
|
+
id: 'te-1',
|
|
13
|
+
date: '2026-07-01',
|
|
14
|
+
hours: 1.5,
|
|
15
|
+
description: 'Debugged the login loop',
|
|
16
|
+
status: 'submitted',
|
|
17
|
+
amount: 18000, // $180.00 in cents (#2401)
|
|
18
|
+
currency: 'USD',
|
|
19
|
+
workerName: 'Ada',
|
|
20
|
+
hourlyRate: 12000, // $120.00/h in cents
|
|
21
|
+
source: 'timer',
|
|
22
|
+
participantKind: 'human',
|
|
23
|
+
...overrides,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
describe('TimeEntryApprovalQueue', () => {
|
|
27
|
+
it('renders date, hours, description, worker, status, and amount', async () => {
|
|
28
|
+
const { container } = render(TimeEntryApprovalQueue, {
|
|
29
|
+
props: { entries: [entryView()] },
|
|
30
|
+
});
|
|
31
|
+
expect(screen.getByText('2026-07-01')).toBeInTheDocument();
|
|
32
|
+
expect(screen.getByText('1.5h')).toBeInTheDocument();
|
|
33
|
+
expect(screen.getByText('Debugged the login loop')).toBeInTheDocument();
|
|
34
|
+
expect(screen.getByText('Ada')).toBeInTheDocument();
|
|
35
|
+
expect(screen.getByText('submitted')).toBeInTheDocument();
|
|
36
|
+
expect(screen.getByText('180.00')).toBeInTheDocument();
|
|
37
|
+
await expectNoA11yViolations(container);
|
|
38
|
+
});
|
|
39
|
+
it('omits the amount when no charge has derived yet', () => {
|
|
40
|
+
render(TimeEntryApprovalQueue, {
|
|
41
|
+
props: { entries: [entryView({ amount: undefined })] },
|
|
42
|
+
});
|
|
43
|
+
expect(screen.queryByText('180.00')).not.toBeInTheDocument();
|
|
44
|
+
});
|
|
45
|
+
it('scales by the currency exponent, not a hard-coded 100', () => {
|
|
46
|
+
// ¥18000 is 18000 minor units — JPY has no minor unit — so dividing by 100
|
|
47
|
+
// here would understate the charge a hundredfold (#2401).
|
|
48
|
+
render(TimeEntryApprovalQueue, {
|
|
49
|
+
props: { entries: [entryView({ currency: 'JPY' })] },
|
|
50
|
+
});
|
|
51
|
+
expect(screen.getByText('18000')).toBeInTheDocument();
|
|
52
|
+
expect(screen.queryByText('180.00')).not.toBeInTheDocument();
|
|
53
|
+
});
|
|
54
|
+
it('invokes onapprove with the entry id', async () => {
|
|
55
|
+
const onapprove = vi.fn();
|
|
56
|
+
render(TimeEntryApprovalQueue, {
|
|
57
|
+
props: { entries: [entryView()], onapprove },
|
|
58
|
+
});
|
|
59
|
+
await userEvent.click(screen.getByRole('button', {
|
|
60
|
+
name: 'Approve time entry: Debugged the login loop',
|
|
61
|
+
}));
|
|
62
|
+
expect(onapprove).toHaveBeenCalledWith('te-1');
|
|
63
|
+
});
|
|
64
|
+
it('collects a reason before invoking onreject', async () => {
|
|
65
|
+
const onreject = vi.fn();
|
|
66
|
+
const { container } = render(TimeEntryApprovalQueue, {
|
|
67
|
+
props: { entries: [entryView()], onreject },
|
|
68
|
+
});
|
|
69
|
+
await userEvent.click(screen.getByRole('button', {
|
|
70
|
+
name: 'Reject time entry: Debugged the login loop',
|
|
71
|
+
}));
|
|
72
|
+
const confirmButton = screen.getByRole('button', {
|
|
73
|
+
name: 'Confirm rejection of: Debugged the login loop',
|
|
74
|
+
});
|
|
75
|
+
expect(confirmButton).toBeDisabled();
|
|
76
|
+
await userEvent.type(screen.getByRole('textbox', {
|
|
77
|
+
name: 'Rejection reason for: Debugged the login loop',
|
|
78
|
+
}), 'duplicate entry');
|
|
79
|
+
await expectNoA11yViolations(container);
|
|
80
|
+
await userEvent.click(confirmButton);
|
|
81
|
+
expect(onreject).toHaveBeenCalledWith('te-1', 'duplicate entry');
|
|
82
|
+
});
|
|
83
|
+
it('cancelling a rejection restores the actions without firing onreject', async () => {
|
|
84
|
+
const onreject = vi.fn();
|
|
85
|
+
render(TimeEntryApprovalQueue, {
|
|
86
|
+
props: { entries: [entryView()], onreject },
|
|
87
|
+
});
|
|
88
|
+
await userEvent.click(screen.getByRole('button', {
|
|
89
|
+
name: 'Reject time entry: Debugged the login loop',
|
|
90
|
+
}));
|
|
91
|
+
await userEvent.click(screen.getByRole('button', {
|
|
92
|
+
name: 'Cancel rejecting: Debugged the login loop',
|
|
93
|
+
}));
|
|
94
|
+
expect(onreject).not.toHaveBeenCalled();
|
|
95
|
+
expect(screen.getByRole('button', {
|
|
96
|
+
name: 'Approve time entry: Debugged the login loop',
|
|
97
|
+
})).toBeInTheDocument();
|
|
98
|
+
});
|
|
99
|
+
it('shows no actions on rows that are not submitted', () => {
|
|
100
|
+
render(TimeEntryApprovalQueue, {
|
|
101
|
+
props: {
|
|
102
|
+
entries: [entryView({ id: 'te-2', status: 'approved' })],
|
|
103
|
+
onapprove: vi.fn(),
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
expect(screen.queryByRole('button')).not.toBeInTheDocument();
|
|
107
|
+
});
|
|
108
|
+
it('shows the empty message when there are no entries', () => {
|
|
109
|
+
render(TimeEntryApprovalQueue, {
|
|
110
|
+
props: { entries: [], emptyMessage: 'Nothing awaiting review' },
|
|
111
|
+
});
|
|
112
|
+
expect(screen.getByText('Nothing awaiting review')).toBeInTheDocument();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
/**
|
|
3
|
+
* Component coverage for TimeEntryCard via the shared S11 harness (#1416).
|
|
4
|
+
*/
|
|
5
|
+
import { expectNoA11yViolations, render, screen, userEvent, } from '@happyvertical/smrt-vitest/svelte';
|
|
6
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
7
|
+
import TimeEntryCard from '../TimeEntryCard.svelte';
|
|
8
|
+
const entry = {
|
|
9
|
+
id: 'e1',
|
|
10
|
+
date: '2026-06-01',
|
|
11
|
+
description: 'Implement feature',
|
|
12
|
+
status: 'draft',
|
|
13
|
+
hours: 3,
|
|
14
|
+
hourlyRate: 10000, // $100.00/h in cents (#2401)
|
|
15
|
+
amount: 30000, // 3 h x $100.00
|
|
16
|
+
workerName: 'Ada Lovelace',
|
|
17
|
+
};
|
|
18
|
+
describe('TimeEntryCard', () => {
|
|
19
|
+
it('renders the entry description, status, and worker', () => {
|
|
20
|
+
render(TimeEntryCard, { props: { entry } });
|
|
21
|
+
expect(screen.getByText('Implement feature')).toBeInTheDocument();
|
|
22
|
+
expect(screen.getByText('DRAFT')).toBeInTheDocument();
|
|
23
|
+
expect(screen.getByText('Ada Lovelace')).toBeInTheDocument();
|
|
24
|
+
});
|
|
25
|
+
it('forwards checkbox selection through onselect', async () => {
|
|
26
|
+
const onselect = vi.fn();
|
|
27
|
+
render(TimeEntryCard, {
|
|
28
|
+
props: { entry, selectable: true, onselect },
|
|
29
|
+
});
|
|
30
|
+
await userEvent.click(screen.getByLabelText('Select time entry for Implement feature'));
|
|
31
|
+
expect(onselect).toHaveBeenCalledWith('e1', true);
|
|
32
|
+
});
|
|
33
|
+
it('invokes onclick when the card is activated', async () => {
|
|
34
|
+
const onclick = vi.fn();
|
|
35
|
+
render(TimeEntryCard, { props: { entry, onclick } });
|
|
36
|
+
await userEvent.click(screen.getByText('Implement feature'));
|
|
37
|
+
expect(onclick).toHaveBeenCalledTimes(1);
|
|
38
|
+
});
|
|
39
|
+
it('is axe-clean', async () => {
|
|
40
|
+
const { container } = render(TimeEntryCard, {
|
|
41
|
+
props: { entry, selectable: true, onselect: vi.fn() },
|
|
42
|
+
});
|
|
43
|
+
await expectNoA11yViolations(container);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
/**
|
|
3
|
+
* Component coverage for TimeEntryList via the shared S11 harness (#1416).
|
|
4
|
+
*/
|
|
5
|
+
import { expectNoA11yViolations, render, screen, userEvent, } from '@happyvertical/smrt-vitest/svelte';
|
|
6
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
7
|
+
import TimeEntryList from '../TimeEntryList.svelte';
|
|
8
|
+
const entries = [
|
|
9
|
+
{
|
|
10
|
+
id: 'e1',
|
|
11
|
+
date: '2026-06-01',
|
|
12
|
+
description: 'First task',
|
|
13
|
+
status: 'draft',
|
|
14
|
+
hours: 2,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: 'e2',
|
|
18
|
+
date: '2026-06-02',
|
|
19
|
+
description: 'Second task',
|
|
20
|
+
status: 'draft',
|
|
21
|
+
hours: 4,
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
describe('TimeEntryList', () => {
|
|
25
|
+
it('renders a card per entry', () => {
|
|
26
|
+
render(TimeEntryList, { props: { entries } });
|
|
27
|
+
expect(screen.getByText('First task')).toBeInTheDocument();
|
|
28
|
+
expect(screen.getByText('Second task')).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
it('shows the empty message when there are no entries', () => {
|
|
31
|
+
render(TimeEntryList, {
|
|
32
|
+
props: { entries: [], emptyMessage: 'No time entries' },
|
|
33
|
+
});
|
|
34
|
+
expect(screen.getByText('No time entries')).toBeInTheDocument();
|
|
35
|
+
});
|
|
36
|
+
it('selects all entries via the header checkbox', async () => {
|
|
37
|
+
const onselectionchange = vi.fn();
|
|
38
|
+
render(TimeEntryList, {
|
|
39
|
+
props: { entries, selectable: true, onselectionchange },
|
|
40
|
+
});
|
|
41
|
+
await userEvent.click(screen.getByLabelText('Select all time entries'));
|
|
42
|
+
expect(onselectionchange).toHaveBeenCalledWith(['e1', 'e2']);
|
|
43
|
+
});
|
|
44
|
+
it('is axe-clean', async () => {
|
|
45
|
+
const { container } = render(TimeEntryList, {
|
|
46
|
+
props: { entries, selectable: true, onselectionchange: vi.fn() },
|
|
47
|
+
});
|
|
48
|
+
await expectNoA11yViolations(container);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
/**
|
|
3
|
+
* Component coverage for TimeSummary via the shared S11 harness (#1416).
|
|
4
|
+
*/
|
|
5
|
+
import { expectNoA11yViolations, render, screen, } from '@happyvertical/smrt-vitest/svelte';
|
|
6
|
+
import { describe, expect, it } from 'vitest';
|
|
7
|
+
import TimeSummary from '../TimeSummary.svelte';
|
|
8
|
+
describe('TimeSummary', () => {
|
|
9
|
+
it('renders the total hours and value cards', () => {
|
|
10
|
+
render(TimeSummary, {
|
|
11
|
+
props: { totalHours: 12, totalAmount: 600, entryCount: 3 },
|
|
12
|
+
});
|
|
13
|
+
expect(screen.getByText('Total Hours')).toBeInTheDocument();
|
|
14
|
+
expect(screen.getByText('Total Value')).toBeInTheDocument();
|
|
15
|
+
expect(screen.getByText('3 entries')).toBeInTheDocument();
|
|
16
|
+
});
|
|
17
|
+
it('shows pending and approved cards when enabled and non-zero', () => {
|
|
18
|
+
render(TimeSummary, {
|
|
19
|
+
props: {
|
|
20
|
+
totalHours: 12,
|
|
21
|
+
totalAmount: 600,
|
|
22
|
+
pendingHours: 4,
|
|
23
|
+
pendingAmount: 200,
|
|
24
|
+
approvedHours: 8,
|
|
25
|
+
approvedAmount: 400,
|
|
26
|
+
showPending: true,
|
|
27
|
+
showApproved: true,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
expect(screen.getByText('Pending Approval')).toBeInTheDocument();
|
|
31
|
+
expect(screen.getByText('Approved')).toBeInTheDocument();
|
|
32
|
+
});
|
|
33
|
+
it('is axe-clean', async () => {
|
|
34
|
+
const { container } = render(TimeSummary, {
|
|
35
|
+
props: { totalHours: 12, totalAmount: 600 },
|
|
36
|
+
});
|
|
37
|
+
await expectNoA11yViolations(container);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for time tracking components
|
|
3
|
+
*/
|
|
4
|
+
export type Currency = 'CAD' | 'USD';
|
|
5
|
+
export type TimeEntryStatus = 'draft' | 'submitted' | 'approved' | 'rejected';
|
|
6
|
+
/** Alias for TimeEntryStatus for approval workflows */
|
|
7
|
+
export type ApprovalStatus = TimeEntryStatus;
|
|
8
|
+
/**
|
|
9
|
+
* Time entry data structure
|
|
10
|
+
*/
|
|
11
|
+
export interface TimeEntry {
|
|
12
|
+
id: string;
|
|
13
|
+
date: Date | string;
|
|
14
|
+
hours: number;
|
|
15
|
+
description: string;
|
|
16
|
+
status: TimeEntryStatus;
|
|
17
|
+
/** Integer minor units of the display currency — $19.99 is 1999 (#2401). */
|
|
18
|
+
amount?: number;
|
|
19
|
+
workerName?: string;
|
|
20
|
+
mileage?: number;
|
|
21
|
+
/** Minor units per hour, so `hours * hourlyRate` is minor units (#2401). */
|
|
22
|
+
hourlyRate?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Status color mapping using M3 design tokens
|
|
26
|
+
*/
|
|
27
|
+
export declare const statusColors: Record<TimeEntryStatus, string>;
|
|
28
|
+
/**
|
|
29
|
+
* Format a date for display (e.g., "Jan 5")
|
|
30
|
+
*/
|
|
31
|
+
export declare function formatDate(date: Date | string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Format a minor-units amount for display.
|
|
34
|
+
*
|
|
35
|
+
* Money in this package is integer minor units (#2401) — `$19.99` is `1999` —
|
|
36
|
+
* and `Intl.NumberFormat` expects major units, so the scale is undone here.
|
|
37
|
+
* Without it a $19.99 charge renders as $1,999.00.
|
|
38
|
+
*
|
|
39
|
+
* The currency's own minor-unit exponent is used rather than a hard-coded 100,
|
|
40
|
+
* so zero-decimal currencies (JPY, KRW) are not divided by anything.
|
|
41
|
+
*/
|
|
42
|
+
export declare function formatCurrency(amount: number, currency?: Currency): string;
|
|
43
|
+
/**
|
|
44
|
+
* Format hours for display (e.g., "8.5h")
|
|
45
|
+
*/
|
|
46
|
+
export declare function formatHours(hours: number): string;
|
|
47
|
+
/**
|
|
48
|
+
* Format hours in HH:MM format (e.g., "8:30")
|
|
49
|
+
*/
|
|
50
|
+
export declare function formatHoursHHMM(hours: number): string;
|
|
51
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,KAAK,CAAC;AAErC,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,WAAW,GAAG,UAAU,GAAG,UAAU,CAAC;AAE9E,uDAAuD;AACvD,MAAM,MAAM,cAAc,GAAG,eAAe,CAAC;AAE7C;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,IAAI,GAAG,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,eAAe,CAAC;IACxB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAKxD,CAAC;AAEF;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAGtD;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,QAAQ,GAAE,QAAgB,GACzB,MAAM,CAcR;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIrD"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for time tracking components
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Status color mapping using M3 design tokens
|
|
6
|
+
*/
|
|
7
|
+
export const statusColors = {
|
|
8
|
+
draft: 'var(--md-sys-color-outline)',
|
|
9
|
+
submitted: 'var(--md-sys-color-tertiary)',
|
|
10
|
+
approved: 'var(--md-sys-color-primary)',
|
|
11
|
+
rejected: 'var(--md-sys-color-error)',
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Format a date for display (e.g., "Jan 5")
|
|
15
|
+
*/
|
|
16
|
+
export function formatDate(date) {
|
|
17
|
+
const d = typeof date === 'string' ? new Date(date) : date;
|
|
18
|
+
return d.toLocaleDateString('en-CA', { month: 'short', day: 'numeric' });
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Format a minor-units amount for display.
|
|
22
|
+
*
|
|
23
|
+
* Money in this package is integer minor units (#2401) — `$19.99` is `1999` —
|
|
24
|
+
* and `Intl.NumberFormat` expects major units, so the scale is undone here.
|
|
25
|
+
* Without it a $19.99 charge renders as $1,999.00.
|
|
26
|
+
*
|
|
27
|
+
* The currency's own minor-unit exponent is used rather than a hard-coded 100,
|
|
28
|
+
* so zero-decimal currencies (JPY, KRW) are not divided by anything.
|
|
29
|
+
*/
|
|
30
|
+
export function formatCurrency(amount, currency = 'CAD') {
|
|
31
|
+
// No fraction-digit override: the currency's own exponent is both the scale
|
|
32
|
+
// to undo and the precision to print. Forcing `minimumFractionDigits: 2`
|
|
33
|
+
// (as this did before minor units) would pin `maximumFractionDigits` to 2 as
|
|
34
|
+
// well, so a zero-decimal currency like JPY would report a scale of 2 — and
|
|
35
|
+
// then render `¥1,999.00` for a currency that has no fractional part at all.
|
|
36
|
+
// For CAD/USD the resolved default is already 2, so their output is
|
|
37
|
+
// unchanged.
|
|
38
|
+
const format = new Intl.NumberFormat('en-CA', {
|
|
39
|
+
style: 'currency',
|
|
40
|
+
currency,
|
|
41
|
+
});
|
|
42
|
+
const digits = format.resolvedOptions().maximumFractionDigits ?? 2;
|
|
43
|
+
return format.format(amount / 10 ** digits);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Format hours for display (e.g., "8.5h")
|
|
47
|
+
*/
|
|
48
|
+
export function formatHours(hours) {
|
|
49
|
+
return `${hours.toFixed(1)}h`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Format hours in HH:MM format (e.g., "8:30")
|
|
53
|
+
*/
|
|
54
|
+
export function formatHoursHHMM(hours) {
|
|
55
|
+
const h = Math.floor(hours);
|
|
56
|
+
const m = Math.round((hours - h) * 60);
|
|
57
|
+
return `${h}:${m.toString().padStart(2, '0')}`;
|
|
58
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message catalog for the timesheets components.
|
|
3
|
+
*
|
|
4
|
+
* The card, list, and summary keys keep the `projects.` namespace they shipped
|
|
5
|
+
* under before these components moved here (#3288): tenant language overrides
|
|
6
|
+
* are stored by key, so renaming them would orphan existing translations.
|
|
7
|
+
* Components added in this package use the `timesheets.` namespace.
|
|
8
|
+
*/
|
|
9
|
+
export declare const M: {
|
|
10
|
+
readonly 'projects.time_entry_card.mileage': "projects.time_entry_card.mileage";
|
|
11
|
+
readonly 'projects.time_entry_card.select_entry': "projects.time_entry_card.select_entry";
|
|
12
|
+
readonly 'projects.time_entry_list.select_all': "projects.time_entry_list.select_all";
|
|
13
|
+
readonly 'projects.time_entry_list.selection_count': "projects.time_entry_list.selection_count";
|
|
14
|
+
readonly 'projects.time_entry_list.select_all_aria': "projects.time_entry_list.select_all_aria";
|
|
15
|
+
readonly 'projects.time_entry_list.select_entry_aria': "projects.time_entry_list.select_entry_aria";
|
|
16
|
+
readonly 'projects.time_summary.total_hours': "projects.time_summary.total_hours";
|
|
17
|
+
readonly 'projects.time_summary.total_value': "projects.time_summary.total_value";
|
|
18
|
+
readonly 'projects.time_summary.pending_approval': "projects.time_summary.pending_approval";
|
|
19
|
+
readonly 'timesheets.approval_queue.empty': "timesheets.approval_queue.empty";
|
|
20
|
+
readonly 'timesheets.approval_queue.reason_placeholder': "timesheets.approval_queue.reason_placeholder";
|
|
21
|
+
readonly 'timesheets.approval_queue.reason_aria': "timesheets.approval_queue.reason_aria";
|
|
22
|
+
readonly 'timesheets.approval_queue.confirm_reject': "timesheets.approval_queue.confirm_reject";
|
|
23
|
+
readonly 'timesheets.approval_queue.confirm_reject_aria': "timesheets.approval_queue.confirm_reject_aria";
|
|
24
|
+
readonly 'timesheets.approval_queue.cancel': "timesheets.approval_queue.cancel";
|
|
25
|
+
readonly 'timesheets.approval_queue.cancel_aria': "timesheets.approval_queue.cancel_aria";
|
|
26
|
+
readonly 'timesheets.approval_queue.approve': "timesheets.approval_queue.approve";
|
|
27
|
+
readonly 'timesheets.approval_queue.approve_aria': "timesheets.approval_queue.approve_aria";
|
|
28
|
+
readonly 'timesheets.approval_queue.reject': "timesheets.approval_queue.reject";
|
|
29
|
+
readonly 'timesheets.approval_queue.reject_aria': "timesheets.approval_queue.reject_aria";
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=i18n.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/svelte/i18n.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,eAAO,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;CAwBZ,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { defineMessages } from '@happyvertical/smrt-ui/i18n';
|
|
2
|
+
/**
|
|
3
|
+
* Message catalog for the timesheets components.
|
|
4
|
+
*
|
|
5
|
+
* The card, list, and summary keys keep the `projects.` namespace they shipped
|
|
6
|
+
* under before these components moved here (#3288): tenant language overrides
|
|
7
|
+
* are stored by key, so renaming them would orphan existing translations.
|
|
8
|
+
* Components added in this package use the `timesheets.` namespace.
|
|
9
|
+
*/
|
|
10
|
+
export const M = defineMessages({
|
|
11
|
+
'projects.time_entry_card.mileage': '+ {mileage} km mileage',
|
|
12
|
+
'projects.time_entry_card.select_entry': 'Select time entry for {description}',
|
|
13
|
+
'projects.time_entry_list.select_all': 'Select All',
|
|
14
|
+
'projects.time_entry_list.selection_count': '{selected} of {total} selected',
|
|
15
|
+
'projects.time_entry_list.select_all_aria': 'Select all time entries',
|
|
16
|
+
'projects.time_entry_list.select_entry_aria': 'Select entry: {description}',
|
|
17
|
+
'projects.time_summary.total_hours': 'Total Hours',
|
|
18
|
+
'projects.time_summary.total_value': 'Total Value',
|
|
19
|
+
'projects.time_summary.pending_approval': 'Pending Approval',
|
|
20
|
+
'timesheets.approval_queue.empty': 'No time entries',
|
|
21
|
+
'timesheets.approval_queue.reason_placeholder': 'Rejection reason',
|
|
22
|
+
'timesheets.approval_queue.reason_aria': 'Rejection reason for: {description}',
|
|
23
|
+
'timesheets.approval_queue.confirm_reject': 'Confirm reject',
|
|
24
|
+
'timesheets.approval_queue.confirm_reject_aria': 'Confirm rejection of: {description}',
|
|
25
|
+
'timesheets.approval_queue.cancel': 'Cancel',
|
|
26
|
+
'timesheets.approval_queue.cancel_aria': 'Cancel rejecting: {description}',
|
|
27
|
+
'timesheets.approval_queue.approve': 'Approve',
|
|
28
|
+
'timesheets.approval_queue.approve_aria': 'Approve time entry: {description}',
|
|
29
|
+
'timesheets.approval_queue.reject': 'Reject',
|
|
30
|
+
'timesheets.approval_queue.reject_aria': 'Reject time entry: {description}',
|
|
31
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @happyvertical/smrt-timesheets/svelte
|
|
3
|
+
*
|
|
4
|
+
* Props-driven Svelte 5 surfaces for time entries: a card, a selectable list,
|
|
5
|
+
* a summary, a duration formatter, and an approval queue. Hosts load data
|
|
6
|
+
* through the package models/services and pass plain view objects.
|
|
7
|
+
* Auto-registers components with ModuleUIRegistry on import.
|
|
8
|
+
*
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
11
|
+
import type { ComponentProps } from 'svelte';
|
|
12
|
+
import DurationDisplay from './components/DurationDisplay.svelte';
|
|
13
|
+
import TimeEntryApprovalQueue from './components/TimeEntryApprovalQueue.svelte';
|
|
14
|
+
import TimeEntryCard from './components/TimeEntryCard.svelte';
|
|
15
|
+
import TimeEntryList from './components/TimeEntryList.svelte';
|
|
16
|
+
import TimeSummary from './components/TimeSummary.svelte';
|
|
17
|
+
export { DurationDisplay, TimeEntryApprovalQueue, TimeEntryCard, TimeEntryList, TimeSummary, };
|
|
18
|
+
export type DurationDisplayProps = ComponentProps<typeof DurationDisplay>;
|
|
19
|
+
export type TimeEntryApprovalQueueProps = ComponentProps<typeof TimeEntryApprovalQueue>;
|
|
20
|
+
export type TimeEntryCardProps = ComponentProps<typeof TimeEntryCard>;
|
|
21
|
+
export type TimeEntryListProps = ComponentProps<typeof TimeEntryList>;
|
|
22
|
+
export type TimeSummaryProps = ComponentProps<typeof TimeSummary>;
|
|
23
|
+
export { type ApprovalStatus, type Currency, formatCurrency, formatDate, formatHours, formatHoursHHMM, statusColors, type TimeEntry, type TimeEntryStatus, } from './components/utils.js';
|
|
24
|
+
export { humanizeTimeEntryStatus, type TimeEntryApprovalView, timeEntryStatusBadgeKey, } from './types.js';
|
|
25
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svelte/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAE7C,OAAO,eAAe,MAAM,qCAAqC,CAAC;AAClE,OAAO,sBAAsB,MAAM,4CAA4C,CAAC;AAChF,OAAO,aAAa,MAAM,mCAAmC,CAAC;AAC9D,OAAO,aAAa,MAAM,mCAAmC,CAAC;AAC9D,OAAO,WAAW,MAAM,iCAAiC,CAAC;AAE1D,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,aAAa,EACb,aAAa,EACb,WAAW,GACZ,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1E,MAAM,MAAM,2BAA2B,GAAG,cAAc,CACtD,OAAO,sBAAsB,CAC9B,CAAC;AACF,MAAM,MAAM,kBAAkB,GAAG,cAAc,CAAC,OAAO,aAAa,CAAC,CAAC;AACtE,MAAM,MAAM,kBAAkB,GAAG,cAAc,CAAC,OAAO,aAAa,CAAC,CAAC;AACtE,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,CAAC;AAElE,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,cAAc,EACd,UAAU,EACV,WAAW,EACX,eAAe,EACf,YAAY,EACZ,KAAK,SAAS,EACd,KAAK,eAAe,GACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,uBAAuB,EACvB,KAAK,qBAAqB,EAC1B,uBAAuB,GACxB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @happyvertical/smrt-timesheets/svelte
|
|
3
|
+
*
|
|
4
|
+
* Props-driven Svelte 5 surfaces for time entries: a card, a selectable list,
|
|
5
|
+
* a summary, a duration formatter, and an approval queue. Hosts load data
|
|
6
|
+
* through the package models/services and pass plain view objects.
|
|
7
|
+
* Auto-registers components with ModuleUIRegistry on import.
|
|
8
|
+
*
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
11
|
+
import { ModuleUIRegistry } from '@happyvertical/smrt-ui/registry';
|
|
12
|
+
import { TIMESHEETS_MODULE_META } from '../ui.js';
|
|
13
|
+
import DurationDisplay from './components/DurationDisplay.svelte';
|
|
14
|
+
import TimeEntryApprovalQueue from './components/TimeEntryApprovalQueue.svelte';
|
|
15
|
+
import TimeEntryCard from './components/TimeEntryCard.svelte';
|
|
16
|
+
import TimeEntryList from './components/TimeEntryList.svelte';
|
|
17
|
+
import TimeSummary from './components/TimeSummary.svelte';
|
|
18
|
+
export { DurationDisplay, TimeEntryApprovalQueue, TimeEntryCard, TimeEntryList, TimeSummary, };
|
|
19
|
+
export { formatCurrency, formatDate, formatHours, formatHoursHHMM, statusColors, } from './components/utils.js';
|
|
20
|
+
export { humanizeTimeEntryStatus, timeEntryStatusBadgeKey, } from './types.js';
|
|
21
|
+
// Auto-register with ModuleUIRegistry
|
|
22
|
+
ModuleUIRegistry.registerModule(TIMESHEETS_MODULE_META);
|
|
23
|
+
ModuleUIRegistry.register('@happyvertical/smrt-timesheets', 'time-entry-card', TimeEntryCard);
|
|
24
|
+
ModuleUIRegistry.register('@happyvertical/smrt-timesheets', 'time-entry-list', TimeEntryList);
|
|
25
|
+
ModuleUIRegistry.register('@happyvertical/smrt-timesheets', 'time-summary', TimeSummary);
|
|
26
|
+
ModuleUIRegistry.register('@happyvertical/smrt-timesheets', 'duration-display', DurationDisplay);
|
|
27
|
+
ModuleUIRegistry.register('@happyvertical/smrt-timesheets', 'time-entry-approval-queue', TimeEntryApprovalQueue);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serializable view types for the timesheets approval surface.
|
|
3
|
+
*
|
|
4
|
+
* Components take plain view objects (not model instances) so hosts can pass
|
|
5
|
+
* data across the server/client boundary. Explicit interfaces (not inline
|
|
6
|
+
* intersections) keep Svelte 5 prop type evaluation cheap.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* View shape for a time entry in approval/review surfaces. The first eight
|
|
10
|
+
* fields deliberately match the presentation `TimeEntry` contract
|
|
11
|
+
* (`./components/utils.ts`), so hosts can reuse either surface over the same
|
|
12
|
+
* view rows. Domain packages extend it — smrt-support's `SupportTimeEntryView`
|
|
13
|
+
* adds `caseId`.
|
|
14
|
+
*/
|
|
15
|
+
export interface TimeEntryApprovalView {
|
|
16
|
+
id: string;
|
|
17
|
+
/** ISO date part (`YYYY-MM-DD`) of the work period start. */
|
|
18
|
+
date: string;
|
|
19
|
+
/** Worked duration in decimal hours, rounded to 2 decimals. */
|
|
20
|
+
hours: number;
|
|
21
|
+
description: string;
|
|
22
|
+
status: string;
|
|
23
|
+
/** Client charge in integer minor units — `$19.99` is `1999` (#2401). */
|
|
24
|
+
amount?: number;
|
|
25
|
+
/**
|
|
26
|
+
* ISO 4217 code `amount` / `hourlyRate` are denominated in. Carried on the
|
|
27
|
+
* view because the minor-unit exponent is a property of the currency, not a
|
|
28
|
+
* constant: `¥1999` is 1999 minor units, not 19.99 (#2401).
|
|
29
|
+
*/
|
|
30
|
+
currency?: string;
|
|
31
|
+
workerName?: string;
|
|
32
|
+
/** Hourly rate in minor units per hour (#2401). */
|
|
33
|
+
hourlyRate?: number;
|
|
34
|
+
source: string;
|
|
35
|
+
participantKind: string;
|
|
36
|
+
}
|
|
37
|
+
/** Human-readable label for a snake_case status value. */
|
|
38
|
+
export declare function humanizeTimeEntryStatus(value: string): string;
|
|
39
|
+
/** Map a time-entry status onto `StatusBadge` default color-scheme keys. */
|
|
40
|
+
export declare function timeEntryStatusBadgeKey(status: string): string;
|
|
41
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/svelte/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,0DAA0D;AAC1D,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,4EAA4E;AAC5E,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAe9D"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serializable view types for the timesheets approval surface.
|
|
3
|
+
*
|
|
4
|
+
* Components take plain view objects (not model instances) so hosts can pass
|
|
5
|
+
* data across the server/client boundary. Explicit interfaces (not inline
|
|
6
|
+
* intersections) keep Svelte 5 prop type evaluation cheap.
|
|
7
|
+
*/
|
|
8
|
+
/** Human-readable label for a snake_case status value. */
|
|
9
|
+
export function humanizeTimeEntryStatus(value) {
|
|
10
|
+
return (value ?? '').replace(/_/g, ' ');
|
|
11
|
+
}
|
|
12
|
+
/** Map a time-entry status onto `StatusBadge` default color-scheme keys. */
|
|
13
|
+
export function timeEntryStatusBadgeKey(status) {
|
|
14
|
+
switch (status) {
|
|
15
|
+
case 'draft':
|
|
16
|
+
return 'inactive';
|
|
17
|
+
case 'submitted':
|
|
18
|
+
return 'pending';
|
|
19
|
+
case 'approved':
|
|
20
|
+
return 'success';
|
|
21
|
+
case 'rejected':
|
|
22
|
+
return 'error';
|
|
23
|
+
case 'corrected':
|
|
24
|
+
return 'warning';
|
|
25
|
+
default:
|
|
26
|
+
return 'pending';
|
|
27
|
+
}
|
|
28
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared time-entry vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* `smrt-projects` and `smrt-support` re-export these names unchanged, so a
|
|
5
|
+
* value typed against either package's alias is the same type.
|
|
6
|
+
*/
|
|
7
|
+
/** How a time entry was produced. */
|
|
8
|
+
export type ServiceTimeEntrySource = 'timer' | 'manual' | 'import' | 'agent';
|
|
9
|
+
/**
|
|
10
|
+
* Time-entry approval lifecycle. `approved → corrected` is the only exit from
|
|
11
|
+
* `approved`, reached by recording a correction as a new row.
|
|
12
|
+
*/
|
|
13
|
+
export type ServiceTimeEntryStatus = 'draft' | 'submitted' | 'approved' | 'rejected' | 'corrected';
|
|
14
|
+
/** Who delivered the work behind a time entry. */
|
|
15
|
+
export type ServiceParticipantKind = 'human' | 'agent';
|
|
16
|
+
/**
|
|
17
|
+
* Legal time-entry status transitions, enforced by `ServiceTimeEntry.save()`.
|
|
18
|
+
* Approved and corrected rows additionally keep their evidence frozen.
|
|
19
|
+
*/
|
|
20
|
+
export declare const SERVICE_TIME_ENTRY_STATUS_TRANSITIONS: Record<ServiceTimeEntryStatus, ServiceTimeEntryStatus[]>;
|
|
21
|
+
/** One piece of supporting evidence attached to a time entry. */
|
|
22
|
+
export interface ServiceEvidence {
|
|
23
|
+
kind: string;
|
|
24
|
+
ref?: string;
|
|
25
|
+
summary?: string;
|
|
26
|
+
capturedAt?: string;
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,qCAAqC;AACrC,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE7E;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAC9B,OAAO,GACP,WAAW,GACX,UAAU,GACV,UAAU,GACV,WAAW,CAAC;AAEhB,kDAAkD;AAClD,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,OAAO,CAAC;AAEvD;;;GAGG;AACH,eAAO,MAAM,qCAAqC,EAAE,MAAM,CACxD,sBAAsB,EACtB,sBAAsB,EAAE,CAOzB,CAAC;AAEF,iEAAiE;AACjE,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/types.ts
|
|
2
|
+
var SERVICE_TIME_ENTRY_STATUS_TRANSITIONS = {
|
|
3
|
+
draft: ["submitted", "rejected"],
|
|
4
|
+
submitted: [
|
|
5
|
+
"approved",
|
|
6
|
+
"rejected",
|
|
7
|
+
"draft"
|
|
8
|
+
],
|
|
9
|
+
approved: ["corrected"],
|
|
10
|
+
rejected: ["draft", "submitted"],
|
|
11
|
+
corrected: []
|
|
12
|
+
};
|
|
13
|
+
//#endregion
|
|
14
|
+
export { SERVICE_TIME_ENTRY_STATUS_TRANSITIONS };
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["/**\n * Shared time-entry vocabulary.\n *\n * `smrt-projects` and `smrt-support` re-export these names unchanged, so a\n * value typed against either package's alias is the same type.\n */\n\n/** How a time entry was produced. */\nexport type ServiceTimeEntrySource = 'timer' | 'manual' | 'import' | 'agent';\n\n/**\n * Time-entry approval lifecycle. `approved → corrected` is the only exit from\n * `approved`, reached by recording a correction as a new row.\n */\nexport type ServiceTimeEntryStatus =\n | 'draft'\n | 'submitted'\n | 'approved'\n | 'rejected'\n | 'corrected';\n\n/** Who delivered the work behind a time entry. */\nexport type ServiceParticipantKind = 'human' | 'agent';\n\n/**\n * Legal time-entry status transitions, enforced by `ServiceTimeEntry.save()`.\n * Approved and corrected rows additionally keep their evidence frozen.\n */\nexport const SERVICE_TIME_ENTRY_STATUS_TRANSITIONS: Record<\n ServiceTimeEntryStatus,\n ServiceTimeEntryStatus[]\n> = {\n draft: ['submitted', 'rejected'],\n submitted: ['approved', 'rejected', 'draft'],\n approved: ['corrected'],\n rejected: ['draft', 'submitted'],\n corrected: [],\n};\n\n/** One piece of supporting evidence attached to a time entry. */\nexport interface ServiceEvidence {\n kind: string;\n ref?: string;\n summary?: string;\n capturedAt?: string;\n [key: string]: unknown;\n}\n"],"mappings":";AA4BO,IAAM,wCAGT;CACF,OAAO,CAAC,aAAa,UAAU;CAC/B,WAAW;EAAC;EAAY;EAAY;CAAO;CAC3C,UAAU,CAAC,WAAW;CACtB,UAAU,CAAC,SAAS,WAAW;CAC/B,WAAW,CAAC;AACd"}
|
package/dist/ui.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timesheets Module UI Slot Declarations
|
|
3
|
+
*
|
|
4
|
+
* UI extension points for the timesheets module. Components are implemented in
|
|
5
|
+
* the ./svelte subpath and are props-driven: hosts load entries and pass plain
|
|
6
|
+
* view objects.
|
|
7
|
+
*/
|
|
8
|
+
import type { ModuleUISlot, SmrtModuleMeta } from '@happyvertical/smrt-types';
|
|
9
|
+
/**
|
|
10
|
+
* Timesheets module UI slots
|
|
11
|
+
*/
|
|
12
|
+
export declare const TIMESHEETS_UI_SLOTS: Record<string, ModuleUISlot>;
|
|
13
|
+
/**
|
|
14
|
+
* Timesheets module metadata
|
|
15
|
+
*/
|
|
16
|
+
export declare const TIMESHEETS_MODULE_META: SmrtModuleMeta;
|
|
17
|
+
//# sourceMappingURL=ui.d.ts.map
|
package/dist/ui.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE9E;;GAEG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CA8C5D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,sBAAsB,EAAE,cAgBpC,CAAC"}
|