change more files

This commit is contained in:
Mohammadali 2026-09-12 16:32:41 +03:30
parent a9268b8a94
commit 7e9ab5d68f
11 changed files with 268 additions and 67 deletions

10
.idea/.gitignore generated vendored Normal file
View 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

View 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
View 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
View 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
View 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>

View file

@ -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>
);
}

View 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>
);
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View 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;
}