Add sentiment feature to employee reports
- Introduced a new `Sentiment` field in the `EmployeeReportRecord` interface to capture report sentiment as 'Positive', 'Negative', or 'Neutral'. - Updated the report creation and editing forms in Svelte to include a sentiment selection dropdown. - Enhanced API endpoints to handle sentiment data during report creation and updates. - Adjusted the display logic to show sentiment information in the report details.
This commit is contained in:
@@ -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<VoxerLink[]>([]);
|
||||
const SENTIMENT_OPTIONS: ReportSentiment[] = ['Positive', 'Negative', 'Neutral'];
|
||||
let reportCreatedLocal = $state('');
|
||||
let reportSentiment = $state<ReportSentiment>('Neutral');
|
||||
let editingReport = $state<EmployeeReportRecord | null>(null);
|
||||
let deletingReport = $state<EmployeeReportRecord | null>(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 @@
|
||||
<Card.Title class="text-base">Report</Card.Title>
|
||||
<p class="text-muted-foreground text-xs">
|
||||
Created {formatDate(report.created)} · Updated {formatDate(report.updated)}
|
||||
{#if report.Sentiment}
|
||||
· <span class="capitalize">{report.Sentiment}</span>
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex shrink-0 gap-1">
|
||||
@@ -512,6 +539,30 @@
|
||||
}}
|
||||
class="grid gap-4 py-4"
|
||||
>
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 sm:items-end">
|
||||
<div class="space-y-2">
|
||||
<Label for="new-created">Created (local time)</Label>
|
||||
<Input
|
||||
id="new-created"
|
||||
type="datetime-local"
|
||||
bind:value={reportCreatedLocal}
|
||||
disabled={reportSaving}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="new-sentiment">Sentiment</Label>
|
||||
<Select.Root bind:value={reportSentiment} disabled={reportSaving}>
|
||||
<Select.Trigger class="w-full" id="new-sentiment">
|
||||
{reportSentiment}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each SENTIMENT_OPTIONS as opt}
|
||||
<Select.Item value={opt} label={opt}>{opt}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="new-report">Report</Label>
|
||||
<textarea
|
||||
@@ -588,6 +639,30 @@
|
||||
}}
|
||||
class="grid gap-4 py-4"
|
||||
>
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 sm:items-end">
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-created">Created (local time)</Label>
|
||||
<Input
|
||||
id="edit-created"
|
||||
type="datetime-local"
|
||||
bind:value={reportCreatedLocal}
|
||||
disabled={reportSaving}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-sentiment">Sentiment</Label>
|
||||
<Select.Root bind:value={reportSentiment} disabled={reportSaving}>
|
||||
<Select.Trigger class="w-full" id="edit-sentiment">
|
||||
{reportSentiment}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each SENTIMENT_OPTIONS as opt}
|
||||
<Select.Item value={opt} label={opt}>{opt}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-report">Report</Label>
|
||||
<textarea
|
||||
|
||||
Reference in New Issue
Block a user