import { useState, useEffect } from "react"; import { Save, Truck, Check } from "lucide-react"; import HeaderWithSupport from "../components/HeaderWithSupport"; import { Button } from "../components/ui/button"; import AppInput from "../components/AppInput"; import { Card } from "../components/ui/card"; import UiProvider from "../components/UiProvider"; import { useServiceGetCouriers, useGetAllCouriers, useCreateSellerCourier, useDeleteSellerCourier, useUpdateSellerCourier, } from "../requestHandler/use-courier-hooks"; import { SellerCourierCreate, SellerCourierUpdate } from "../../src/api/types"; import { useToast } from "../hooks/use-toast"; import { useParams, useNavigate } from "@remix-run/react"; import { useRequireOwner } from "../hooks/useStoreOwnership"; import { MetaFunction } from "@remix-run/node"; import { getPersianTextOfPrice, safeGoBack } from "~/utils/helpers"; interface CourierFormData { courierId: string; courierName: string; deliveryPrice: number | null; estimatedDays: number | null; isEnabled: boolean; isConfigured: boolean; sellerCourierId?: string; // ID of the seller courier configuration isFaceToFace: boolean; isFreightCollect: boolean; } export default function ShippingMethod() { const { storeId } = useParams(); const { toast } = useToast(); const navigate = useNavigate(); const [savingCourierId, setSavingCourierId] = useState(null); // Shipping/courier config is owner-only — redirect staff back to the dashboard. const { isLoading: isOwnershipLoading } = useRequireOwner(storeId); // Fetch all available couriers const { data: allCouriers, isLoading: isAllCouriersLoading, isError: isAllCouriersError, refetch: refetchAllCouriers, } = useGetAllCouriers(); // Fetch seller's configured couriers const { data: sellerCouriers, isLoading: isSellerCouriersLoading, isError: isSellerCouriersError, refetch: refetchSellerCouriers, } = useServiceGetCouriers(storeId || ""); const createSellerCourier = useCreateSellerCourier(); const deleteSellerCourier = useDeleteSellerCourier(); const updateSellerCourier = useUpdateSellerCourier(); // Combine all couriers with seller configurations const [courierData, setCourierData] = useState([]); const [initialCourierData, setInitialCourierData] = useState< CourierFormData[] >([]); // Initialize courier data when both APIs return data useEffect(() => { if (allCouriers && sellerCouriers) { const combined = allCouriers.map((courier) => { const sellerConfig = sellerCouriers.find( (sc) => sc.courier === courier.id ); return { courierId: courier.id || "", courierName: courier.name, deliveryPrice: sellerConfig?.deliveryPrice || null, estimatedDays: sellerConfig?.estimatedDays || null, isEnabled: sellerConfig?.isEnabled || false, isConfigured: !!sellerConfig, sellerCourierId: sellerConfig?.id, isFaceToFace: courier.isFaceToFace || false, isFreightCollect: courier.isFreightCollect || false, }; }); setCourierData(combined); setInitialCourierData(combined); } }, [allCouriers, sellerCouriers]); const handleSaveCourier = async (courier: CourierFormData) => { setSavingCourierId(courier.courierId); const initialCourier = initialCourierData.find( (c) => c.courierId === courier.courierId ); // Check if this is the first time activating (not configured before) const isFirstTimeActivation = !initialCourier?.isConfigured && courier.isEnabled; if (isFirstTimeActivation) { // Use API for first time activation try { const data: SellerCourierCreate = { courierId: courier.courierId, // For face-to-face, set default values (0) deliveryPrice: courier.isFaceToFace || courier.isFreightCollect ? 0 : courier.deliveryPrice, estimatedDays: courier.isFaceToFace ? 0 : courier.estimatedDays, isEnabled: courier.isEnabled, }; const result = await createSellerCourier.mutateAsync(data); toast({ title: "موفقیت", description: "روش پرداخت با موفقیت فعال شد", }); // Update local state setCourierData((prev) => prev.map((c) => c.courierId === courier.courierId ? { ...c, isConfigured: true, sellerCourierId: result.id } : c ) ); // Update initial data to reflect new saved state setInitialCourierData((prev) => prev.map((c) => c.courierId === courier.courierId ? { ...courier, isConfigured: true, sellerCourierId: result.id } : c ) ); } catch (error) { toast({ title: "خطا", description: "خطا در فعال کردن روش پرداخت", variant: "destructive", }); console.error("Error creating courier:", error); } finally { setSavingCourierId(null); } return; } // For editing or deactivating, use appropriate APIs if (!courier.isEnabled && courier.isConfigured && courier.sellerCourierId) { // Use delete API for deactivating try { await deleteSellerCourier.mutateAsync(courier.sellerCourierId); toast({ title: "موفقیت", description: "روش پرداخت غیرفعال شد", }); // Update local state setCourierData((prev) => prev.map((c) => c.courierId === courier.courierId ? { ...c, isEnabled: false, isConfigured: false, sellerCourierId: undefined, } : c ) ); // Update initial data to reflect new saved state setInitialCourierData((prev) => prev.map((c) => c.courierId === courier.courierId ? { ...c, isEnabled: false, isConfigured: false, sellerCourierId: undefined, } : c ) ); } catch (error) { toast({ title: "خطا", description: "خطا در غیرفعال کردن روش پرداخت", variant: "destructive", }); console.error("Error deactivating courier:", error); } finally { setSavingCourierId(null); } return; } else if (courier.isConfigured && courier.sellerCourierId) { // Use update API for editing existing configuration try { const updateData: SellerCourierUpdate = { // For face-to-face, set default values (0) deliveryPrice: courier.isFaceToFace || courier.isFreightCollect ? 0 : courier.deliveryPrice, estimatedDays: courier.isFaceToFace ? 0 : courier.estimatedDays, isEnabled: courier.isEnabled, }; await updateSellerCourier.mutateAsync({ id: courier.sellerCourierId, data: updateData, }); toast({ title: "موفقیت", description: "تنظیمات ارسال ویرایش شد", }); // Update local state setCourierData((prev) => prev.map((c) => c.courierId === courier.courierId ? { ...courier } : c ) ); // Update initial data to reflect new saved state setInitialCourierData((prev) => prev.map((c) => c.courierId === courier.courierId ? { ...courier } : c ) ); } catch (error) { toast({ title: "خطا", description: "خطا در ویرایش تنظیمات ارسال", variant: "destructive", }); console.error("Error updating courier:", error); } finally { setSavingCourierId(null); } return; } setSavingCourierId(null); }; const updateCourier = ( courierId: string, updates: Partial ) => { setCourierData((prev) => prev.map((c) => (c.courierId === courierId ? { ...c, ...updates } : c)) ); }; const isSaveDisabled = (courier: CourierFormData) => { const initialCourier = initialCourierData.find( (c) => c.courierId === courier.courierId ); if (!initialCourier) return true; // Check if enabled state has changed const enabledStateChanged = courier.isEnabled !== initialCourier.isEnabled; // For face-to-face couriers, only check if enabled state changed if (courier.isFaceToFace) { return !enabledStateChanged; } // For non-face-to-face couriers, check price and delivery days const priceChanged = courier.deliveryPrice !== initialCourier.deliveryPrice; const daysChanged = courier.estimatedDays !== initialCourier.estimatedDays; const dataChanged = priceChanged || daysChanged; // No changes at all if (!enabledStateChanged && !dataChanged) return true; // If being activated or is active, check if estimatedDays is valid (must be > 0) if ( courier.isEnabled && (!courier.estimatedDays || courier.estimatedDays <= 0) ) { return true; } return false; }; const isLoading = isAllCouriersLoading || isSellerCouriersLoading; const isError = isAllCouriersError || isSellerCouriersError; const handleBack = () => { safeGoBack(navigate); }; // Show loading while checking ownership if (isOwnershipLoading) { return (
); } return (
{ refetchAllCouriers(); refetchSellerCouriers(); }} type="seller" >

روش های ارسال مورد نظر خود را انتخاب کنید.

می‌توانید چند گزینه را همزمان فعال کنید.

راهنمای تنظیم ارسال

برای هر روش ارسال، قیمت و ماکسیمم مدت زمان تحویل تخمینی را تعیین کنید. مشتریان در هنگام خرید، این گزینه‌ها را خواهند دید.

{courierData.map((courier) => ( updateCourier(courier.courierId, updates) } onSave={() => handleSaveCourier(courier)} isSaving={savingCourierId === courier.courierId} isDisabled={isSaveDisabled(courier)} /> ))}
); } interface CourierCardProps { courier: CourierFormData; onUpdate: (updates: Partial) => void; onSave: () => void; isSaving: boolean; isDisabled: boolean; } function CourierCard({ courier, onUpdate, onSave, isSaving, isDisabled, }: CourierCardProps) { return (

{courier.courierName}

{courier.isConfigured && (
( فعال)
)}
{courier.isEnabled && !courier.isFaceToFace && (
) => onUpdate({ deliveryPrice: e.target.value ? Number(e.target.value) : null, }) } className="h-[30px] rounded-[8px]" /> {courier.isFreightCollect && (

این روش «پس‌پرداخت» است و قیمت را نمی‌توان تعیین کرد.

)}
) => { const value = e.target.value; const numValue = Number(value); // Only allow positive numbers (> 0) for non-face-to-face if (value === "") { onUpdate({ estimatedDays: null }); } else if (numValue > 0) { onUpdate({ estimatedDays: numValue }); } }} className="h-[30px] rounded-[8px]" />
)} {!courier.isFaceToFace && (
شما برای این روش مبلغ{" "} {courier.isFreightCollect ? "پس پرداخت" : getPersianTextOfPrice(courier.deliveryPrice?.toString() || "0")} {" "} و ماکسیمم مدت زمان تحویل{" "} {courier.estimatedDays} {" "} روز را تعیین کرده اید.
)}
); } export const meta: MetaFunction = () => { return [ { title: "نحوه ارسال - ویترون" }, { name: "description", content: "مدیریت و مشاهده تمام نحوه ارسال در فروشگاه شما. فیلتر بر اساس وضعیت و جستجو در نحوه ارسال.", }, { name: "keywords", content: "نحوه ارسال، مدیریت نحوه ارسال، فروشگاه، مشتری، ویترون", }, { name: "robots", content: "noindex, follow" }, { property: "og:title", content: "نحوه ارسال - ویترون" }, { property: "og:description", content: "مدیریت و مشاهده تمام نحوه ارسال در فروشگاه شما. فیلتر بر اساس وضعیت و جستجو در نحوه ارسال.", }, { property: "og:type", content: "website" }, { name: "twitter:card", content: "summary" }, { name: "twitter:title", content: "نحوه ارسال - ویترون" }, { name: "twitter:description", content: "مدیریت و مشاهده تمام نحوه ارسال در فروشگاه شما. فیلتر بر اساس وضعیت و جستجو در نحوه ارسال.", }, ]; };