diff --git a/src/features/auth/ui/login.tsx b/src/features/auth/ui/login.tsx index 3e1ae5e..502f3ec 100644 --- a/src/features/auth/ui/login.tsx +++ b/src/features/auth/ui/login.tsx @@ -69,7 +69,29 @@ export default function LoginForm() { }, onError: (error: AxiosError) => { const data = error.response?.data as { message?: string }; - toast.error(data?.message || "Xatolik yuz berdi"); + const errorData = error.response?.data as { + messages?: { + token_class: string; + token_type: string; + message: string; + }[]; + }; + const errorName = error.response?.data as { + data?: { + name: string[]; + }; + }; + + const message = + Array.isArray(errorName.data?.name) && errorName.data.name.length + ? errorName.data.name[0] + : data?.message || + (Array.isArray(errorData?.messages) && errorData.messages.length + ? errorData.messages[0].message + : undefined) || + "Xatolik yuz berdi"; + + toast.error(message); }, }); diff --git a/src/features/district/ui/district.tsx b/src/features/district/ui/district.tsx index 00b0011..4bc97fb 100644 --- a/src/features/district/ui/district.tsx +++ b/src/features/district/ui/district.tsx @@ -95,12 +95,16 @@ export default function District() { }; }; - toast.error( - errorName.data?.name[0] || - data.message || - errorData?.messages?.[0].message || - "Xatolik yuz berdi", - ); + const message = + Array.isArray(errorName.data?.name) && errorName.data.name.length + ? errorName.data.name[0] + : data?.message || + (Array.isArray(errorData?.messages) && errorData.messages.length + ? errorData.messages[0].message + : undefined) || + "Xatolik yuz berdi"; + + toast.error(message); }, }); @@ -128,12 +132,16 @@ export default function District() { }; }; - toast.error( - errorName.data?.name[0] || - data.message || - errorData?.messages?.[0].message || - "Xatolik yuz berdi", - ); + const message = + Array.isArray(errorName.data?.name) && errorName.data.name.length + ? errorName.data.name[0] + : data?.message || + (Array.isArray(errorData?.messages) && errorData.messages.length + ? errorData.messages[0].message + : undefined) || + "Xatolik yuz berdi"; + + toast.error(message); }, }); @@ -160,12 +168,16 @@ export default function District() { }; }; - toast.error( - errorName.data?.name[0] || - data.message || - errorData?.messages?.[0].message || - "Xatolik yuz berdi", - ); + const message = + Array.isArray(errorName.data?.name) && errorName.data.name.length + ? errorName.data.name[0] + : data?.message || + (Array.isArray(errorData?.messages) && errorData.messages.length + ? errorData.messages[0].message + : undefined) || + "Xatolik yuz berdi"; + + toast.error(message); }, }); diff --git a/src/features/doctor/ui/CreateDoctor.tsx b/src/features/doctor/ui/CreateDoctor.tsx index 4839987..8de7792 100644 --- a/src/features/doctor/ui/CreateDoctor.tsx +++ b/src/features/doctor/ui/CreateDoctor.tsx @@ -119,12 +119,16 @@ const CreateDoctor = () => { }; }; - toast.error( - errorName.data?.name[0] || - data.message || - errorData?.messages?.[0].message || - "Xatolik yuz berdi", - ); + const message = + Array.isArray(errorName.data?.name) && errorName.data.name.length + ? errorName.data.name[0] + : data?.message || + (Array.isArray(errorData?.messages) && errorData.messages.length + ? errorData.messages[0].message + : undefined) || + "Xatolik yuz berdi"; + + toast.error(message); }, }); @@ -150,12 +154,16 @@ const CreateDoctor = () => { }; }; - toast.error( - errorName.data?.name[0] || - data.message || - errorData?.messages?.[0].message || - "Xatolik yuz berdi", - ); + const message = + Array.isArray(errorName.data?.name) && errorName.data.name.length + ? errorName.data.name[0] + : data?.message || + (Array.isArray(errorData?.messages) && errorData.messages.length + ? errorData.messages[0].message + : undefined) || + "Xatolik yuz berdi"; + + toast.error(message); }, }); diff --git a/src/features/doctor/ui/Doctor.tsx b/src/features/doctor/ui/Doctor.tsx index ccbbc7c..b33360f 100644 --- a/src/features/doctor/ui/Doctor.tsx +++ b/src/features/doctor/ui/Doctor.tsx @@ -55,12 +55,16 @@ const Doctor = () => { }; }; - toast.error( - errorName.data?.name[0] || - data.message || - errorData?.messages?.[0].message || - "Xatolik yuz berdi", - ); + const message = + Array.isArray(errorName.data?.name) && errorName.data.name.length + ? errorName.data.name[0] + : data?.message || + (Array.isArray(errorData?.messages) && errorData.messages.length + ? errorData.messages[0].message + : undefined) || + "Xatolik yuz berdi"; + + toast.error(message); }, }); diff --git a/src/features/home/ui/Home.tsx b/src/features/home/ui/Home.tsx index 1f6da5f..bd5444e 100644 --- a/src/features/home/ui/Home.tsx +++ b/src/features/home/ui/Home.tsx @@ -78,12 +78,16 @@ export default function Home() { }; }; - toast.error( - errorName.data?.name[0] || - data.message || - errorData?.messages?.[0].message || - "Xatolik yuz berdi", - ); + const message = + Array.isArray(errorName.data?.name) && errorName.data.name.length + ? errorName.data.name[0] + : data?.message || + (Array.isArray(errorData?.messages) && errorData.messages.length + ? errorData.messages[0].message + : undefined) || + "Xatolik yuz berdi"; + + toast.error(message); }, }); diff --git a/src/features/location/ui/MyLocation.tsx b/src/features/location/ui/MyLocation.tsx index e6e86e1..120418f 100644 --- a/src/features/location/ui/MyLocation.tsx +++ b/src/features/location/ui/MyLocation.tsx @@ -68,13 +68,16 @@ const MyLocation: React.FC = () => { }; }; - toast.error( - errorName.data?.name?.[0] || - data.message || - errorData?.messages?.[0]?.message || - "Xatolik yuz berdi", - ); - setLoadingButtonId(null); + const message = + Array.isArray(errorName.data?.name) && errorName.data.name.length + ? errorName.data.name[0] + : data?.message || + (Array.isArray(errorData?.messages) && errorData.messages.length + ? errorData.messages[0].message + : undefined) || + "Xatolik yuz berdi"; + + toast.error(message); }, }); diff --git a/src/features/object/ui/CreateObject.tsx b/src/features/object/ui/CreateObject.tsx index 5ec47d8..823ad0d 100644 --- a/src/features/object/ui/CreateObject.tsx +++ b/src/features/object/ui/CreateObject.tsx @@ -87,12 +87,16 @@ const CreateObject = () => { }; }; - toast.error( - errorName.data?.name[0] || - data.message || - errorData?.messages?.[0].message || - "Xatolik yuz berdi", - ); + const message = + Array.isArray(errorName.data?.name) && errorName.data.name.length + ? errorName.data.name[0] + : data?.message || + (Array.isArray(errorData?.messages) && errorData.messages.length + ? errorData.messages[0].message + : undefined) || + "Xatolik yuz berdi"; + + toast.error(message); }, }); diff --git a/src/features/phamarcy/ui/CreatePharmacy.tsx b/src/features/phamarcy/ui/CreatePharmacy.tsx index 5915c1d..78d231a 100644 --- a/src/features/phamarcy/ui/CreatePharmacy.tsx +++ b/src/features/phamarcy/ui/CreatePharmacy.tsx @@ -85,12 +85,16 @@ const CreatePharmacy = () => { }; }; - toast.error( - errorName.data?.name[0] || - data.message || - errorData?.messages?.[0].message || - "Xatolik yuz berdi", - ); + const message = + Array.isArray(errorName.data?.name) && errorName.data.name.length + ? errorName.data.name[0] + : data?.message || + (Array.isArray(errorData?.messages) && errorData.messages.length + ? errorData.messages[0].message + : undefined) || + "Xatolik yuz berdi"; + + toast.error(message); }, }); diff --git a/src/features/plan-tour/lib/api.ts b/src/features/plan-tour/lib/api.ts new file mode 100644 index 0000000..a69b369 --- /dev/null +++ b/src/features/plan-tour/lib/api.ts @@ -0,0 +1,9 @@ +import httpClient from "@/shared/config/api/httpClient"; +import { TOUR_PLAN } from "@/shared/config/api/URLs"; + +export const tour_plan_api = { + async list() { + const res = await httpClient.get(TOUR_PLAN); + return res; + }, +}; diff --git a/src/features/plan-tour/lib/data.ts b/src/features/plan-tour/lib/data.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/features/plan-tour/ui/PlanPrice.tsx b/src/features/plan-tour/ui/PlanPrice.tsx index eb59b3f..3ab4532 100644 --- a/src/features/plan-tour/ui/PlanPrice.tsx +++ b/src/features/plan-tour/ui/PlanPrice.tsx @@ -1,60 +1,41 @@ +import { order_api } from "@/features/specification/lib/api"; +import type { OrderListData } from "@/features/specification/lib/data"; import { LanguageRoutes } from "@/shared/config/i18n/types"; import { formatPrice } from "@/shared/lib/formatPrice"; import { Alert, AlertDescription } from "@/shared/ui/alert"; import { Button } from "@/shared/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/shared/ui/card"; import { Input } from "@/shared/ui/input"; -import { Building2, Check, DollarSign, Edit2, MapPin, X } from "lucide-react"; -import { useEffect, useState } from "react"; -import { FalkeDataPlanTour } from "../lib/mock"; - -// TYPES -interface MonthData { - amount: number; - locked: boolean; -} - -interface Pharmacy { - id: number; - name: string; - district: string; - address: string; - monthlyData: Record; -} +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import type { AxiosError } from "axios"; +import { + Banknote, + Building2, + Check, + DollarSign, + Edit2, + Loader2, + X, +} from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; interface PlanPriceProps { selectedMonth: string; + pharmacies: OrderListData[]; } -const PlanPrice = ({ selectedMonth }: PlanPriceProps) => { - const [pharmacies, setPharmacies] = useState(FalkeDataPlanTour); +const PlanPrice = ({ selectedMonth, pharmacies }: PlanPriceProps) => { const currentDate = new Date(); + const queryClient = useQueryClient(); const currentMonthKey = `${currentDate.getFullYear()}-${String( currentDate.getMonth() + 1, ).padStart(2, "0")}`; - useEffect(() => { - setPharmacies((prev) => - prev.map((pharmacy) => { - if (pharmacy.monthlyData[selectedMonth]) return pharmacy; - - return { - ...pharmacy, - monthlyData: { - ...pharmacy.monthlyData, - [selectedMonth]: { amount: 0, locked: false }, - }, - }; - }), - ); - }, [selectedMonth]); - - // Editing const [editingId, setEditingId] = useState(null); const [tempAmount, setTempAmount] = useState(""); const [displayPrice, setDisplayPrice] = useState(""); - // === HELPERS === const formatCurrency = (amount: number): string => new Intl.NumberFormat("uz-UZ").format(amount) + " so'm"; @@ -77,6 +58,43 @@ const PlanPrice = ({ selectedMonth }: PlanPriceProps) => { return `${months[parseInt(month) - 1]} ${year}`; }; + const { mutate, isPending } = useMutation({ + mutationFn: ({ body, id }: { id: number; body: { paid_price: number } }) => + order_api.update({ body, id }), + onSuccess: () => { + toast.success("Lokatsiya jo'natildi"); + queryClient.refetchQueries({ queryKey: ["order_list"] }); + setEditingId(null); + setTempAmount(""); + }, + onError: (error: AxiosError) => { + const data = error.response?.data as { message?: string }; + const errorData = error.response?.data as { + messages?: { + token_class: string; + token_type: string; + message: string; + }[]; + }; + const errorName = error.response?.data as { + data?: { + name: string[]; + }; + }; + + const message = + Array.isArray(errorName.data?.name) && errorName.data.name.length + ? errorName.data.name[0] + : data?.message || + (Array.isArray(errorData?.messages) && errorData.messages.length + ? errorData.messages[0].message + : undefined) || + "Xatolik yuz berdi"; + + toast.error(message); + }, + }); + const handleEdit = (pharmacyId: number) => { const pharmacy = pharmacies.find((p) => p.id === pharmacyId); const currentAmount = pharmacy?.monthlyData[selectedMonth]?.amount ?? 0; @@ -88,25 +106,12 @@ const PlanPrice = ({ selectedMonth }: PlanPriceProps) => { const handleSave = (pharmacyId: number) => { const amount = parseInt(tempAmount) || 0; - setPharmacies((prev) => - prev.map((pharmacy) => - pharmacy.id === pharmacyId - ? { - ...pharmacy, - monthlyData: { - ...pharmacy.monthlyData, - [selectedMonth]: { - amount, - locked: selectedMonth !== currentMonthKey, - }, - }, - } - : pharmacy, - ), - ); - - setEditingId(null); - setTempAmount(""); + mutate({ + body: { + paid_price: amount, + }, + id: pharmacyId, + }); }; const handleCancel = () => { @@ -114,7 +119,7 @@ const PlanPrice = ({ selectedMonth }: PlanPriceProps) => { setTempAmount(""); }; - const isLocked = (pharmacy: Pharmacy): boolean => + const isLocked = (pharmacy: OrderListData): boolean => pharmacy.monthlyData[selectedMonth]?.locked === true || selectedMonth !== currentMonthKey; @@ -158,18 +163,20 @@ const PlanPrice = ({ selectedMonth }: PlanPriceProps) => {
- {pharmacy.name} + {pharmacy.employee_name}
-
- - {pharmacy.district} -
-
- {pharmacy.address} + Farmasevtika: {pharmacy.factory.name} +
+
+ +
+
+ + Qolgan summa: {formatPrice(pharmacy.overdue_price)}
@@ -182,13 +189,9 @@ const PlanPrice = ({ selectedMonth }: PlanPriceProps) => {
- +
- - {isEditing ? (
{ className="flex-1 bg-green-600 hover:bg-green-700" > - Saqlash + {isPending ? ( + + ) : ( + "Saqlash" + )} - ))} -
- - - -
+
+

+ Oylik hisobotlar +

+

+ Dorixonalar uchun oylik summalarni boshqaring +

+ + {isLoading && ( +
+ +
+ )} + + {isError && ( +
+ Ma'lumotlarni yuklashda xatolik yuz berdi! +
+ )} + + + + + + Oy tanlash + + + + +
+ {availableMonths().map((month) => ( + + ))} +
+
+
+ + {/* 🔥 Oylik narxlar */} +
); diff --git a/src/features/plan/ui/PlanDetailsDialogProps.tsx b/src/features/plan/ui/PlanDetailsDialogProps.tsx index c2ebd63..c238341 100644 --- a/src/features/plan/ui/PlanDetailsDialogProps.tsx +++ b/src/features/plan/ui/PlanDetailsDialogProps.tsx @@ -49,10 +49,22 @@ export function PlanDetailsDialog({ message: string; }[]; }; + const errorName = error.response?.data as { + data?: { + name: string[]; + }; + }; - toast.error( - data.message || errorData?.messages?.[0].message || "Xatolik yuz berdi", - ); + const message = + Array.isArray(errorName.data?.name) && errorName.data.name.length + ? errorName.data.name[0] + : data?.message || + (Array.isArray(errorData?.messages) && errorData.messages.length + ? errorData.messages[0].message + : undefined) || + "Xatolik yuz berdi"; + + toast.error(message); }, }); diff --git a/src/features/plan/ui/addPlans.tsx b/src/features/plan/ui/addPlans.tsx index df76e2d..1878ed8 100644 --- a/src/features/plan/ui/addPlans.tsx +++ b/src/features/plan/ui/addPlans.tsx @@ -94,10 +94,22 @@ export const AddPlans = ({ message: string; }[]; }; + const errorName = error.response?.data as { + data?: { + name: string[]; + }; + }; - toast.error( - data.message || errorData?.messages?.[0].message || "Xatolik yuz berdi", - ); + const message = + Array.isArray(errorName.data?.name) && errorName.data.name.length + ? errorName.data.name[0] + : data?.message || + (Array.isArray(errorData?.messages) && errorData.messages.length + ? errorData.messages[0].message + : undefined) || + "Xatolik yuz berdi"; + + toast.error(message); }, }); @@ -119,10 +131,22 @@ export const AddPlans = ({ message: string; }[]; }; + const errorName = error.response?.data as { + data?: { + name: string[]; + }; + }; - toast.error( - data.message || errorData?.messages?.[0].message || "Xatolik yuz berdi", - ); + const message = + Array.isArray(errorName.data?.name) && errorName.data.name.length + ? errorName.data.name[0] + : data?.message || + (Array.isArray(errorData?.messages) && errorData.messages.length + ? errorData.messages[0].message + : undefined) || + "Xatolik yuz berdi"; + + toast.error(message); }, }); diff --git a/src/features/plan/ui/plans.tsx b/src/features/plan/ui/plans.tsx index 98ca784..81e12d1 100644 --- a/src/features/plan/ui/plans.tsx +++ b/src/features/plan/ui/plans.tsx @@ -61,10 +61,22 @@ export default function Plans() { message: string; }[]; }; + const errorName = error.response?.data as { + data?: { + name: string[]; + }; + }; - toast.error( - data.message || errorData?.messages?.[0].message || "Xatolik yuz berdi", - ); + const message = + Array.isArray(errorName.data?.name) && errorName.data.name.length + ? errorName.data.name[0] + : data?.message || + (Array.isArray(errorData?.messages) && errorData.messages.length + ? errorData.messages[0].message + : undefined) || + "Xatolik yuz berdi"; + + toast.error(message); }, }); diff --git a/src/features/specification/lib/api.ts b/src/features/specification/lib/api.ts new file mode 100644 index 0000000..e1ffd5f --- /dev/null +++ b/src/features/specification/lib/api.ts @@ -0,0 +1,36 @@ +import type { + FactoryListRes, + OrderCreateReq, + OrderList, + ProductListRes, +} from "@/features/specification/lib/data"; +import httpClient from "@/shared/config/api/httpClient"; +import { FACTORY, ORDER, PRODUCT } from "@/shared/config/api/URLs"; +import type { AxiosResponse } from "axios"; + +export const order_api = { + async factory_list(): Promise> { + const res = await httpClient.get(FACTORY); + return res; + }, + + async product_list(): Promise> { + const res = await httpClient.get(PRODUCT); + return res; + }, + + async order_list(): Promise> { + const res = await httpClient.get(`${ORDER}list/`); + return res; + }, + + async order_create(body: OrderCreateReq) { + const res = await httpClient.post(`${ORDER}create/`, body); + return res; + }, + + async update({ body, id }: { id: number; body: { paid_price: number } }) { + const res = await httpClient.patch(`${ORDER}${id}/update/`, body); + return res; + }, +}; diff --git a/src/features/specification/lib/data.ts b/src/features/specification/lib/data.ts new file mode 100644 index 0000000..48f3d89 --- /dev/null +++ b/src/features/specification/lib/data.ts @@ -0,0 +1,72 @@ +export interface FactoryListRes { + status_code: number; + status: string; + message: string; + data: FactoryListData[]; +} + +export interface FactoryListData { + id: number; + name: string; + created_at: string; +} +export interface ProductListRes { + status_code: number; + status: string; + message: string; + data: ProductListData[]; +} + +export interface ProductListData { + id: number; + name: string; + price: string; + created_at: string; +} + +export interface OrderCreateReq { + factory_id: number; + paid_price: string; + total_price: string; + advance: number; + employee_name: string; + items: { + product: number; + quantity: number; + total_price: string; + }[]; +} + +export interface OrderList { + status_code: number; + status: string; + message: string; + data: OrderListData[]; +} + +export interface OrderListData { + id: number; + factory: { + id: number; + name: string; + }; + total_price: string; + paid_price: string; + advance: number; + employee_name: string; + overdue_price: string; + order_items: { + id: number; + product: number; + quantity: number; + total_price: string; + }[]; + file: string; + + monthlyData: { + [key: string]: { + amount: number; + locked: boolean; + }; + }; +} diff --git a/src/features/specification/ui/DetailViewPage.tsx b/src/features/specification/ui/DetailViewPage.tsx index 220af3d..14dcc96 100644 --- a/src/features/specification/ui/DetailViewPage.tsx +++ b/src/features/specification/ui/DetailViewPage.tsx @@ -1,81 +1,35 @@ "use client"; -import formatDate from "@/shared/lib/formatDate"; +import { order_api } from "@/features/specification/lib/api"; import { formatPrice } from "@/shared/lib/formatPrice"; +import { Button } from "@/shared/ui/button"; import { DashboardLayout } from "@/widgets/dashboard-layout/ui"; -import { Building2, Calendar, User } from "lucide-react"; +import { useQuery } from "@tanstack/react-query"; +import { Building2, User } from "lucide-react"; import { useParams } from "react-router-dom"; -interface MedicineItem { - id: number; - name: string; - price: number; - quantity: number; - total: number; -} - -interface SpecificationHistory { - id: string; - date: string; - buyerName: string; - pharmacy: string; - priceType: "regular" | "special"; - paymentPercentage: number; - medicines: MedicineItem[]; - totalAmount: number; - paymentAmount: number; -} - -// Sample data -const SAMPLE_HISTORY: SpecificationHistory[] = [ - { - id: "1", - date: "2024-11-15 14:30", - buyerName: "Abdullayev Aziz", - pharmacy: "Farmaciya #1", - priceType: "special", - paymentPercentage: 100, - medicines: [ - { id: 1, name: "Aspirin", price: 3500, quantity: 2, total: 7000 }, - { id: 2, name: "Paracetamol", price: 5500, quantity: 3, total: 16500 }, - ], - totalAmount: 23500, - paymentAmount: 23500, - }, - { - id: "2", - date: "2024-11-14 10:15", - buyerName: "Karimova Malika", - pharmacy: "Farmaciya #2", - priceType: "regular", - paymentPercentage: 50, - medicines: [ - { id: 3, name: "Ibuprofen", price: 10000, quantity: 1, total: 10000 }, - { id: 4, name: "Amoxicillin", price: 15000, quantity: 2, total: 30000 }, - { id: 5, name: "Metformin", price: 20000, quantity: 1, total: 20000 }, - ], - totalAmount: 60000, - paymentAmount: 30000, - }, - { - id: "3", - date: "2024-11-13 16:45", - buyerName: "Tursunov Bobur", - pharmacy: "Farmaciya #3", - priceType: "special", - paymentPercentage: 75, - medicines: [ - { id: 6, name: "Lisinopril", price: 8500, quantity: 4, total: 34000 }, - ], - totalAmount: 34000, - paymentAmount: 25500, - }, -]; - export function DetailViewPage() { const { id } = useParams(); - const item = SAMPLE_HISTORY.find((h) => h.id === id); + const { data } = useQuery({ + queryKey: ["order_list"], + queryFn: () => order_api.order_list(), + select(data) { + return data.data.data; + }, + }); + + const { data: product } = useQuery({ + queryKey: ["product_list"], + queryFn: () => order_api.product_list(), + select(data) { + return data.data.data; + }, + }); + + const item = data && data.find((h) => h.id === Number(id)); + + const handleDownloadPDF = async () => {}; if (!item) return
{"Ma'lumot topilmadi"}
; @@ -92,22 +46,24 @@ export function DetailViewPage() { {/* Info Cards */}
-
+ {/*
Sana

- {formatDate.format(item.date, "DD-MM-YYYY")} + {formatDate.format(item., "DD-MM-YYYY")}

-
+
*/}
Xaridor ismi
-

{item.buyerName}

+

+ {item.employee_name} +

@@ -117,7 +73,9 @@ export function DetailViewPage() { Farmasevtikaga tegishli
-

{item.pharmacy}

+

+ {item.factory.name} +

@@ -143,22 +101,28 @@ export function DetailViewPage() { - {item.medicines.map((medicine) => ( - - - {medicine.name} - - - {formatPrice(medicine.price)} - - - {medicine.quantity} - - - {formatPrice(medicine.total)} - - - ))} + {item.order_items.map((medicine) => { + const pro = + product && + product.find((e) => medicine.product === e.id); + + return ( + + + {pro && pro.name} + + + {pro && formatPrice(pro.price)} + + + {medicine.quantity} + + + {formatPrice(medicine.total_price)} + + + ); + })} @@ -170,22 +134,24 @@ export function DetailViewPage() {
Jami summa: - {formatPrice(item.totalAmount)} + {formatPrice(item.total_price)}
- {"To'langan"} ({item.paymentPercentage}%): + {"To'langan"} ({item.advance}%): - {formatPrice(item.paymentAmount)} + {formatPrice(item.paid_price)}
- {item.paymentPercentage < 100 && ( + {item.advance < 100 && (
Qoldiq: - {formatPrice(item.totalAmount - item.paymentAmount)} + {formatPrice( + Number(item.total_price) - Number(item.paid_price), + )}
)} @@ -193,6 +159,13 @@ export function DetailViewPage() { + ); diff --git a/src/features/specification/ui/HistoryListPage.tsx b/src/features/specification/ui/HistoryListPage.tsx index 389ac99..b71d21b 100644 --- a/src/features/specification/ui/HistoryListPage.tsx +++ b/src/features/specification/ui/HistoryListPage.tsx @@ -1,16 +1,48 @@ "use client"; +import { order_api } from "@/features/specification/lib/api"; import { formatPrice } from "@/shared/lib/formatPrice"; import AddedButton from "@/shared/ui/added-button"; import { Button } from "@/shared/ui/button"; import { DashboardLayout } from "@/widgets/dashboard-layout/ui"; +import { useQuery } from "@tanstack/react-query"; import { Eye } from "lucide-react"; import { useNavigate } from "react-router-dom"; -import { SAMPLE_HISTORY } from "../lib/mock"; export function HistoryListPage() { const router = useNavigate(); + const { data, isLoading, isError } = useQuery({ + queryKey: ["order_list"], + queryFn: () => order_api.order_list(), + select(data) { + return data.data.data; + }, + }); + + if (isLoading) { + return ( + +
+

Yuklanmoqda...

+
+
+ ); + } + + if (isError) { + return ( + +
+

Xatolik yuz berdi

+ +
+
+ ); + } + return (
@@ -29,8 +61,8 @@ export function HistoryListPage() {
- {SAMPLE_HISTORY.length > 0 ? ( - SAMPLE_HISTORY.map((item) => ( + {data && data.length > 0 ? ( + data.map((item) => (
Buyurtma â„–{item.id}

-

- Sana: {item.date} -

Mijoz

-

{item.buyerName}

+

{item.employee_name}

Farmasevtika

-

{item.pharmacy}

+

{item.factory.name}

Hisoblangan narxi

- {formatPrice(item.totalAmount)} + {formatPrice(item.total_price)}

@@ -68,7 +97,7 @@ export function HistoryListPage() {

{"To'langan foizi"}

-

{item.paymentPercentage}%

+

{item.advance}%

@@ -76,7 +105,7 @@ export function HistoryListPage() { {"To'langan narxi"}

- {formatPrice(item.paymentAmount)} + {formatPrice(item.paid_price)}

diff --git a/src/features/specification/ui/MedicineRow.tsx b/src/features/specification/ui/MedicineRow.tsx index 8572630..3d582ad 100644 --- a/src/features/specification/ui/MedicineRow.tsx +++ b/src/features/specification/ui/MedicineRow.tsx @@ -1,26 +1,21 @@ "use client"; +import type { ProductListData } from "@/features/specification/lib/data"; import { LanguageRoutes } from "@/shared/config/i18n/types"; import { formatPrice } from "@/shared/lib/formatPrice"; import { Input } from "@/shared/ui/input"; import { useParams } from "react-router-dom"; -interface Medicine { - id: number; - name: string; - regularPrice: number; - specialPrice: number; - quantity: number; -} +type MedicineItem = ProductListData & { quantity: number }; interface MedicineRowProps { - medicine: Medicine; + medicine: MedicineItem; onQuantityChange: (id: number, quantity: number) => void; } export function MedicineRow({ medicine, onQuantityChange }: MedicineRowProps) { const { locale } = useParams(); - const total = medicine.regularPrice * medicine.quantity; + const total = Number(medicine.price) * medicine.quantity; const handleQuantityChange = (value: string) => { const numValue = parseInt(value, 10) || 0; @@ -33,8 +28,7 @@ export function MedicineRow({ medicine, onQuantityChange }: MedicineRowProps) {

{medicine.name}

- Narxi:{" "} - {formatPrice(medicine.regularPrice, locale as LanguageRoutes, true)} + Narxi: {formatPrice(medicine.price, locale as LanguageRoutes, true)}

diff --git a/src/features/specification/ui/Specification.tsx b/src/features/specification/ui/Specification.tsx index b76f45d..8625f99 100644 --- a/src/features/specification/ui/Specification.tsx +++ b/src/features/specification/ui/Specification.tsx @@ -1,5 +1,10 @@ "use client"; +import { order_api } from "@/features/specification/lib/api"; +import type { + OrderCreateReq, + ProductListData, +} from "@/features/specification/lib/data"; import type { LanguageRoutes } from "@/shared/config/i18n/types"; import { formatPrice } from "@/shared/lib/formatPrice"; import { Button } from "@/shared/ui/button"; @@ -13,40 +18,100 @@ import { SelectValue, } from "@/shared/ui/select"; import { DashboardLayout } from "@/widgets/dashboard-layout/ui"; -import { useState } from "react"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import type { AxiosError } from "axios"; +import { Loader2 } from "lucide-react"; +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { toast } from "sonner"; import { MedicineRow } from "./MedicineRow"; import { PaymentPercentage } from "./PaymentPercentageProps"; -// Sample medicines data -const MEDICINES_DATA = [ - { id: 1, name: "Aspirin", regularPrice: 5000, specialPrice: 3500 }, - { id: 2, name: "Paracetamol", regularPrice: 8000, specialPrice: 5500 }, - { id: 3, name: "Ibuprofen", regularPrice: 10000, specialPrice: 7000 }, - { id: 4, name: "Amoxicillin", regularPrice: 15000, specialPrice: 10500 }, - { id: 5, name: "Metformin", regularPrice: 20000, specialPrice: 14000 }, - { id: 6, name: "Lisinopril", regularPrice: 12000, specialPrice: 8500 }, -]; - -const PHARMACIES = [ - "Farmasevtika #1", - "Farmasevtika #2", - "Farmasevtika #3", - "Farmasevtika #4", -]; - -interface MedicineItem { - id: number; - name: string; - regularPrice: number; - specialPrice: number; - quantity: number; -} +type MedicineItem = ProductListData & { quantity: number }; export function SpecificationPage() { + const navigate = useNavigate(); + const queryClient = useQueryClient(); + const { data: product } = useQuery({ + queryKey: ["product_list"], + queryFn: () => order_api.product_list(), + select(data) { + return data.data.data; + }, + }); + const { data } = useQuery({ + queryKey: ["factory_list"], + queryFn: () => order_api.factory_list(), + select(data) { + return data.data.data; + }, + }); + + // const { data: pharmacy } = useQuery({ + // queryKey: ["pharmacy_list"], + // queryFn: () => pharmacy_api.list(), + // select(data) { + // return data.data.data; + // }, + // }); + + const { mutate, isPending } = useMutation({ + mutationFn: (body: OrderCreateReq) => order_api.order_create(body), + onSuccess: () => { + navigate("/specification"); + queryClient.refetchQueries({ queryKey: ["order_list"] }); + }, + onError: (error: AxiosError) => { + const data = error.response?.data as { message?: string }; + const errorData = error.response?.data as { + messages?: { + token_class: string; + token_type: string; + message: string; + }[]; + }; + const errorName = error.response?.data as { + data?: { + name: string[]; + }; + }; + + const message = + Array.isArray(errorName.data?.name) && errorName.data.name.length + ? errorName.data.name[0] + : data?.message || + (Array.isArray(errorData?.messages) && errorData.messages.length + ? errorData.messages[0].message + : undefined) || + "Xatolik yuz berdi"; + + toast.error(message); + }, + }); const [medicines, setMedicines] = useState( - MEDICINES_DATA.map((m) => ({ ...m, quantity: 0 })), + product?.map((m) => ({ ...m, quantity: 0 })) || [], ); - const [selectedPharmacy, setSelectedPharmacy] = useState(PHARMACIES[0]); + + const [selectedPharmacy, setSelectedPharmacy] = useState( + data ? data[0].id : "", + ); + + // const [selectedPharm, setSelectedPharm] = useState( + // pharmacy ? pharmacy[0].id : "", + // ); + + useEffect(() => { + if (product) { + setMedicines(product?.map((m) => ({ ...m, quantity: 0 }))); + } + if (data) { + setSelectedPharmacy(data[0].id); + } + // if (pharmacy) { + // setSelectedPharm(pharmacy[0].id); + // } + }, [product, data]); + const [buyerName, setBuyerName] = useState(""); const [paymentPercentage, setPaymentPercentage] = useState(100); @@ -58,8 +123,8 @@ export function SpecificationPage() { ); }; - const getPrice = (medicine: MedicineItem) => { - return medicine.regularPrice; + const getPrice = (medicine: ProductListData) => { + return Number(medicine.price); }; const calculateTotal = () => { @@ -80,16 +145,21 @@ export function SpecificationPage() { pharmacy: selectedPharmacy, paymentPercentage, medicines: selectedMedicines.map((m) => ({ - id: m.id, - name: m.name, - price: getPrice(m), + product: m.id, quantity: m.quantity, - total: getPrice(m) * m.quantity, + total_price: String(Number(m.price) * m.quantity), })), totalAmount: calculateTotal(), paymentAmount: calculatePaymentAmount(), }; - console.log("PDF Data:", data); + mutate({ + employee_name: data.buyerName, + factory_id: Number(data.pharmacy), + items: data.medicines, + paid_price: String(data.paymentAmount), + total_price: String(data.totalAmount), + advance: data.paymentPercentage, + }); }; const hasSelectedMedicines = medicines.some((m) => m.quantity > 0); @@ -128,22 +198,49 @@ export function SpecificationPage() {

Farmasevtika

+ {/* +

Dorixa

+ +
*/} + {/* Buyer name */}

Xaridor Nomi

@@ -204,7 +301,11 @@ export function SpecificationPage() { className="w-full h-14 text-md bg-green-600 hover:bg-green-700 text-white" size="lg" > - PDF ga yuklab olish + {isPending ? ( + + ) : ( + "Saqlash va yuklab olish" + )} )} diff --git a/src/features/tour-plan/lib/api.ts b/src/features/tour-plan/lib/api.ts index a69b369..9c6be36 100644 --- a/src/features/tour-plan/lib/api.ts +++ b/src/features/tour-plan/lib/api.ts @@ -1,9 +1,25 @@ +import type { TourItem } from "@/features/tour-plan/lib/types"; import httpClient from "@/shared/config/api/httpClient"; import { TOUR_PLAN } from "@/shared/config/api/URLs"; +import type { AxiosResponse } from "axios"; export const tour_plan_api = { - async list() { - const res = await httpClient.get(TOUR_PLAN); + async list(): Promise> { + const res = await httpClient.get(`${TOUR_PLAN}list/`); + return res; + }, + + async send_location({ + id, + body, + }: { + body: { + longitude: number; + latitude: number; + }; + id: number; + }): Promise> { + const res = await httpClient.patch(`${TOUR_PLAN}${id}/update/`, body); return res; }, }; diff --git a/src/features/tour-plan/lib/column.tsx b/src/features/tour-plan/lib/column.tsx index 8c2a247..ed64184 100644 --- a/src/features/tour-plan/lib/column.tsx +++ b/src/features/tour-plan/lib/column.tsx @@ -1,6 +1,6 @@ "use client"; -import type { TourItem } from "@/features/tour-plan/lib/types"; +import type { TourItemData } from "@/features/tour-plan/lib/types"; import formatDate from "@/shared/lib/formatDate"; import type { ColumnDef } from "@tanstack/react-table"; import { Send } from "lucide-react"; @@ -9,9 +9,9 @@ export const getColumns = ({ sendLocation, canSend, }: { - sendLocation: (tour: TourItem) => void; + sendLocation: (tour: TourItemData) => void; canSend: (date: string) => boolean; -}): ColumnDef[] => [ +}): ColumnDef[] => [ { accessorKey: "date", header: "Sana", @@ -24,6 +24,9 @@ export const getColumns = ({ { accessorKey: "district", header: "Tuman", + cell: ({ row }) => ( +
{row.original.place_name}
+ ), }, { id: "actions", @@ -35,9 +38,13 @@ export const getColumns = ({
canSend(tour.date) && sendLocation(tour)} + onClick={() => + canSend(tour.date) && !tour.location_send && sendLocation(tour) + } />
); diff --git a/src/features/tour-plan/lib/data-table.tsx b/src/features/tour-plan/lib/data-table.tsx index b0dcfbb..9207162 100644 --- a/src/features/tour-plan/lib/data-table.tsx +++ b/src/features/tour-plan/lib/data-table.tsx @@ -1,6 +1,6 @@ "use client"; -import type { TourItem } from "@/features/tour-plan/lib/types"; +import type { TourItemData } from "@/features/tour-plan/lib/types"; import { Table, TableBody, @@ -16,12 +16,11 @@ import { type ColumnDef, } from "@tanstack/react-table"; -interface DataTableProps { - columns: ColumnDef[]; - data: TourItem[]; +interface DataTableProps { + columns: ColumnDef[]; + data: TourItemData[]; } - -export function DataTable({ columns, data }: DataTableProps) { +export function DataTable({ columns, data }: DataTableProps) { const table = useReactTable({ data, columns, diff --git a/src/features/tour-plan/lib/types.ts b/src/features/tour-plan/lib/types.ts index fe94ca0..1bfb814 100644 --- a/src/features/tour-plan/lib/types.ts +++ b/src/features/tour-plan/lib/types.ts @@ -1,11 +1,16 @@ -export interface TourItem { +export interface TourItemData { id: number; + place_name: string; + longitude: number; + latitude: number; + location_send: boolean; + created_at: string; date: string; - district: string; } -export const mockTourData: TourItem[] = [ - { id: 1, date: "2025-11-01", district: "Yunusobod tumani" }, - { id: 2, date: "2025-11-15", district: "Mirzo Ulug'bek tumani" }, - { id: 3, date: "2025-11-20", district: "Chilonzor tumani" }, -]; +export interface TourItem { + status_code: number; + status: string; + message: string; + data: TourItemData[]; +} diff --git a/src/features/tour-plan/ui/TourPlan.tsx b/src/features/tour-plan/ui/TourPlan.tsx index 0ac433e..1c677be 100644 --- a/src/features/tour-plan/ui/TourPlan.tsx +++ b/src/features/tour-plan/ui/TourPlan.tsx @@ -1,7 +1,8 @@ "use client"; -import { DataTable } from "@/features/doctor/lib/data-table"; -import type { TourItem } from "@/features/tour-plan/lib/types"; +import { tour_plan_api } from "@/features/tour-plan/lib/api"; +import { DataTable } from "@/features/tour-plan/lib/data-table"; +import type { TourItemData } from "@/features/tour-plan/lib/types"; import { Select, SelectContent, @@ -11,42 +12,89 @@ import { SelectValue, } from "@/shared/ui/select"; import { DashboardLayout } from "@/widgets/dashboard-layout/ui"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import type { AxiosError } from "axios"; +import { Loader2 } from "lucide-react"; // 🔥 spinner import { useMemo, useState } from "react"; +import { toast } from "sonner"; import { getColumns } from "../lib/column"; -const mockTourData: TourItem[] = [ - { - id: 1, - date: "2025-11-01", - district: "Yunusobod tumani", - }, - { - id: 2, - date: "2025-11-15", - district: "Mirzo Ulug'bek tumani", - }, - { - id: 3, - date: "2025-11-20", - district: "Chilonzor tumani", - }, -]; - export default function TourPlan() { const currentYear = new Date().getFullYear().toString(); const currentMonth = (new Date().getMonth() + 1).toString().padStart(2, "0"); + const queryClient = useQueryClient(); + + const { data, isLoading, isError } = useQuery({ + queryKey: ["tour_plan_list"], + queryFn: () => tour_plan_api.list(), + select(data) { + return data.data.data; + }, + }); + + const { mutate } = useMutation({ + mutationFn: ({ + id, + body, + }: { + body: { + longitude: number; + latitude: number; + }; + id: number; + }) => tour_plan_api.send_location({ body, id }), + onSuccess: () => { + toast.success("Lokatsiya jo'natildi"); + queryClient.refetchQueries({ queryKey: ["tour_plan_list"] }); + }, + onError: (error: AxiosError) => { + const data = error.response?.data as { message?: string }; + const errorData = error.response?.data as { + messages?: { + token_class: string; + token_type: string; + message: string; + }[]; + }; + const errorName = error.response?.data as { + data?: { + name: string[]; + }; + }; + + const message = + Array.isArray(errorName.data?.name) && errorName.data.name.length + ? errorName.data.name[0] + : data?.message || + (Array.isArray(errorData?.messages) && errorData.messages.length + ? errorData.messages[0].message + : undefined) || + "Xatolik yuz berdi"; + + toast.error(message); + }, + }); const [year, setYear] = useState(currentYear); const [month, setMonth] = useState(currentMonth); - const sendLocation = () => { + + const sendLocation = (tour: TourItemData) => { navigator.geolocation.getCurrentPosition( (pos) => { - console.log("📌 Lokatsiya olindi:"); - console.log("Latitude:", pos.coords.latitude); - console.log("Longitude:", pos.coords.longitude); + const latitude = pos.coords.latitude; + const longitude = pos.coords.longitude; + + mutate({ + id: tour.id, + body: { + latitude, + longitude, + }, + }); }, (err) => { console.error("Lokatsiya olishda xatolik:", err); + toast.error("Lokatsiya olishga ruxsat berilmadi!"); }, { enableHighAccuracy: true, @@ -57,17 +105,25 @@ export default function TourPlan() { }; const canSend = (date: string) => { - return new Date(date) >= new Date(); + const d = new Date(date); + const today = new Date(); + + d.setHours(0, 0, 0, 0); + today.setHours(0, 0, 0, 0); + + return d.getTime() >= today.getTime(); }; const columnsProps = getColumns({ - sendLocation: sendLocation, - canSend: canSend, + sendLocation, + canSend, }); const filteredData = useMemo(() => { - return mockTourData.filter((item) => { - const d = new Date(item.date); + if (!data) return []; + + return data.filter((item) => { + const d = new Date(item.date + "T00:00:00"); const itemYear = d.getFullYear().toString(); const itemMonth = (d.getMonth() + 1).toString().padStart(2, "0"); @@ -77,18 +133,19 @@ export default function TourPlan() { return yearMatch && monthMatch; }); - }, [year, month]); + }, [data, year, month]); return (

Tur Plan

- {/* 🔹 FILTER UI */} + {/* FILTERS */}
+ {/* Year */} + {/* Month */}
-
- -
+ {/* LOADING UI */} + {isLoading && ( +
+ +
+ )} + + {/* ERROR UI */} + {isError && ( +
+ Ma'lumotlarni yuklashda xatolik yuz berdi! +
+ )} + + {/* TABLE */} + {!isLoading && !isError && ( +
+ +
+ )}
); diff --git a/src/global.d.ts b/src/global.d.ts index b898a9d..95960f1 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -8,6 +8,7 @@ interface Window { last_name?: string; }; }; + sendData?: (data: string) => void; }; }; } diff --git a/src/pages/specification/added/page.tsx b/src/pages/specification/added/page.tsx index 4d2c2b3..0a1e7ad 100644 --- a/src/pages/specification/added/page.tsx +++ b/src/pages/specification/added/page.tsx @@ -1,7 +1,12 @@ import { SpecificationPage } from "@/features/specification/ui/Specification"; +import TokenLayout from "@/token-layaout"; const SpecificationAdded = () => { - return ; + return ( + + + + ); }; export default SpecificationAdded; diff --git a/src/pages/specification/history/[id]/page.tsx b/src/pages/specification/history/[id]/page.tsx index 3c60803..42d8224 100644 --- a/src/pages/specification/history/[id]/page.tsx +++ b/src/pages/specification/history/[id]/page.tsx @@ -1,7 +1,12 @@ import { DetailViewPage } from "@/features/specification/ui/DetailViewPage"; +import TokenLayout from "@/token-layaout"; const SpecificationDetail = () => { - return ; + return ( + + + + ); }; export default SpecificationDetail; diff --git a/src/pages/specification/page.tsx b/src/pages/specification/page.tsx index 782b678..dd1969e 100644 --- a/src/pages/specification/page.tsx +++ b/src/pages/specification/page.tsx @@ -1,5 +1,10 @@ import { HistoryListPage } from "@/features/specification/ui/HistoryListPage"; +import TokenLayout from "@/token-layaout"; export default function Specification() { - return ; + return ( + + + + ); } diff --git a/src/pages/tour-plan/page.tsx b/src/pages/tour-plan/page.tsx index 3097880..98d07b1 100644 --- a/src/pages/tour-plan/page.tsx +++ b/src/pages/tour-plan/page.tsx @@ -1,7 +1,12 @@ import TourPlan from "@/features/tour-plan/ui/TourPlan"; +import TokenLayout from "@/token-layaout"; export const TourPlanPage = () => { - return ; + return ( + + + + ); }; export default TourPlanPage; diff --git a/src/pages/type-plan/page.tsx b/src/pages/type-plan/page.tsx index 924c51f..f94fed1 100644 --- a/src/pages/type-plan/page.tsx +++ b/src/pages/type-plan/page.tsx @@ -1,5 +1,10 @@ import PlanTour from "@/features/plan-tour/ui/PlanTour"; +import TokenLayout from "@/token-layaout"; export const TypePlan = () => { - return ; + return ( + + + + ); }; diff --git a/src/shared/config/api/URLs.ts b/src/shared/config/api/URLs.ts index d3eacf6..73a6d1c 100644 --- a/src/shared/config/api/URLs.ts +++ b/src/shared/config/api/URLs.ts @@ -10,18 +10,24 @@ const OBJECT = "/api/v1/shared/place/"; const DOCTOR = "/api/v1/shared/doctor/"; const PHARMACY = "/api/v1/shared/pharmacy/"; const LOCATION = "/api/v1/shared/location/"; -const TOUR_PLAN = "/api/v1/shared/tour_plan/list"; +const TOUR_PLAN = "/api/v1/shared/tour_plan/"; +const FACTORY = "/api/v1/shared/factory/list/"; +const PRODUCT = "/api/v1/orders/product/list/"; +const ORDER = "/api/v1/orders/order/"; export { BASE_URL, CREATE_USER, DISCTRICT, DOCTOR, + FACTORY, LOCATION, LOGIN_USER, OBJECT, + ORDER, PHARMACY, PLANS, + PRODUCT, REGIONS, TOUR_PLAN, };