From f4358eb00b003a55a6f6f36ac706ad9b0f75055e Mon Sep 17 00:00:00 2001 From: Firman Ramdhani <33869609+firmanramdhani@users.noreply.github.com> Date: Fri, 11 Sep 2026 10:40:23 +0700 Subject: [PATCH] feat: add description field to orders and support marking menu items as sold out --- prisma/schema.prisma | 10 +- src/app/(app)/my-orders/[id]/page.tsx | 141 ++++++------------ src/app/(app)/my-orders/page.tsx | 76 ++++++++-- src/app/(app)/my-purchases/page.tsx | 65 +++++++- .../(app)/order/[id]/PublicOrderActions.tsx | 2 +- src/app/(app)/order/[id]/page.tsx | 98 ++++++------ src/app/(auth)/login/page.tsx | 2 +- src/app/(auth)/register/page.tsx | 2 +- src/app/actions.ts | 36 ++++- src/app/not-found.tsx | 2 +- src/components/OrderCard.tsx | 63 +++++--- src/components/OrderFormModal.tsx | 48 +++--- src/components/Sidebar.tsx | 2 +- src/components/SummaryGenerator.tsx | 99 ++++++++++++ 14 files changed, 435 insertions(+), 211 deletions(-) create mode 100644 src/components/SummaryGenerator.tsx diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a7eca3e..fc469bb 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -27,6 +27,7 @@ model User { model Order { id String @id @default(uuid()) title String + description String? date DateTime allow_custom Boolean @default(false) status String @default("DRAFT") @@ -39,10 +40,11 @@ model Order { } model AvailableItem { - id String @id @default(uuid()) - order_id String - name String - order Order @relation(fields: [order_id], references: [id], onDelete: Cascade) + id String @id @default(uuid()) + order_id String + name String + is_sold_out Boolean @default(false) + order Order @relation(fields: [order_id], references: [id], onDelete: Cascade) created_at DateTime @default(now()) updated_at DateTime @updatedAt } diff --git a/src/app/(app)/my-orders/[id]/page.tsx b/src/app/(app)/my-orders/[id]/page.tsx index b83c661..f31c1b9 100644 --- a/src/app/(app)/my-orders/[id]/page.tsx +++ b/src/app/(app)/my-orders/[id]/page.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from 'react' import { useParams, useRouter, notFound } from 'next/navigation' import { getOrderDetail, updateSubmissionPayment, updateOrderStatus, updateOrder, deleteOrder, getSessionUser, getBalancesAsCreator } from '@/app/actions' import { Card, CardContent } from '@/components/ui/card' +import { SummaryGenerator } from '@/components/SummaryGenerator' import { Button, buttonVariants } from '@/components/ui/button' import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog' import { cn } from '@/lib/utils' @@ -109,40 +110,11 @@ export default function OrderDetailPage() { notFound() } - const isCreator = userId === order.creator_id + const isCreator = order.creator_id === userId const isClosed = order.status === 'CLOSE' const isDraft = order.status === 'DRAFT' const canDelete = isDraft || isClosed - let summaryByPerson = `${order.title}\n` - const itemCounts: Record = {} - - order.submissions.forEach((sub: any) => { - const itemStrings = sub.items.map((i: any) => `${i.name} ${i.qty}x`) - summaryByPerson += `- ${sub.user.name} : ${itemStrings.join(', ')}\n` - - sub.items.forEach((i: any) => { - itemCounts[i.name] = (itemCounts[i.name] || 0) + i.qty - }) - }) - - let summaryByItem = `${order.title}\n` - Object.entries(itemCounts).forEach(([name, qty]) => { - summaryByItem += `- ${name} : ${qty} pcs\n` - }) - - const handleCopyPerson = () => { - navigator.clipboard.writeText(summaryByPerson) - setCopiedPerson(true) - setTimeout(() => setCopiedPerson(false), 2000) - } - - const handleCopyItem = () => { - navigator.clipboard.writeText(summaryByItem) - setCopiedItem(true) - setTimeout(() => setCopiedItem(false), 2000) - } - return (
{/* Top Breadcrumb Header */} @@ -255,6 +227,11 @@ export default function OrderDetailPage() {

{order.title}

+ {order.description && ( +

+ {order.description} +

+ )}

Oleh {order.creator.name}

@@ -434,61 +411,7 @@ export default function OrderDetailPage() {
{/* Right 1 Col: Summary Generators */} -
-
-

- - Generator Rekap -

-

Salin format teks siap kirim ke WhatsApp / grup.

-
- - {/* Rekap Per Orang */} - -
- - Rekap per Orang - - -
- -
-                {summaryByPerson}
-              
-
-
- - {/* Rekap Per Item */} - -
- - Rekap per Item (Akumulasi) - - -
- -
-                {summaryByItem}
-              
-
-
-
+ ) @@ -728,18 +651,20 @@ function SubmissionRow({ function EditDetailOrderModal({ order, onClose, onSuccess }: { order: any, onClose: () => void, onSuccess: () => void }) { const [title, setTitle] = useState(order.title || '') + const [description, setDescription] = useState(order.description || '') const [allowCustom, setAllowCustom] = useState(order.allow_custom || false) - const [items, setItems] = useState>( + const [items, setItems] = useState>( order.available_items?.length > 0 - ? order.available_items.map((ai: any) => ({ id: ai.id, name: ai.name })) - : [{ id: '1', name: '' }] + ? order.available_items.map((ai: any) => ({ id: ai.id, name: ai.name, is_sold_out: ai.is_sold_out || false })) + : [{ id: '1', name: '', is_sold_out: false }] ) const [loading, setLoading] = useState(false) const [error, setError] = useState('') - const addItem = () => setItems([...items, { id: Math.random().toString(), name: '' }]) + const addItem = () => setItems([...items, { id: Math.random().toString(), name: '', is_sold_out: false }]) const removeItem = (id: string) => setItems(items.filter(i => i.id !== id)) const updateItem = (id: string, name: string) => setItems(items.map(i => i.id === id ? { ...i, name } : i)) + const toggleSoldOut = (id: string, is_sold_out: boolean) => setItems(items.map(i => i.id === id ? { ...i, is_sold_out } : i)) const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() @@ -747,7 +672,7 @@ function EditDetailOrderModal({ order, onClose, onSuccess }: { order: any, onClo if (!title.trim()) return setError('Judul PO wajib diisi.') - const validItems = items.filter(i => i.name.trim()).map(i => i.name.trim()) + const validItems = items.filter(i => i.name.trim()).map(i => ({ name: i.name.trim(), is_sold_out: i.is_sold_out })) if (!allowCustom && validItems.length === 0) { return setError('Wajib menambahkan minimal 1 menu pilihan jika tidak mengizinkan custom item.') } @@ -756,6 +681,7 @@ function EditDetailOrderModal({ order, onClose, onSuccess }: { order: any, onClo const res = await updateOrder(order.id, { title: title.trim(), + description: description.trim(), allow_custom: allowCustom, available_items: validItems }) @@ -794,6 +720,19 @@ function EditDetailOrderModal({ order, onClose, onSuccess }: { order: any, onClo /> +
+ + setDescription(e.target.value)} + className="h-11 rounded-xl border-slate-200 dark:border-slate-800 focus-visible:ring-4 focus-visible:ring-[#1B2CC1]/15 focus-visible:border-[#1B2CC1]" + /> +
+ {/* Custom Item Checkbox */}
updateItem(item.id, e.target.value)} - className="flex-1 h-9 rounded-lg bg-white dark:bg-slate-900 text-xs font-medium" + className={cn( + "flex-1 h-9 rounded-lg bg-white dark:bg-slate-900 text-xs font-medium transition-all", + item.is_sold_out && "opacity-60 cursor-not-allowed line-through decoration-rose-500/50" + )} /> + diff --git a/src/app/(app)/my-orders/page.tsx b/src/app/(app)/my-orders/page.tsx index ee35930..4cd77fb 100644 --- a/src/app/(app)/my-orders/page.tsx +++ b/src/app/(app)/my-orders/page.tsx @@ -312,9 +312,16 @@ export default function MyOrdersPage() { {/* Left: Info */}
-

- {order.title} -

+
+

+ {order.title} +

+ {order.description && ( +

+ {order.description} +

+ )} +
{format(date, 'EEEE, dd MMM yyyy', { locale: idLocale })} @@ -538,6 +545,7 @@ export default function MyOrdersPage() { function CreateOrderModal({ userId, onSuccess, initialData }: { userId: string, onSuccess: () => void, initialData?: any }) { const [title, setTitle] = useState(initialData ? `${initialData.title} (Copy)` : '') + const [description, setDescription] = useState(initialData ? (initialData.description || '') : '') const [allowCustom, setAllowCustom] = useState(initialData ? initialData.allow_custom : false) const [items, setItems] = useState<{ id: string; name: string }[]>( initialData && initialData.available_items?.length > 0 @@ -567,6 +575,7 @@ function CreateOrderModal({ userId, onSuccess, initialData }: { userId: string, const res = await createOrder({ creator_id: userId, title: title.trim(), + description: description.trim(), date: new Date(), allow_custom: allowCustom, available_items: validItems @@ -609,6 +618,19 @@ function CreateOrderModal({ userId, onSuccess, initialData }: { userId: string, />
+
+ + setDescription(e.target.value)} + className="h-11 rounded-xl border-slate-200 dark:border-slate-800 focus-visible:ring-4 focus-visible:ring-[#1B2CC1]/15 focus-visible:border-[#1B2CC1]" + /> +
+ {/* Custom Item Checkbox */}
void, onSuccess: () => void }) { const [title, setTitle] = useState(order.title || '') + const [description, setDescription] = useState(order.description || '') const [allowCustom, setAllowCustom] = useState(order.allow_custom || false) - const [items, setItems] = useState>( + const [items, setItems] = useState>( order.available_items?.length > 0 - ? order.available_items.map((ai: any) => ({ id: ai.id, name: ai.name })) - : [{ id: '1', name: '' }] + ? order.available_items.map((ai: any) => ({ id: ai.id, name: ai.name, is_sold_out: ai.is_sold_out || false })) + : [{ id: '1', name: '', is_sold_out: false }] ) const [loading, setLoading] = useState(false) const [error, setError] = useState('') - const addItem = () => setItems([...items, { id: Math.random().toString(), name: '' }]) + const addItem = () => setItems([...items, { id: Math.random().toString(), name: '', is_sold_out: false }]) const removeItem = (id: string) => setItems(items.filter(i => i.id !== id)) const updateItem = (id: string, name: string) => setItems(items.map(i => i.id === id ? { ...i, name } : i)) + const toggleSoldOut = (id: string, is_sold_out: boolean) => setItems(items.map(i => i.id === id ? { ...i, is_sold_out } : i)) const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() @@ -708,7 +732,7 @@ function EditOrderModal({ order, onClose, onSuccess }: { order: any, onClose: () if (!title.trim()) return setError('Judul PO wajib diisi.') - const validItems = items.filter(i => i.name.trim()).map(i => i.name.trim()) + const validItems = items.filter(i => i.name.trim()).map(i => ({ name: i.name.trim(), is_sold_out: i.is_sold_out })) if (!allowCustom && validItems.length === 0) { return setError('Wajib menambahkan minimal 1 menu pilihan jika tidak mengizinkan custom item.') } @@ -717,6 +741,7 @@ function EditOrderModal({ order, onClose, onSuccess }: { order: any, onClose: () const res = await updateOrder(order.id, { title: title.trim(), + description: description.trim(), allow_custom: allowCustom, available_items: validItems }) @@ -758,6 +783,19 @@ function EditOrderModal({ order, onClose, onSuccess }: { order: any, onClose: () />
+
+ + setDescription(e.target.value)} + className="h-11 rounded-xl border-slate-200 dark:border-slate-800 focus-visible:ring-4 focus-visible:ring-[#1B2CC1]/15 focus-visible:border-[#1B2CC1]" + /> +
+ {/* Custom Item Checkbox */}
updateItem(item.id, e.target.value)} - className="flex-1 h-9 rounded-lg bg-white dark:bg-slate-900 text-xs font-medium" + className={cn( + "flex-1 h-9 rounded-lg bg-white dark:bg-slate-900 text-xs font-medium transition-all", + item.is_sold_out && "opacity-60 cursor-not-allowed line-through decoration-rose-500/50" + )} /> + diff --git a/src/app/(app)/my-purchases/page.tsx b/src/app/(app)/my-purchases/page.tsx index 23a0acd..a6069f2 100644 --- a/src/app/(app)/my-purchases/page.tsx +++ b/src/app/(app)/my-purchases/page.tsx @@ -1,7 +1,7 @@ 'use client' import { useEffect, useState } from 'react' -import { getMyPurchases, submitOrder, getUserSubmission, getSessionUser } from '@/app/actions' +import { getMyPurchases, submitOrder, getUserSubmission, getSessionUser, deleteSubmission } from '@/app/actions' import { Card, CardContent, CardHeader, CardTitle, CardFooter } from '@/components/ui/card' import { Button, buttonVariants } from '@/components/ui/button' import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog' @@ -23,7 +23,8 @@ import { Sparkles, CheckCircle2, ChevronLeft, - ChevronRight + ChevronRight, + Trash2 } from 'lucide-react' import Link from 'next/link' @@ -33,10 +34,25 @@ export default function MyPurchasesPage() { const [loading, setLoading] = useState(true) const [filter, setFilter] = useState<'ALL' | 'BELUM_BAYAR' | 'LUNAS'>('ALL') const [editingPurchase, setEditingPurchase] = useState(null) + const [deletingPurchase, setDeletingPurchase] = useState(null) + const [isDeleting, setIsDeleting] = useState(false) const [currentPage, setCurrentPage] = useState(1) const [searchQuery, setSearchQuery] = useState('') const ITEMS_PER_PAGE = 5 + const handleDelete = async (submissionId: string) => { + if (!userId) return + setIsDeleting(true) + const res = await deleteSubmission(submissionId, userId) + if (res.success) { + loadPurchases(userId) + setDeletingPurchase(null) + } else { + alert(res.error || 'Gagal membatalkan pesanan') + } + setIsDeleting(false) + } + useEffect(() => { const init = async () => { const user = await getSessionUser() @@ -153,6 +169,30 @@ export default function MyPurchasesPage() { )} + {/* Delete Confirmation Modal */} + !open && setDeletingPurchase(null)}> + +
+
+ +
+
+ Batalkan Titipan? +

+ Apakah Anda yakin ingin membatalkan titipan Anda untuk PO {deletingPurchase?.order?.title}? Aksi ini tidak dapat dibatalkan. +

+
+
+ + +
+
+
+
+ {filteredPurchases.length === 0 ? (
@@ -279,12 +319,21 @@ export default function MyPurchasesPage() { {/* Update Order Action Button */}
{isOrderOpen ? ( - +
+ + +
) : (
diff --git a/src/app/(app)/order/[id]/PublicOrderActions.tsx b/src/app/(app)/order/[id]/PublicOrderActions.tsx index 9c40b5b..3944d6f 100644 --- a/src/app/(app)/order/[id]/PublicOrderActions.tsx +++ b/src/app/(app)/order/[id]/PublicOrderActions.tsx @@ -22,7 +22,7 @@ export function PublicOrderActions({ order }: { order: any }) { Titip Sekarang - setOpen(false)} /> + {open && setOpen(false)} />}
- {/* Daftar Penitip (Submissions) */} -
-
-
-
- +
+ {/* Left 2 Cols: Daftar Penitip */} +
+
+
+
+
+ +
+

Daftar Penitip

+
+
+ +
+ {order.submissions.length > 0 ? ( + order.submissions.map((sub: any) => ( +
+ {sub.user.photo ? ( + {sub.user.name} + ) : ( +
+ {sub.user.name.charAt(0).toUpperCase()} +
+ )} + +
+

{sub.user.name}

+
+ {sub.items.map((item: any) => ( + + {item.qty}x {item.name} + + ))} +
+
+
+ )) + ) : ( +
+

Belum ada yang menitip.

+

Jadilah yang pertama untuk menitip pesanan!

+
+ )}
-

Daftar Penitip

-
- {order.submissions.length > 0 ? ( - order.submissions.map((sub: any) => ( -
- {sub.user.photo ? ( - {sub.user.name} - ) : ( -
- {sub.user.name.charAt(0).toUpperCase()} -
- )} - -
-

{sub.user.name}

-
- {sub.items.map((item: any) => ( - - {item.qty}x {item.name} - - ))} -
-
-
- )) - ) : ( -
-

Belum ada yang menitip.

-

Jadilah yang pertama untuk menitip pesanan!

-
- )} -
+ {/* Right 1 Col: Summary Generators */} +
) diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx index a6ebbbd..78f29da 100644 --- a/src/app/(auth)/login/page.tsx +++ b/src/app/(auth)/login/page.tsx @@ -156,7 +156,7 @@ export default function LoginPage() {

- © {new Date().getFullYear()} TitipIn - Sistem Titip Pesanan by firmanramdhani + © {new Date().getFullYear()} TitipIn - Sistem Titip Pesanan

diff --git a/src/app/(auth)/register/page.tsx b/src/app/(auth)/register/page.tsx index c23305c..0f9f7c6 100644 --- a/src/app/(auth)/register/page.tsx +++ b/src/app/(auth)/register/page.tsx @@ -204,7 +204,7 @@ export default function RegisterPage() {

- © {new Date().getFullYear()} TitipIn - Sistem Titip Pesanan by firmanramdhani + © {new Date().getFullYear()} TitipIn - Sistem Titip Pesanan

diff --git a/src/app/actions.ts b/src/app/actions.ts index 20eb85b..37c0654 100644 --- a/src/app/actions.ts +++ b/src/app/actions.ts @@ -237,6 +237,7 @@ export async function getMyOrders(creator_id: string) { export async function createOrder(data: { creator_id: string; title: string; + description?: string; date: Date; allow_custom: boolean; available_items: string[]; @@ -245,6 +246,7 @@ export async function createOrder(data: { const order = await prisma.order.create({ data: { title: data.title, + description: data.description || null, date: data.date, allow_custom: data.allow_custom, creator_id: data.creator_id, @@ -273,12 +275,13 @@ export async function duplicateOrder(order_id: string) { const newOrder = await prisma.order.create({ data: { title: oldOrder.title + ' (Copy)', + description: oldOrder.description, date: new Date(), allow_custom: oldOrder.allow_custom, creator_id: oldOrder.creator_id, status: 'DRAFT', available_items: { - create: oldOrder.available_items.map(ai => ({ name: ai.name })) + create: oldOrder.available_items.map(ai => ({ name: ai.name, is_sold_out: ai.is_sold_out })) } } }) @@ -291,8 +294,9 @@ export async function duplicateOrder(order_id: string) { export async function updateOrder(order_id: string, data: { title: string; + description?: string; allow_custom: boolean; - available_items: string[]; + available_items: { name: string; is_sold_out: boolean }[]; }) { try { const order = await prisma.order.findUnique({ @@ -312,9 +316,10 @@ export async function updateOrder(order_id: string, data: { where: { id: order_id }, data: { title: data.title, + description: data.description || null, allow_custom: data.allow_custom, available_items: { - create: data.available_items.map(name => ({ name })) + create: data.available_items.map(item => ({ name: item.name, is_sold_out: item.is_sold_out })) } } }) @@ -323,8 +328,9 @@ export async function updateOrder(order_id: string, data: { revalidatePath(`/my-orders/${order_id}`) revalidatePath('/') return { success: true, order: updatedOrder } - } catch (error) { - return { success: false, error: 'Gagal memperbarui order.' } + } catch (error: any) { + console.error("Update Order Error:", error) + return { success: false, error: 'Gagal memperbarui order: ' + error?.message } } } @@ -697,3 +703,23 @@ export async function getBalancesAsSubmittor(user_id: string) { return Array.from(balanceMap.values()).filter(b => b.amount !== 0) } + +export async function deleteSubmission(submission_id: string, user_id: string) { + try { + const submission = await prisma.submission.findUnique({ + where: { id: submission_id }, + include: { order: true } + }); + + if (!submission) return { success: false, error: 'Pesanan tidak ditemukan' }; + if (submission.user_id !== user_id) return { success: false, error: 'Tidak ada akses' }; + if (submission.order.status !== 'OPEN') return { success: false, error: 'Hanya bisa membatalkan PO yang OPEN' }; + + await prisma.submission.delete({ + where: { id: submission_id } + }); + return { success: true }; + } catch (error) { + return { success: false, error: 'Gagal membatalkan pesanan' }; + } +} diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index 6cfaf79..e9ad52d 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -43,7 +43,7 @@ export default function NotFound() { {/* Footer info */}

- TitipIn © {new Date().getFullYear()} - Sistem Titip Pesanan by firmanramdhani + TitipIn © {new Date().getFullYear()} - Sistem Titip Pesanan

diff --git a/src/components/OrderCard.tsx b/src/components/OrderCard.tsx index ea8ce81..5665f5f 100644 --- a/src/components/OrderCard.tsx +++ b/src/components/OrderCard.tsx @@ -8,9 +8,10 @@ import { Checkbox } from '@/components/ui/checkbox' import { Label } from '@/components/ui/label' import { Input } from '@/components/ui/input' import { submitOrder, getUserSubmission, getSessionUser } from '@/app/actions' -import { PlusCircle, MinusCircle, User, CheckCircle2, ShoppingBag, Sparkles, Users } from 'lucide-react' +import { PlusCircle, MinusCircle, User, CheckCircle2, ShoppingBag, Sparkles, Users, Eye } from 'lucide-react' import { OrderFormModal } from '@/components/OrderFormModal' import { ShareButton } from '@/components/ShareButton' +import Link from 'next/link' import { format } from 'date-fns' import { id as idLocale } from 'date-fns/locale' @@ -18,15 +19,24 @@ import { id as idLocale } from 'date-fns/locale' export function OrderCard({ order }: { order: any }) { const [open, setOpen] = useState(false) + const activeItems = order.available_items.filter((i: any) => !i.is_sold_out) + return (
{/* Left: Info */}
-

- {order.title} -

+
+

+ {order.title} +

+ {order.description && ( +

+ {order.description} +

+ )} +
{format(new Date(order.date), 'EEEE, dd MMM yyyy', { locale: idLocale })} @@ -64,7 +74,7 @@ export function OrderCard({ order }: { order: any }) {
- Item Tersedia ({order.available_items.length}) + Item Tersedia ({activeItems.length}) {order.allow_custom && (
@@ -75,7 +85,7 @@ export function OrderCard({ order }: { order: any }) {
    - {order.available_items.map((item: any) => ( + {activeItems.map((item: any) => (
  • @@ -83,34 +93,41 @@ export function OrderCard({ order }: { order: any }) { {item.name}
  • ))} - {order.available_items.length === 0 && ( -
  • Hanya menerima item kustom.
  • + {activeItems.length === 0 && ( +
  • + {order.allow_custom ? "Hanya menerima kustom." : "Semua item habis."} +
  • )}
{/* Right: Actions */} -
+
- - + + Titip Sekarang - setOpen(false)} /> + {open && setOpen(false)} />} - - + +
+ + + Detail + + + +
) } - - diff --git a/src/components/OrderFormModal.tsx b/src/components/OrderFormModal.tsx index 111a712..7c2e1a0 100644 --- a/src/components/OrderFormModal.tsx +++ b/src/components/OrderFormModal.tsx @@ -7,10 +7,11 @@ import { DialogContent, DialogTitle } from '@/components/ui/dialog' import { Checkbox } from '@/components/ui/checkbox' import { Label } from '@/components/ui/label' import { Input } from '@/components/ui/input' -import { submitOrder, getUserSubmission, getSessionUser } from '@/app/actions' +import { submitOrder, getUserSubmission, getSessionUser, getOrderDetail } from '@/app/actions' import { PlusCircle, MinusCircle, CheckCircle2 } from 'lucide-react' export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: () => void }) { + const [liveOrder, setLiveOrder] = useState(order) const [userId, setUserId] = useState('') const [items, setItems] = useState>({}) const [customItems, setCustomItems] = useState>([]) @@ -19,32 +20,35 @@ export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: () useEffect(() => { const init = async () => { + const freshOrder = await getOrderDetail(order.id) + if (freshOrder) setLiveOrder(freshOrder) + const user = await getSessionUser() if (user?.id) { setUserId(user.id) - loadExistingSubmission(user.id) + loadExistingSubmission(user.id, freshOrder || order) } } init() }, [order.id]) - const loadExistingSubmission = async (uid: string) => { - const sub = await getUserSubmission(order.id, uid) + const loadExistingSubmission = async (uid: string, currentOrder: any) => { + const sub = await getUserSubmission(currentOrder.id, uid) if (sub) { - const newItems: any = { ...items } + const freshItems: any = {} const newCustoms: any[] = [] sub.items.forEach((item: any) => { if (!item.is_custom) { - const stdItem = order.available_items.find((ai: any) => ai.name === item.name) + const stdItem = currentOrder.available_items.find((ai: any) => ai.name === item.name) if (stdItem) { - newItems[stdItem.id] = { selected: true, qty: item.qty } + freshItems[stdItem.id] = { selected: true, qty: item.qty } } } else { newCustoms.push({ id: Math.random().toString(), name: item.name, qty: item.qty }) } }) - setItems(newItems) + setItems(freshItems) setCustomItems(newCustoms) } } @@ -86,7 +90,7 @@ export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: () const payloadItems: any[] = [] - order.available_items.forEach((ai: any) => { + liveOrder.available_items.forEach((ai: any) => { const state = items[ai.id] if (state?.selected && state.qty > 0) { payloadItems.push({ name: ai.name, qty: state.qty, is_custom: false }) @@ -106,7 +110,7 @@ export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: () } const res = await submitOrder({ - order_id: order.id, + order_id: liveOrder.id, user_id: userId, items: payloadItems }) @@ -124,7 +128,7 @@ export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: ()
Form Titipan - {order.title} + {liveOrder.title}

Pilih menu yang tersedia di bawah atau tambahkan item khusus jika diizinkan. @@ -138,7 +142,7 @@ export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: () Daftar Menu Tersedia

- {order.available_items.map((item: any) => { + {liveOrder.available_items.map((item: any) => { const isSelected = items[item.id]?.selected || false const qty = items[item.id]?.qty || 0 return ( @@ -154,12 +158,18 @@ export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: ()
handleStandardItemToggle(item.id, c as boolean)} - className="rounded-lg data-[state=checked]:bg-[#1B2CC1] data-[state=checked]:border-[#1B2CC1]" + checked={isSelected && !item.is_sold_out} + disabled={item.is_sold_out} + onCheckedChange={(c) => !item.is_sold_out && handleStandardItemToggle(item.id, c as boolean)} + className="rounded-lg data-[state=checked]:bg-[#1B2CC1] data-[state=checked]:border-[#1B2CC1] disabled:opacity-50" /> -
{isSelected && ( @@ -188,7 +198,7 @@ export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: ()
) })} - {order.available_items.length === 0 && ( + {liveOrder.available_items.length === 0 && (

Tidak ada menu standar yang ditentukan.

@@ -197,7 +207,7 @@ export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: ()
{/* Custom Items */} - {order.allow_custom && ( + {liveOrder.allow_custom && (