change more files
This commit is contained in:
parent
a9268b8a94
commit
7e9ab5d68f
11 changed files with 268 additions and 67 deletions
10
.idea/.gitignore
generated
vendored
Normal file
10
.idea/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Ignored default folder with query files
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/winofy-front.iml" filepath="$PROJECT_DIR$/.idea/winofy-front.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/winofy-front.iml
generated
Normal file
8
.idea/winofy-front.iml
generated
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
|
|
@ -1,22 +1,29 @@
|
|||
import OrderItems from '@/components/orders/order-items'
|
||||
import OrderPaymentSticky from '@/components/orders/order-payment-sticky'
|
||||
import OrderStepperStatus from '@/components/orders/order-stepper-status'
|
||||
import OrderTimeAddressInfoAlert from '@/components/orders/order-time-address-info-alert'
|
||||
import React from 'react'
|
||||
import { requireSession } from "@/lib/auth/session";
|
||||
import { getOrderGroup } from "@/lib/api/orders";
|
||||
import { OrderDetailProvider } from "@/lib/providers/order-detail-provider";
|
||||
import OrderDetailContent from "@/components/orders/order-detail-content";
|
||||
|
||||
const OrderDetail = () => {
|
||||
return <section className='relative'>
|
||||
|
||||
<h2 className="w-full p-3 px-4 bg-primary-action text-primary-foreground text-md font-medium">
|
||||
در حال آمادهسازی
|
||||
</h2>
|
||||
<OrderStepperStatus currentStep={2} />
|
||||
<OrderTimeAddressInfoAlert />
|
||||
<OrderItems />
|
||||
<OrderPaymentSticky />
|
||||
|
||||
|
||||
</section>
|
||||
interface Props {
|
||||
params: Promise<{ orderId: string }>;
|
||||
}
|
||||
|
||||
export default OrderDetail
|
||||
export default async function OrderDetailPage({ params }: Props) {
|
||||
await requireSession("/login");
|
||||
|
||||
const { orderId } = await params;
|
||||
|
||||
let orderGroup = null;
|
||||
let error: string | null = null;
|
||||
|
||||
try {
|
||||
orderGroup = await getOrderGroup(orderId);
|
||||
} catch (e: any) {
|
||||
error = e?.message ?? "خطا در دریافت اطلاعات سفارش";
|
||||
}
|
||||
|
||||
return (
|
||||
<OrderDetailProvider order={orderGroup} error={error}>
|
||||
<OrderDetailContent />
|
||||
</OrderDetailProvider>
|
||||
);
|
||||
}
|
||||
63
src/components/orders/order-detail-content.tsx
Normal file
63
src/components/orders/order-detail-content.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
"use client";
|
||||
|
||||
import { useOrderDetail } from "@/lib/providers/order-detail-provider";
|
||||
import OrderStepperStatus from "./order-stepper-status";
|
||||
import OrderTimeAddressInfoAlert from "./order-time-address-info-alert";
|
||||
import OrderItems from "./order-items";
|
||||
import OrderPaymentSticky from "./order-payment-sticky";
|
||||
|
||||
const STATUS_LABELS: Record<string, string> = {
|
||||
placed: "ثبت شد",
|
||||
preparing: "در حال آمادهسازی",
|
||||
ready_to_ship: "آماده ارسال",
|
||||
handed_to_courier: "ارسال شد",
|
||||
delivered: "تحویل داده شد",
|
||||
cancelled: "لغو شد",
|
||||
};
|
||||
|
||||
export default function OrderDetailContent() {
|
||||
const { data: orderGroup, error, isLoading } = useOrderDetail();
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="p-8 text-center text-gray-500">در حال بارگذاری...</div>;
|
||||
}
|
||||
|
||||
if (error || !orderGroup) {
|
||||
return (
|
||||
<div className="p-8 text-center text-destructive">
|
||||
{error ?? "سفارش یافت نشد"}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// وضعیت اصلی رو از اولین سفارش میگیریم (یا میتونی منطق پیچیدهتری بذاری)
|
||||
const mainStatus = orderGroup.orders[0]?.status ?? "placed";
|
||||
|
||||
const statusToStep: Record<string, number> = {
|
||||
placed: 0,
|
||||
preparing: 2,
|
||||
ready_to_ship: 3,
|
||||
handed_to_courier: 3,
|
||||
delivered: 4,
|
||||
cancelled: -1,
|
||||
};
|
||||
|
||||
const currentStep = statusToStep[mainStatus] ?? 0;
|
||||
const statusLabel = STATUS_LABELS[mainStatus] ?? mainStatus;
|
||||
|
||||
return (
|
||||
<section className="relative pb-32">
|
||||
<h2 className="w-full p-3 px-4 bg-primary-action text-primary-foreground text-md font-medium">
|
||||
{statusLabel}
|
||||
</h2>
|
||||
|
||||
{mainStatus !== "cancelled" && (
|
||||
<OrderStepperStatus currentStep={currentStep} />
|
||||
)}
|
||||
|
||||
<OrderTimeAddressInfoAlert order={orderGroup} />
|
||||
<OrderItems order={orderGroup} />
|
||||
<OrderPaymentSticky order={orderGroup} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,20 +1,45 @@
|
|||
import React from 'react'
|
||||
"use client";
|
||||
|
||||
const OrderItems = () => {
|
||||
return (
|
||||
<div className="mt-5 grid grid-cols-1 gap-2 mx-8">
|
||||
<h1 className="text-lg font-bold mb-3">اقلام سفارش</h1>
|
||||
import type { OrderGroup } from "@/types/api";
|
||||
import {formatTomanPrice} from "@/lib/utils/priceUtils";
|
||||
|
||||
{/* order items */}
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div className="flex gap-3">
|
||||
<span className="font-semibold text-sm">نان سنگک</span>
|
||||
<span className="text-xs font-medium text-gray-700">10 عدد</span>
|
||||
</div>
|
||||
<span className="text-sm font-bold">۱۲،۰۰۰ تومان</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
interface Props {
|
||||
order: OrderGroup;
|
||||
}
|
||||
|
||||
export default OrderItems
|
||||
const OrderItems = ({ order }: Props) => {
|
||||
// همه آیتمهای همه فروشگاهها رو جمع میکنیم
|
||||
const allItems = order.orders.flatMap((o) =>
|
||||
o.items.map((item) => ({
|
||||
...item,
|
||||
storeName: o.store.name,
|
||||
}))
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mt-5 grid grid-cols-1 gap-3 mx-8">
|
||||
<h1 className="text-lg font-bold mb-3">اقلام سفارش</h1>
|
||||
|
||||
{allItems.map((item) => (
|
||||
<div key={item.uuid} className="flex items-center justify-between w-full">
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<div className="flex gap-3 items-center">
|
||||
<span className="font-semibold text-sm">{item.product_name_snapshot}</span>
|
||||
<span className="text-xs font-medium text-gray-700">
|
||||
{item.quantity.toLocaleString("fa-IR")} عدد
|
||||
</span>
|
||||
</div>
|
||||
{order.orders.length > 1 && (
|
||||
<span className="text-xs text-gray-500">{item.storeName}</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-sm font-bold">
|
||||
{formatTomanPrice(item.line_total)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrderItems;
|
||||
|
|
@ -1,26 +1,40 @@
|
|||
const OrderPaymentSticky = () => {
|
||||
"use client";
|
||||
|
||||
import type { OrderGroup } from "@/types/api";
|
||||
import {formatTomanPrice} from "@/lib/utils/priceUtils";
|
||||
|
||||
interface Props {
|
||||
order: OrderGroup;
|
||||
}
|
||||
|
||||
const OrderPaymentSticky = ({ order }: Props) => {
|
||||
const paymentMethodLabel: Record<string, string> = {
|
||||
wallet: "پرداخت از کیف پول",
|
||||
online: "پرداخت آنلاین",
|
||||
cash_on_delivery: "پرداخت در محل",
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-0 left-0 right-0 z-50 bg-background border-t border-gray-100 w-full">
|
||||
<div className="px-5 pt-5 pb-3 max-w-sm mx-auto w-full">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex flex-col items-start">
|
||||
<span className="text-lg font-medium text-foreground">
|
||||
مبلغ پرداختی
|
||||
</span>
|
||||
<span className="text-lg font-medium text-foreground">مبلغ پرداختی</span>
|
||||
<span className="text-xs font-normal text-gray-700">
|
||||
پرداخت از کیف پول
|
||||
</span>
|
||||
{paymentMethodLabel[order.payment_method] ?? order.payment_method}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-lg font-medium text-foreground">
|
||||
1000000 تومان
|
||||
</span>
|
||||
{formatTomanPrice(order.total_amount)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* فعلاً فقط نمایش، بعداً میتونی دکمه لغو اضافه کنی */}
|
||||
<button
|
||||
type="button"
|
||||
className="w-full h-12 rounded-xl border border-error bg-background text-error text-[15px] font-medium active:scale-[0.98] transition-transform cursor-pointer"
|
||||
>
|
||||
پرداخت از کیف پول
|
||||
لغو سفارش
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -28,7 +42,7 @@ const OrderPaymentSticky = () => {
|
|||
<div className="w-[134px] h-[5px] rounded-full bg-gray-300" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default OrderPaymentSticky
|
||||
export default OrderPaymentSticky;
|
||||
|
|
@ -1,23 +1,35 @@
|
|||
import { ClockIcon, MapPinIcon } from 'lucide-react'
|
||||
import React from 'react'
|
||||
"use client";
|
||||
|
||||
const OrderTimeAddressInfoAlert = () => {
|
||||
return (
|
||||
<div className="flex flex-col gap-4 items-start p-4 bg-teal-bg rounded-2xl my-4 mx-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<ClockIcon className="size-5 text-primary-action" />
|
||||
<p className="text-md font-bold text-primary-action">
|
||||
زمان تخمینی تحویل: ۳۰–۴۵ دقیقه دیگر
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<MapPinIcon className="size-5 text-primary-action" />
|
||||
<p className="text-sm text-gray-700">
|
||||
نارمک، خیابان شهید دستغیب، پلاک ۱۲، واحد ۳
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
import { ClockIcon, MapPinIcon } from "lucide-react";
|
||||
import type { OrderGroup } from "@/types/api";
|
||||
|
||||
interface Props {
|
||||
order: OrderGroup;
|
||||
}
|
||||
|
||||
export default OrderTimeAddressInfoAlert
|
||||
const OrderTimeAddressInfoAlert = ({ order }: Props) => {
|
||||
// زمان تخمینی (فعلاً ساده)
|
||||
const estimatedTime =
|
||||
order.delivery_type === "express"
|
||||
? "۳۰–۴۵ دقیقه دیگر"
|
||||
: order.scheduled_at
|
||||
? new Date(order.scheduled_at).toLocaleString("fa-IR")
|
||||
: "زمان مشخص نشده";
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 items-start p-4 bg-teal-bg rounded-2xl my-4 mx-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<ClockIcon className="size-5 text-primary-action" />
|
||||
<p className="text-md font-bold text-primary-action">
|
||||
زمان تخمینی تحویل: {estimatedTime}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<MapPinIcon className="size-5 text-primary-action" />
|
||||
<p className="text-sm text-gray-700">{order.full_address}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrderTimeAddressInfoAlert;
|
||||
42
src/lib/providers/order-detail-provider.tsx
Normal file
42
src/lib/providers/order-detail-provider.tsx
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
"use client";
|
||||
|
||||
import {createContext, useContext, type ReactNode} from "react";
|
||||
import type {Order, OrderGroup} from "@/types/api";
|
||||
|
||||
type OrderDetailContextValue = {
|
||||
data: OrderGroup | null;
|
||||
error: string | null;
|
||||
isLoading: boolean;
|
||||
};
|
||||
|
||||
const OrderDetailContext = createContext<OrderDetailContextValue | undefined>(undefined);
|
||||
|
||||
export function OrderDetailProvider({
|
||||
children,
|
||||
order,
|
||||
error = null,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
order: OrderGroup | null;
|
||||
error?: string | null;
|
||||
}) {
|
||||
return (
|
||||
<OrderDetailContext.Provider
|
||||
value={{
|
||||
data: order,
|
||||
error,
|
||||
isLoading: false,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</OrderDetailContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useOrderDetail() {
|
||||
const ctx = useContext(OrderDetailContext);
|
||||
if (ctx === undefined) {
|
||||
throw new Error("useOrderDetail must be used within OrderDetailProvider");
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue