148 lines
4.6 KiB
TypeScript
148 lines
4.6 KiB
TypeScript
|
|
'use client'
|
|||
|
|
|
|||
|
|
import { useState } from 'react'
|
|||
|
|
import { trpc } from '@/lib/trpc/client'
|
|||
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|||
|
|
import { Button } from '@/components/ui/button'
|
|||
|
|
import {
|
|||
|
|
Dialog,
|
|||
|
|
DialogContent,
|
|||
|
|
DialogHeader,
|
|||
|
|
DialogTitle,
|
|||
|
|
DialogFooter,
|
|||
|
|
} from '@/components/ui/dialog'
|
|||
|
|
import { Send, Eye } from 'lucide-react'
|
|||
|
|
import { toast } from 'sonner'
|
|||
|
|
|
|||
|
|
export function LunchRecapActions({
|
|||
|
|
programId,
|
|||
|
|
recapSentAt,
|
|||
|
|
extraRecipientCount,
|
|||
|
|
}: {
|
|||
|
|
programId: string
|
|||
|
|
recapSentAt: Date | null
|
|||
|
|
extraRecipientCount: number
|
|||
|
|
}) {
|
|||
|
|
const utils = trpc.useUtils()
|
|||
|
|
const [previewOpen, setPreviewOpen] = useState(false)
|
|||
|
|
|
|||
|
|
const send = trpc.lunch.sendRecap.useMutation({
|
|||
|
|
onSuccess: () => {
|
|||
|
|
utils.lunch.getEvent.invalidate({ programId })
|
|||
|
|
toast.success('Recap sent')
|
|||
|
|
},
|
|||
|
|
onError: (e) => {
|
|||
|
|
if (e.data?.code === 'PRECONDITION_FAILED') {
|
|||
|
|
if (
|
|||
|
|
confirm(
|
|||
|
|
"You've already sent a recap. Send updated version to all recipients?",
|
|||
|
|
)
|
|||
|
|
) {
|
|||
|
|
send.mutate({ programId, forceUpdate: true })
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
toast.error(e.message)
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const { data: preview, isLoading: loadingPreview } =
|
|||
|
|
trpc.lunch.getRecapPreview.useQuery(
|
|||
|
|
{ programId },
|
|||
|
|
{ enabled: previewOpen },
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<Card>
|
|||
|
|
<CardHeader>
|
|||
|
|
<CardTitle>Recap</CardTitle>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent className="space-y-4">
|
|||
|
|
<div className="flex flex-wrap gap-2">
|
|||
|
|
<Button variant="outline" onClick={() => setPreviewOpen(true)}>
|
|||
|
|
<Eye className="mr-2 h-4 w-4" /> Preview recap
|
|||
|
|
</Button>
|
|||
|
|
<Button
|
|||
|
|
onClick={() => send.mutate({ programId })}
|
|||
|
|
disabled={send.isPending}
|
|||
|
|
>
|
|||
|
|
<Send className="mr-2 h-4 w-4" /> Send recap now
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
<p className="text-muted-foreground text-xs">
|
|||
|
|
{recapSentAt
|
|||
|
|
? `Last sent: ${new Date(recapSentAt).toLocaleString()}. Recipients: edition admins${extraRecipientCount > 0 ? ` + ${extraRecipientCount} extra` : ''}.`
|
|||
|
|
: 'Recap has not been sent yet.'}
|
|||
|
|
</p>
|
|||
|
|
</CardContent>
|
|||
|
|
|
|||
|
|
<Dialog open={previewOpen} onOpenChange={setPreviewOpen}>
|
|||
|
|
<DialogContent className="max-w-3xl">
|
|||
|
|
<DialogHeader>
|
|||
|
|
<DialogTitle>Recap preview</DialogTitle>
|
|||
|
|
</DialogHeader>
|
|||
|
|
{loadingPreview && (
|
|||
|
|
<p className="text-muted-foreground text-sm">Loading…</p>
|
|||
|
|
)}
|
|||
|
|
{preview && (
|
|||
|
|
<div className="space-y-4 text-sm">
|
|||
|
|
<p>
|
|||
|
|
<strong>
|
|||
|
|
{preview.summary.picked}/{preview.summary.total}
|
|||
|
|
</strong>{' '}
|
|||
|
|
picked
|
|||
|
|
{preview.summary.missing > 0
|
|||
|
|
? ` · ${preview.summary.missing} missing`
|
|||
|
|
: ''}
|
|||
|
|
</p>
|
|||
|
|
{Object.keys(preview.dishCounts).length > 0 && (
|
|||
|
|
<div>
|
|||
|
|
<h4 className="font-medium">Dishes</h4>
|
|||
|
|
<ul className="ml-4 list-disc">
|
|||
|
|
{Object.entries(preview.dishCounts).map(([n, c]) => (
|
|||
|
|
<li key={n}>
|
|||
|
|
{c}× {n}
|
|||
|
|
</li>
|
|||
|
|
))}
|
|||
|
|
</ul>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
{Object.keys(preview.dietaryCounts).length > 0 && (
|
|||
|
|
<div>
|
|||
|
|
<h4 className="font-medium">Dietary tags</h4>
|
|||
|
|
<ul className="ml-4 list-disc">
|
|||
|
|
{Object.entries(preview.dietaryCounts).map(([n, c]) => (
|
|||
|
|
<li key={n}>
|
|||
|
|
{c}× {n.replace('_', ' ').toLowerCase()}
|
|||
|
|
</li>
|
|||
|
|
))}
|
|||
|
|
</ul>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
<div>
|
|||
|
|
<h4 className="font-medium">Allergens</h4>
|
|||
|
|
{Object.keys(preview.allergenCounts).length === 0 ? (
|
|||
|
|
<p className="text-muted-foreground">None reported.</p>
|
|||
|
|
) : (
|
|||
|
|
<ul className="ml-4 list-disc">
|
|||
|
|
{Object.entries(preview.allergenCounts).map(([n, c]) => (
|
|||
|
|
<li key={n}>
|
|||
|
|
{c}× {n.replace('_', ' ').toLowerCase()}
|
|||
|
|
</li>
|
|||
|
|
))}
|
|||
|
|
</ul>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
<DialogFooter>
|
|||
|
|
<Button variant="outline" onClick={() => setPreviewOpen(false)}>
|
|||
|
|
Close
|
|||
|
|
</Button>
|
|||
|
|
</DialogFooter>
|
|||
|
|
</DialogContent>
|
|||
|
|
</Dialog>
|
|||
|
|
</Card>
|
|||
|
|
)
|
|||
|
|
}
|