From f7dbb665a0438a00cba20ea9ab1babdf836130f0 Mon Sep 17 00:00:00 2001
From: Samandar Turgunboyev
Date: Tue, 2 Dec 2025 19:31:37 +0500
Subject: [PATCH] apilar ulandi
---
src/features/auth/lib/api.ts | 4 +-
src/features/districts/lib/api.ts | 13 +-
src/features/doctors/lib/api.ts | 10 +-
src/features/location/lib/api.ts | 40 ++
src/features/location/lib/data.ts | 48 ++
src/features/location/ui/DeleteLocation.tsx | 114 ++++
.../location/ui/LocationDetailDialog.tsx | 117 ++++-
src/features/location/ui/LocationFilter.tsx | 28 +
src/features/location/ui/LocationList.tsx | 126 +++--
src/features/location/ui/LocationTable.tsx | 18 +-
.../location/ui/UserLocationTable.tsx | 92 ++++
src/features/objects/lib/api.ts | 10 +-
src/features/pharm/lib/api.ts | 33 ++
src/features/pharm/lib/data.ts | 22 +
src/features/pharm/ui/AddedPharm.tsx | 87 ++--
src/features/pharm/ui/DeletePharm.tsx | 89 ++++
src/features/pharm/ui/PharmList.tsx | 199 +++----
src/features/pharmacies/lib/api.ts | 13 +-
src/features/pill/lib/api.ts | 30 ++
src/features/pill/lib/data.ts | 24 +
src/features/pill/ui/AddedPill.tsx | 91 ++--
src/features/pill/ui/DeletePill.tsx | 88 ++++
src/features/pill/ui/PillList.tsx | 103 ++--
src/features/plans/lib/api.ts | 36 ++
src/features/plans/lib/data.ts | 39 ++
src/features/plans/lib/form.ts | 1 +
src/features/plans/ui/AddedPlan.tsx | 299 ++++++++---
src/features/plans/ui/DeletePlan.tsx | 89 ++++
src/features/plans/ui/FilterPlans.tsx | 13 +-
src/features/plans/ui/PalanTable.tsx | 162 +++---
src/features/plans/ui/PlanDetail.tsx | 12 +-
src/features/plans/ui/PlansList.tsx | 103 ++--
src/features/region/lib/api.ts | 13 +-
src/features/reports/lib/api.ts | 14 +
src/features/reports/lib/data.ts | 23 +
src/features/reports/ui/ReportsList.tsx | 50 +-
src/features/reports/ui/ReportsTable.tsx | 91 ++--
src/features/specifications/lib/api.ts | 33 ++
src/features/specifications/lib/data.ts | 68 +++
.../specifications/ui/AddedSpecification.tsx | 491 +++++++++++++-----
.../specifications/ui/DeleteOrder.tsx | 88 ++++
.../ui/SpecificationDetail .tsx | 79 ++-
.../specifications/ui/SpecificationsList.tsx | 234 ++++-----
src/features/tour-plan/lib/api.ts | 39 ++
src/features/tour-plan/lib/data.ts | 43 ++
src/features/tour-plan/lib/form.ts | 2 -
src/features/tour-plan/ui/AddedTourPlan.tsx | 254 ++++++---
src/features/tour-plan/ui/DeleteTourPlab.tsx | 89 ++++
src/features/tour-plan/ui/FilterTourPlan.tsx | 113 ++++
.../tour-plan/ui/TourPlanDetailDialog.tsx | 34 +-
src/features/tour-plan/ui/TourPlanList.tsx | 312 +++--------
src/features/tour-plan/ui/TourPlanTable.tsx | 135 +++++
src/features/users/lib/api.ts | 12 +-
src/shared/config/api/URLs.ts | 40 +-
src/shared/config/api/httpClient.ts | 4 +-
src/widgets/sidebar-layout/index.tsx | 10 +-
56 files changed, 3235 insertions(+), 1189 deletions(-)
create mode 100644 src/features/location/lib/api.ts
create mode 100644 src/features/location/ui/DeleteLocation.tsx
create mode 100644 src/features/location/ui/UserLocationTable.tsx
create mode 100644 src/features/pharm/lib/api.ts
create mode 100644 src/features/pharm/ui/DeletePharm.tsx
create mode 100644 src/features/pill/lib/api.ts
create mode 100644 src/features/pill/ui/DeletePill.tsx
create mode 100644 src/features/plans/lib/api.ts
create mode 100644 src/features/plans/ui/DeletePlan.tsx
create mode 100644 src/features/reports/lib/api.ts
create mode 100644 src/features/specifications/lib/api.ts
create mode 100644 src/features/specifications/ui/DeleteOrder.tsx
create mode 100644 src/features/tour-plan/lib/api.ts
create mode 100644 src/features/tour-plan/ui/DeleteTourPlab.tsx
create mode 100644 src/features/tour-plan/ui/FilterTourPlan.tsx
create mode 100644 src/features/tour-plan/ui/TourPlanTable.tsx
diff --git a/src/features/auth/lib/api.ts b/src/features/auth/lib/api.ts
index 904fd92..207dc44 100644
--- a/src/features/auth/lib/api.ts
+++ b/src/features/auth/lib/api.ts
@@ -1,5 +1,5 @@
import httpClient from "@/shared/config/api/httpClient";
-import { LOGIN } from "@/shared/config/api/URLs";
+import { API_URLS } from "@/shared/config/api/URLs";
import type { AxiosResponse } from "axios";
interface LoginRes {
@@ -16,7 +16,7 @@ export const auth_pai = {
username: string;
password: string;
}): Promise> {
- const res = await httpClient.post(LOGIN, body);
+ const res = await httpClient.post(API_URLS.LOGIN, body);
return res;
},
};
diff --git a/src/features/districts/lib/api.ts b/src/features/districts/lib/api.ts
index f6789a3..25c6273 100644
--- a/src/features/districts/lib/api.ts
+++ b/src/features/districts/lib/api.ts
@@ -1,6 +1,6 @@
import type { DistrictListRes } from "@/features/districts/lib/data";
import httpClient from "@/shared/config/api/httpClient";
-import { DISTRICT } from "@/shared/config/api/URLs";
+import { API_URLS } from "@/shared/config/api/URLs";
import type { AxiosResponse } from "axios";
export const discrit_api = {
@@ -10,7 +10,7 @@ export const discrit_api = {
name?: string;
user?: number;
}): Promise> {
- const res = await httpClient.get(`${DISTRICT}list/`, { params });
+ const res = await httpClient.get(`${API_URLS.DISTRICT}list/`, { params });
return res;
},
@@ -18,7 +18,7 @@ export const discrit_api = {
name: string;
user_id: number;
}): Promise> {
- const res = await httpClient.post(`${DISTRICT}create/`, body);
+ const res = await httpClient.post(`${API_URLS.DISTRICT}create/`, body);
return res;
},
@@ -32,12 +32,15 @@ export const discrit_api = {
user: number;
};
}): Promise> {
- const res = await httpClient.patch(`${DISTRICT}${id}/update/`, body);
+ const res = await httpClient.patch(
+ `${API_URLS.DISTRICT}${id}/update/`,
+ body,
+ );
return res;
},
async delete(id: number): Promise> {
- const res = await httpClient.delete(`${DISTRICT}${id}/delete/`);
+ const res = await httpClient.delete(`${API_URLS.DISTRICT}${id}/delete/`);
return res;
},
};
diff --git a/src/features/doctors/lib/api.ts b/src/features/doctors/lib/api.ts
index 69005db..e980426 100644
--- a/src/features/doctors/lib/api.ts
+++ b/src/features/doctors/lib/api.ts
@@ -4,7 +4,7 @@ import type {
UpdateDoctorReq,
} from "@/features/doctors/lib/data";
import httpClient from "@/shared/config/api/httpClient";
-import { DOCTOR } from "@/shared/config/api/URLs";
+import { API_URLS } from "@/shared/config/api/URLs";
import type { AxiosResponse } from "axios";
export const doctor_api = {
@@ -18,22 +18,22 @@ export const doctor_api = {
sphere?: string;
user?: string;
}): Promise> {
- const res = await httpClient.get(`${DOCTOR}list/`, { params });
+ const res = await httpClient.get(`${API_URLS.DOCTOR}list/`, { params });
return res;
},
async create(body: CreateDoctorReq) {
- const res = await httpClient.post(`${DOCTOR}create/`, body);
+ const res = await httpClient.post(`${API_URLS.DOCTOR}create/`, body);
return res;
},
async update({ body, id }: { id: number; body: UpdateDoctorReq }) {
- const res = await httpClient.patch(`${DOCTOR}${id}/update/`, body);
+ const res = await httpClient.patch(`${API_URLS.DOCTOR}${id}/update/`, body);
return res;
},
async delete(id: number) {
- const res = await httpClient.delete(`${DOCTOR}${id}/delete/`);
+ const res = await httpClient.delete(`${API_URLS.DOCTOR}${id}/delete/`);
return res;
},
};
diff --git a/src/features/location/lib/api.ts b/src/features/location/lib/api.ts
new file mode 100644
index 0000000..1b0cccf
--- /dev/null
+++ b/src/features/location/lib/api.ts
@@ -0,0 +1,40 @@
+import type { LocationListRes } from "@/features/location/lib/data";
+import httpClient from "@/shared/config/api/httpClient";
+import { API_URLS } from "@/shared/config/api/URLs";
+import type { AxiosResponse } from "axios";
+
+export const location_api = {
+ async list(params: {
+ limit?: number;
+ offset?: number;
+ date?: string;
+ user?: string;
+ }): Promise> {
+ const res = await httpClient.get(`${API_URLS.LOCATION}list/`, { params });
+ return res;
+ },
+
+ async delete(id: number) {
+ const res = await httpClient.delete(`${API_URLS.LOCATION}${id}/delete/`);
+ return res;
+ },
+
+ async list_user_location(params: {
+ limit?: number;
+ offset?: number;
+ date?: string;
+ user?: string;
+ }): Promise> {
+ const res = await httpClient.get(`${API_URLS.USER_LOCATION}list/`, {
+ params,
+ });
+ return res;
+ },
+
+ async list_user_location_delete(id: number) {
+ const res = await httpClient.delete(
+ `${API_URLS.USER_LOCATION}${id}/delete/`,
+ );
+ return res;
+ },
+};
diff --git a/src/features/location/lib/data.ts b/src/features/location/lib/data.ts
index 985d8c0..64fcb16 100644
--- a/src/features/location/lib/data.ts
+++ b/src/features/location/lib/data.ts
@@ -75,3 +75,51 @@ export const LocationFakeData: LocationListType[] = [
createdAt: new Date("2025-02-01T10:15:00"),
},
];
+
+export interface LocationListRes {
+ status_code: number;
+ status: string;
+ message: string;
+ data: {
+ count: number;
+ next: null | string;
+ previous: null | string;
+ results: LocationListDataRes[];
+ };
+}
+
+export interface LocationListDataRes {
+ id: number;
+ longitude: number;
+ latitude: number;
+ created_at: string;
+ district: {
+ id: number;
+ name: string;
+ };
+ place: {
+ id: number;
+ name: string;
+ longitude: number;
+ latitude: number;
+ };
+ doctor: {
+ id: number;
+ first_name: string;
+ last_name: string;
+ longitude: number;
+ latitude: number;
+ };
+ pharmacy: {
+ id: number;
+ name: string;
+ longitude: number;
+ latitude: number;
+ };
+ user: {
+ id: number;
+ first_name: string;
+ last_name: string;
+ };
+ updated_at: string;
+}
diff --git a/src/features/location/ui/DeleteLocation.tsx b/src/features/location/ui/DeleteLocation.tsx
new file mode 100644
index 0000000..cb661cb
--- /dev/null
+++ b/src/features/location/ui/DeleteLocation.tsx
@@ -0,0 +1,114 @@
+import { location_api } from "@/features/location/lib/api";
+import type { LocationListDataRes } from "@/features/location/lib/data";
+import { Button } from "@/shared/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/shared/ui/dialog";
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import type { AxiosError } from "axios";
+import { Loader2, Trash, X } from "lucide-react";
+import type { Dispatch, SetStateAction } from "react";
+import { toast } from "sonner";
+
+interface Props {
+ opneDelete: boolean;
+ setOpenDelete: Dispatch>;
+ setLocationDelete: Dispatch>;
+ locationDelete: LocationListDataRes | null;
+ viewLocation: "user_send" | "user_send_object";
+}
+const DeleteLocation = ({
+ opneDelete,
+ viewLocation,
+ locationDelete,
+ setOpenDelete,
+ setLocationDelete,
+}: Props) => {
+ const queryClient = useQueryClient();
+
+ const { mutate: deleteUser, isPending } = useMutation({
+ mutationFn: (id: number) => location_api.delete(id),
+
+ onSuccess: () => {
+ queryClient.refetchQueries({ queryKey: ["location_list"] });
+ toast.success(`Jo'natilgan lokatsiya o'chirildi`);
+ setOpenDelete(false);
+ setLocationDelete(null);
+ },
+ onError: (err: AxiosError) => {
+ const errMessage = err.response?.data as { message: string };
+ const messageText = errMessage.message;
+ toast.error(messageText || "Xatolik yuz berdi", {
+ richColors: true,
+ position: "top-center",
+ });
+ },
+ });
+
+ const { mutate: deleteUserLocation, isPending: deleteUserLocationPen } =
+ useMutation({
+ mutationFn: (id: number) => location_api.list_user_location_delete(id),
+
+ onSuccess: () => {
+ queryClient.refetchQueries({ queryKey: ["user_location_list"] });
+ toast.success(`Jo'natilgan lokatsiya o'chirildi`);
+ setOpenDelete(false);
+ setLocationDelete(null);
+ },
+ onError: (err: AxiosError) => {
+ const errMessage = err.response?.data as { message: string };
+ const messageText = errMessage.message;
+ toast.error(messageText || "Xatolik yuz berdi", {
+ richColors: true,
+ position: "top-center",
+ });
+ },
+ });
+ return (
+
+ );
+};
+
+export default DeleteLocation;
diff --git a/src/features/location/ui/LocationDetailDialog.tsx b/src/features/location/ui/LocationDetailDialog.tsx
index 99fa6d9..100f25c 100644
--- a/src/features/location/ui/LocationDetailDialog.tsx
+++ b/src/features/location/ui/LocationDetailDialog.tsx
@@ -1,4 +1,4 @@
-import type { LocationListType } from "@/features/location/lib/data";
+import type { LocationListDataRes } from "@/features/location/lib/data";
import formatDate from "@/shared/lib/formatDate";
import {
Dialog,
@@ -7,26 +7,100 @@ import {
DialogHeader,
DialogTitle,
} from "@/shared/ui/dialog";
-import { Circle, Map, Placemark, YMaps } from "@pbe/react-yandex-maps";
+import { Circle, Map, Placemark, Polygon, YMaps } from "@pbe/react-yandex-maps";
import { useEffect, useState, type Dispatch, type SetStateAction } from "react";
interface Props {
detail: boolean;
setDetail: Dispatch>;
- object: LocationListType | null;
+ object: LocationListDataRes | null;
+}
+
+interface CoordsData {
+ lat: number;
+ lon: number;
+ polygon: [number, number][][];
}
const LocationDetailDialog = ({ detail, object, setDetail }: Props) => {
const [circle, setCircle] = useState([""]);
+ const [coords, setCoords] = useState<[number, number]>([
+ 41.311081, 69.240562,
+ ]);
+
+ const [polygonCoords, setPolygonCoords] = useState<[number, number][][]>([]);
+
+ const getCoords = async (name: string): Promise => {
+ try {
+ const res = await fetch(
+ `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(
+ name,
+ )}&format=json&polygon_geojson=1&limit=1`,
+ );
+ const data = await res.json();
+
+ if (!data.length || !data[0].geojson) return null;
+
+ const lat = parseFloat(data[0].lat);
+ const lon = parseFloat(data[0].lon);
+
+ let polygon: [number, number][][] = [];
+
+ if (data[0].geojson.type === "Polygon") {
+ polygon = data[0].geojson.coordinates.map((ring: []) =>
+ ring.map((c) => [c[1], c[0]]),
+ );
+ }
+
+ if (data[0].geojson.type === "MultiPolygon") {
+ polygon = data[0].geojson.coordinates[0].map((ring: []) =>
+ ring.map((c) => [c[1], c[0]]),
+ );
+ }
+
+ return { lat, lon, polygon };
+ } catch {
+ return null;
+ }
+ };
useEffect(() => {
- if (object && object.object) {
- setCircle([object.object.lat, object.object.long]);
- } else if (object && object.pharmcies) {
- setCircle([object.pharmcies.lat, object.pharmcies.long]);
+ if (!object) return;
+
+ const load = async () => {
+ if (object.district) {
+ const district = await getCoords(object.district.name);
+
+ if (district) {
+ setPolygonCoords(district.polygon);
+ }
+ } else {
+ setPolygonCoords([]);
+ }
+
+ setCoords([Number(object.latitude), Number(object.longitude)]);
+ };
+
+ load();
+ }, [object]);
+
+ useEffect(() => {
+ if (object && object.place) {
+ setCircle([
+ object.place.latitude.toString(),
+ object.place.longitude.toString(),
+ ]);
+ } else if (object && object.pharmacy) {
+ setCircle([
+ object.pharmacy.latitude.toString(),
+ object.pharmacy.longitude.toString(),
+ ]);
} else if (object && object.doctor) {
- setCircle([object.doctor.lat, object.doctor.long]);
+ setCircle([
+ object.doctor.latitude.toString(),
+ object.doctor.longitude.toString(),
+ ]);
} else {
setCircle(undefined);
}
@@ -49,13 +123,13 @@ const LocationDetailDialog = ({ detail, object, setDetail }: Props) => {
Jo'natgan foydalanvchi:
- {object.user.firstName} {object.user.lastName}
+ {object.user.first_name} {object.user.last_name}
Jo'natgan vaqti:
- {formatDate.format(object.createdAt, "DD-MM-YYYY")}
+ {formatDate.format(object.created_at, "DD-MM-YYYY")}
{object.district && (
@@ -69,15 +143,16 @@ const LocationDetailDialog = ({ detail, object, setDetail }: Props) => {
diff --git a/src/features/location/ui/LocationFilter.tsx b/src/features/location/ui/LocationFilter.tsx
index 1d46cc6..1e090d9 100644
--- a/src/features/location/ui/LocationFilter.tsx
+++ b/src/features/location/ui/LocationFilter.tsx
@@ -2,6 +2,13 @@ import { Button } from "@/shared/ui/button";
import { Calendar } from "@/shared/ui/calendar";
import { Input } from "@/shared/ui/input";
import { Popover, PopoverContent, PopoverTrigger } from "@/shared/ui/popover";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/shared/ui/select";
import { ChevronDownIcon } from "lucide-react";
import type { Dispatch, SetStateAction } from "react";
@@ -12,6 +19,8 @@ interface Props {
setDateFilter: Dispatch>;
searchUser: string;
setSearchUser: Dispatch>;
+ viewLocation: "user_send" | "user_send_object";
+ setViewLocation: Dispatch>;
}
const LocationFilter = ({
@@ -21,9 +30,28 @@ const LocationFilter = ({
setDateFilter,
searchUser,
setSearchUser,
+ viewLocation,
+ setViewLocation,
}: Props) => {
return (
+
+
diff --git a/src/features/location/ui/UserLocationTable.tsx b/src/features/location/ui/UserLocationTable.tsx
new file mode 100644
index 0000000..7f85abe
--- /dev/null
+++ b/src/features/location/ui/UserLocationTable.tsx
@@ -0,0 +1,92 @@
+import type { LocationListDataRes } from "@/features/location/lib/data";
+import formatDate from "@/shared/lib/formatDate";
+import { Button } from "@/shared/ui/button";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/shared/ui/table";
+import { Eye, Trash2 } from "lucide-react";
+import type { Dispatch, SetStateAction } from "react";
+
+interface Props {
+ filtered: LocationListDataRes[] | [];
+ setDetail: Dispatch>;
+ setDetailDialog: Dispatch>;
+ handleDelete: (id: LocationListDataRes) => void;
+}
+
+const UserLocationTable = ({
+ filtered,
+ setDetail,
+ setDetailDialog,
+ handleDelete,
+}: Props) => {
+ return (
+
+
+
+
+ #
+ Jo'natgan foydalanuvchi
+ Jo'natgan vaqti
+ Qayerdan jo'natdi
+ Amallar
+
+
+
+ {filtered.map((item, index) => (
+
+ {index + 1}
+
+ {item.user.first_name} {item.user.last_name}
+
+
+ {formatDate.format(item.created_at, "DD-MM-YYYY")}
+
+
+
+ {item.district
+ ? "Tuman"
+ : item.place
+ ? "Obyekt"
+ : item.doctor
+ ? "Shifokor"
+ : item.pharmacy
+ ? "Dorixona"
+ : "Turgan joyidan"}
+
+
+
+
+
+
+
+ ))}
+
+
+
+ );
+};
+
+export default UserLocationTable;
diff --git a/src/features/objects/lib/api.ts b/src/features/objects/lib/api.ts
index 85b5d79..9f96669 100644
--- a/src/features/objects/lib/api.ts
+++ b/src/features/objects/lib/api.ts
@@ -4,7 +4,7 @@ import type {
ObjectUpdate,
} from "@/features/objects/lib/data";
import httpClient from "@/shared/config/api/httpClient";
-import { OBJECT } from "@/shared/config/api/URLs";
+import { API_URLS } from "@/shared/config/api/URLs";
import type { AxiosResponse } from "axios";
export const object_api = {
@@ -15,22 +15,22 @@ export const object_api = {
district?: string;
user?: string;
}): Promise> {
- const res = await httpClient.get(`${OBJECT}list/`, { params });
+ const res = await httpClient.get(`${API_URLS.OBJECT}list/`, { params });
return res;
},
async create(body: ObjectCreate) {
- const res = await httpClient.post(`${OBJECT}create/`, body);
+ const res = await httpClient.post(`${API_URLS.OBJECT}create/`, body);
return res;
},
async update({ body, id }: { id: number; body: ObjectUpdate }) {
- const res = await httpClient.patch(`${OBJECT}${id}/update/`, body);
+ const res = await httpClient.patch(`${API_URLS.OBJECT}${id}/update/`, body);
return res;
},
async delete(id: number) {
- const res = await httpClient.delete(`${OBJECT}${id}/delete/`);
+ const res = await httpClient.delete(`${API_URLS.OBJECT}${id}/delete/`);
return res;
},
};
diff --git a/src/features/pharm/lib/api.ts b/src/features/pharm/lib/api.ts
new file mode 100644
index 0000000..0d3b5a7
--- /dev/null
+++ b/src/features/pharm/lib/api.ts
@@ -0,0 +1,33 @@
+import type { FactoryCreate, FactoryListRes } from "@/features/pharm/lib/data";
+import httpClient from "@/shared/config/api/httpClient";
+import { API_URLS } from "@/shared/config/api/URLs";
+import type { AxiosResponse } from "axios";
+
+export const factory_api = {
+ async list(params: {
+ limit?: number;
+ offset?: number;
+ name?: string;
+ }): Promise> {
+ const res = await httpClient.get(`${API_URLS.FACTORY}list/`, { params });
+ return res;
+ },
+
+ async create(body: FactoryCreate) {
+ const res = await httpClient.post(`${API_URLS.FACTORY}create/`, body);
+ return res;
+ },
+
+ async update({ id, body }: { id: number; body: FactoryCreate }) {
+ const res = await httpClient.patch(
+ `${API_URLS.FACTORY}${id}/update/`,
+ body,
+ );
+ return res;
+ },
+
+ async delete(id: number) {
+ const res = await httpClient.delete(`${API_URLS.FACTORY}${id}/delete/`);
+ return res;
+ },
+};
diff --git a/src/features/pharm/lib/data.ts b/src/features/pharm/lib/data.ts
index 9dc1e98..238d56f 100644
--- a/src/features/pharm/lib/data.ts
+++ b/src/features/pharm/lib/data.ts
@@ -9,3 +9,25 @@ export const pharmData: PharmType[] = [
name: "Meridyn",
},
];
+
+export interface FactoryListRes {
+ status_code: number;
+ status: string;
+ message: string;
+ data: {
+ count: number;
+ next: null | string;
+ previous: null | string;
+ results: FactoryListDataRes[];
+ };
+}
+
+export interface FactoryListDataRes {
+ id: number;
+ name: string;
+ created_at: string;
+}
+
+export interface FactoryCreate {
+ name: string;
+}
diff --git a/src/features/pharm/ui/AddedPharm.tsx b/src/features/pharm/ui/AddedPharm.tsx
index 90af041..738ccb4 100644
--- a/src/features/pharm/ui/AddedPharm.tsx
+++ b/src/features/pharm/ui/AddedPharm.tsx
@@ -1,4 +1,5 @@
-import type { PharmType } from "@/features/pharm/lib/data";
+import { factory_api } from "@/features/pharm/lib/api";
+import type { FactoryCreate, PharmType } from "@/features/pharm/lib/data";
import { pharmForm } from "@/features/pharm/lib/form";
import { Button } from "@/shared/ui/button";
import {
@@ -11,19 +12,21 @@ import {
import { Input } from "@/shared/ui/input";
import { Label } from "@/shared/ui/label";
import { zodResolver } from "@hookform/resolvers/zod";
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import type { AxiosError } from "axios";
import { Loader2 } from "lucide-react";
-import { useState, type Dispatch, type SetStateAction } from "react";
+import { type Dispatch, type SetStateAction } from "react";
import { useForm } from "react-hook-form";
+import { toast } from "sonner";
import type z from "zod";
interface Props {
initialValues: PharmType | null;
setDialogOpen: Dispatch>;
- setPlans: Dispatch>;
}
-const AddedPharm = ({ initialValues, setDialogOpen, setPlans }: Props) => {
- const [load, setLoad] = useState(false);
+const AddedPharm = ({ initialValues, setDialogOpen }: Props) => {
+ const queryClient = useQueryClient();
const form = useForm>({
resolver: zodResolver(pharmForm),
defaultValues: {
@@ -31,35 +34,51 @@ const AddedPharm = ({ initialValues, setDialogOpen, setPlans }: Props) => {
},
});
+ const { mutate, isPending } = useMutation({
+ mutationFn: (body: FactoryCreate) => factory_api.create(body),
+ onSuccess: () => {
+ queryClient.refetchQueries({ queryKey: ["factory_list"] });
+ setDialogOpen(false);
+ },
+ onError: (err: AxiosError) => {
+ const errMessage = err.response?.data as { message: string };
+ const messageText = errMessage.message;
+ toast.error(messageText || "Xatolik yuz berdi", {
+ richColors: true,
+ position: "top-center",
+ });
+ },
+ });
+
+ const { mutate: update, isPending: updatePending } = useMutation({
+ mutationFn: ({ id, body }: { id: number; body: FactoryCreate }) =>
+ factory_api.update({ body, id }),
+ onSuccess: () => {
+ queryClient.refetchQueries({ queryKey: ["factory_list"] });
+ setDialogOpen(false);
+ },
+ onError: (err: AxiosError) => {
+ const errMessage = err.response?.data as { message: string };
+ const messageText = errMessage.message;
+ toast.error(messageText || "Xatolik yuz berdi", {
+ richColors: true,
+ position: "top-center",
+ });
+ },
+ });
+
function onSubmit(data: z.infer) {
- setLoad(true);
- if (initialValues) {
- setTimeout(() => {
- setPlans((prev) =>
- prev.map((plan) =>
- plan.id === initialValues.id
- ? {
- ...plan,
- ...data,
- }
- : plan,
- ),
- );
- setLoad(false);
- setDialogOpen(false);
- }, 2000);
+ if (!initialValues) {
+ mutate({
+ name: data.name,
+ });
} else {
- setTimeout(() => {
- setPlans((prev) => [
- ...prev,
- {
- id: prev.length ? prev[prev.length - 1].id + 1 : 1,
- name: data.name,
- },
- ]);
- setLoad(false);
- setDialogOpen(false);
- }, 2000);
+ update({
+ body: {
+ name: data.name,
+ },
+ id: initialValues.id,
+ });
}
}
@@ -87,9 +106,9 @@ const AddedPharm = ({ initialValues, setDialogOpen, setPlans }: Props) => {
@@ -68,13 +101,13 @@ export const SpecificationDetail = ({
- {specification.medicines.length}
+ {specification.order_items.length}
Dorilar ro'yxati
- {specification.medicines.map((med, index) => (
+ {specification.order_items.map((med, index) => (
- {med.name}
+ {med.product}
- Miqdor: {med.count} ta
+ Miqdor: {med.quantity} ta
×
- Narx: {formatPrice(med.price)}
+ Narx:{" "}
+
+ {formatPrice(
+ Number(med.total_price) / med.quantity,
+ )}
+
Jami
- {formatPrice(med.count * med.price)}
+ {formatPrice(med.total_price)}
@@ -121,29 +159,38 @@ export const SpecificationDetail = ({
Jami narx:
- {formatPrice(specification.totalPrice)}
+ {formatPrice(specification.total_price)}
- Chegirma foizi:
+ To'langan foizi:
- {specification.percentage}%
+ {specification.advance}%
- To'lanadi:
+ To'langan:
- {formatPrice(specification.paidPrice)}
+ {formatPrice(specification.paid_price)}
+
+ downloadFile(specification.file, `order-${specification.id}`)
+ }
+ >
+
+ PDF faylda yuklab olish
+
diff --git a/src/features/specifications/ui/SpecificationsList.tsx b/src/features/specifications/ui/SpecificationsList.tsx
index 0968558..b92d8f7 100644
--- a/src/features/specifications/ui/SpecificationsList.tsx
+++ b/src/features/specifications/ui/SpecificationsList.tsx
@@ -1,10 +1,9 @@
"use client";
-import {
- FakeSpecifications,
- type SpecificationsType,
-} from "@/features/specifications/lib/data";
+import { order_api } from "@/features/specifications/lib/api";
+import { type OrderListDataRes } from "@/features/specifications/lib/data";
import { AddedSpecification } from "@/features/specifications/ui/AddedSpecification";
+import DeleteOrder from "@/features/specifications/ui/DeleteOrder";
import { SpecificationDetail } from "@/features/specifications/ui/SpecificationDetail ";
import formatPrice from "@/shared/lib/formatPrice";
import { Button } from "@/shared/ui/button";
@@ -15,6 +14,7 @@ import {
DialogTitle,
DialogTrigger,
} from "@/shared/ui/dialog";
+import Pagination from "@/shared/ui/pagination";
import {
Table,
TableBody,
@@ -23,30 +23,38 @@ import {
TableHeader,
TableRow,
} from "@/shared/ui/table";
-import clsx from "clsx";
-import {
- ChevronLeft,
- ChevronRight,
- Eye,
- Pencil,
- Plus,
- Trash2,
-} from "lucide-react";
+import { useQuery } from "@tanstack/react-query";
+import { Eye, Loader2, Pencil, Plus, Trash2 } from "lucide-react";
import { useState } from "react";
const SpecificationsList = () => {
- const [data, setData] = useState(FakeSpecifications);
- const [editingPlan, setEditingPlan] = useState(
- null,
- );
- const [detail, setDetail] = useState(null);
+ const [editingPlan, setEditingPlan] = useState(null);
+ const [detail, setDetail] = useState(null);
const [detailOpen, setDetailOpen] = useState(false);
const [dialogOpen, setDialogOpen] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
- const totalPages = 5;
+ const limit = 20;
- const handleDelete = (id: number) =>
- setData((prev) => prev.filter((e) => e.id !== id));
+ const {
+ data: order,
+ isLoading,
+ isError,
+ } = useQuery({
+ queryKey: ["order_list", currentPage],
+ queryFn: () => order_api.list({ limit, offset: (currentPage - 1) * limit }),
+ select(data) {
+ return data.data.data;
+ },
+ });
+ const totalPages = order ? Math.ceil(order.count / limit) : 1;
+
+ const [openDelete, setOpenDelete] = useState(false);
+ const [pillDelete, setPillDelete] = useState(null);
+
+ const handleDelete = (id: OrderListDataRes) => {
+ setOpenDelete(true);
+ setPillDelete(id);
+ };
return (
@@ -70,7 +78,6 @@ const SpecificationsList = () => {
@@ -83,103 +90,98 @@ const SpecificationsList = () => {
-
-
-
- #
- Foydalanuvchi
- Farmasevtika
- Zakaz qilgan
- Jami
- % To‘langan
- To‘langan summa
- Amallar
-
-
-
- {data.map((item, idx) => (
-
- {idx + 1}
-
- {item.user.firstName} {item.user.lastName}
-
- {item.pharm.name}
- {item.client}
- {formatPrice(item.totalPrice)}
- {item.percentage}%
- {formatPrice(item.paidPrice)}
-
- {
- setDetail(item);
- setDetailOpen(true);
- }}
- className="bg-green-500 hover:bg-green-500 hover:text-white text-white cursor-pointer"
- >
-
-
- {
- setEditingPlan(item);
- setDialogOpen(true);
- }}
- >
-
-
- handleDelete(item.id)}
- >
-
-
-
+ {isLoading && (
+
+
+
+
+
+ )}
+
+ {isError && (
+
+
+ Ma'lumotlarni olishda xatolik yuz berdi.
+
+
+ )}
+ {!isLoading && !isError && (
+
+
+
+ #
+ Foydalanuvchi
+ Farmasevtika
+ Zakaz qilgan
+ Jami
+ % To‘langan
+ To‘langan summa
+ Amallar
- ))}
-
-
+
+
+ {order?.results.map((item, idx) => (
+
+ {idx + 1}
+
+ {item.user.first_name} {item.user.last_name}
+
+ {item.factory.name}
+ {item.employee_name}
+ {formatPrice(item.total_price)}
+ {item.advance}%
+ {formatPrice(item.paid_price)}
+
+ {
+ setDetail(item);
+ setDetailOpen(true);
+ }}
+ className="bg-green-500 hover:bg-green-500 hover:text-white text-white cursor-pointer"
+ >
+
+
+ {
+ setEditingPlan(item);
+ setDialogOpen(true);
+ }}
+ >
+
+
+ handleDelete(item)}
+ >
+
+
+
+
+ ))}
+
+
+ )}
- {/* Pagination */}
-
- setCurrentPage((prev) => Math.max(prev - 1, 1))}
- >
-
-
- {Array.from({ length: totalPages }, (_, i) => (
- setCurrentPage(i + 1)}
- >
- {i + 1}
-
- ))}
-
- setCurrentPage((prev) => Math.min(prev + 1, totalPages))
- }
- >
-
-
-
+
+
+
);
};
diff --git a/src/features/tour-plan/lib/api.ts b/src/features/tour-plan/lib/api.ts
new file mode 100644
index 0000000..afb9c43
--- /dev/null
+++ b/src/features/tour-plan/lib/api.ts
@@ -0,0 +1,39 @@
+import type {
+ PlanTourCreate,
+ PlanTourListRes,
+ PlanTourUpdate,
+} from "@/features/tour-plan/lib/data";
+import httpClient from "@/shared/config/api/httpClient";
+import { API_URLS } from "@/shared/config/api/URLs";
+import type { AxiosResponse } from "axios";
+
+export const tour_plan_api = {
+ async list(params: {
+ limit?: number;
+ offset?: number;
+ name?: string;
+ date?: string;
+ user?: string;
+ }): Promise> {
+ const res = await httpClient.get(`${API_URLS.TOUR_PLAN}list/`, { params });
+ return res;
+ },
+
+ async create(body: PlanTourCreate) {
+ const res = await httpClient.post(`${API_URLS.TOUR_PLAN}create/`, body);
+ return res;
+ },
+
+ async update({ body, id }: { id: number; body: PlanTourUpdate }) {
+ const res = await httpClient.patch(
+ `${API_URLS.TOUR_PLAN}${id}/update/`,
+ body,
+ );
+ return res;
+ },
+
+ async delete(id: number) {
+ const res = await httpClient.delete(`${API_URLS.TOUR_PLAN}${id}/delete/`);
+ return res;
+ },
+};
diff --git a/src/features/tour-plan/lib/data.ts b/src/features/tour-plan/lib/data.ts
index 83e3e5b..3bbe79a 100644
--- a/src/features/tour-plan/lib/data.ts
+++ b/src/features/tour-plan/lib/data.ts
@@ -58,3 +58,46 @@ export const fakeTourPlan: TourPlanType[] = [
status: "planned",
},
];
+
+export interface PlanTourListRes {
+ status_code: number;
+ status: string;
+ message: string;
+ data: {
+ count: number;
+ next: null | string;
+ previous: null | string;
+ results: PlanTourListDataRes[];
+ };
+}
+
+export interface PlanTourListDataRes {
+ id: number;
+ place_name: string;
+ user: {
+ id: number;
+ first_name: string;
+ last_name: string;
+ };
+ latitude: null | string;
+ longitude: null | string;
+ location_send: boolean;
+ date: null | string;
+ created_at: string;
+}
+
+export interface PlanTourCreate {
+ place_name: string;
+ // latitude: number;
+ // longitude: number;
+ user_id: number;
+ date: string;
+}
+
+export interface PlanTourUpdate {
+ place_name: string;
+ user: number;
+ // latitude: number;
+ // longitude: number;
+ date: string;
+}
diff --git a/src/features/tour-plan/lib/form.ts b/src/features/tour-plan/lib/form.ts
index 9306c68..962fd30 100644
--- a/src/features/tour-plan/lib/form.ts
+++ b/src/features/tour-plan/lib/form.ts
@@ -4,6 +4,4 @@ export const tourPlanForm = z.object({
district: z.string().min(1, { message: "Majburiy maydon" }),
user: z.string().min(1, { message: "Majburiy maydon" }),
date: z.date().min(1, { message: "Majburiy maydon" }),
- long: z.string().min(1, { message: "Majburiy maydon" }),
- lat: z.string().min(1, { message: "Majburiy maydon" }),
});
diff --git a/src/features/tour-plan/ui/AddedTourPlan.tsx b/src/features/tour-plan/ui/AddedTourPlan.tsx
index e54342f..635b4c9 100644
--- a/src/features/tour-plan/ui/AddedTourPlan.tsx
+++ b/src/features/tour-plan/ui/AddedTourPlan.tsx
@@ -1,8 +1,23 @@
-import type { TourPlanType } from "@/features/tour-plan/lib/data";
+import { tour_plan_api } from "@/features/tour-plan/lib/api";
+import type {
+ PlanTourCreate,
+ PlanTourListDataRes,
+ PlanTourUpdate,
+} from "@/features/tour-plan/lib/data";
import { tourPlanForm } from "@/features/tour-plan/lib/form";
-import { FakeUserList } from "@/features/users/lib/data";
+import { user_api } from "@/features/users/lib/api";
+import formatDate from "@/shared/lib/formatDate";
+import { cn } from "@/shared/lib/utils";
import { Button } from "@/shared/ui/button";
import { Calendar } from "@/shared/ui/calendar";
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/shared/ui/command";
import {
Form,
FormControl,
@@ -13,103 +28,196 @@ import {
import { Input } from "@/shared/ui/input";
import { Label } from "@/shared/ui/label";
import { Popover, PopoverContent, PopoverTrigger } from "@/shared/ui/popover";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/shared/ui/select";
import { zodResolver } from "@hookform/resolvers/zod";
-import { Circle, Map, Placemark, YMaps } from "@pbe/react-yandex-maps";
-import { ChevronDownIcon, Loader2 } from "lucide-react";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import type { AxiosError } from "axios";
+import { Check, ChevronDownIcon, ChevronsUpDown, Loader2 } from "lucide-react";
import { useState, type Dispatch, type SetStateAction } from "react";
import { useForm } from "react-hook-form";
+import { toast } from "sonner";
import type z from "zod";
interface Props {
- initialValues: TourPlanType | null;
+ initialValues: PlanTourListDataRes | null;
setDialogOpen: Dispatch>;
- setPlans: Dispatch>;
}
-const AddedTourPlan = ({ initialValues, setDialogOpen, setPlans }: Props) => {
- const [load, setLoad] = useState(false);
+const AddedTourPlan = ({ initialValues, setDialogOpen }: Props) => {
+ const queryClient = useQueryClient();
const form = useForm>({
resolver: zodResolver(tourPlanForm),
defaultValues: {
- date: initialValues?.date || undefined,
- district: initialValues?.district || "",
- lat: initialValues?.lat || "41.2949",
- long: initialValues?.long || "69.2361",
+ date: initialValues?.date ? new Date(initialValues?.date) : undefined,
+ district: initialValues?.place_name || "",
+
user: initialValues?.user.id.toString() || "",
},
});
+ const { mutate, isPending } = useMutation({
+ mutationFn: (body: PlanTourCreate) => tour_plan_api.create(body),
+ onSuccess: () => {
+ queryClient.refetchQueries({ queryKey: ["tour_plan_list"] });
+ setDialogOpen(false);
+ },
+ onError: (err: AxiosError) => {
+ const errMessage = err.response?.data as { message: string };
+ const messageText = errMessage.message;
+ toast.error(messageText || "Xatolik yuz berdi", {
+ richColors: true,
+ position: "top-center",
+ });
+ },
+ });
+
+ const { mutate: edit, isPending: editPending } = useMutation({
+ mutationFn: ({ body, id }: { id: number; body: PlanTourUpdate }) =>
+ tour_plan_api.update({ body, id }),
+ onSuccess: () => {
+ queryClient.refetchQueries({ queryKey: ["tour_plan_list"] });
+ setDialogOpen(false);
+ },
+ onError: (err: AxiosError) => {
+ const errMessage = err.response?.data as { message: string };
+ const messageText = errMessage.message;
+ toast.error(messageText || "Xatolik yuz berdi", {
+ richColors: true,
+ position: "top-center",
+ });
+ },
+ });
+
const [open, setOpen] = useState(false);
+ const [searchUser, setSearchUser] = useState("");
- const lat = form.watch("lat");
- const long = form.watch("long");
+ const [openUser, setOpenUser] = useState(false);
+ const { data: user, isLoading: isUserLoading } = useQuery({
+ queryKey: ["user_list", searchUser],
+ queryFn: () => {
+ const params: {
+ limit?: number;
+ offset?: number;
+ search?: string;
+ is_active?: boolean | string;
+ region_id?: number;
+ } = {
+ limit: 8,
+ search: searchUser,
+ };
- const handleMapClick = (e: { get: (key: string) => number[] }) => {
- const coords = e.get("coords");
- form.setValue("lat", coords[0].toString());
- form.setValue("long", coords[1].toString());
- };
+ return user_api.list(params);
+ },
+ select(data) {
+ return data.data.data;
+ },
+ });
function onSubmit(values: z.infer) {
- setLoad(true);
- const newObject: TourPlanType = {
- id: initialValues ? initialValues.id : Date.now(),
- user: FakeUserList.find((u) => u.id === Number(values.user))!,
- date: values.date,
- district: values.district,
- lat: values.lat,
- long: values.long,
- status: "planned",
- };
-
- setTimeout(() => {
- setPlans((prev) => {
- if (initialValues) {
- return prev.map((item) =>
- item.id === initialValues.id ? newObject : item,
- );
- } else {
- return [...prev, newObject];
- }
+ if (!initialValues) {
+ mutate({
+ date: formatDate.format(values.date, "YYYY-MM-DD"),
+ place_name: values.district,
+ user_id: Number(values.user),
});
- setLoad(false);
- setDialogOpen(false);
- }, 2000);
+ } else if (initialValues) {
+ edit({
+ body: {
+ user: Number(values.user),
+ date: formatDate.format(values.date, "YYYY-MM-DD"),
+ place_name: values.district,
+ },
+ id: initialValues.id,
+ });
+ }
}
return (