Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
160 lines
5.4 KiB
Svelte
160 lines
5.4 KiB
Svelte
<script lang="ts">
|
|
import { Button } from '$lib/components/ui/button';
|
|
import { Input } from '$lib/components/ui/input';
|
|
import { Separator } from '$lib/components/ui/separator';
|
|
import { generateUUID } from '$lib/utils/uuid';
|
|
|
|
type CartItem = {
|
|
id: string;
|
|
label: string;
|
|
price: number;
|
|
qty: number;
|
|
};
|
|
|
|
let cart = $state<CartItem[]>([]);
|
|
let giftCardAmount = $state('25');
|
|
let showGiftCardInput = $state(false);
|
|
|
|
let subtotal = $derived(cart.reduce((sum, item) => sum + item.price * item.qty, 0));
|
|
let itemCount = $derived(cart.reduce((sum, item) => sum + item.qty, 0));
|
|
|
|
function formatCurrency(n: number): string {
|
|
return new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP' }).format(n);
|
|
}
|
|
|
|
function addItem(label: string, price: number) {
|
|
const existing = cart.find(i => i.label === label);
|
|
if (existing) {
|
|
existing.qty++;
|
|
} else {
|
|
cart = [...cart, { id: generateUUID(), label, price, qty: 1 }];
|
|
}
|
|
}
|
|
|
|
function addGiftCard() {
|
|
const amt = parseFloat(giftCardAmount);
|
|
if (isNaN(amt) || amt <= 0) return;
|
|
addItem('Gift Card', amt);
|
|
giftCardAmount = '25';
|
|
showGiftCardInput = false;
|
|
}
|
|
|
|
function removeItem(id: string) {
|
|
cart = cart.filter(i => i.id !== id);
|
|
}
|
|
|
|
function updateQty(id: string, delta: number) {
|
|
cart = cart.map(i => {
|
|
if (i.id !== id) return i;
|
|
const next = i.qty + delta;
|
|
return next <= 0 ? null : { ...i, qty: next };
|
|
}).filter((i): i is CartItem => i !== null);
|
|
}
|
|
</script>
|
|
|
|
<div class="rounded-xl border bg-card">
|
|
<div class="flex items-center justify-between border-b border-gray-200 px-5 py-4">
|
|
<h3 class="text-base font-semibold">Till Sales</h3>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-2 p-4 sm:grid-cols-3">
|
|
<Button variant="outline" size="sm" class="justify-start gap-2" onclick={() => addItem('Cuticle Oil', 8)}>
|
|
Cuticle Oil - £8
|
|
</Button>
|
|
<Button variant="outline" size="sm" class="justify-start gap-2" onclick={() => addItem('Nail Files (Pack)', 5)}>
|
|
Nail Files - £5
|
|
</Button>
|
|
<Button variant="outline" size="sm" class="justify-start gap-2" onclick={() => addItem('Hand Cream', 6)}>
|
|
Hand Cream - £6
|
|
</Button>
|
|
<Button variant="outline" size="sm" class="justify-start gap-2" onclick={() => addItem('Base Coat', 7)}>
|
|
Base Coat - £7
|
|
</Button>
|
|
<Button variant="outline" size="sm" class="justify-start gap-2" onclick={() => addItem('Top Coat', 7)}>
|
|
Top Coat - £7
|
|
</Button>
|
|
<div class="relative">
|
|
{#if showGiftCardInput}
|
|
<div class="flex gap-1">
|
|
<div class="relative flex-1">
|
|
<span class="absolute left-2 top-1/2 -translate-y-1/2 text-xs text-gray-400">£</span>
|
|
<Input
|
|
type="text"
|
|
inputmode="decimal"
|
|
bind:value={giftCardAmount}
|
|
class="h-9 pl-5 text-sm"
|
|
onkeydown={(e) => { if (e.key === 'Enter') addGiftCard(); }}
|
|
/>
|
|
</div>
|
|
<Button size="sm" variant="outline" onclick={addGiftCard} class="h-9 px-2 text-xs">Add</Button>
|
|
</div>
|
|
{:else}
|
|
<Button variant="outline" size="sm" class="w-full justify-start gap-2" onclick={() => showGiftCardInput = true}>
|
|
Gift Card
|
|
</Button>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<Separator />
|
|
|
|
<div class="px-4 py-3">
|
|
{#if cart.length === 0}
|
|
<p class="py-6 text-center text-sm text-muted-foreground">Tap items above to add them to the sale.</p>
|
|
{:else}
|
|
<div class="max-h-48 space-y-1 overflow-y-auto">
|
|
{#each cart as item (item.id)}
|
|
<div class="flex items-center justify-between rounded-md border px-3 py-2 text-sm">
|
|
<div class="min-w-0 flex-1">
|
|
<span class="font-medium text-card-foreground">{item.label}</span>
|
|
<span class="ml-2 text-xs text-muted-foreground">{formatCurrency(item.price)} each</span>
|
|
</div>
|
|
<div class="flex items-center gap-2 shrink-0">
|
|
<button
|
|
type="button"
|
|
class="flex h-6 w-6 items-center justify-center rounded border text-xs text-muted-foreground hover:bg-accent"
|
|
onclick={() => updateQty(item.id, -1)}
|
|
>
|
|
−
|
|
</button>
|
|
<span class="w-5 text-center text-sm font-semibold tabular-nums">{item.qty}</span>
|
|
<button
|
|
type="button"
|
|
class="flex h-6 w-6 items-center justify-center rounded border text-xs text-muted-foreground hover:bg-accent"
|
|
onclick={() => updateQty(item.id, 1)}
|
|
>
|
|
+
|
|
</button>
|
|
<span class="w-14 text-right text-sm font-semibold tabular-nums">{formatCurrency(item.price * item.qty)}</span>
|
|
<button
|
|
type="button"
|
|
aria-label="Remove item"
|
|
class="ml-1 flex h-6 w-6 items-center justify-center rounded text-xs text-muted-foreground hover:bg-red-50 hover:text-red-600"
|
|
onclick={() => removeItem(item.id)}
|
|
>
|
|
<svg class="h-3 w-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
|
|
<Separator class="my-3" />
|
|
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-sm text-muted-foreground">
|
|
{itemCount} item{itemCount !== 1 ? 's' : ''}
|
|
</span>
|
|
<span class="text-lg font-bold tabular-nums">{formatCurrency(subtotal)}</span>
|
|
</div>
|
|
|
|
<Button class="mt-3 w-full" disabled>
|
|
Charge {formatCurrency(subtotal)}
|
|
</Button>
|
|
<p class="mt-1 text-xs text-muted-foreground">Payment flow and backend integration coming soon.</p>
|
|
{/if}
|
|
</div>
|
|
</div>
|