style: apply consistent code formatting across project files
This commit is contained in:
+143
-102
@@ -10,11 +10,15 @@ import { Input } from '@/components/ui/input'
|
||||
import { submitOrder, getUserSubmission, getSessionUser, getOrderDetail } from '@/app/actions'
|
||||
import { PlusCircle, MinusCircle, CheckCircle2 } from 'lucide-react'
|
||||
|
||||
export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: () => void }) {
|
||||
export function OrderFormModal({ order, onSuccess }: { order: any; onSuccess: () => void }) {
|
||||
const [liveOrder, setLiveOrder] = useState<any>(order)
|
||||
const [userId, setUserId] = useState<string>('')
|
||||
const [items, setItems] = useState<Record<string, { selected: boolean, qty: number, note?: string }>>({})
|
||||
const [customItems, setCustomItems] = useState<Array<{ id: string, name: string, qty: number, note?: string }>>([])
|
||||
const [items, setItems] = useState<
|
||||
Record<string, { selected: boolean; qty: number; note?: string }>
|
||||
>({})
|
||||
const [customItems, setCustomItems] = useState<
|
||||
Array<{ id: string; name: string; qty: number; note?: string }>
|
||||
>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
|
||||
@@ -22,7 +26,7 @@ export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: ()
|
||||
const init = async () => {
|
||||
const freshOrder = await getOrderDetail(order.id)
|
||||
if (freshOrder) setLiveOrder(freshOrder)
|
||||
|
||||
|
||||
const user = await getSessionUser()
|
||||
if (user?.id) {
|
||||
setUserId(user.id)
|
||||
@@ -37,7 +41,7 @@ export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: ()
|
||||
if (sub) {
|
||||
const freshItems: any = {}
|
||||
const newCustoms: any[] = []
|
||||
|
||||
|
||||
sub.items.forEach((item: any) => {
|
||||
if (!item.is_custom) {
|
||||
const stdItem = currentOrder.available_items.find((ai: any) => ai.name === item.name)
|
||||
@@ -49,7 +53,7 @@ export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: ()
|
||||
id: Math.random().toString(36).substr(2, 9),
|
||||
name: item.name,
|
||||
qty: item.qty,
|
||||
note: item.note || ''
|
||||
note: item.note || '',
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -59,9 +63,9 @@ export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: ()
|
||||
}
|
||||
|
||||
const handleStandardItemToggle = (id: string, checked: boolean) => {
|
||||
setItems(prev => ({
|
||||
setItems((prev) => ({
|
||||
...prev,
|
||||
[id]: { selected: checked, qty: checked ? 1 : 0, note: prev[id]?.note || '' }
|
||||
[id]: { selected: checked, qty: checked ? 1 : 0, note: prev[id]?.note || '' },
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -70,30 +74,39 @@ export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: ()
|
||||
handleStandardItemToggle(id, false)
|
||||
return
|
||||
}
|
||||
setItems(prev => ({
|
||||
setItems((prev) => ({
|
||||
...prev,
|
||||
[id]: { ...prev[id], qty }
|
||||
[id]: { ...prev[id], qty },
|
||||
}))
|
||||
}
|
||||
|
||||
const handleStandardItemNote = (id: string, note: string) => {
|
||||
setItems(prev => ({
|
||||
setItems((prev) => ({
|
||||
...prev,
|
||||
[id]: { ...prev[id], note }
|
||||
[id]: { ...prev[id], note },
|
||||
}))
|
||||
}
|
||||
|
||||
const addCustomItem = () => {
|
||||
setCustomItems(prev => [...prev, { id: Math.random().toString(36).substr(2, 9), name: '', qty: 1, note: '' }])
|
||||
setCustomItems((prev) => [
|
||||
...prev,
|
||||
{ id: Math.random().toString(36).substr(2, 9), name: '', qty: 1, note: '' },
|
||||
])
|
||||
}
|
||||
|
||||
const handleCustomItemChange = (id: string, field: 'name' | 'qty' | 'note', value: string | number) => {
|
||||
setCustomItems(prev => prev.map(item => {
|
||||
if (item.id === id) {
|
||||
return { ...item, [field]: value }
|
||||
}
|
||||
return item
|
||||
}))
|
||||
const handleCustomItemChange = (
|
||||
id: string,
|
||||
field: 'name' | 'qty' | 'note',
|
||||
value: string | number
|
||||
) => {
|
||||
setCustomItems((prev) =>
|
||||
prev.map((item) => {
|
||||
if (item.id === id) {
|
||||
return { ...item, [field]: value }
|
||||
}
|
||||
return item
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const handleCustomItemQty = (id: string, qty: number) => {
|
||||
@@ -102,16 +115,16 @@ export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: ()
|
||||
}
|
||||
|
||||
const removeCustomItem = (id: string) => {
|
||||
setCustomItems(customItems.filter(c => c.id !== id))
|
||||
setCustomItems(customItems.filter((c) => c.id !== id))
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setError('')
|
||||
setLoading(true)
|
||||
|
||||
|
||||
const payloadItems: any[] = []
|
||||
|
||||
|
||||
liveOrder.available_items.forEach((ai: any) => {
|
||||
const selectedItem = items[ai.id]
|
||||
if (selectedItem?.selected && selectedItem.qty > 0) {
|
||||
@@ -119,22 +132,22 @@ export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: ()
|
||||
name: ai.name,
|
||||
qty: selectedItem.qty,
|
||||
is_custom: false,
|
||||
note: selectedItem.note?.trim() || undefined
|
||||
note: selectedItem.note?.trim() || undefined,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
customItems.forEach(ci => {
|
||||
|
||||
customItems.forEach((ci) => {
|
||||
if (ci.name.trim() && ci.qty > 0) {
|
||||
payloadItems.push({
|
||||
name: ci.name.trim(),
|
||||
qty: ci.qty,
|
||||
is_custom: true,
|
||||
note: ci.note?.trim() || undefined
|
||||
note: ci.note?.trim() || undefined,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
if (payloadItems.length === 0) {
|
||||
setError('Harap pilih minimal 1 item atau tambahkan item lainnya.')
|
||||
setLoading(false)
|
||||
@@ -144,7 +157,7 @@ export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: ()
|
||||
const res = await submitOrder({
|
||||
order_id: liveOrder.id,
|
||||
user_id: userId,
|
||||
items: payloadItems
|
||||
items: payloadItems,
|
||||
})
|
||||
|
||||
if (res.success) {
|
||||
@@ -156,21 +169,26 @@ export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: ()
|
||||
}
|
||||
|
||||
return (
|
||||
<DialogContent className="sm:max-w-[520px] p-0 overflow-hidden rounded-3xl border-slate-200/90 dark:border-slate-800 shadow-2xl max-h-[90vh] flex flex-col">
|
||||
<DialogContent className="flex max-h-[90vh] flex-col overflow-hidden rounded-3xl border-slate-200/90 p-0 shadow-2xl sm:max-w-[520px] dark:border-slate-800">
|
||||
<div className="bg-gradient-to-br from-[#1B2CC1] to-[#121E85] p-6 text-white">
|
||||
<span className="text-[11px] font-bold uppercase tracking-wider text-blue-200">Form Titipan</span>
|
||||
<DialogTitle className="text-2xl font-black tracking-tight text-white mt-1">
|
||||
<span className="text-[11px] font-bold tracking-wider text-blue-200 uppercase">
|
||||
Form Titipan
|
||||
</span>
|
||||
<DialogTitle className="mt-1 text-2xl font-black tracking-tight text-white">
|
||||
{liveOrder.title}
|
||||
</DialogTitle>
|
||||
<p className="text-xs text-blue-100 mt-1">
|
||||
<p className="mt-1 text-xs text-blue-100">
|
||||
Pilih menu yang tersedia di bawah atau tambahkan item khusus jika diizinkan.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="p-6 overflow-y-auto space-y-6 flex-1 bg-white dark:bg-slate-900">
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex-1 space-y-6 overflow-y-auto bg-white p-6 dark:bg-slate-900"
|
||||
>
|
||||
{/* Standard Items */}
|
||||
<div className="space-y-3">
|
||||
<Label className="text-xs font-bold uppercase tracking-wider text-slate-400">
|
||||
<Label className="text-xs font-bold tracking-wider text-slate-400 uppercase">
|
||||
Daftar Menu Tersedia
|
||||
</Label>
|
||||
<div className="space-y-2">
|
||||
@@ -178,50 +196,64 @@ export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: ()
|
||||
const isSelected = items[item.id]?.selected || false
|
||||
const qty = items[item.id]?.qty || 0
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
<div
|
||||
key={item.id}
|
||||
className={cn(
|
||||
"flex flex-col gap-2 p-3.5 rounded-xl border transition-all",
|
||||
isSelected ? "border-[#1B2CC1]/40 bg-blue-50/20 dark:bg-blue-950/10 shadow-sm" : "border-slate-200/90 dark:border-slate-800 bg-slate-50/70 dark:bg-slate-800/40"
|
||||
'flex flex-col gap-2 rounded-xl border p-3.5 transition-all',
|
||||
isSelected
|
||||
? 'border-[#1B2CC1]/40 bg-blue-50/20 shadow-sm dark:bg-blue-950/10'
|
||||
: 'border-slate-200/90 bg-slate-50/70 dark:border-slate-800 dark:bg-slate-800/40'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3 flex-1 min-w-0">
|
||||
<Checkbox
|
||||
id={`item-${item.id}`}
|
||||
<div className="flex min-w-0 flex-1 items-center gap-3">
|
||||
<Checkbox
|
||||
id={`item-${item.id}`}
|
||||
checked={isSelected && !item.is_sold_out}
|
||||
disabled={item.is_sold_out || loading}
|
||||
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"
|
||||
onCheckedChange={(c) =>
|
||||
!item.is_sold_out && handleStandardItemToggle(item.id, c as boolean)
|
||||
}
|
||||
className="rounded-lg disabled:opacity-50 data-[state=checked]:border-[#1B2CC1] data-[state=checked]:bg-[#1B2CC1]"
|
||||
/>
|
||||
<Label htmlFor={`item-${item.id}`} className={cn("text-sm font-bold truncate flex items-center gap-2", item.is_sold_out ? "text-slate-400 dark:text-slate-500 cursor-not-allowed" : "text-slate-800 dark:text-slate-200 cursor-pointer")}>
|
||||
<span className={item.is_sold_out ? "line-through" : ""}>{item.name}</span>
|
||||
<Label
|
||||
htmlFor={`item-${item.id}`}
|
||||
className={cn(
|
||||
'flex items-center gap-2 truncate text-sm font-bold',
|
||||
item.is_sold_out
|
||||
? 'cursor-not-allowed text-slate-400 dark:text-slate-500'
|
||||
: 'cursor-pointer text-slate-800 dark:text-slate-200'
|
||||
)}
|
||||
>
|
||||
<span className={item.is_sold_out ? 'line-through' : ''}>{item.name}</span>
|
||||
{item.is_sold_out && (
|
||||
<span className="text-[9px] bg-rose-100 dark:bg-rose-950/30 text-rose-600 dark:text-rose-400 px-1.5 py-0.5 rounded font-black uppercase tracking-wider border border-rose-200 dark:border-rose-900/50">
|
||||
<span className="rounded border border-rose-200 bg-rose-100 px-1.5 py-0.5 text-[9px] font-black tracking-wider text-rose-600 uppercase dark:border-rose-900/50 dark:bg-rose-950/30 dark:text-rose-400">
|
||||
Sold Out
|
||||
</span>
|
||||
)}
|
||||
</Label>
|
||||
</div>
|
||||
{isSelected && (
|
||||
<div className="flex items-center gap-1 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg p-0.5 shadow-sm shrink-0">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
<div className="flex shrink-0 items-center gap-1 rounded-lg border border-slate-200 bg-white p-0.5 shadow-sm dark:border-slate-700 dark:bg-slate-800">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
disabled={loading}
|
||||
className="h-7 w-7 text-slate-500 hover:text-slate-800"
|
||||
className="h-7 w-7 text-slate-500 hover:text-slate-800"
|
||||
onClick={() => handleStandardItemQty(item.id, qty - 1)}
|
||||
>
|
||||
<MinusCircle className="h-4 w-4" />
|
||||
</Button>
|
||||
<span className="w-7 text-center text-xs font-extrabold text-[#1B2CC1] dark:text-blue-400">{qty}</span>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
<span className="w-7 text-center text-xs font-extrabold text-[#1B2CC1] dark:text-blue-400">
|
||||
{qty}
|
||||
</span>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
disabled={loading}
|
||||
className="h-7 w-7 text-slate-500 hover:text-slate-800"
|
||||
className="h-7 w-7 text-slate-500 hover:text-slate-800"
|
||||
onClick={() => handleStandardItemQty(item.id, qty + 1)}
|
||||
>
|
||||
<PlusCircle className="h-4 w-4" />
|
||||
@@ -230,10 +262,10 @@ export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: ()
|
||||
)}
|
||||
</div>
|
||||
{isSelected && (
|
||||
<div className="pl-7 mt-1">
|
||||
<Input
|
||||
placeholder="Catatan (opsional), misal: 1/2 porsi"
|
||||
className="h-8 text-xs bg-slate-50/50 dark:bg-slate-900/50 border-slate-200/60 dark:border-slate-700/60"
|
||||
<div className="mt-1 pl-7">
|
||||
<Input
|
||||
placeholder="Catatan (opsional), misal: 1/2 porsi"
|
||||
className="h-8 border-slate-200/60 bg-slate-50/50 text-xs dark:border-slate-700/60 dark:bg-slate-900/50"
|
||||
value={items[item.id]?.note || ''}
|
||||
disabled={loading}
|
||||
onChange={(e) => handleStandardItemNote(item.id, e.target.value)}
|
||||
@@ -244,7 +276,7 @@ export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: ()
|
||||
)
|
||||
})}
|
||||
{liveOrder.available_items.length === 0 && (
|
||||
<p className="text-xs text-slate-400 text-center py-3 bg-slate-50 dark:bg-slate-800/40 rounded-xl border border-dashed border-slate-200 dark:border-slate-800">
|
||||
<p className="rounded-xl border border-dashed border-slate-200 bg-slate-50 py-3 text-center text-xs text-slate-400 dark:border-slate-800 dark:bg-slate-800/40">
|
||||
Tidak ada menu standar yang ditentukan.
|
||||
</p>
|
||||
)}
|
||||
@@ -255,48 +287,53 @@ export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: ()
|
||||
{liveOrder.allow_custom && (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-xs font-bold uppercase tracking-wider text-slate-400">
|
||||
<Label className="text-xs font-bold tracking-wider text-slate-400 uppercase">
|
||||
Item Tambahan (Kustom)
|
||||
</Label>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={loading}
|
||||
onClick={addCustomItem}
|
||||
className="h-8 text-xs font-bold rounded-lg border-dashed border-[#1B2CC1]/40 text-[#1B2CC1] hover:bg-[#1B2CC1]/10 gap-1.5"
|
||||
onClick={addCustomItem}
|
||||
className="h-8 gap-1.5 rounded-lg border-dashed border-[#1B2CC1]/40 text-xs font-bold text-[#1B2CC1] hover:bg-[#1B2CC1]/10"
|
||||
>
|
||||
<PlusCircle className="h-3.5 w-3.5" /> Tambah Kustom
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
{customItems.length > 0 ? (
|
||||
<div className="space-y-2.5">
|
||||
{customItems.map((ci, idx) => (
|
||||
<div key={ci.id} className="flex gap-2 items-center p-3 rounded-xl border border-slate-200/90 dark:border-slate-800 bg-slate-50/70 dark:bg-slate-800/40">
|
||||
<span className="text-xs font-bold text-slate-400 w-4">{idx + 1}.</span>
|
||||
<Input
|
||||
placeholder="Nama Menu Kustom"
|
||||
value={ci.name}
|
||||
<div
|
||||
key={ci.id}
|
||||
className="flex items-center gap-2 rounded-xl border border-slate-200/90 bg-slate-50/70 p-3 dark:border-slate-800 dark:bg-slate-800/40"
|
||||
>
|
||||
<span className="w-4 text-xs font-bold text-slate-400">{idx + 1}.</span>
|
||||
<Input
|
||||
placeholder="Nama Menu Kustom"
|
||||
value={ci.name}
|
||||
disabled={loading}
|
||||
onChange={(e) => handleCustomItemChange(ci.id, 'name', e.target.value)}
|
||||
className="flex-1 h-9 rounded-lg bg-white dark:bg-slate-900 text-xs font-medium"
|
||||
className="h-9 flex-1 rounded-lg bg-white text-xs font-medium dark:bg-slate-900"
|
||||
/>
|
||||
<Input
|
||||
type="number"
|
||||
min="1"
|
||||
value={ci.qty}
|
||||
<Input
|
||||
type="number"
|
||||
min="1"
|
||||
value={ci.qty}
|
||||
disabled={loading}
|
||||
onChange={(e) => handleCustomItemChange(ci.id, 'qty', parseInt(e.target.value) || 1)}
|
||||
className="w-16 h-9 rounded-lg bg-white dark:bg-slate-900 text-center font-bold text-xs"
|
||||
onChange={(e) =>
|
||||
handleCustomItemChange(ci.id, 'qty', parseInt(e.target.value) || 1)
|
||||
}
|
||||
className="h-9 w-16 rounded-lg bg-white text-center text-xs font-bold dark:bg-slate-900"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
disabled={loading}
|
||||
onClick={() => removeCustomItem(ci.id)}
|
||||
className="h-8 w-8 text-red-500 hover:text-red-700 hover:bg-red-50 dark:hover:bg-red-950/30 rounded-lg shrink-0"
|
||||
onClick={() => removeCustomItem(ci.id)}
|
||||
className="h-8 w-8 shrink-0 rounded-lg text-red-500 hover:bg-red-50 hover:text-red-700 dark:hover:bg-red-950/30"
|
||||
>
|
||||
<MinusCircle className="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -304,29 +341,33 @@ export function OrderFormModal({ order, onSuccess }: { order: any, onSuccess: ()
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
<div
|
||||
onClick={addCustomItem}
|
||||
className="p-4 border-2 border-dashed border-slate-200 dark:border-slate-800 hover:border-[#1B2CC1]/50 rounded-2xl text-center bg-slate-50/50 dark:bg-slate-800/30 cursor-pointer transition-all group"
|
||||
className="group cursor-pointer rounded-2xl border-2 border-dashed border-slate-200 bg-slate-50/50 p-4 text-center transition-all hover:border-[#1B2CC1]/50 dark:border-slate-800 dark:bg-slate-800/30"
|
||||
>
|
||||
<PlusCircle className="w-5 h-5 text-slate-400 group-hover:text-[#1B2CC1] mx-auto mb-1 transition-colors" />
|
||||
<p className="text-xs font-bold text-slate-600 dark:text-slate-300">Klik untuk Tambah Item Custom</p>
|
||||
<p className="text-[11px] text-slate-400 mt-0.5">Ingin titip menu lain? Masukkan nama dan kuantitasnya di sini.</p>
|
||||
<PlusCircle className="mx-auto mb-1 h-5 w-5 text-slate-400 transition-colors group-hover:text-[#1B2CC1]" />
|
||||
<p className="text-xs font-bold text-slate-600 dark:text-slate-300">
|
||||
Klik untuk Tambah Item Custom
|
||||
</p>
|
||||
<p className="mt-0.5 text-[11px] text-slate-400">
|
||||
Ingin titip menu lain? Masukkan nama dan kuantitasnya di sini.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="p-3 bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-900 rounded-xl text-xs text-red-600 dark:text-red-400 font-semibold">
|
||||
<div className="rounded-xl border border-red-200 bg-red-50 p-3 text-xs font-semibold text-red-600 dark:border-red-900 dark:bg-red-950/30 dark:text-red-400">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end gap-3 pt-2 border-t border-slate-100 dark:border-slate-800">
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full h-11 rounded-xl bg-[#1B2CC1] hover:bg-[#15229E] text-white font-bold shadow-md shadow-[#1B2CC1]/25"
|
||||
|
||||
<div className="flex justify-end gap-3 border-t border-slate-100 pt-2 dark:border-slate-800">
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="h-11 w-full rounded-xl bg-[#1B2CC1] font-bold text-white shadow-md shadow-[#1B2CC1]/25 hover:bg-[#15229E]"
|
||||
>
|
||||
{loading ? 'Menyimpan Titipan...' : 'Kirim Titip Pesanan'}
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user