diff --git a/src/lib/components/ui/select/index.ts b/src/lib/components/ui/select/index.ts
new file mode 100644
index 0000000..4dec358
--- /dev/null
+++ b/src/lib/components/ui/select/index.ts
@@ -0,0 +1,37 @@
+import Root from "./select.svelte";
+import Group from "./select-group.svelte";
+import Label from "./select-label.svelte";
+import Item from "./select-item.svelte";
+import Content from "./select-content.svelte";
+import Trigger from "./select-trigger.svelte";
+import Separator from "./select-separator.svelte";
+import ScrollDownButton from "./select-scroll-down-button.svelte";
+import ScrollUpButton from "./select-scroll-up-button.svelte";
+import GroupHeading from "./select-group-heading.svelte";
+import Portal from "./select-portal.svelte";
+
+export {
+ Root,
+ Group,
+ Label,
+ Item,
+ Content,
+ Trigger,
+ Separator,
+ ScrollDownButton,
+ ScrollUpButton,
+ GroupHeading,
+ Portal,
+ //
+ Root as Select,
+ Group as SelectGroup,
+ Label as SelectLabel,
+ Item as SelectItem,
+ Content as SelectContent,
+ Trigger as SelectTrigger,
+ Separator as SelectSeparator,
+ ScrollDownButton as SelectScrollDownButton,
+ ScrollUpButton as SelectScrollUpButton,
+ GroupHeading as SelectGroupHeading,
+ Portal as SelectPortal,
+};
diff --git a/src/lib/components/ui/select/select-content.svelte b/src/lib/components/ui/select/select-content.svelte
new file mode 100644
index 0000000..4b9ca43
--- /dev/null
+++ b/src/lib/components/ui/select/select-content.svelte
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+ {@render children?.()}
+
+
+
+
diff --git a/src/lib/components/ui/select/select-group-heading.svelte b/src/lib/components/ui/select/select-group-heading.svelte
new file mode 100644
index 0000000..1fab5f0
--- /dev/null
+++ b/src/lib/components/ui/select/select-group-heading.svelte
@@ -0,0 +1,21 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/select/select-group.svelte b/src/lib/components/ui/select/select-group.svelte
new file mode 100644
index 0000000..a1f43bf
--- /dev/null
+++ b/src/lib/components/ui/select/select-group.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/src/lib/components/ui/select/select-item.svelte b/src/lib/components/ui/select/select-item.svelte
new file mode 100644
index 0000000..b85eef6
--- /dev/null
+++ b/src/lib/components/ui/select/select-item.svelte
@@ -0,0 +1,38 @@
+
+
+
+ {#snippet children({ selected, highlighted })}
+
+ {#if selected}
+
+ {/if}
+
+ {#if childrenProp}
+ {@render childrenProp({ selected, highlighted })}
+ {:else}
+ {label || value}
+ {/if}
+ {/snippet}
+
diff --git a/src/lib/components/ui/select/select-label.svelte b/src/lib/components/ui/select/select-label.svelte
new file mode 100644
index 0000000..4696025
--- /dev/null
+++ b/src/lib/components/ui/select/select-label.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/select/select-portal.svelte b/src/lib/components/ui/select/select-portal.svelte
new file mode 100644
index 0000000..424bcdd
--- /dev/null
+++ b/src/lib/components/ui/select/select-portal.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/src/lib/components/ui/select/select-scroll-down-button.svelte b/src/lib/components/ui/select/select-scroll-down-button.svelte
new file mode 100644
index 0000000..3629205
--- /dev/null
+++ b/src/lib/components/ui/select/select-scroll-down-button.svelte
@@ -0,0 +1,20 @@
+
+
+
+
+
diff --git a/src/lib/components/ui/select/select-scroll-up-button.svelte b/src/lib/components/ui/select/select-scroll-up-button.svelte
new file mode 100644
index 0000000..1aa2300
--- /dev/null
+++ b/src/lib/components/ui/select/select-scroll-up-button.svelte
@@ -0,0 +1,20 @@
+
+
+
+
+
diff --git a/src/lib/components/ui/select/select-separator.svelte b/src/lib/components/ui/select/select-separator.svelte
new file mode 100644
index 0000000..0eac3eb
--- /dev/null
+++ b/src/lib/components/ui/select/select-separator.svelte
@@ -0,0 +1,18 @@
+
+
+
diff --git a/src/lib/components/ui/select/select-trigger.svelte b/src/lib/components/ui/select/select-trigger.svelte
new file mode 100644
index 0000000..dbb81df
--- /dev/null
+++ b/src/lib/components/ui/select/select-trigger.svelte
@@ -0,0 +1,29 @@
+
+
+
+ {@render children?.()}
+
+
diff --git a/src/lib/components/ui/select/select.svelte b/src/lib/components/ui/select/select.svelte
new file mode 100644
index 0000000..05eb663
--- /dev/null
+++ b/src/lib/components/ui/select/select.svelte
@@ -0,0 +1,11 @@
+
+
+
diff --git a/src/lib/components/ui/separator/index.ts b/src/lib/components/ui/separator/index.ts
new file mode 100644
index 0000000..82442d2
--- /dev/null
+++ b/src/lib/components/ui/separator/index.ts
@@ -0,0 +1,7 @@
+import Root from "./separator.svelte";
+
+export {
+ Root,
+ //
+ Root as Separator,
+};
diff --git a/src/lib/components/ui/separator/separator.svelte b/src/lib/components/ui/separator/separator.svelte
new file mode 100644
index 0000000..f40999f
--- /dev/null
+++ b/src/lib/components/ui/separator/separator.svelte
@@ -0,0 +1,21 @@
+
+
+
diff --git a/src/lib/date-utils.ts b/src/lib/date-utils.ts
new file mode 100644
index 0000000..f8d7d5c
--- /dev/null
+++ b/src/lib/date-utils.ts
@@ -0,0 +1,48 @@
+/**
+ * Utilities for converting between local date/time and UTC ISO strings for PocketBase.
+ * PocketBase stores dates in UTC (ISO 8601). We display and edit in the user's local timezone.
+ */
+
+/** Format expected by : "YYYY-MM-DDTHH:mm" in local time */
+export const DATETIME_LOCAL_FORMAT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/;
+
+/**
+ * Convert a UTC ISO string (from PocketBase) to local datetime for datetime-local input.
+ * Handles both "2024-01-15T12:00:00.000Z" and "2024-01-15 12:00:00.000Z" styles.
+ */
+export function utcIsoToLocalDatetimeLocal(isoUtc: string | undefined): string {
+ if (!isoUtc || typeof isoUtc !== 'string') return '';
+ const d = new Date(isoUtc);
+ if (Number.isNaN(d.getTime())) return '';
+ const y = d.getFullYear();
+ const m = String(d.getMonth() + 1).padStart(2, '0');
+ const day = String(d.getDate()).padStart(2, '0');
+ const h = String(d.getHours()).padStart(2, '0');
+ const min = String(d.getMinutes()).padStart(2, '0');
+ return `${y}-${m}-${day}T${h}:${min}`;
+}
+
+/**
+ * Convert current local date/time to datetime-local string (for default "now" when creating).
+ */
+export function nowToLocalDatetimeLocal(): string {
+ const d = new Date();
+ const y = d.getFullYear();
+ const m = String(d.getMonth() + 1).padStart(2, '0');
+ const day = String(d.getDate()).padStart(2, '0');
+ const h = String(d.getHours()).padStart(2, '0');
+ const min = String(d.getMinutes()).padStart(2, '0');
+ return `${y}-${m}-${day}T${h}:${min}`;
+}
+
+/**
+ * Parse a datetime-local value (local time) and return an ISO UTC string for PocketBase.
+ */
+export function localDatetimeLocalToUtcIso(localStr: string | undefined): string {
+ if (!localStr || typeof localStr !== 'string' || !DATETIME_LOCAL_FORMAT.test(localStr.trim())) {
+ return new Date().toISOString();
+ }
+ const d = new Date(localStr);
+ if (Number.isNaN(d.getTime())) return new Date().toISOString();
+ return d.toISOString();
+}
diff --git a/src/routes/[id]/+page.server.ts b/src/routes/[id]/+page.server.ts
index ef58869..bfe1516 100644
--- a/src/routes/[id]/+page.server.ts
+++ b/src/routes/[id]/+page.server.ts
@@ -21,12 +21,15 @@ export interface VoxerLink {
url: string;
}
+export type ReportSentiment = 'Positive' | 'Negative' | 'Neutral';
+
export interface EmployeeReportRecord {
id: string;
report?: string;
voxers?: VoxerLink[] | string; // JSON array or string from PB
created: string;
updated: string;
+ Sentiment?: ReportSentiment;
[key: string]: unknown;
}
diff --git a/src/routes/[id]/+page.svelte b/src/routes/[id]/+page.svelte
index 1e20d5d..ba51b16 100644
--- a/src/routes/[id]/+page.svelte
+++ b/src/routes/[id]/+page.svelte
@@ -10,8 +10,15 @@
import type {
EmployeeRecord,
EmployeeReportRecord,
+ ReportSentiment,
VoxerLink
} from './+page.server';
+ import * as Select from '$lib/components/ui/select/index.js';
+ import {
+ utcIsoToLocalDatetimeLocal,
+ nowToLocalDatetimeLocal,
+ localDatetimeLocalToUtcIso
+ } from '$lib/date-utils';
import ArrowLeftIcon from '@lucide/svelte/icons/arrow-left';
import LinkIcon from '@lucide/svelte/icons/link';
import PencilIcon from '@lucide/svelte/icons/pencil';
@@ -48,6 +55,9 @@
let openDeleteReport = $state(false);
let reportFormText = $state('');
let reportVoxers = $state([]);
+ const SENTIMENT_OPTIONS: ReportSentiment[] = ['Positive', 'Negative', 'Neutral'];
+ let reportCreatedLocal = $state('');
+ let reportSentiment = $state('Neutral');
let editingReport = $state(null);
let deletingReport = $state(null);
let reportSaving = $state(false);
@@ -143,6 +153,8 @@
function openCreateReportDialog() {
reportFormText = '';
reportVoxers = [];
+ reportCreatedLocal = nowToLocalDatetimeLocal();
+ reportSentiment = 'Neutral';
reportError = null;
editingReport = null;
openCreateReport = true;
@@ -152,6 +164,8 @@
editingReport = report;
reportFormText = report.report ?? '';
reportVoxers = parseVoxers(report).map((v) => ({ ...v }));
+ reportCreatedLocal = utcIsoToLocalDatetimeLocal(report.created);
+ reportSentiment = (report.Sentiment === 'Positive' || report.Sentiment === 'Negative' ? report.Sentiment : 'Neutral') as ReportSentiment;
reportError = null;
openEditReport = true;
}
@@ -177,7 +191,12 @@
const res = await fetch(`/api/employees/${employeeId}/reports`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ report: reportFormText, voxers: reportVoxers })
+ body: JSON.stringify({
+ report: reportFormText,
+ voxers: reportVoxers,
+ created: localDatetimeLocalToUtcIso(reportCreatedLocal),
+ Sentiment: reportSentiment
+ })
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
@@ -200,7 +219,12 @@
const res = await fetch(`/api/employees/${employeeId}/reports/${editingReport.id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ report: reportFormText, voxers: reportVoxers })
+ body: JSON.stringify({
+ report: reportFormText,
+ voxers: reportVoxers,
+ created: localDatetimeLocalToUtcIso(reportCreatedLocal),
+ Sentiment: reportSentiment
+ })
});
if (!res.ok) throw new Error('Update failed');
openEditReport = false;
@@ -416,6 +440,9 @@
Report
Created {formatDate(report.created)} · Updated {formatDate(report.updated)}
+ {#if report.Sentiment}
+ · {report.Sentiment}
+ {/if}
@@ -512,6 +539,30 @@
}}
class="grid gap-4 py-4"
>
+
+
+
+
+
+
+
+
+
+ {reportSentiment}
+
+
+ {#each SENTIMENT_OPTIONS as opt}
+ {opt}
+ {/each}
+
+
+
+