-
Saldo Penitip
+
+ Saldo Penitip
+
{/* From Creator perspective: positive balance of submittor is bad for creator (creator holds their money) */}
-
0 ? "text-rose-600" : "text-emerald-600")}>
- {b.amount > 0 ?
:
}
+
0 ? 'text-rose-600' : 'text-emerald-600'
+ )}
+ >
+ {b.amount > 0 ? (
+
+ ) : (
+
+ )}
{formatRupiah(b.amount)}
diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx
index 9866903..90beb3a 100644
--- a/src/app/(app)/layout.tsx
+++ b/src/app/(app)/layout.tsx
@@ -1,9 +1,5 @@
-import { AppLayout } from "@/components/AppLayout";
+import { AppLayout } from '@/components/AppLayout'
export default function Layout({ children }: { children: React.ReactNode }) {
- return (
-
- {children}
-
- );
+ return
{children}
}
diff --git a/src/app/(app)/my-orders/page.tsx b/src/app/(app)/my-orders/page.tsx
index 136ddca..8696d13 100644
--- a/src/app/(app)/my-orders/page.tsx
+++ b/src/app/(app)/my-orders/page.tsx
@@ -1,13 +1,34 @@
'use client'
import { useEffect, useState } from 'react'
-import { getMyOrders, createOrder, updateOrder, duplicateOrder, updateOrderStatus, deleteOrder, getSessionUser } from '@/app/actions'
+import {
+ getMyOrders,
+ createOrder,
+ updateOrder,
+ duplicateOrder,
+ updateOrderStatus,
+ deleteOrder,
+ getSessionUser,
+} from '@/app/actions'
import { Button, buttonVariants } from '@/components/ui/button'
import { cn } from '@/lib/utils'
-import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { ShareButton } from '@/components/ShareButton'
-import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from '@/components/ui/dialog'
import { Textarea } from '@/components/ui/textarea'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
@@ -15,22 +36,22 @@ import { Checkbox } from '@/components/ui/checkbox'
import { format, isToday } from 'date-fns'
import { id as idLocale } from 'date-fns/locale'
import Link from 'next/link'
-import {
- PlusCircle,
- MinusCircle,
- Copy,
- FileText,
- Loader2,
- Plus,
- Users,
- Sparkles,
+import {
+ PlusCircle,
+ MinusCircle,
+ Copy,
+ FileText,
+ Loader2,
+ Plus,
+ Users,
+ Sparkles,
Layers,
Pencil,
Lock,
Trash2,
AlertTriangle,
ChevronLeft,
- ChevronRight
+ ChevronRight,
} from 'lucide-react'
export default function MyOrdersPage() {
@@ -51,9 +72,9 @@ export default function MyOrdersPage() {
const init = async () => {
const user = await getSessionUser()
if (user?.id) {
- setUserId(user.id)
- loadOrders(user.id)
- }
+ setUserId(user.id)
+ loadOrders(user.id)
+ }
}
init()
}, [])
@@ -72,8 +93,6 @@ export default function MyOrdersPage() {
}
}
-
-
const handleDelete = async () => {
if (!orderToDelete) return
setDeleting(true)
@@ -86,29 +105,29 @@ export default function MyOrdersPage() {
}
const getStatusBadge = (status: string) => {
- switch(status) {
- case 'OPEN':
+ switch (status) {
+ case 'OPEN':
return (
-
+
● OPEN
)
- case 'CLOSE':
+ case 'CLOSE':
return (
-
+
● CLOSE
)
- default:
+ default:
return (
-
+
● DRAFT
)
}
}
- const filteredOrders = orders.filter(o => {
+ const filteredOrders = orders.filter((o) => {
const matchTab = activeTab === 'ALL' ? true : o.status === activeTab
const matchSearch = o.title.toLowerCase().includes(searchQuery.toLowerCase())
return matchTab && matchSearch
@@ -126,34 +145,34 @@ export default function MyOrdersPage() {
if (loading) {
return (
-
-
-
Memuat data PO Anda...
+
+
+ Memuat data PO Anda...
)
}
return (
-
+
{/* Top Controls & Segmented Control */}
-
-
+
+
{/* Segmented Control / Tabs */}
-
+
{(['ALL', 'OPEN', 'DRAFT', 'CLOSE'] as const).map((tab) => (
setActiveTab(tab)}
className={cn(
- "px-4 py-2 rounded-lg transition-all duration-200 whitespace-nowrap",
- activeTab === tab
- ? "bg-white dark:bg-slate-900 text-[#1B2CC1] dark:text-white shadow-sm font-black"
- : "text-slate-500 hover:text-slate-900 dark:hover:text-white"
+ 'rounded-lg px-4 py-2 whitespace-nowrap transition-all duration-200',
+ activeTab === tab
+ ? 'bg-white font-black text-[#1B2CC1] shadow-sm dark:bg-slate-900 dark:text-white'
+ : 'text-slate-500 hover:text-slate-900 dark:hover:text-white'
)}
>
{tab === 'ALL' ? 'Semua PO' : tab}
- ({tab === 'ALL' ? orders.length : orders.filter(o => o.status === tab).length})
+ ({tab === 'ALL' ? orders.length : orders.filter((o) => o.status === tab).length})
))}
@@ -161,35 +180,46 @@ export default function MyOrdersPage() {
{/* Search Input */}
{/* Action Button */}
-
- Buka Jasa PO Baru
+
+ Buka Jasa PO Baru
- {
setIsCreateOpen(false)
if (userId) loadOrders(userId)
- }}
+ }}
/>
@@ -210,14 +240,17 @@ export default function MyOrdersPage() {
{/* Duplicate Order Modal */}
{orderToDuplicate && (
-
!open && setOrderToDuplicate(null)}>
- !open && setOrderToDuplicate(null)}
+ >
+ {
setOrderToDuplicate(null)
if (userId) loadOrders(userId)
- }}
+ }}
/>
)}
@@ -225,31 +258,38 @@ export default function MyOrdersPage() {
{/* Delete Confirmation Modal */}
{orderToDelete && (
!open && setOrderToDelete(null)}>
-
-
-
-
+
+
+
Hapus Jasa PO Ini?
-
+
Tindakan ini permanen dan tidak dapat dibatalkan.
-
-
-
{orderToDelete.title}
-
- Status: {orderToDelete.status} • {orderToDelete.submissions?.length || 0} titipan pemesan
+
+
+
+ {orderToDelete.title}
+
+
+ Status:{' '}
+
+ {orderToDelete.status}
+ {' '}
+ • {orderToDelete.submissions?.length || 0} titipan pemesan
-
- Seluruh data pesanan dan daftar menu yang ada di dalam PO ini akan dihapus dari sistem.
+
+ Seluruh data pesanan dan daftar menu yang ada di dalam PO ini akan dihapus dari
+ sistem.
@@ -258,7 +298,7 @@ export default function MyOrdersPage() {
variant="outline"
onClick={() => setOrderToDelete(null)}
disabled={deleting}
- className="w-1/2 h-10 rounded-xl font-bold text-xs"
+ className="h-10 w-1/2 rounded-xl text-xs font-bold"
>
Batal
@@ -266,9 +306,13 @@ export default function MyOrdersPage() {
type="button"
onClick={handleDelete}
disabled={deleting}
- className="w-1/2 h-10 rounded-xl bg-red-600 hover:bg-red-700 text-white font-bold text-xs shadow-md shadow-red-600/20 gap-1.5"
+ className="h-10 w-1/2 gap-1.5 rounded-xl bg-red-600 text-xs font-bold text-white shadow-md shadow-red-600/20 hover:bg-red-700"
>
- {deleting ? : }
+ {deleting ? (
+
+ ) : (
+
+ )}
{deleting ? 'Menghapus...' : 'Ya, Hapus PO'}
@@ -279,246 +323,280 @@ export default function MyOrdersPage() {
{/* Order List */}
{filteredOrders.length === 0 ? (
-
-
-
+
+
+
-
+
{activeTab === 'ALL' ? 'Belum Ada Jasa PO' : `Tidak Ada PO Berstatus ${activeTab}`}
-
+
Mulai buka jasa titip pesanan makanan atau kebutuhan untuk teman kantor.
-
setIsCreateOpen(true)}
- className="bg-[#1B2CC1] hover:bg-[#15229E] text-white font-bold rounded-xl shadow-md shadow-[#1B2CC1]/20 gap-2 h-10"
+ className="h-10 gap-2 rounded-xl bg-[#1B2CC1] font-bold text-white shadow-md shadow-[#1B2CC1]/20 hover:bg-[#15229E]"
>
- Buat PO Baru
+ Buat PO Baru
) : (
-
-
+
+
{paginatedOrders.map((order) => {
- const date = new Date(order.date)
- const isClosed = order.status === 'CLOSE'
- const isDraft = order.status === 'DRAFT'
- const canDelete = isDraft || isClosed
-
- return (
-
- {/* Left: Info */}
-
-
-
-
- {order.title}
-
- {order.description && (
-
- {order.description}
-
- )}
-
-
-
- {format(date, 'EEEE, dd MMM yyyy', { locale: idLocale })}
-
- {getStatusBadge(order.status)}
-
-
+ const date = new Date(order.date)
+ const isClosed = order.status === 'CLOSE'
+ const isDraft = order.status === 'DRAFT'
+ const canDelete = isDraft || isClosed
-
-
-
-
- {order.submissions.length} Orang Menitip
-
- {isClosed && order.submissions.length > 0 && (
-
-
- Lunas: {order.submissions.filter((s: any) => s.payment_status === 'LUNAS').length}
-
-
- Belum: {order.submissions.filter((s: any) => s.payment_status !== 'LUNAS').length}
-
-
- )}
-
-
-
-
-
- {order.available_items.length > 0 && (
-
- {order.available_items.length} Menu Pilihan
+ return (
+
+ {/* Left: Info */}
+
+
+
+
+ {order.title}
+
+ {order.description && (
+
+ {order.description}
+
+ )}
+
+
+
+ {format(date, 'EEEE, dd MMM yyyy', { locale: idLocale })}
- )}
- {order.allow_custom && (
-
- Custom Item Aktif
-
- )}
+ {getStatusBadge(order.status)}
+
-
-
- {/* Right: Actions */}
-
-
- {/* Detail Link */}
-
-
-
Detail
-
+
+
+
+
+ {order.submissions.length} Orang Menitip
+
+ {isClosed && order.submissions.length > 0 && (
+
+
+ Lunas:{' '}
+ {
+ order.submissions.filter((s: any) => s.payment_status === 'LUNAS')
+ .length
+ }
+
+
+ Belum:{' '}
+ {
+ order.submissions.filter((s: any) => s.payment_status !== 'LUNAS')
+ .length
+ }
+
+
+ )}
+
- {/* Status Action Button */}
-
- {order.status === 'DRAFT' && (
-
handleStatusChange(order.id, 'OPEN')}
- disabled={!isToday(new Date(order.date))}
- className={cn(
- "w-full h-10 rounded-xl font-bold text-xs shadow-sm gap-1.5 transition-all px-2",
- isToday(new Date(order.date))
- ? "bg-emerald-600 hover:bg-emerald-700 text-white cursor-pointer"
- : "bg-slate-200 text-slate-500 cursor-not-allowed opacity-50"
- )}
- title={isToday(new Date(order.date)) ? "Buka PO" : "Hanya PO hari ini yang bisa dibuka"}
- >
-
- Buka
-
- )}
- {order.status === 'OPEN' && (
-
handleStatusChange(order.id, 'CLOSE')}
- className="w-full h-10 rounded-xl bg-rose-600 hover:bg-rose-700 text-white font-bold text-xs shadow-sm gap-1.5 transition-all cursor-pointer px-2"
- title="Tutup PO"
- >
-
- Tutup
-
- )}
- {order.status === 'CLOSE' && (
-
handleStatusChange(order.id, 'OPEN')}
- disabled={!isToday(new Date(order.date))}
- className={cn(
- "w-full h-10 rounded-xl font-bold text-xs shadow-sm gap-1.5 transition-all px-2",
- isToday(new Date(order.date))
- ? "bg-emerald-600 hover:bg-emerald-700 text-white cursor-pointer"
- : "bg-slate-200 text-slate-500 cursor-not-allowed opacity-50"
- )}
- title={isToday(new Date(order.date)) ? "Buka Kembali PO" : "Hanya PO hari ini yang bisa dibuka"}
- >
-
- Buka
-
- )}
+
+
+
+ {order.available_items.length > 0 && (
+
+ {order.available_items.length} Menu Pilihan
+
+ )}
+ {order.allow_custom && (
+
+ Custom Item Aktif
+
+ )}
+
- {/* Utility Tools Row */}
-
-
setEditingOrder(order)}
- className={cn(
- "flex-1 h-8 rounded-lg font-bold text-[10px] sm:text-[11px] gap-1 px-1 transition-all",
- (isClosed || !isToday(new Date(order.date)))
- ? "opacity-35 cursor-not-allowed text-slate-400"
- : "text-slate-600 hover:text-[#1B2CC1] hover:bg-[#1B2CC1]/10 dark:text-slate-300"
- )}
- title={isClosed ? "PO sudah CLOSE" : !isToday(new Date(order.date)) ? "Hanya PO hari ini yang bisa diedit" : "Edit PO"}
- >
-
- Edit
-
+ {/* Right: Actions */}
+
+
+ {/* Detail Link */}
+
+
+
Detail
+
-
setOrderToDuplicate(order)}
- className="flex-1 h-8 rounded-lg font-bold text-[10px] sm:text-[11px] gap-1 px-1 text-slate-600 hover:text-slate-900 hover:bg-slate-200/60 dark:text-slate-300 transition-all"
- title="Duplikasi PO ini"
- >
-
- Duplikat
-
+ {/* Status Action Button */}
+
+ {order.status === 'DRAFT' && (
+ handleStatusChange(order.id, 'OPEN')}
+ disabled={!isToday(new Date(order.date))}
+ className={cn(
+ 'h-10 w-full gap-1.5 rounded-xl px-2 text-xs font-bold shadow-sm transition-all',
+ isToday(new Date(order.date))
+ ? 'cursor-pointer bg-emerald-600 text-white hover:bg-emerald-700'
+ : 'cursor-not-allowed bg-slate-200 text-slate-500 opacity-50'
+ )}
+ title={
+ isToday(new Date(order.date))
+ ? 'Buka PO'
+ : 'Hanya PO hari ini yang bisa dibuka'
+ }
+ >
+
+ Buka
+
+ )}
+ {order.status === 'OPEN' && (
+ handleStatusChange(order.id, 'CLOSE')}
+ className="h-10 w-full cursor-pointer gap-1.5 rounded-xl bg-rose-600 px-2 text-xs font-bold text-white shadow-sm transition-all hover:bg-rose-700"
+ title="Tutup PO"
+ >
+
+ Tutup
+
+ )}
+ {order.status === 'CLOSE' && (
+ handleStatusChange(order.id, 'OPEN')}
+ disabled={!isToday(new Date(order.date))}
+ className={cn(
+ 'h-10 w-full gap-1.5 rounded-xl px-2 text-xs font-bold shadow-sm transition-all',
+ isToday(new Date(order.date))
+ ? 'cursor-pointer bg-emerald-600 text-white hover:bg-emerald-700'
+ : 'cursor-not-allowed bg-slate-200 text-slate-500 opacity-50'
+ )}
+ title={
+ isToday(new Date(order.date))
+ ? 'Buka Kembali PO'
+ : 'Hanya PO hari ini yang bisa dibuka'
+ }
+ >
+
+ Buka
+
+ )}
+
+
-
setOrderToDelete(order)}
- className={cn(
- "flex-1 h-8 rounded-lg font-bold text-[10px] sm:text-[11px] gap-1 px-1 transition-all",
- !canDelete
- ? "opacity-35 cursor-not-allowed text-slate-400"
- : "text-red-600 hover:text-red-700 hover:bg-red-50 dark:hover:bg-red-950/40"
- )}
- title={!canDelete ? "PO OPEN tidak dapat dihapus" : "Hapus PO"}
- >
-
- Hapus
-
-
- {order.status === 'OPEN' && (
-
+
- )}
+ size="sm"
+ disabled={isClosed || !isToday(new Date(order.date))}
+ onClick={() => setEditingOrder(order)}
+ className={cn(
+ 'h-8 flex-1 gap-1 rounded-lg px-1 text-[10px] font-bold transition-all sm:text-[11px]',
+ isClosed || !isToday(new Date(order.date))
+ ? 'cursor-not-allowed text-slate-400 opacity-35'
+ : 'text-slate-600 hover:bg-[#1B2CC1]/10 hover:text-[#1B2CC1] dark:text-slate-300'
+ )}
+ title={
+ isClosed
+ ? 'PO sudah CLOSE'
+ : !isToday(new Date(order.date))
+ ? 'Hanya PO hari ini yang bisa diedit'
+ : 'Edit PO'
+ }
+ >
+
+ Edit
+
+
+ setOrderToDuplicate(order)}
+ className="h-8 flex-1 gap-1 rounded-lg px-1 text-[10px] font-bold text-slate-600 transition-all hover:bg-slate-200/60 hover:text-slate-900 sm:text-[11px] dark:text-slate-300"
+ title="Duplikasi PO ini"
+ >
+
+ Duplikat
+
+
+ setOrderToDelete(order)}
+ className={cn(
+ 'h-8 flex-1 gap-1 rounded-lg px-1 text-[10px] font-bold transition-all sm:text-[11px]',
+ !canDelete
+ ? 'cursor-not-allowed text-slate-400 opacity-35'
+ : 'text-red-600 hover:bg-red-50 hover:text-red-700 dark:hover:bg-red-950/40'
+ )}
+ title={!canDelete ? 'PO OPEN tidak dapat dihapus' : 'Hapus PO'}
+ >
+
+ Hapus
+
+
+ {order.status === 'OPEN' && (
+
+ )}
+
-
- )
- })}
+ )
+ })}
-
+
{/* Pagination Controls */}
{totalPages > 1 && (
-
-
- Menampilkan {(currentPage - 1) * ITEMS_PER_PAGE + 1} hingga {Math.min(currentPage * ITEMS_PER_PAGE, filteredOrders.length)} dari {filteredOrders.length} PO
+
+
+ Menampilkan{' '}
+
+ {(currentPage - 1) * ITEMS_PER_PAGE + 1}
+ {' '}
+ hingga{' '}
+
+ {Math.min(currentPage * ITEMS_PER_PAGE, filteredOrders.length)}
+ {' '}
+ dari{' '}
+
+ {filteredOrders.length}
+ {' '}
+ PO
-
+
setCurrentPage(prev => Math.max(1, prev - 1))}
+ onClick={() => setCurrentPage((prev) => Math.max(1, prev - 1))}
disabled={currentPage === 1}
- className="h-8 w-8 p-0 rounded-lg"
+ className="h-8 w-8 rounded-lg p-0"
>
-
+
-
+
- {Array.from({ length: totalPages }, (_, i) => i + 1).map(page => (
+ {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
setCurrentPage(page)}
className={cn(
- "h-8 w-8 p-0 rounded-lg text-xs font-bold transition-all",
- currentPage === page
- ? "bg-[#1B2CC1] text-white hover:bg-[#15229E] hover:text-white"
- : "text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800"
+ 'h-8 w-8 rounded-lg p-0 text-xs font-bold transition-all',
+ currentPage === page
+ ? 'bg-[#1B2CC1] text-white hover:bg-[#15229E] hover:text-white'
+ : 'text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800'
)}
>
{page}
@@ -529,11 +607,11 @@ export default function MyOrdersPage() {
setCurrentPage(prev => Math.min(totalPages, prev + 1))}
+ onClick={() => setCurrentPage((prev) => Math.min(totalPages, prev + 1))}
disabled={currentPage === totalPages}
- className="h-8 w-8 p-0 rounded-lg"
+ className="h-8 w-8 rounded-lg p-0"
>
-
+
@@ -544,42 +622,59 @@ export default function MyOrdersPage() {
)
}
-function CreateOrderModal({ userId, onSuccess, initialData }: { userId: string, onSuccess: () => void, initialData?: any }) {
+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 [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
- ? initialData.available_items.map((ai: any) => ({ id: Math.random().toString(), name: ai.name }))
+ ? initialData.available_items.map((ai: any) => ({
+ id: Math.random().toString(),
+ name: ai.name,
+ }))
: [{ id: '1', name: '' }]
)
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const addItem = () => setItems([...items, { id: Math.random().toString(), name: '' }])
- const removeItem = (id: string) => setItems(items.filter((i: { id: string; name: string }) => i.id !== id))
- const updateItem = (id: string, name: string) => setItems(items.map((i: { id: string; name: string }) => i.id === id ? { ...i, name } : i))
+ const removeItem = (id: string) =>
+ setItems(items.filter((i: { id: string; name: string }) => i.id !== id))
+ const updateItem = (id: string, name: string) =>
+ setItems(items.map((i: { id: string; name: string }) => (i.id === id ? { ...i, name } : i)))
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError('')
-
+
if (!title.trim()) return setError('Judul PO wajib diisi.')
-
- const validItems = items.filter((i: { id: string; name: string }) => i.name.trim()).map((i: { id: string; name: string }) => i.name.trim())
+
+ const validItems = items
+ .filter((i: { id: string; name: string }) => i.name.trim())
+ .map((i: { id: string; name: string }) => i.name.trim())
if (!allowCustom && validItems.length === 0) {
- return setError('Wajib menambahkan minimal 1 menu pilihan jika tidak mengizinkan custom item.')
+ return setError(
+ 'Wajib menambahkan minimal 1 menu pilihan jika tidak mengizinkan custom item.'
+ )
}
setLoading(true)
-
+
const res = await createOrder({
creator_id: userId,
title: title.trim(),
description: description.trim(),
date: new Date(),
allow_custom: allowCustom,
- available_items: validItems
+ available_items: validItems,
})
if (res.success) {
@@ -591,57 +686,71 @@ function CreateOrderModal({ userId, onSuccess, initialData }: { userId: string,
}
return (
-
-
Form Buka PO
-
+
+ Form Buka PO
+
+
Buka Jasa Titip Baru
-
+
Tentukan nama pesanan dan daftar menu yang bisa dipesan teman kantor.
-