Merge consulting order details into single card

- Consolidate "Informasi Order" and "Detail Sesi Konsultasi" cards into one
- Remove duplicate "Order Info" card for consulting orders
- Display all information in single, cleaner merged card:
  - Session details (time, date, category, notes, meet link)
  - QR code for pending payments
  - Expired/cancelled order handling with rebooking
  - Status alerts (paid/cancelled/pending)
  - Total payment
- Keep separate cards for product orders unchanged

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
dwindown
2025-12-28 22:01:58 +07:00
parent ac88e17856
commit 52ec0b9b86

View File

@@ -333,7 +333,8 @@ export default function OrderDetail() {
</Badge>
</div>
{/* Order Info */}
{/* Order Info - Only show for product orders */}
{!isConsultingOrder && (
<Card className="border-2 border-border mb-6">
<CardHeader>
<CardTitle className="text-lg flex items-center gap-2">
@@ -442,42 +443,11 @@ export default function OrderDetail() {
<Alert className="mb-4 border-orange-200 bg-orange-50">
<AlertCircle className="h-4 w-4 text-orange-600" />
<AlertDescription className="text-orange-900">
{isConsultingOrder
? "Waktu pembayaran telah habis. Slot konsultasi telah dilepaskan. Silakan buat booking baru."
: "QR Code telah kadaluarsa. Anda dapat me-regenerate QR code untuk melanjutkan pembayaran."}
QR Code telah kadaluarsa. Anda dapat me-regenerate QR code untuk melanjutkan pembayaran.
</AlertDescription>
</Alert>
{isConsultingOrder ? (
// Consulting order - show booking button with pre-filled data
<div className="text-center space-y-4">
<p className="text-sm text-muted-foreground">
Order ini telah dibatalkan secara otomatis karena waktu pembayaran habis.
</p>
<Button
onClick={() => {
// Pass expired order data to prefill the booking form
const expiredData = {
fromExpiredOrder: true,
orderId: order.id,
topicCategory: consultingSlots[0]?.topic_category || '',
notes: consultingSlots[0]?.notes || ''
};
// Store in sessionStorage for the booking page to retrieve
sessionStorage.setItem('expiredConsultingOrder', JSON.stringify(expiredData));
navigate("/consulting");
}}
className="shadow-sm"
>
<CalendarIcon className="w-4 h-4 mr-2" />
Buat Booking Baru
</Button>
<p className="text-xs text-muted-foreground">
Kategori dan catatan akan terisi otomatis dari order sebelumnya
</p>
</div>
) : (
// Product order - show regenerate button
{/* Product order - show regenerate button */}
<div className="space-y-4">
<div className="text-center">
<p className="text-2xl font-bold">{formatIDR(order.total_amount)}</p>
@@ -513,7 +483,6 @@ export default function OrderDetail() {
Kembali ke Produk
</Button>
</div>
)}
</div>
)}
@@ -523,39 +492,11 @@ export default function OrderDetail() {
<Alert className="mb-4 border-red-200 bg-red-50">
<AlertCircle className="h-4 w-4 text-red-600" />
<AlertDescription className="text-red-900">
{isConsultingOrder
? "Order ini telah dibatalkan. Slot konsultasi telah dilepaskan."
: "Order ini telah dibatalkan."}
Order ini telah dibatalkan.
</AlertDescription>
</Alert>
{isConsultingOrder ? (
// Consulting order - show booking button with pre-filled data
<div className="text-center space-y-4">
<Button
onClick={() => {
// Pass expired order data to prefill the booking form
const expiredData = {
fromExpiredOrder: true,
orderId: order.id,
topicCategory: consultingSlots[0]?.topic_category || '',
notes: consultingSlots[0]?.notes || ''
};
// Store in sessionStorage for the booking page to retrieve
sessionStorage.setItem('expiredConsultingOrder', JSON.stringify(expiredData));
navigate("/consulting");
}}
className="shadow-sm"
>
<CalendarIcon className="w-4 h-4 mr-2" />
Buat Booking Baru
</Button>
<p className="text-xs text-muted-foreground">
Kategori dan catatan akan terisi otomatis dari order sebelumnya
</p>
</div>
) : (
// Product order - show back to products button
{/* Product order - show back to products button */}
<Button
onClick={() => navigate("/products")}
variant="outline"
@@ -564,7 +505,6 @@ export default function OrderDetail() {
<Package className="w-4 h-4 mr-2" />
Kembali ke Produk
</Button>
)}
</div>
)}
@@ -581,11 +521,11 @@ export default function OrderDetail() {
)}
</CardContent>
</Card>
)}
{/* Smart Item/Service Display */}
{consultingSlots.length > 0 ? (
// === Consulting Orders (NO order_items, has consulting_sessions) ===
<>
// === Consulting Orders - Single Merged Card ===
<Card className="border-2 border-primary bg-primary/5 mb-6">
<CardHeader>
<CardTitle className="text-lg flex items-center gap-2">
@@ -594,7 +534,7 @@ export default function OrderDetail() {
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{/* Summary Card */}
{/* Session Information */}
<div className="bg-background p-4 rounded-lg border-2 border-border">
<div className="grid grid-cols-1 gap-4 text-sm">
<div>
@@ -663,6 +603,159 @@ export default function OrderDetail() {
</div>
</div>
{/* QR Code Display for pending QRIS payments */}
{order.payment_status === "pending" && order.payment_method === "qris" && !isQrExpired && (
<div className="pt-4">
{order.qr_string ? (
<>
<Alert className="mb-4">
<Clock className="h-4 w-4" />
<AlertDescription>
Scan QR code ini dengan aplikasi e-wallet atau mobile banking Anda
{timeRemaining && (
<span className="ml-2 font-medium">
(Kadaluarsa dalam {timeRemaining})
</span>
)}
</AlertDescription>
</Alert>
<div className="bg-white p-6 rounded-lg border-2 border-border flex flex-col items-center justify-center space-y-4">
<div className="bg-white p-2 rounded">
<QRCodeSVG value={order.qr_string} size={200} />
</div>
<div className="text-center space-y-2">
<p className="text-2xl font-bold">{formatIDR(order.total_amount)}</p>
<p className="text-sm text-muted-foreground">
Order ID: {order.id.slice(0, 8)}
</p>
</div>
{isPolling && (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<RefreshCw className="w-4 h-4 animate-spin" />
Menunggu pembayaran...
</div>
)}
<div className="flex items-center justify-center gap-4 text-xs text-muted-foreground">
<span>🔒 Pembayaran Aman</span>
<span> QRIS Standar Bank Indonesia</span>
</div>
{order.payment_url && (
<Button asChild variant="outline" className="w-full">
<a href={order.payment_url} target="_blank" rel="noopener noreferrer">
<CreditCard className="w-4 h-4 mr-2" />
Bayar di Halaman Pembayaran
</a>
</Button>
)}
</div>
</>
) : (
<Alert className="border-orange-200 bg-orange-50">
<Clock className="h-4 w-4 text-orange-600" />
<AlertDescription className="text-orange-900">
Sedang memproses QR code...
{order.payment_url && (
<Button asChild className="mt-2" variant="outline" size="sm">
<a href={order.payment_url} target="_blank" rel="noopener noreferrer">
<CreditCard className="w-4 h-4 mr-2" />
Bayar di Halaman Pembayaran
</a>
</Button>
)}
</AlertDescription>
</Alert>
)}
</div>
)}
{/* Expired QR Handling for Consulting */}
{order.payment_status === "pending" && order.payment_method === "qris" && isQrExpired && (
<div className="pt-4">
<Alert className="mb-4 border-orange-200 bg-orange-50">
<AlertCircle className="h-4 w-4 text-orange-600" />
<AlertDescription className="text-orange-900">
Waktu pembayaran telah habis. Slot konsultasi telah dilepaskan. Silakan buat booking baru.
</AlertDescription>
</Alert>
<div className="text-center space-y-4">
<p className="text-sm text-muted-foreground">
Order ini telah dibatalkan secara otomatis karena waktu pembayaran habis.
</p>
<Button
onClick={() => {
const expiredData = {
fromExpiredOrder: true,
orderId: order.id,
topicCategory: consultingSlots[0]?.topic_category || '',
notes: consultingSlots[0]?.notes || ''
};
sessionStorage.setItem('expiredConsultingOrder', JSON.stringify(expiredData));
navigate("/consulting");
}}
className="shadow-sm"
>
<CalendarIcon className="w-4 h-4 mr-2" />
Buat Booking Baru
</Button>
<p className="text-xs text-muted-foreground">
Kategori dan catatan akan terisi otomatis dari order sebelumnya
</p>
</div>
</div>
)}
{/* Cancelled Order Handling for Consulting */}
{order.status === "cancelled" && (
<div className="pt-4">
<Alert className="mb-4 border-red-200 bg-red-50">
<AlertCircle className="h-4 w-4 text-red-600" />
<AlertDescription className="text-red-900">
Order ini telah dibatalkan. Slot konsultasi telah dilepaskan.
</AlertDescription>
</Alert>
<div className="text-center space-y-4">
<Button
onClick={() => {
const expiredData = {
fromExpiredOrder: true,
orderId: order.id,
topicCategory: consultingSlots[0]?.topic_category || '',
notes: consultingSlots[0]?.notes || ''
};
sessionStorage.setItem('expiredConsultingOrder', JSON.stringify(expiredData));
navigate("/consulting");
}}
className="shadow-sm"
>
<CalendarIcon className="w-4 h-4 mr-2" />
Buat Booking Baru
</Button>
<p className="text-xs text-muted-foreground">
Kategori dan catatan akan terisi otomatis dari order sebelumnya
</p>
</div>
</div>
)}
{/* Fallback button for pending payments without QR */}
{order.payment_status === "pending" && !order.qr_string && order.payment_url && (
<div className="pt-4">
<Button asChild className="w-full shadow-sm">
<a href={order.payment_url} target="_blank" rel="noopener noreferrer">
<CreditCard className="w-4 h-4 mr-2" />
Lanjutkan Pembayaran
</a>
</Button>
</div>
)}
{/* Status Alert */}
{order.payment_status === "paid" ? (
<Alert className="bg-green-50 border-green-200">
@@ -671,21 +764,14 @@ export default function OrderDetail() {
Pembayaran berhasil! Silakan bergabung sesuai jadwal.
</AlertDescription>
</Alert>
) : order.status === "cancelled" ? (
<Alert className="bg-red-50 border-red-200">
<AlertCircle className="h-4 w-4" />
<AlertDescription>
Order telah dibatalkan. Silakan buat booking baru jika masih tertarik.
</AlertDescription>
</Alert>
) : (
) : order.status !== "cancelled" && order.payment_status === "pending" && !isQrExpired ? (
<Alert className="bg-yellow-50 border-yellow-200">
<Clock className="h-4 w-4" />
<AlertDescription>
Selesaikan pembayaran untuk mengkonfirmasi jadwal sesi konsultasi.
</AlertDescription>
</Alert>
)}
) : null}
{/* Total */}
<div className="flex items-center justify-between text-lg font-bold pt-4 border-t">
@@ -694,7 +780,6 @@ export default function OrderDetail() {
</div>
</CardContent>
</Card>
</>
) : order.order_items.length > 0 ? (
// === Product Orders (has order_items) ===
<Card className="border-2 border-border mb-6">