style: apply consistent code formatting across project files

This commit is contained in:
Firman Ramdhani
2026-09-16 13:14:02 +07:00
parent d5791e759e
commit 51d05653c6
48 changed files with 3585 additions and 2575 deletions
+143 -102
View File
@@ -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>