feat: change email template + add list of items
This commit is contained in:
@@ -142,7 +142,6 @@ export default function DashboardLayout({
|
||||
</nav>
|
||||
|
||||
<div className="p-4 border-t">
|
||||
<LiveIndicator />
|
||||
<LanguageSelector />
|
||||
{user && (
|
||||
<div className="mb-3 text-sm">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Fragment, useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { api, commandsApi, plannedTabsApi, remindersApi } from '@/lib/api/client';
|
||||
import { PageHeader } from '@/components/page-header';
|
||||
@@ -17,7 +17,7 @@ import { LoadingState } from '@/components/loading-state';
|
||||
import { ErrorState } from '@/components/error-state';
|
||||
import { toast } from '@/components/ui/toast';
|
||||
import { formatCents, formatRelativeTime } from '@/lib/utils';
|
||||
import { RefreshCw, Plus, X, Users, Mail, Send, Upload } from 'lucide-react';
|
||||
import { RefreshCw, Plus, X, Users, Mail, Send, Upload, ChevronRight, ChevronDown } from 'lucide-react';
|
||||
import { ImportCustomersDialog } from '@/components/import-customers-dialog';
|
||||
import { useLang } from '@/lib/i18n';
|
||||
import { useRealtime } from '@/lib/realtime';
|
||||
@@ -29,6 +29,7 @@ export default function TabsPage() {
|
||||
const queryClient = useQueryClient();
|
||||
const [venueId, setVenueId] = useState<string | null>(null);
|
||||
const [closeDialog, setCloseDialog] = useState<{ open: boolean; tab: any }>({ open: false, tab: null });
|
||||
const [expandedTabs, setExpandedTabs] = useState<Set<string>>(new Set());
|
||||
const [paymentMethod, setPaymentMethod] = useState<string>('');
|
||||
const [creating, setCreating] = useState(false);
|
||||
const [pendingCommandIds, setPendingCommandIds] = useState<Set<string>>(new Set());
|
||||
@@ -233,9 +234,22 @@ export default function TabsPage() {
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{tabs.map((tab: any) => (
|
||||
<TableRow key={tab.tabId}>
|
||||
<TableCell className="font-medium">{tab.customerName}</TableCell>
|
||||
{tabs.map((tab: any) => {
|
||||
const expanded = expandedTabs.has(tab.tabId);
|
||||
const toggle = () => setExpandedTabs((current) => {
|
||||
const next = new Set(current);
|
||||
if (next.has(tab.tabId)) next.delete(tab.tabId); else next.add(tab.tabId);
|
||||
return next;
|
||||
});
|
||||
return (
|
||||
<Fragment key={tab.tabId}>
|
||||
<TableRow>
|
||||
<TableCell className="font-medium">
|
||||
<button onClick={toggle} className="flex items-center gap-1 hover:text-primary" aria-expanded={expanded} aria-label={`${expanded ? 'Hide' : 'Show'} items for ${tab.customerName}`}>
|
||||
{expanded ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
|
||||
{tab.customerName}
|
||||
</button>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{tab.email ? (
|
||||
<div className="flex items-center gap-1.5 text-sm text-muted-foreground">
|
||||
@@ -265,7 +279,19 @@ export default function TabsPage() {
|
||||
<Button size="sm" disabled={tab.totalInCents <= 0} onClick={() => { setCloseDialog({ open: true, tab }); setPaymentMethod(''); }}>Close</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
{expanded && (
|
||||
<TableRow className="bg-muted/30 hover:bg-muted/30">
|
||||
<TableCell colSpan={7} className="py-3 pl-4 sm:pl-10">
|
||||
{/* Pinned to the visible area so lines stay readable while the table scrolls sideways on phones */}
|
||||
<div className="sticky left-4 max-w-[min(28rem,calc(100vw-7rem))]">
|
||||
<TabLines items={tab.items} />
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
@@ -290,6 +316,7 @@ export default function TabsPage() {
|
||||
<div className="space-y-3 py-4">
|
||||
<div className="flex justify-between text-sm"><span className="text-muted-foreground">Amount</span><span className="font-semibold">{formatCents(closeDialog.tab.totalInCents)}</span></div>
|
||||
<div className="flex justify-between text-sm"><span className="text-muted-foreground">Items</span><span>{closeDialog.tab.itemCount}</span></div>
|
||||
<div className="rounded-md border p-3"><TabLines items={closeDialog.tab.items} /></div>
|
||||
<div className="flex justify-between text-sm"><span className="text-muted-foreground">Version</span><span>{closeDialog.tab.version}</span></div>
|
||||
<div className="space-y-3 pt-2">
|
||||
<Label className="text-base font-semibold">Payment method</Label>
|
||||
@@ -310,4 +337,29 @@ export default function TabsPage() {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type TabLine = { productName: string; quantity: number; unitPriceInCents: number };
|
||||
|
||||
/** The lines on a tab, e.g. "10× Cola · €2.50 each · €25.00". */
|
||||
function TabLines({ items }: { items: TabLine[] | null | undefined }) {
|
||||
if (!items) {
|
||||
return <p className="text-sm text-muted-foreground">Items aren't reported by this terminal yet. Update the app on the tablet to see them.</p>;
|
||||
}
|
||||
if (items.length === 0) {
|
||||
return <p className="text-sm text-muted-foreground">No items on this tab.</p>;
|
||||
}
|
||||
return (
|
||||
<ul className="space-y-1 text-sm">
|
||||
{items.map((item, i) => (
|
||||
<li key={i} className="flex justify-between gap-4">
|
||||
<span>
|
||||
<span className="font-medium">{item.quantity}×</span> {item.productName}
|
||||
<span className="text-muted-foreground"> · {formatCents(item.unitPriceInCents)} each</span>
|
||||
</span>
|
||||
<span className="tabular-nums">{formatCents(item.quantity * item.unitPriceInCents)}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user