@happyvertical/smrt-timesheets 0.55.0 → 0.55.1
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 +8 -0
- package/dist/attendance.js +2 -2
- package/dist/chunks/{attendance-kUM0ANfE.js → attendance-CF0GBN7q.js} +3 -3
- package/dist/chunks/{attendance-kUM0ANfE.js.map → attendance-CF0GBN7q.js.map} +1 -1
- package/dist/chunks/{models-DNbDLJCb.js → models-BciT0p0z.js} +2 -2
- package/dist/chunks/{models-DNbDLJCb.js.map → models-BciT0p0z.js.map} +1 -1
- package/dist/chunks/{rollup-D18ToLCo.js → rollup-DURZiNG_.js} +3 -3
- package/dist/chunks/{rollup-D18ToLCo.js.map → rollup-DURZiNG_.js.map} +1 -1
- package/dist/chunks/{service-time-entry-BAkx99ks.js → service-time-entry-BHCWB7Zr.js} +2 -2
- package/dist/chunks/{service-time-entry-BAkx99ks.js.map → service-time-entry-BHCWB7Zr.js.map} +1 -1
- package/dist/index.js +3 -3
- package/dist/manifest.json +1 -1
- package/dist/rollup.js +1 -1
- package/dist/smrt-knowledge.json +5 -5
- package/dist/svelte/components/TimeEntryApprovalQueue.svelte +21 -11
- package/dist/svelte/components/TimeEntryApprovalQueue.svelte.d.ts +8 -0
- package/dist/svelte/components/TimeEntryApprovalQueue.svelte.d.ts.map +1 -1
- package/dist/svelte/components/TimeEntryCard.svelte +10 -1
- package/dist/svelte/components/TimeEntryCard.svelte.d.ts +5 -1
- package/dist/svelte/components/TimeEntryCard.svelte.d.ts.map +1 -1
- package/dist/svelte/components/TimeEntryList.svelte +11 -1
- package/dist/svelte/components/TimeEntryList.svelte.d.ts +6 -1
- package/dist/svelte/components/TimeEntryList.svelte.d.ts.map +1 -1
- package/dist/svelte/components/TimeSummary.svelte +12 -4
- package/dist/svelte/components/TimeSummary.svelte.d.ts +3 -1
- package/dist/svelte/components/TimeSummary.svelte.d.ts.map +1 -1
- package/dist/svelte/components/utils.d.ts +6 -1
- package/dist/svelte/components/utils.d.ts.map +1 -1
- package/dist/svelte/components/utils.js +41 -9
- package/dist/svelte/index.d.ts +1 -1
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { a as validateDurationHours, i as ServiceTimeEntryCollection, n as SERVICE_TIME_ENTRY_FROZEN_FIELDS, r as ServiceTimeEntry, t as SERVICE_DURATION_HOURS_EVIDENCE } from "./chunks/service-time-entry-
|
|
2
|
-
import { a as AttendanceReplay, i as AttendancePunchCollection, n as AttendanceBreakCollection, o as AttendanceReplayCollection, r as AttendancePunch, t as AttendanceBreak } from "./chunks/models-
|
|
1
|
+
import { a as validateDurationHours, i as ServiceTimeEntryCollection, n as SERVICE_TIME_ENTRY_FROZEN_FIELDS, r as ServiceTimeEntry, t as SERVICE_DURATION_HOURS_EVIDENCE } from "./chunks/service-time-entry-BHCWB7Zr.js";
|
|
2
|
+
import { a as AttendanceReplay, i as AttendancePunchCollection, n as AttendanceBreakCollection, o as AttendanceReplayCollection, r as AttendancePunch, t as AttendanceBreak } from "./chunks/models-BciT0p0z.js";
|
|
3
3
|
import { SERVICE_TIME_ENTRY_STATUS_TRANSITIONS } from "./types.js";
|
|
4
4
|
import { AttendanceConflict, AttendanceService } from "./attendance.js";
|
|
5
|
-
import { a as PeriodTimecard, c as TimecardAdjustmentCollection, o as PeriodTimecardCollection, s as TimecardAdjustment, t as PeriodRollupService } from "./chunks/rollup-
|
|
5
|
+
import { a as PeriodTimecard, c as TimecardAdjustmentCollection, o as PeriodTimecardCollection, s as TimecardAdjustment, t as PeriodRollupService } from "./chunks/rollup-DURZiNG_.js";
|
|
6
6
|
import { TIMESHEETS_MODULE_META, TIMESHEETS_UI_SLOTS } from "./ui.js";
|
|
7
7
|
import { SmrtCollection, SmrtObject, ValidationError, field, foreignKey, smrt } from "@happyvertical/smrt-core";
|
|
8
8
|
import { TenantScoped, tenantId } from "@happyvertical/smrt-tenancy";
|
package/dist/manifest.json
CHANGED
package/dist/rollup.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as PeriodTimecard, c as TimecardAdjustmentCollection, o as PeriodTimecardCollection, s as TimecardAdjustment, t as PeriodRollupService } from "./chunks/rollup-
|
|
1
|
+
import { a as PeriodTimecard, c as TimecardAdjustmentCollection, o as PeriodTimecardCollection, s as TimecardAdjustment, t as PeriodRollupService } from "./chunks/rollup-DURZiNG_.js";
|
|
2
2
|
export { PeriodRollupService, PeriodTimecard, PeriodTimecardCollection, TimecardAdjustment, TimecardAdjustmentCollection };
|
package/dist/smrt-knowledge.json
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
"sensitiveFieldsExcluded": true,
|
|
4
4
|
"generatedAt": "1970-01-01T00:00:00.000Z",
|
|
5
5
|
"packageName": "@happyvertical/smrt-timesheets",
|
|
6
|
-
"packageVersion": "0.55.
|
|
6
|
+
"packageVersion": "0.55.1",
|
|
7
7
|
"sourceManifestPath": "dist/manifest.json",
|
|
8
8
|
"agentDocPath": "AGENTS.md",
|
|
9
9
|
"sourceHashes": {
|
|
10
|
-
"manifest": "
|
|
11
|
-
"packageJson": "
|
|
12
|
-
"agents": "
|
|
10
|
+
"manifest": "5b23d2c9494b884563791ea7e0d221e9f1c685d243142a83168072443a161edf",
|
|
11
|
+
"packageJson": "a5a15dae1f24ba683bf882519b682414c9d937d4fb830abb042e576dd50e8e63",
|
|
12
|
+
"agents": "46a4e8437424b494645e90ce4a4869be04956c5da6a42f89697cbd3ab2199a7e",
|
|
13
13
|
"moduleDoc:agents/period-rollup.md": "5fb741659d81dbef2e4850c10c2d65f09e3f5a83e8a88e2e0f19585f25158a6a"
|
|
14
14
|
},
|
|
15
15
|
"exports": [
|
|
@@ -1914,7 +1914,7 @@
|
|
|
1914
1914
|
"polymorphicAssociations": 0,
|
|
1915
1915
|
"uuidColumns": 62
|
|
1916
1916
|
},
|
|
1917
|
-
"agentDoc": "# smrt-timesheets\n\nShared time entries: who (a `smrt-profiles` Profile, or an agent) worked how\nlong on what (a generic work reference), whether it was approved, and the\nimmutable charge / compensation snapshots taken at approval. Moved out of\n`smrt-projects` / `smrt-support` in #3288 so shops, builders, and service\ndesks share one model instead of re-implementing it.\n\nThis package contains **layer 1 (entry)**, **layer 2 (attendance)**, and\n**layer 3 (period rollup)**.\n`src/attendance/` owns punches, breaks, atomic transitions and durable offline\nreceipts; `./attendance` is the public subpath. Period rollup belongs in this\npackage too, never projects or support. See the README attendance contract.\nAttendance services accept trusted session actors, serialize mutations per\nactor, and bind every write to one executor. The new tables use non-null unique\nopen slots, not nullable unique keys. A linked entry is draft evidence, and\nrollups must deduplicate `AttendancePunch.serviceTimeEntryId`.\n\n## Models\n\n| Model | Table | Notes |\n|---|---|---|\n| `ServiceTimeEntry` | `service_time_entries` | `workRefType` + `workRefId`, `participantKind` (`human` \\| `agent`), `participantProfileId` / `agentRef`, `source`, `startedAt` / `endedAt` / `durationSeconds`, status machine, `correctionOfId` |\n| `ServiceChargeSnapshot` | `service_charge_snapshots` | Client charge, integer minor units (#2401), one per entry |\n| `ServiceCompensationSnapshot` | `service_compensation_snapshots` | Provider earning, integer minor units, one per entry |\n\n`ServiceEvidenceService` records, submits, approves (writing both snapshots\nthrough a `ServiceCommercialResolver`), and corrects. `smrt-projects` supplies\n`SubscriptionServiceCommercialResolver` for #1925 pricing.\n\n## Invariants\n\n- **No domain foreign key on the base.** Work context is the generic\n `workRefType` (qualified class name) + `workRefId`. A domain adds its own\n context by subclassing over the same table — smrt-support's\n `ServiceTimeEntry` adds `caseId` / `specialistId` and extends\n `frozenFieldNames()` so they freeze on approval.\n- **Entries carry no rate.** Money lives only on the snapshots, `= 0`\n integer initializers (INTEGER columns). Never `= 0.0`. Snapshot `save()`\n rejects an `amount` that is not a safe integer (`VALIDATION_INVALID_VALUE`),\n so SQLite cannot silently store `19.99`; fix a fractional resolver at its\n source rather than rounding here.\n- **Approved evidence never changes.** `save()` enforces\n `SERVICE_TIME_ENTRY_STATUS_TRANSITIONS` and compares the frozen fields with\n the stored row. A correction is a new row (`correctionOfId`), the original\n flips to `corrected`. Snapshots are immutable after their first save.\n- **Old qualified names are aliases.** Each model declares its pre-#3288\n `@happyvertical/smrt-projects:*` name in `previousQualifiedNames` (#3338;\n `packages/core/agents/model-moves.md`). smrt-projects must never list these\n classes in its manifests again (`CONFIG_QUALIFIED_NAME_ALIAS_COLLISION`),\n and no subtype (smrt-support's, or a consumer's closing subclass) may\n redeclare an alias (two claimants). Known gap: a same-named subtype that\n replaces a moved class drops that class's alias, and the replaced class's\n own `@happyvertical/smrt-timesheets:*` name stops resolving too — guidance\n for apps with smrt-support must name `@happyvertical/smrt-support:ServiceTimeEntry`.\n Support's vitest environment keeps both classes registered, so verify this\n against built packages, not package tests. `projects/src/__tests__/legacy-qualified-identity.test.ts` pins\n the projects-only contract.\n- **Table names are a compatibility contract.** `service_time_entries` and the\n two snapshot tables hold existing production rows from smrt-projects /\n smrt-support; renaming any of them, or adding a discriminator, is a data\n migration.\n\n## One table family\n\nClasses may share a table only as one inheritance chain. Core resolves\nsame-named subclasses by constructor ancestry at runtime and qualified manifest\nancestry at build registration; the deepest subtype wins across N levels.\nSibling or unrelated types cannot share the table. `smrt-projects` re-exports\nthe base; `smrt-support` subclasses it directly.\n\nConsumers closing package routes should call\n`ObjectRegistry.registerOverride(qualifiedName, { api: false, mcp: false,\ncli: false, tenancy: { mode: 'required' } })` after importing the model and before\nconstructing transports. This tightens the existing registration without a new\nsubtype. See core's README for startup order, static route generation, and reset\nsemantics. `src/__tests__/consumer-closed-surface.test.ts` also covers existing\nmanifest subclass consumers. The snapshots' explicit `time_entry_id` conflict\nkey is not rewritten when a consumer requires tenancy; widening it remains a\nconsumer schema change.\n\nSubclasses restate their fields: package-isolated manifest scanning does not\nread dependency sources, so an unrestated subclass manifest has no columns.\n\n## Svelte UI\n\n`./svelte` exports `TimeEntryCard`, `TimeEntryList`, `TimeSummary`,\n`DurationDisplay`, and `TimeEntryApprovalQueue` (props-driven, plain view\nobjects, `smrt-ui` primitives, `smrtRawPrimitives: \"strict\"`). smrt-projects\nand smrt-support re-export them from their own `./svelte` subpaths.\nCard/list/summary message keys keep their `projects.*` namespace: tenant\nlanguage overrides are stored by key, and renaming would orphan them. New\nkeys use `timesheets.*`.\n\n## Validation\n\n```sh\npnpm --filter @happyvertical/smrt-timesheets test\npnpm --filter @happyvertical/smrt-timesheets test:postgres\npnpm --filter @happyvertical/smrt-timesheets typecheck\npnpm --filter @happyvertical/smrt-timesheets build\npnpm --filter @happyvertical/smrt-projects test\npnpm --filter @happyvertical/smrt-support test\n```\n\nSchema-affecting edits must keep `service-time-entry-compat.test.ts` (projects,\nsupport) and `service-time-entry-migration-plan.test.ts` (support) green: a\npre-move table must plan no DDL.\n\n## Period rollup\n\nLayer 3 lives under `src/rollup/` and the `./rollup` subpath. See\n[period-rollup.md](agents/period-rollup.md) for resolver, source clipping,\nconfirmation, attributed adjustments, isolation, and transaction contracts.\nTimecards and adjustments have closed generated surfaces and service-only\nmodel writes; never bypass the instance capability with generic persistence.\n",
|
|
1917
|
+
"agentDoc": "# smrt-timesheets\n\nShared time entries: who (a `smrt-profiles` Profile, or an agent) worked how\nlong on what (a generic work reference), whether it was approved, and the\nimmutable charge / compensation snapshots taken at approval. Moved out of\n`smrt-projects` / `smrt-support` in #3288 so shops, builders, and service\ndesks share one model instead of re-implementing it.\n\nThis package contains **layer 1 (entry)**, **layer 2 (attendance)**, and\n**layer 3 (period rollup)**.\n`src/attendance/` owns punches, breaks, atomic transitions and durable offline\nreceipts; `./attendance` is the public subpath. Period rollup belongs in this\npackage too, never projects or support. See the README attendance contract.\nAttendance services accept trusted session actors, serialize mutations per\nactor, and bind every write to one executor. The new tables use non-null unique\nopen slots, not nullable unique keys. A linked entry is draft evidence, and\nrollups must deduplicate `AttendancePunch.serviceTimeEntryId`.\n\n## Models\n\n| Model | Table | Notes |\n|---|---|---|\n| `ServiceTimeEntry` | `service_time_entries` | `workRefType` + `workRefId`, `participantKind` (`human` \\| `agent`), `participantProfileId` / `agentRef`, `source`, `startedAt` / `endedAt` / `durationSeconds`, status machine, `correctionOfId` |\n| `ServiceChargeSnapshot` | `service_charge_snapshots` | Client charge, integer minor units (#2401), one per entry |\n| `ServiceCompensationSnapshot` | `service_compensation_snapshots` | Provider earning, integer minor units, one per entry |\n\n`ServiceEvidenceService` records, submits, approves (writing both snapshots\nthrough a `ServiceCommercialResolver`), and corrects. `smrt-projects` supplies\n`SubscriptionServiceCommercialResolver` for #1925 pricing.\n\n## Invariants\n\n- **No domain foreign key on the base.** Work context is the generic\n `workRefType` (qualified class name) + `workRefId`. A domain adds its own\n context by subclassing over the same table — smrt-support's\n `ServiceTimeEntry` adds `caseId` / `specialistId` and extends\n `frozenFieldNames()` so they freeze on approval.\n- **Entries carry no rate.** Money lives only on the snapshots, `= 0`\n integer initializers (INTEGER columns). Never `= 0.0`. Snapshot `save()`\n rejects an `amount` that is not a safe integer (`VALIDATION_INVALID_VALUE`),\n so SQLite cannot silently store `19.99`; fix a fractional resolver at its\n source rather than rounding here.\n- **Approved evidence never changes.** `save()` enforces\n `SERVICE_TIME_ENTRY_STATUS_TRANSITIONS` and compares the frozen fields with\n the stored row. A correction is a new row (`correctionOfId`), the original\n flips to `corrected`. Snapshots are immutable after their first save.\n- **Old qualified names are aliases.** Each model declares its pre-#3288\n `@happyvertical/smrt-projects:*` name in `previousQualifiedNames` (#3338;\n `packages/core/agents/model-moves.md`). smrt-projects must never list these\n classes in its manifests again (`CONFIG_QUALIFIED_NAME_ALIAS_COLLISION`),\n and no subtype (smrt-support's, or a consumer's closing subclass) may\n redeclare an alias (two claimants). Known gap: a same-named subtype that\n replaces a moved class drops that class's alias, and the replaced class's\n own `@happyvertical/smrt-timesheets:*` name stops resolving too — guidance\n for apps with smrt-support must name `@happyvertical/smrt-support:ServiceTimeEntry`.\n Support's vitest environment keeps both classes registered, so verify this\n against built packages, not package tests. `projects/src/__tests__/legacy-qualified-identity.test.ts` pins\n the projects-only contract.\n- **Table names are a compatibility contract.** `service_time_entries` and the\n two snapshot tables hold existing production rows from smrt-projects /\n smrt-support; renaming any of them, or adding a discriminator, is a data\n migration.\n\n## One table family\n\nClasses may share a table only as one inheritance chain. Core resolves\nsame-named subclasses by constructor ancestry at runtime and qualified manifest\nancestry at build registration; the deepest subtype wins across N levels.\nSibling or unrelated types cannot share the table. `smrt-projects` re-exports\nthe base; `smrt-support` subclasses it directly.\n\nConsumers closing package routes should call\n`ObjectRegistry.registerOverride(qualifiedName, { api: false, mcp: false,\ncli: false, tenancy: { mode: 'required' } })` after importing the model and before\nconstructing transports. This tightens the existing registration without a new\nsubtype. See core's README for startup order, static route generation, and reset\nsemantics. `src/__tests__/consumer-closed-surface.test.ts` also covers existing\nmanifest subclass consumers. The snapshots' explicit `time_entry_id` conflict\nkey is not rewritten when a consumer requires tenancy; widening it remains a\nconsumer schema change.\n\nSubclasses restate their fields: package-isolated manifest scanning does not\nread dependency sources, so an unrestated subclass manifest has no columns.\n\n## Svelte UI\n\n`./svelte` exports `TimeEntryCard`, `TimeEntryList`, `TimeSummary`,\n`DurationDisplay`, and `TimeEntryApprovalQueue` (props-driven, plain view\nobjects, `smrt-ui` primitives, `smrtRawPrimitives: \"strict\"`). smrt-projects\nand smrt-support re-export them from their own `./svelte` subpaths.\nCard/list/summary message keys keep their `projects.*` namespace: tenant\nlanguage overrides are stored by key, and renaming would orphan them. New\nkeys use `timesheets.*`.\n\nDisplay components keep the legacy one-decimal hours format by default. A\nconsumer that retains more precise source evidence supplies `hoursFormatter`;\ncard/list/queue detail snippets carry domain evidence without widening the base\nview. ApprovalQueue's optional action snippet replaces its callback controls so\nthe host can own a native form, request identity, authorization and retry flow.\nDisplay currency codes are open ISO strings and amounts remain integer minor\nunits; never put private rates into a view that should not expose them.\n\n## Validation\n\n```sh\npnpm --filter @happyvertical/smrt-timesheets test\npnpm --filter @happyvertical/smrt-timesheets test:postgres\npnpm --filter @happyvertical/smrt-timesheets typecheck\npnpm --filter @happyvertical/smrt-timesheets build\npnpm --filter @happyvertical/smrt-projects test\npnpm --filter @happyvertical/smrt-support test\n```\n\nSchema-affecting edits must keep `service-time-entry-compat.test.ts` (projects,\nsupport) and `service-time-entry-migration-plan.test.ts` (support) green: a\npre-move table must plan no DDL.\n\n## Period rollup\n\nLayer 3 lives under `src/rollup/` and the `./rollup` subpath. See\n[period-rollup.md](agents/period-rollup.md) for resolver, source clipping,\nconfirmation, attributed adjustments, isolation, and transaction contracts.\nTimecards and adjustments have closed generated surfaces and service-only\nmodel writes; never bypass the instance capability with generic persistence.\n",
|
|
1918
1918
|
"moduleDocs": [
|
|
1919
1919
|
{
|
|
1920
1920
|
"path": "agents/period-rollup.md",
|
|
@@ -13,12 +13,18 @@
|
|
|
13
13
|
import { Button, StatusBadge } from '@happyvertical/smrt-ui';
|
|
14
14
|
import { Input } from '@happyvertical/smrt-ui/forms';
|
|
15
15
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
16
|
+
import type { Snippet } from 'svelte';
|
|
16
17
|
import { M } from '../i18n.js';
|
|
17
18
|
import {
|
|
18
19
|
humanizeTimeEntryStatus,
|
|
19
20
|
type TimeEntryApprovalView,
|
|
20
21
|
timeEntryStatusBadgeKey,
|
|
21
22
|
} from '../types.js';
|
|
23
|
+
import {
|
|
24
|
+
currencyMinorUnitExponent,
|
|
25
|
+
formatHours,
|
|
26
|
+
type HoursFormatter,
|
|
27
|
+
} from './utils.js';
|
|
22
28
|
|
|
23
29
|
const { t } = useI18n();
|
|
24
30
|
|
|
@@ -31,6 +37,12 @@ export interface TimeEntryApprovalQueueProps {
|
|
|
31
37
|
onreject?: (id: string, reason: string) => void;
|
|
32
38
|
/** Message displayed when the approval queue is empty. */
|
|
33
39
|
emptyMessage?: string;
|
|
40
|
+
/** Formats decimal hours; defaults to the legacy one-decimal display. */
|
|
41
|
+
hoursFormatter?: HoursFormatter;
|
|
42
|
+
/** Renders caller-owned evidence for each entry. */
|
|
43
|
+
details?: Snippet<[TimeEntryApprovalView]>;
|
|
44
|
+
/** Replaces callback actions with caller-owned controls, including native forms. */
|
|
45
|
+
actions?: Snippet<[TimeEntryApprovalView]>;
|
|
34
46
|
}
|
|
35
47
|
|
|
36
48
|
const {
|
|
@@ -38,6 +50,9 @@ const {
|
|
|
38
50
|
onapprove,
|
|
39
51
|
onreject,
|
|
40
52
|
emptyMessage,
|
|
53
|
+
hoursFormatter = formatHours,
|
|
54
|
+
details,
|
|
55
|
+
actions,
|
|
41
56
|
}: TimeEntryApprovalQueueProps = $props();
|
|
42
57
|
|
|
43
58
|
/** The entry currently collecting a rejection reason (one at a time). */
|
|
@@ -61,10 +76,6 @@ function confirmReject(id: string): void {
|
|
|
61
76
|
cancelReject();
|
|
62
77
|
}
|
|
63
78
|
|
|
64
|
-
function formatHours(hours: number): string {
|
|
65
|
-
return `${hours.toFixed(1)}h`;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
79
|
/**
|
|
69
80
|
* Render a minor-units charge for display.
|
|
70
81
|
*
|
|
@@ -76,11 +87,7 @@ function formatHours(hours: number): string {
|
|
|
76
87
|
* and the host app supplies the label.
|
|
77
88
|
*/
|
|
78
89
|
function formatAmount(amount: number, currency = 'USD'): string {
|
|
79
|
-
const digits =
|
|
80
|
-
new Intl.NumberFormat(undefined, {
|
|
81
|
-
style: 'currency',
|
|
82
|
-
currency,
|
|
83
|
-
}).resolvedOptions().maximumFractionDigits ?? 2;
|
|
90
|
+
const digits = currencyMinorUnitExponent(currency);
|
|
84
91
|
return (amount / 10 ** digits).toFixed(digits);
|
|
85
92
|
}
|
|
86
93
|
</script>
|
|
@@ -102,7 +109,7 @@ function formatAmount(amount: number, currency = 'USD'): string {
|
|
|
102
109
|
{/if}
|
|
103
110
|
</span>
|
|
104
111
|
<span class="time-approval-meta">
|
|
105
|
-
<span class="time-approval-hours">{
|
|
112
|
+
<span class="time-approval-hours">{hoursFormatter(entry.hours)}</span>
|
|
106
113
|
<StatusBadge
|
|
107
114
|
status={timeEntryStatusBadgeKey(entry.status)}
|
|
108
115
|
label={humanizeTimeEntryStatus(entry.status)}
|
|
@@ -115,9 +122,12 @@ function formatAmount(amount: number, currency = 'USD'): string {
|
|
|
115
122
|
{/if}
|
|
116
123
|
</span>
|
|
117
124
|
</div>
|
|
125
|
+
{@render details?.(entry)}
|
|
118
126
|
{#if entry.status === 'submitted'}
|
|
119
127
|
<div class="time-approval-actions">
|
|
120
|
-
{#if
|
|
128
|
+
{#if actions}
|
|
129
|
+
{@render actions(entry)}
|
|
130
|
+
{:else if rejectingId === entry.id}
|
|
121
131
|
<Input
|
|
122
132
|
bind:value={rejectReason}
|
|
123
133
|
placeholder={t(M['timesheets.approval_queue.reason_placeholder'])}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
1
2
|
import { type TimeEntryApprovalView } from '../types.js';
|
|
3
|
+
import { type HoursFormatter } from './utils.js';
|
|
2
4
|
export interface TimeEntryApprovalQueueProps {
|
|
3
5
|
/** Array of time entries awaiting approval review. */
|
|
4
6
|
entries: TimeEntryApprovalView[];
|
|
@@ -8,6 +10,12 @@ export interface TimeEntryApprovalQueueProps {
|
|
|
8
10
|
onreject?: (id: string, reason: string) => void;
|
|
9
11
|
/** Message displayed when the approval queue is empty. */
|
|
10
12
|
emptyMessage?: string;
|
|
13
|
+
/** Formats decimal hours; defaults to the legacy one-decimal display. */
|
|
14
|
+
hoursFormatter?: HoursFormatter;
|
|
15
|
+
/** Renders caller-owned evidence for each entry. */
|
|
16
|
+
details?: Snippet<[TimeEntryApprovalView]>;
|
|
17
|
+
/** Replaces callback actions with caller-owned controls, including native forms. */
|
|
18
|
+
actions?: Snippet<[TimeEntryApprovalView]>;
|
|
11
19
|
}
|
|
12
20
|
declare const TimeEntryApprovalQueue: import("svelte").Component<TimeEntryApprovalQueueProps, {}, "">;
|
|
13
21
|
type TimeEntryApprovalQueue = ReturnType<typeof TimeEntryApprovalQueue>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TimeEntryApprovalQueue.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/TimeEntryApprovalQueue.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TimeEntryApprovalQueue.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/TimeEntryApprovalQueue.svelte.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,EAEL,KAAK,qBAAqB,EAE3B,MAAM,aAAa,CAAC;AACrB,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,2BAA2B;IAC1C,sDAAsD;IACtD,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACjC,kEAAkE;IAClE,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,uFAAuF;IACvF,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,oDAAoD;IACpD,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC;IAC3C,oFAAoF;IACpF,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC;CAC5C;AAwHD,QAAA,MAAM,sBAAsB,iEAAwC,CAAC;AACrE,KAAK,sBAAsB,GAAG,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC;AACxE,eAAe,sBAAsB,CAAC"}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
formatCurrency,
|
|
13
13
|
formatDate,
|
|
14
14
|
formatHours,
|
|
15
|
+
type HoursFormatter,
|
|
15
16
|
statusColors,
|
|
16
17
|
type TimeEntry,
|
|
17
18
|
} from './utils.js';
|
|
@@ -32,6 +33,10 @@ export interface Props {
|
|
|
32
33
|
onselect?: (id: string, selected: boolean) => void;
|
|
33
34
|
/** Currency code for formatting hourly rates and amounts. */
|
|
34
35
|
currency?: Currency;
|
|
36
|
+
/** Formats decimal hours; defaults to the legacy one-decimal display. */
|
|
37
|
+
hoursFormatter?: HoursFormatter;
|
|
38
|
+
/** Renders caller-owned evidence below the standard entry fields. */
|
|
39
|
+
details?: Snippet<[TimeEntry]>;
|
|
35
40
|
/** Renders action buttons on the right side of the card. */
|
|
36
41
|
actions?: Snippet;
|
|
37
42
|
}
|
|
@@ -44,6 +49,8 @@ let {
|
|
|
44
49
|
selected = false,
|
|
45
50
|
onselect,
|
|
46
51
|
currency = 'CAD',
|
|
52
|
+
hoursFormatter = formatHours,
|
|
53
|
+
details,
|
|
47
54
|
actions,
|
|
48
55
|
}: Props = $props();
|
|
49
56
|
|
|
@@ -140,7 +147,7 @@ function handleKeydown(event: KeyboardEvent) {
|
|
|
140
147
|
|
|
141
148
|
<div class="entry-footer">
|
|
142
149
|
<div class="hours">
|
|
143
|
-
<span class="hours-value">{
|
|
150
|
+
<span class="hours-value">{hoursFormatter(entry.hours)}</span>
|
|
144
151
|
{#if entry.hourlyRate}
|
|
145
152
|
<span class="rate">@ {formatCurrency(entry.hourlyRate, currency)}/hr</span>
|
|
146
153
|
{/if}
|
|
@@ -155,6 +162,8 @@ function handleKeydown(event: KeyboardEvent) {
|
|
|
155
162
|
{t(M['projects.time_entry_card.mileage'], { mileage: entry.mileage })}
|
|
156
163
|
</div>
|
|
157
164
|
{/if}
|
|
165
|
+
|
|
166
|
+
{@render details?.(entry)}
|
|
158
167
|
{/snippet}
|
|
159
168
|
|
|
160
169
|
<style>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
-
import { type Currency, type TimeEntry } from './utils.js';
|
|
2
|
+
import { type Currency, type HoursFormatter, type TimeEntry } from './utils.js';
|
|
3
3
|
/** Props for TimeEntryCard component */
|
|
4
4
|
export interface Props {
|
|
5
5
|
/** Time entry data to display. */
|
|
@@ -16,6 +16,10 @@ export interface Props {
|
|
|
16
16
|
onselect?: (id: string, selected: boolean) => void;
|
|
17
17
|
/** Currency code for formatting hourly rates and amounts. */
|
|
18
18
|
currency?: Currency;
|
|
19
|
+
/** Formats decimal hours; defaults to the legacy one-decimal display. */
|
|
20
|
+
hoursFormatter?: HoursFormatter;
|
|
21
|
+
/** Renders caller-owned evidence below the standard entry fields. */
|
|
22
|
+
details?: Snippet<[TimeEntry]>;
|
|
19
23
|
/** Renders action buttons on the right side of the card. */
|
|
20
24
|
actions?: Snippet;
|
|
21
25
|
}
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,EAIb,KAAK,cAAc,EAEnB,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,yEAAyE;IACzE,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,qEAAqE;IACrE,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC/B,4DAA4D;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAwHD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
8
|
+
import type { Snippet } from 'svelte';
|
|
8
9
|
import { M } from '../i18n.js';
|
|
9
10
|
import {
|
|
10
11
|
type Currency,
|
|
11
12
|
formatCurrency,
|
|
12
13
|
formatDate,
|
|
13
14
|
formatHours,
|
|
15
|
+
type HoursFormatter,
|
|
14
16
|
statusColors,
|
|
15
17
|
type TimeEntry,
|
|
16
18
|
} from './utils.js';
|
|
@@ -33,6 +35,10 @@ export interface Props {
|
|
|
33
35
|
baseHref?: string;
|
|
34
36
|
/** Currency code for formatting amounts. */
|
|
35
37
|
currency?: Currency;
|
|
38
|
+
/** Formats decimal hours; defaults to the legacy one-decimal display. */
|
|
39
|
+
hoursFormatter?: HoursFormatter;
|
|
40
|
+
/** Renders caller-owned evidence in each entry row. */
|
|
41
|
+
details?: Snippet<[TimeEntry]>;
|
|
36
42
|
/** Filter function to determine which entries can be selected */
|
|
37
43
|
canSelect?: (entry: TimeEntry) => boolean;
|
|
38
44
|
}
|
|
@@ -45,6 +51,8 @@ let {
|
|
|
45
51
|
emptyMessage = 'No time entries',
|
|
46
52
|
baseHref,
|
|
47
53
|
currency = 'CAD',
|
|
54
|
+
hoursFormatter = formatHours,
|
|
55
|
+
details,
|
|
48
56
|
canSelect = () => true,
|
|
49
57
|
}: Props = $props();
|
|
50
58
|
|
|
@@ -161,7 +169,7 @@ function getEntryHref(entry: TimeEntry): string | undefined {
|
|
|
161
169
|
</div>
|
|
162
170
|
|
|
163
171
|
<div class="hours-cell">
|
|
164
|
-
{
|
|
172
|
+
{hoursFormatter(entry.hours)}
|
|
165
173
|
</div>
|
|
166
174
|
|
|
167
175
|
<div class="status-cell">
|
|
@@ -175,6 +183,8 @@ function getEntryHref(entry: TimeEntry): string | undefined {
|
|
|
175
183
|
{formatCurrency(entry.amount, currency)}
|
|
176
184
|
</div>
|
|
177
185
|
{/if}
|
|
186
|
+
|
|
187
|
+
{@render details?.(entry)}
|
|
178
188
|
{/snippet}
|
|
179
189
|
|
|
180
190
|
<style>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import { type Currency, type HoursFormatter, type TimeEntry } from './utils.js';
|
|
2
3
|
/** Props for TimeEntryList component */
|
|
3
4
|
export interface Props {
|
|
4
5
|
/** Time entries to display in the list. */
|
|
@@ -15,6 +16,10 @@ export interface Props {
|
|
|
15
16
|
baseHref?: string;
|
|
16
17
|
/** Currency code for formatting amounts. */
|
|
17
18
|
currency?: Currency;
|
|
19
|
+
/** Formats decimal hours; defaults to the legacy one-decimal display. */
|
|
20
|
+
hoursFormatter?: HoursFormatter;
|
|
21
|
+
/** Renders caller-owned evidence in each entry row. */
|
|
22
|
+
details?: Snippet<[TimeEntry]>;
|
|
18
23
|
/** Filter function to determine which entries can be selected */
|
|
19
24
|
canSelect?: (entry: TimeEntry) => boolean;
|
|
20
25
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TimeEntryList.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/TimeEntryList.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TimeEntryList.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/TimeEntryList.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,EACL,KAAK,QAAQ,EAIb,KAAK,cAAc,EAEnB,KAAK,SAAS,EACf,MAAM,YAAY,CAAC;AAGpB,wCAAwC;AACxC,MAAM,WAAW,KAAK;IACpB,2CAA2C;IAC3C,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,8CAA8C;IAC9C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,0CAA0C;IAC1C,iBAAiB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC5C,oDAAoD;IACpD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,yEAAyE;IACzE,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,uDAAuD;IACvD,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC/B,iEAAiE;IACjE,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC;CAC3C;AAgJD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
8
8
|
import { M } from '../i18n.js';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
type Currency,
|
|
11
|
+
formatCurrency,
|
|
12
|
+
formatHours,
|
|
13
|
+
type HoursFormatter,
|
|
14
|
+
} from './utils.js';
|
|
10
15
|
|
|
11
16
|
/** Props for TimeSummary component */
|
|
12
17
|
export interface Props {
|
|
@@ -26,6 +31,8 @@ export interface Props {
|
|
|
26
31
|
entryCount?: number;
|
|
27
32
|
/** Currency code for formatting amounts. */
|
|
28
33
|
currency?: Currency;
|
|
34
|
+
/** Formats decimal hours; defaults to the legacy one-decimal display. */
|
|
35
|
+
hoursFormatter?: HoursFormatter;
|
|
29
36
|
/** Shows the pending hours and amount card when there are pending items. */
|
|
30
37
|
showPending?: boolean;
|
|
31
38
|
/** Shows the approved hours and amount card when there are approved items. */
|
|
@@ -43,6 +50,7 @@ let {
|
|
|
43
50
|
approvedAmount = 0,
|
|
44
51
|
entryCount,
|
|
45
52
|
currency = 'CAD',
|
|
53
|
+
hoursFormatter = formatHours,
|
|
46
54
|
showPending = true,
|
|
47
55
|
showApproved = false,
|
|
48
56
|
layout = 'grid',
|
|
@@ -54,7 +62,7 @@ const { t } = useI18n();
|
|
|
54
62
|
<div class="time-summary" class:horizontal={layout === 'horizontal'}>
|
|
55
63
|
<div class="summary-card">
|
|
56
64
|
<span class="label">{t(M['projects.time_summary.total_hours'])}</span>
|
|
57
|
-
<span class="value">{
|
|
65
|
+
<span class="value">{hoursFormatter(totalHours)}</span>
|
|
58
66
|
{#if entryCount !== undefined}
|
|
59
67
|
<span class="count">{entryCount} {entryCount === 1 ? 'entry' : 'entries'}</span>
|
|
60
68
|
{/if}
|
|
@@ -68,7 +76,7 @@ const { t } = useI18n();
|
|
|
68
76
|
{#if showPending && (pendingHours > 0 || pendingAmount > 0)}
|
|
69
77
|
<div class="summary-card highlight">
|
|
70
78
|
<span class="label">{t(M['projects.time_summary.pending_approval'])}</span>
|
|
71
|
-
<span class="value">{
|
|
79
|
+
<span class="value">{hoursFormatter(pendingHours)}</span>
|
|
72
80
|
<span class="sub-value">{formatCurrency(pendingAmount, currency)}</span>
|
|
73
81
|
</div>
|
|
74
82
|
{/if}
|
|
@@ -76,7 +84,7 @@ const { t } = useI18n();
|
|
|
76
84
|
{#if showApproved && (approvedHours > 0 || approvedAmount > 0)}
|
|
77
85
|
<div class="summary-card success">
|
|
78
86
|
<span class="label">Approved</span>
|
|
79
|
-
<span class="value">{
|
|
87
|
+
<span class="value">{hoursFormatter(approvedHours)}</span>
|
|
80
88
|
<span class="sub-value">{formatCurrency(approvedAmount, currency)}</span>
|
|
81
89
|
</div>
|
|
82
90
|
{/if}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Currency } from './utils.js';
|
|
1
|
+
import { type Currency, type HoursFormatter } from './utils.js';
|
|
2
2
|
/** Props for TimeSummary component */
|
|
3
3
|
export interface Props {
|
|
4
4
|
/** Total hours across all time entries. */
|
|
@@ -17,6 +17,8 @@ export interface Props {
|
|
|
17
17
|
entryCount?: number;
|
|
18
18
|
/** Currency code for formatting amounts. */
|
|
19
19
|
currency?: Currency;
|
|
20
|
+
/** Formats decimal hours; defaults to the legacy one-decimal display. */
|
|
21
|
+
hoursFormatter?: HoursFormatter;
|
|
20
22
|
/** Shows the pending hours and amount card when there are pending items. */
|
|
21
23
|
showPending?: boolean;
|
|
22
24
|
/** Shows the approved hours and amount card when there are approved items. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TimeSummary.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/TimeSummary.svelte.ts"],"names":[],"mappings":"AASA,OAAO,
|
|
1
|
+
{"version":3,"file":"TimeSummary.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/TimeSummary.svelte.ts"],"names":[],"mappings":"AASA,OAAO,EACL,KAAK,QAAQ,EAGb,KAAK,cAAc,EACpB,MAAM,YAAY,CAAC;AAGpB,sCAAsC;AACtC,MAAM,WAAW,KAAK;IACpB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gCAAgC;IAChC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sDAAsD;IACtD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,yEAAyE;IACzE,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,4EAA4E;IAC5E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,8EAA8E;IAC9E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;CAChC;AA8DD,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared utilities for time tracking components
|
|
3
3
|
*/
|
|
4
|
-
|
|
4
|
+
/** ISO 4217 currency code used to display integer minor-unit amounts. */
|
|
5
|
+
export type Currency = string;
|
|
6
|
+
/** Caller-controlled decimal-hours presentation. */
|
|
7
|
+
export type HoursFormatter = (hours: number) => string;
|
|
5
8
|
export type TimeEntryStatus = 'draft' | 'submitted' | 'approved' | 'rejected';
|
|
6
9
|
/** Alias for TimeEntryStatus for approval workflows */
|
|
7
10
|
export type ApprovalStatus = TimeEntryStatus;
|
|
@@ -29,6 +32,8 @@ export declare const statusColors: Record<TimeEntryStatus, string>;
|
|
|
29
32
|
* Format a date for display (e.g., "Jan 5")
|
|
30
33
|
*/
|
|
31
34
|
export declare function formatDate(date: Date | string): string;
|
|
35
|
+
/** Return the ISO 4217 minor-unit exponent used by timesheet amounts. */
|
|
36
|
+
export declare function currencyMinorUnitExponent(currency: Currency): number;
|
|
32
37
|
/**
|
|
33
38
|
* Format a minor-units amount for display.
|
|
34
39
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,QAAQ,GAAG,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,yEAAyE;AACzE,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAE9B,oDAAoD;AACpD,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;AAEvD,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;AAmCD,yEAAyE;AACzE,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAEpE;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,QAAQ,GAAE,QAAgB,GACzB,MAAM,CAUR;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIrD"}
|
|
@@ -17,6 +17,42 @@ export function formatDate(date) {
|
|
|
17
17
|
const d = typeof date === 'string' ? new Date(date) : date;
|
|
18
18
|
return d.toLocaleDateString('en-CA', { month: 'short', day: 'numeric' });
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* ISO 4217 exponents, matching the SDK accounting and commerce tables.
|
|
22
|
+
* ICU display precision is not a monetary scale (notably IQD, MGA and ALL).
|
|
23
|
+
*/
|
|
24
|
+
const ISO_EXPONENTS = {
|
|
25
|
+
BIF: 0,
|
|
26
|
+
CLP: 0,
|
|
27
|
+
DJF: 0,
|
|
28
|
+
GNF: 0,
|
|
29
|
+
ISK: 0,
|
|
30
|
+
JPY: 0,
|
|
31
|
+
KMF: 0,
|
|
32
|
+
KRW: 0,
|
|
33
|
+
PYG: 0,
|
|
34
|
+
RWF: 0,
|
|
35
|
+
UGX: 0,
|
|
36
|
+
UYI: 0,
|
|
37
|
+
VND: 0,
|
|
38
|
+
VUV: 0,
|
|
39
|
+
XAF: 0,
|
|
40
|
+
XOF: 0,
|
|
41
|
+
XPF: 0,
|
|
42
|
+
BHD: 3,
|
|
43
|
+
IQD: 3,
|
|
44
|
+
JOD: 3,
|
|
45
|
+
KWD: 3,
|
|
46
|
+
LYD: 3,
|
|
47
|
+
OMR: 3,
|
|
48
|
+
TND: 3,
|
|
49
|
+
CLF: 4,
|
|
50
|
+
UYW: 4,
|
|
51
|
+
};
|
|
52
|
+
/** Return the ISO 4217 minor-unit exponent used by timesheet amounts. */
|
|
53
|
+
export function currencyMinorUnitExponent(currency) {
|
|
54
|
+
return ISO_EXPONENTS[currency.trim().toUpperCase()] ?? 2;
|
|
55
|
+
}
|
|
20
56
|
/**
|
|
21
57
|
* Format a minor-units amount for display.
|
|
22
58
|
*
|
|
@@ -28,18 +64,14 @@ export function formatDate(date) {
|
|
|
28
64
|
* so zero-decimal currencies (JPY, KRW) are not divided by anything.
|
|
29
65
|
*/
|
|
30
66
|
export function formatCurrency(amount, currency = 'CAD') {
|
|
31
|
-
|
|
32
|
-
|
|
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.
|
|
67
|
+
const code = currency.trim().toUpperCase();
|
|
68
|
+
const digits = currencyMinorUnitExponent(code);
|
|
38
69
|
const format = new Intl.NumberFormat('en-CA', {
|
|
39
70
|
style: 'currency',
|
|
40
|
-
currency,
|
|
71
|
+
currency: code,
|
|
72
|
+
minimumFractionDigits: digits,
|
|
73
|
+
maximumFractionDigits: digits,
|
|
41
74
|
});
|
|
42
|
-
const digits = format.resolvedOptions().maximumFractionDigits ?? 2;
|
|
43
75
|
return format.format(amount / 10 ** digits);
|
|
44
76
|
}
|
|
45
77
|
/**
|
package/dist/svelte/index.d.ts
CHANGED
|
@@ -20,6 +20,6 @@ export type TimeEntryApprovalQueueProps = ComponentProps<typeof TimeEntryApprova
|
|
|
20
20
|
export type TimeEntryCardProps = ComponentProps<typeof TimeEntryCard>;
|
|
21
21
|
export type TimeEntryListProps = ComponentProps<typeof TimeEntryList>;
|
|
22
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';
|
|
23
|
+
export { type ApprovalStatus, type Currency, currencyMinorUnitExponent, formatCurrency, formatDate, formatHours, formatHoursHHMM, type HoursFormatter, statusColors, type TimeEntry, type TimeEntryStatus, } from './components/utils.js';
|
|
24
24
|
export { humanizeTimeEntryStatus, type TimeEntryApprovalView, timeEntryStatusBadgeKey, } from './types.js';
|
|
25
25
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +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"}
|
|
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,yBAAyB,EACzB,cAAc,EACd,UAAU,EACV,WAAW,EACX,eAAe,EACf,KAAK,cAAc,EACnB,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"}
|
package/dist/svelte/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import TimeEntryCard from './components/TimeEntryCard.svelte';
|
|
|
16
16
|
import TimeEntryList from './components/TimeEntryList.svelte';
|
|
17
17
|
import TimeSummary from './components/TimeSummary.svelte';
|
|
18
18
|
export { DurationDisplay, TimeEntryApprovalQueue, TimeEntryCard, TimeEntryList, TimeSummary, };
|
|
19
|
-
export { formatCurrency, formatDate, formatHours, formatHoursHHMM, statusColors, } from './components/utils.js';
|
|
19
|
+
export { currencyMinorUnitExponent, formatCurrency, formatDate, formatHours, formatHoursHHMM, statusColors, } from './components/utils.js';
|
|
20
20
|
export { humanizeTimeEntryStatus, timeEntryStatusBadgeKey, } from './types.js';
|
|
21
21
|
// Auto-register with ModuleUIRegistry
|
|
22
22
|
ModuleUIRegistry.registerModule(TIMESHEETS_MODULE_META);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-timesheets",
|
|
3
|
-
"version": "0.55.
|
|
3
|
+
"version": "0.55.1",
|
|
4
4
|
"smrtJsdoc": "strict",
|
|
5
5
|
"description": "Shared time entries for the SMRT framework: who worked how long on what, approval and correction, with immutable charge and compensation snapshots",
|
|
6
6
|
"type": "module",
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
}
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@happyvertical/smrt-core": "0.55.
|
|
43
|
-
"@happyvertical/smrt-tenancy": "0.55.
|
|
44
|
-
"@happyvertical/smrt-types": "0.55.
|
|
45
|
-
"@happyvertical/smrt-ui": "0.55.
|
|
42
|
+
"@happyvertical/smrt-core": "0.55.1",
|
|
43
|
+
"@happyvertical/smrt-tenancy": "0.55.1",
|
|
44
|
+
"@happyvertical/smrt-types": "0.55.1",
|
|
45
|
+
"@happyvertical/smrt-ui": "0.55.1",
|
|
46
46
|
"@happyvertical/sql": "^0.102.1"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@happyvertical/smrt-vitest": "0.55.
|
|
57
|
+
"@happyvertical/smrt-vitest": "0.55.1",
|
|
58
58
|
"@sveltejs/package": "^2.5.8",
|
|
59
59
|
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
60
60
|
"@types/node": "24.13.6",
|