129 lines
3.8 KiB
Svelte
129 lines
3.8 KiB
Svelte
<script lang="ts">
|
|
import ItemActions from '$lib/components/admin/ItemActions.svelte';
|
|
import { IconEdit, IconTrash } from '$lib/components/icons';
|
|
import { formatDate } from '$lib/utils/date';
|
|
|
|
interface Labor {
|
|
id: string;
|
|
amount: number | null;
|
|
startDate: string | null;
|
|
endDate: string | null;
|
|
}
|
|
|
|
interface Props {
|
|
labors: Labor[];
|
|
showHistory: boolean;
|
|
onAdd: () => void;
|
|
onEdit: (labor: Labor) => void;
|
|
onDelete: (laborId: string) => void;
|
|
onToggleHistory: () => void;
|
|
}
|
|
|
|
let { labors, showHistory, onAdd, onEdit, onDelete, onToggleHistory }: Props = $props();
|
|
|
|
let currentLabor = $derived(labors.find((l) => !l.endDate));
|
|
let historicalLabors = $derived(labors.filter((l) => l.endDate));
|
|
|
|
function formatCurrency(amount: number | null | undefined): string {
|
|
if (amount === null || amount === undefined) return '$0.00';
|
|
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount);
|
|
}
|
|
</script>
|
|
|
|
<div class="mt-3 border-t border-theme pt-3">
|
|
<div class="mb-2 flex items-center justify-between">
|
|
<p class="text-xs font-medium tracking-wide text-theme-muted uppercase">Labor</p>
|
|
<button
|
|
type="button"
|
|
onclick={onAdd}
|
|
class="text-xs font-medium text-primary-500 hover:text-primary-600"
|
|
>
|
|
+ Add
|
|
</button>
|
|
</div>
|
|
|
|
{#if currentLabor}
|
|
<div
|
|
class="rounded border-2 border-blue-200 bg-blue-50 p-2 text-sm dark:border-blue-800 dark:bg-blue-900/20"
|
|
>
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center gap-2">
|
|
<span class="font-bold text-blue-700 dark:text-blue-400">
|
|
{formatCurrency(currentLabor.amount)}
|
|
</span>
|
|
<span
|
|
class="rounded bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/40 dark:text-blue-400"
|
|
>
|
|
Current
|
|
</span>
|
|
</div>
|
|
<div class="flex items-center gap-1">
|
|
<button
|
|
type="button"
|
|
onclick={() => onEdit(currentLabor)}
|
|
class="rounded p-1 text-blue-600 hover:bg-blue-100 dark:text-blue-400 dark:hover:bg-blue-900/40"
|
|
aria-label="Edit labor"
|
|
>
|
|
<IconEdit class="h-3.5 w-3.5" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onclick={() => onDelete(currentLabor.id)}
|
|
class="rounded p-1 text-blue-600 hover:bg-red-50 hover:text-red-500 dark:text-blue-400 dark:hover:bg-red-900/20"
|
|
aria-label="Delete labor"
|
|
>
|
|
<IconTrash class="h-3.5 w-3.5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<p class="mt-1 text-xs text-blue-600 dark:text-blue-500">
|
|
As of {formatDate(currentLabor.startDate) || '—'}
|
|
</p>
|
|
</div>
|
|
{:else}
|
|
<p class="text-xs text-theme-muted">No current labor rate set.</p>
|
|
{/if}
|
|
|
|
{#if historicalLabors.length > 0}
|
|
<button
|
|
type="button"
|
|
onclick={onToggleHistory}
|
|
class="mt-2 flex w-full items-center justify-between text-xs text-theme-secondary hover:text-theme"
|
|
>
|
|
<span>History ({historicalLabors.length})</span>
|
|
<svg
|
|
class="h-3 w-3 transition-transform {showHistory ? 'rotate-180' : ''}"
|
|
style="fill: none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
|
</svg>
|
|
</button>
|
|
|
|
{#if showHistory}
|
|
<div class="mt-2 space-y-1">
|
|
{#each historicalLabors as labor (labor.id)}
|
|
<div
|
|
class="flex items-center justify-between rounded border border-theme bg-theme-card p-2 text-xs"
|
|
>
|
|
<div>
|
|
<span class="font-medium text-theme">{formatCurrency(labor.amount)}</span>
|
|
<span class="text-theme-muted">
|
|
({formatDate(labor.startDate) || '—'} - {formatDate(labor.endDate) || 'Present'})
|
|
</span>
|
|
</div>
|
|
<ItemActions
|
|
size="sm"
|
|
editLabel="Edit labor"
|
|
deleteLabel="Delete labor"
|
|
onEdit={() => onEdit(labor)}
|
|
onDelete={() => onDelete(labor.id)}
|
|
/>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
{/if}
|
|
</div>
|