update api request and response
This commit is contained in:
17
src/features/distributed/lib/api.ts
Normal file
17
src/features/distributed/lib/api.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { DistributedList } from "@/features/distributed/lib/data";
|
||||
import httpClient from "@/shared/config/api/httpClient";
|
||||
import { API_URLS } from "@/shared/config/api/URLs";
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
export const distributed_api = {
|
||||
async list(params: {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
product?: string;
|
||||
user?: string;
|
||||
date?: string;
|
||||
}): Promise<AxiosResponse<DistributedList>> {
|
||||
const res = await httpClient.get(API_URLS.DISTRIBUTED, { params });
|
||||
return res;
|
||||
},
|
||||
};
|
||||
29
src/features/distributed/lib/data.ts
Normal file
29
src/features/distributed/lib/data.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export interface DistributedList {
|
||||
status_code: number;
|
||||
status: string;
|
||||
message: string;
|
||||
data: {
|
||||
count: number;
|
||||
next: null | string;
|
||||
previous: null | string;
|
||||
results: DistributedListData[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface DistributedListData {
|
||||
id: number;
|
||||
product: {
|
||||
id: number;
|
||||
name: string;
|
||||
price: number;
|
||||
};
|
||||
quantity: number;
|
||||
employee_name: string;
|
||||
user: {
|
||||
id: number;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
};
|
||||
created_at: string;
|
||||
date: string;
|
||||
}
|
||||
199
src/features/distributed/ui/DistributedList.tsx
Normal file
199
src/features/distributed/ui/DistributedList.tsx
Normal file
@@ -0,0 +1,199 @@
|
||||
import { distributed_api } from "@/features/distributed/lib/api";
|
||||
import type { DistributedListData } from "@/features/distributed/lib/data";
|
||||
import { DistributedDetail } from "@/features/distributed/ui/SpecificationDetail ";
|
||||
import formatDate from "@/shared/lib/formatDate";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import { Calendar } from "@/shared/ui/calendar";
|
||||
import { Input } from "@/shared/ui/input";
|
||||
import Pagination from "@/shared/ui/pagination";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/shared/ui/popover";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/shared/ui/table";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ChevronDownIcon, Eye, Loader2 } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
const DistributedList = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [nameFilter, setNameFilter] = useState<string>("");
|
||||
const limit = 20;
|
||||
const [disctritFilter, setDisctritFilter] = useState<string>("");
|
||||
const [openDate, setOpenDate] = useState<boolean>(false);
|
||||
const [dateFilter, setDateFilter] = useState<Date | undefined>(undefined);
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [supportDetail, setSupportDetail] =
|
||||
useState<DistributedListData | null>(null);
|
||||
|
||||
const { data, isLoading, isError } = useQuery({
|
||||
queryKey: [
|
||||
"distributed_list",
|
||||
currentPage,
|
||||
nameFilter,
|
||||
disctritFilter,
|
||||
dateFilter,
|
||||
],
|
||||
queryFn: () =>
|
||||
distributed_api.list({
|
||||
limit,
|
||||
offset: (currentPage - 1) * limit,
|
||||
product: nameFilter,
|
||||
user: disctritFilter,
|
||||
date: dateFilter && formatDate.format(dateFilter, "YYYY-MM-DD"),
|
||||
}),
|
||||
select(data) {
|
||||
return data.data.data;
|
||||
},
|
||||
});
|
||||
|
||||
const totalPages = data ? Math.ceil(data?.count / limit) : 1;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full p-10 w-full">
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-4 gap-4">
|
||||
<h1 className="text-2xl font-bold">Yordam so'rovlari ro'yxati</h1>
|
||||
|
||||
<div className="flex gap-2 mb-4">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Foydalanuvchi nomi"
|
||||
className="h-12"
|
||||
value={disctritFilter}
|
||||
onChange={(e) => setDisctritFilter(e.target.value)}
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Mahsulot nomi"
|
||||
className="h-12"
|
||||
value={nameFilter}
|
||||
onChange={(e) => setNameFilter(e.target.value)}
|
||||
/>
|
||||
|
||||
<Popover open={openDate} onOpenChange={setOpenDate}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
id="date"
|
||||
className="w-48 justify-between font-normal h-12"
|
||||
>
|
||||
{dateFilter ? dateFilter.toDateString() : "Sana"}
|
||||
<ChevronDownIcon />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-auto overflow-hidden p-0"
|
||||
align="start"
|
||||
>
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={dateFilter}
|
||||
captionLayout="dropdown"
|
||||
onSelect={(date) => {
|
||||
setDateFilter(date);
|
||||
setOpenDate(false);
|
||||
}}
|
||||
/>
|
||||
<div className="p-2 border-t bg-white">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
onClick={() => {
|
||||
setDateFilter(undefined);
|
||||
setOpenDate(false);
|
||||
}}
|
||||
>
|
||||
Tozalash
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 overflow-auto">
|
||||
{isLoading && (
|
||||
<div className="h-full flex items-center justify-center bg-white/70 z-10">
|
||||
<span className="text-lg font-medium">
|
||||
<Loader2 className="animate-spin" />
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isError && (
|
||||
<div className="h-full flex items-center justify-center z-10">
|
||||
<span className="text-lg font-medium text-red-600">
|
||||
Ma'lumotlarni olishda xatolik yuz berdi.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{!isLoading && !isError && (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="text-center">
|
||||
<TableHead className="text-start">ID</TableHead>
|
||||
<TableHead className="text-start">Kim qo'shgan</TableHead>
|
||||
<TableHead className="text-start">Xaridorning ismi</TableHead>
|
||||
<TableHead className="text-start">Mahsulot nomi</TableHead>
|
||||
<TableHead className="text-start">Nechta berilgan</TableHead>
|
||||
<TableHead className="text-start">Topshirilgan sana</TableHead>
|
||||
<TableHead className="text-start">Amallar</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data && data.results.length > 0 ? (
|
||||
data?.results.map((plan) => (
|
||||
<TableRow key={plan.id} className="text-start">
|
||||
<TableCell>{plan.id}</TableCell>
|
||||
<TableCell>
|
||||
{plan.user.first_name} {plan.user.last_name}
|
||||
</TableCell>
|
||||
<TableCell>{plan.employee_name}</TableCell>
|
||||
<TableCell>{plan.product.name}</TableCell>
|
||||
<TableCell>{plan.quantity}</TableCell>
|
||||
<TableCell>
|
||||
{formatDate.format(plan.date, "YYYY-MM-DD")}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Button
|
||||
className="bg-blue-500 hover:bg-blue-500 cursor-pointer"
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
setSupportDetail(plan);
|
||||
}}
|
||||
>
|
||||
<Eye />
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={7} className="text-center py-4 text-lg">
|
||||
Farmasevtika topilmadi.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)}
|
||||
</div>
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
setCurrentPage={setCurrentPage}
|
||||
totalPages={totalPages}
|
||||
/>
|
||||
|
||||
<DistributedDetail
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
specification={supportDetail}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DistributedList;
|
||||
92
src/features/distributed/ui/SpecificationDetail .tsx
Normal file
92
src/features/distributed/ui/SpecificationDetail .tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
"use client";
|
||||
|
||||
import type { DistributedListData } from "@/features/distributed/lib/data";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/shared/ui/dialog";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
|
||||
interface Props {
|
||||
specification: DistributedListData | null;
|
||||
open: boolean;
|
||||
setOpen: Dispatch<SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
export const DistributedDetail = ({ specification, open, setOpen }: Props) => {
|
||||
if (!specification) return null;
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent className="max-w-3xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader className="border-b pb-4">
|
||||
<DialogTitle className="text-2xl font-bold text-gray-800">
|
||||
Tafsilot
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-6 mt-6">
|
||||
{/* Asosiy ma'lumotlar - Grid */}
|
||||
<div className="grid grid-cols-1">
|
||||
{/* Xaridor */}
|
||||
<div className="bg-gradient-to-br from-blue-50 to-blue-100 rounded-lg p-4 border border-blue-200">
|
||||
<p className="text-sm text-blue-600 font-medium mb-1">
|
||||
Xaridorning ismi
|
||||
</p>
|
||||
<p className="text-lg font-semibold text-gray-800">
|
||||
{specification.employee_name}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Foydalanuvchi */}
|
||||
<div className="bg-gradient-to-br mt-5 from-purple-50 to-purple-100 rounded-lg p-4 border border-purple-200 md:col-span-2">
|
||||
<p className="text-sm text-purple-600 font-medium mb-1">
|
||||
Mas'ul xodim
|
||||
</p>
|
||||
<p className="text-lg font-semibold text-gray-800">
|
||||
{specification.user.first_name} {specification.user.last_name}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-br mt-5 from-green-50 to-green-100 rounded-lg p-4 border border-green-200 md:col-span-2">
|
||||
<p className="text-sm text-green-600 font-medium mb-1">
|
||||
Topshirilgan sanasi
|
||||
</p>
|
||||
<p className="text-lg font-semibold text-gray-800">
|
||||
{specification.date}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Dorilar ro'yxati */}
|
||||
<div className="bg-gray-50 rounded-lg p-5 border border-gray-200">
|
||||
<h3 className="text-lg font-bold text-gray-800 mb-4 flex items-center">
|
||||
Topshirilgan dori
|
||||
</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="bg-white rounded-lg p-4 border border-gray-200 hover:border-indigo-300 transition-colors">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<p className="font-semibold text-gray-800">
|
||||
{specification.product.name}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 text-sm text-gray-600">
|
||||
<span>
|
||||
Miqdor: <strong>{specification.quantity} ta</strong>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -17,6 +17,7 @@ export const doctor_api = {
|
||||
work_place?: string;
|
||||
sphere?: string;
|
||||
user?: string;
|
||||
user_id?: number;
|
||||
}): Promise<AxiosResponse<DoctorListRes>> {
|
||||
const res = await httpClient.get(`${API_URLS.DOCTOR}list/`, { params });
|
||||
return res;
|
||||
|
||||
@@ -106,16 +106,19 @@ const AddedDoctor = ({ initialValues, setDialogOpen }: Props) => {
|
||||
return data.data.data;
|
||||
},
|
||||
});
|
||||
const user_id = form.watch("user");
|
||||
|
||||
const { data: object, isLoading: isObjectLoading } = useQuery({
|
||||
queryKey: ["object_list", searchUser, selectDiscrit],
|
||||
queryKey: ["object_list", searchUser, selectDiscrit, user_id],
|
||||
queryFn: () => {
|
||||
const params: {
|
||||
name?: string;
|
||||
district?: string;
|
||||
user_id?: number;
|
||||
} = {
|
||||
name: searchUser,
|
||||
district: selectDiscrit,
|
||||
user_id: Number(user_id),
|
||||
};
|
||||
|
||||
return object_api.list(params);
|
||||
@@ -125,8 +128,6 @@ const AddedDoctor = ({ initialValues, setDialogOpen }: Props) => {
|
||||
},
|
||||
});
|
||||
|
||||
const user_id = form.watch("user");
|
||||
|
||||
const { data: discrit, isLoading: discritLoading } = useQuery({
|
||||
queryKey: ["discrit_list", searchDiscrit, user_id],
|
||||
queryFn: () => {
|
||||
|
||||
@@ -14,6 +14,7 @@ export const object_api = {
|
||||
name?: string;
|
||||
district?: string;
|
||||
user?: string;
|
||||
user_id?: number;
|
||||
}): Promise<AxiosResponse<ObjectListRes>> {
|
||||
const res = await httpClient.get(`${API_URLS.OBJECT}list/`, { params });
|
||||
return res;
|
||||
|
||||
@@ -15,6 +15,7 @@ export const pharmacies_api = {
|
||||
place?: string;
|
||||
district?: string;
|
||||
user?: string;
|
||||
user_id?: number;
|
||||
}): Promise<AxiosResponse<PharmaciesListRes>> {
|
||||
const res = await httpClient.get(`${API_URLS.PHARMACIES}list/`, { params });
|
||||
return res;
|
||||
|
||||
@@ -102,16 +102,19 @@ const AddedPharmacies = ({ initialValues, setDialogOpen }: Props) => {
|
||||
return data.data.data;
|
||||
},
|
||||
});
|
||||
const user_id = form.watch("user");
|
||||
|
||||
const { data: object, isLoading: isObjectLoading } = useQuery({
|
||||
queryKey: ["object_list", searchUser, selectDiscrit],
|
||||
queryKey: ["object_list", searchUser, selectDiscrit, user_id],
|
||||
queryFn: () => {
|
||||
const params: {
|
||||
name?: string;
|
||||
district?: string;
|
||||
user_id?: number;
|
||||
} = {
|
||||
name: searchUser,
|
||||
district: selectDiscrit,
|
||||
user_id: Number(user_id),
|
||||
};
|
||||
|
||||
return object_api.list(params);
|
||||
@@ -121,8 +124,6 @@ const AddedPharmacies = ({ initialValues, setDialogOpen }: Props) => {
|
||||
},
|
||||
});
|
||||
|
||||
const user_id = form.watch("user");
|
||||
|
||||
const { data: discrit, isLoading: discritLoading } = useQuery({
|
||||
queryKey: ["discrit_list", searchDiscrit, user_id],
|
||||
queryFn: () => {
|
||||
|
||||
@@ -26,12 +26,27 @@ export interface PlanListData {
|
||||
title: string;
|
||||
description: string;
|
||||
date: string;
|
||||
comment: null | string;
|
||||
doctor: {
|
||||
id: number;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
} | null;
|
||||
pharmacy: {
|
||||
id: number;
|
||||
name: string;
|
||||
} | null;
|
||||
user: {
|
||||
id: number;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
};
|
||||
is_done: true;
|
||||
longitude: number;
|
||||
latitude: number;
|
||||
extra_location: {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
};
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
@@ -40,10 +55,18 @@ export interface PlanCreateReq {
|
||||
description: string;
|
||||
date: string;
|
||||
user_id: number;
|
||||
doctor_id: number | null;
|
||||
pharmacy_id: number | null;
|
||||
longitude: number;
|
||||
latitude: number;
|
||||
extra_location: { longitude: number; latitude: number };
|
||||
}
|
||||
|
||||
export interface PlanUpdateReq {
|
||||
title: string;
|
||||
description: string;
|
||||
date: string;
|
||||
longitude: number;
|
||||
latitude: number;
|
||||
extra_location: { longitude: number; latitude: number };
|
||||
}
|
||||
|
||||
@@ -5,4 +5,10 @@ export const createPlanFormData = z.object({
|
||||
description: z.string().min(1, { message: "Majburiy maydon" }),
|
||||
user: z.string().min(1, { message: "Majburiy maydon" }),
|
||||
date: z.string().min(1, { message: "Majburiy maydon" }),
|
||||
doctor_id: z.string().optional(),
|
||||
pharmacy_id: z.string().optional(),
|
||||
});
|
||||
|
||||
// longitude: number;
|
||||
// latitude: number;
|
||||
// extra_location: { longitude: number; latitude: number };
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { doctor_api } from "@/features/doctors/lib/api";
|
||||
import { pharmacies_api } from "@/features/pharmacies/lib/api";
|
||||
import { plans_api } from "@/features/plans/lib/api";
|
||||
import type {
|
||||
PlanCreateReq,
|
||||
@@ -53,8 +55,16 @@ const AddedPlan = ({ initialValues, setDialogOpen }: Props) => {
|
||||
date: initialValues ? initialValues?.date : "",
|
||||
},
|
||||
});
|
||||
|
||||
const [type, setType] = useState<"DOCTOR" | "PHARM">("DOCTOR");
|
||||
const [long, setLong] = useState<number>(41.233);
|
||||
const [lat, setLat] = useState<number>(63.233);
|
||||
const [searchUser, setSearchUser] = useState<string>("");
|
||||
const [openUser, setOpenUser] = useState<boolean>(false);
|
||||
const [searchDoctor, setSearchDoctor] = useState<string>("");
|
||||
const [openDoctor, setOpenDoctor] = useState<boolean>(false);
|
||||
const [searchPharm, setSearchPharm] = useState<string>("");
|
||||
const [openPharm, setOpenPharm] = useState<boolean>(false);
|
||||
const queryClient = useQueryClient();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
@@ -79,6 +89,50 @@ const AddedPlan = ({ initialValues, setDialogOpen }: Props) => {
|
||||
},
|
||||
});
|
||||
|
||||
const user_id = form.watch("user");
|
||||
|
||||
const { data: doctor, isLoading: isDoctorLoading } = useQuery({
|
||||
queryKey: ["doctor_list", searchDoctor, user_id],
|
||||
queryFn: () => {
|
||||
const params: {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
user_id?: number;
|
||||
full_name?: string;
|
||||
} = {
|
||||
limit: 8,
|
||||
full_name: searchDoctor,
|
||||
user_id: Number(user_id),
|
||||
};
|
||||
|
||||
return doctor_api.list(params);
|
||||
},
|
||||
select(data) {
|
||||
return data.data.data;
|
||||
},
|
||||
});
|
||||
|
||||
const { data: pharm, isLoading: isPharmLoading } = useQuery({
|
||||
queryKey: ["pharm_list", searchPharm, user_id],
|
||||
queryFn: () => {
|
||||
const params: {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
user_id?: number;
|
||||
name?: string;
|
||||
} = {
|
||||
limit: 8,
|
||||
name: searchPharm,
|
||||
user_id: Number(user_id),
|
||||
};
|
||||
|
||||
return pharmacies_api.list(params);
|
||||
},
|
||||
select(data) {
|
||||
return data.data.data;
|
||||
},
|
||||
});
|
||||
|
||||
const { mutate, isPending } = useMutation({
|
||||
mutationFn: (body: PlanCreateReq) => plans_api.create(body),
|
||||
onSuccess: () => {
|
||||
@@ -117,18 +171,32 @@ const AddedPlan = ({ initialValues, setDialogOpen }: Props) => {
|
||||
function onSubmit(data: z.infer<typeof createPlanFormData>) {
|
||||
if (initialValues) {
|
||||
edit({
|
||||
id: initialValues.id,
|
||||
body: {
|
||||
date: formatDate.format(data.date, "YYYY-MM-DD"),
|
||||
description: data.description,
|
||||
extra_location: {
|
||||
latitude: initialValues.latitude,
|
||||
longitude: initialValues.longitude,
|
||||
},
|
||||
latitude: initialValues.latitude,
|
||||
longitude: initialValues.longitude,
|
||||
title: data.name,
|
||||
},
|
||||
id: initialValues.id,
|
||||
});
|
||||
} else {
|
||||
mutate({
|
||||
date: formatDate.format(data.date, "YYYY-MM-DD"),
|
||||
description: data.description,
|
||||
extra_location: {
|
||||
latitude: lat,
|
||||
longitude: long,
|
||||
},
|
||||
latitude: lat,
|
||||
longitude: long,
|
||||
title: data.name,
|
||||
doctor_id: data.doctor_id ? Number(data.doctor_id) : null,
|
||||
pharmacy_id: data.pharmacy_id ? Number(data.pharmacy_id) : null,
|
||||
user_id: Number(data.user),
|
||||
});
|
||||
}
|
||||
@@ -223,6 +291,213 @@ const AddedPlan = ({ initialValues, setDialogOpen }: Props) => {
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => setType("DOCTOR")}
|
||||
className={cn(
|
||||
"cursor-pointer h-10 text-sm",
|
||||
type === "DOCTOR"
|
||||
? "bg-blue-700 hover:bg-blue-700 text-white"
|
||||
: "bg-gray-300 hover:bg-gray-300 text-black/80",
|
||||
)}
|
||||
>
|
||||
Shifokorga birlashtirish
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setType("PHARM")}
|
||||
type="button"
|
||||
className={cn(
|
||||
"cursor-pointer h-10 text-sm",
|
||||
type === "PHARM"
|
||||
? "bg-blue-700 hover:bg-blue-700 text-white"
|
||||
: "bg-gray-300 hover:bg-gray-300 text-black/80",
|
||||
)}
|
||||
>
|
||||
Dorixonaga birlashtirish
|
||||
</Button>
|
||||
</div>
|
||||
{type === "DOCTOR" && (
|
||||
<FormField
|
||||
name="doctor_id"
|
||||
control={form.control}
|
||||
render={({ field }) => {
|
||||
const selectedUser = doctor?.results.find(
|
||||
(u) => String(u.id) === field.value,
|
||||
);
|
||||
return (
|
||||
<FormItem className="flex flex-col">
|
||||
<Label className="text-md">Shifokorlar</Label>
|
||||
|
||||
<Popover open={openDoctor} onOpenChange={setOpenDoctor}>
|
||||
<PopoverTrigger asChild disabled={initialValues !== null}>
|
||||
<FormControl>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={openDoctor}
|
||||
className={cn(
|
||||
"w-full h-12 justify-between",
|
||||
!field.value && "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{selectedUser
|
||||
? `${selectedUser.first_name} ${selectedUser.last_name}`
|
||||
: "Shifokorni tanlang"}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
|
||||
<PopoverContent
|
||||
className="w-[--radix-popover-trigger-width] p-0"
|
||||
align="start"
|
||||
>
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
placeholder="Qidirish..."
|
||||
className="h-9"
|
||||
value={searchDoctor}
|
||||
onValueChange={setSearchDoctor}
|
||||
/>
|
||||
|
||||
<CommandList>
|
||||
{isDoctorLoading ? (
|
||||
<div className="py-6 text-center text-sm">
|
||||
<Loader2 className="mx-auto h-4 w-4 animate-spin" />
|
||||
</div>
|
||||
) : doctor && doctor.results.length > 0 ? (
|
||||
<CommandGroup>
|
||||
{doctor.results.map((u) => (
|
||||
<CommandItem
|
||||
key={u.id}
|
||||
value={`${u.id}`}
|
||||
onSelect={() => {
|
||||
field.onChange(String(u.id));
|
||||
setOpenDoctor(false);
|
||||
setLat(u.latitude);
|
||||
setLong(u.longitude);
|
||||
form.setValue("pharmacy_id", undefined);
|
||||
}}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
field.value === String(u.id)
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
{u.first_name} {u.last_name}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
) : (
|
||||
<CommandEmpty>Shifokor topilmadi</CommandEmpty>
|
||||
)}
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{type === "PHARM" && (
|
||||
<FormField
|
||||
name="pharmacy_id"
|
||||
control={form.control}
|
||||
render={({ field }) => {
|
||||
const selectedUser = pharm?.results.find(
|
||||
(u) => String(u.id) === field.value,
|
||||
);
|
||||
return (
|
||||
<FormItem className="flex flex-col">
|
||||
<Label className="text-md">Dorixonalar</Label>
|
||||
|
||||
<Popover open={openPharm} onOpenChange={setOpenPharm}>
|
||||
<PopoverTrigger asChild disabled={initialValues !== null}>
|
||||
<FormControl>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={openPharm}
|
||||
className={cn(
|
||||
"w-full h-12 justify-between",
|
||||
!field.value && "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{selectedUser
|
||||
? `${selectedUser.name}`
|
||||
: "Dorixonani tanlang"}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
|
||||
<PopoverContent
|
||||
className="w-[--radix-popover-trigger-width] p-0"
|
||||
align="start"
|
||||
>
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
placeholder="Qidirish..."
|
||||
className="h-9"
|
||||
value={searchPharm}
|
||||
onValueChange={setSearchPharm}
|
||||
/>
|
||||
|
||||
<CommandList>
|
||||
{isPharmLoading ? (
|
||||
<div className="py-6 text-center text-sm">
|
||||
<Loader2 className="mx-auto h-4 w-4 animate-spin" />
|
||||
</div>
|
||||
) : pharm && pharm.results.length > 0 ? (
|
||||
<CommandGroup>
|
||||
{pharm.results.map((u) => (
|
||||
<CommandItem
|
||||
key={u.id}
|
||||
value={`${u.id}`}
|
||||
onSelect={() => {
|
||||
field.onChange(String(u.id));
|
||||
setOpenPharm(false);
|
||||
setLat(u.latitude);
|
||||
setLong(u.longitude);
|
||||
form.setValue("doctor_id", undefined);
|
||||
}}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
field.value === String(u.id)
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
{u.name}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
) : (
|
||||
<CommandEmpty>Dorixona topilmadi</CommandEmpty>
|
||||
)}
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -307,7 +582,9 @@ const AddedPlan = ({ initialValues, setDialogOpen }: Props) => {
|
||||
|
||||
<Button
|
||||
className="w-full h-12 text-lg rounded-lg bg-blue-600 hover:bg-blue-600 cursor-pointer"
|
||||
disabled={isPending || editPending || initialValues?.is_done}
|
||||
disabled={
|
||||
isPending || editPending || initialValues?.comment ? true : false
|
||||
}
|
||||
>
|
||||
{isPending || editPending ? (
|
||||
<Loader2 className="animate-spin" />
|
||||
|
||||
@@ -59,6 +59,10 @@ const PalanTable = ({
|
||||
<TableHead className="text-start">Reja nomi</TableHead>
|
||||
<TableHead className="text-start">Tavsifi</TableHead>
|
||||
<TableHead className="text-start">Kimga tegishli</TableHead>
|
||||
<TableHead className="text-start">Shifokor biriktirgan</TableHead>
|
||||
<TableHead className="text-start">
|
||||
Dorixonaga biriktirgan
|
||||
</TableHead>
|
||||
<TableHead className="text-start">Status</TableHead>
|
||||
<TableHead className="text-right">Harakatlar</TableHead>
|
||||
</TableRow>
|
||||
@@ -72,12 +76,20 @@ const PalanTable = ({
|
||||
<TableCell>
|
||||
{plan.user.first_name + " " + plan.user.last_name}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{plan.doctor
|
||||
? plan.doctor.first_name + " " + plan.doctor.last_name
|
||||
: "-"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{plan.pharmacy ? plan.pharmacy.name : "-"}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
className={clsx(
|
||||
plan.is_done ? "text-green-500" : "text-red-500",
|
||||
plan.comment ? "text-green-500" : "text-red-500",
|
||||
)}
|
||||
>
|
||||
{plan.is_done ? "Bajarilgan" : "Bajarilmagan"}
|
||||
{plan.comment ? "Bajarilgan" : "Bajarilmagan"}
|
||||
</TableCell>
|
||||
<TableCell className="flex gap-2 justify-end">
|
||||
<Button
|
||||
@@ -94,7 +106,7 @@ const PalanTable = ({
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={plan.is_done}
|
||||
disabled={plan.comment ? true : false}
|
||||
className="bg-blue-500 text-white hover:bg-blue-500 hover:text-white cursor-pointer"
|
||||
onClick={() => {
|
||||
setEditingPlan(plan);
|
||||
@@ -107,7 +119,7 @@ const PalanTable = ({
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
className="cursor-pointer"
|
||||
disabled={plan.is_done}
|
||||
disabled={plan.comment ? true : false}
|
||||
onClick={() => handleDelete(plan)}
|
||||
>
|
||||
<Trash className="h-4 w-4" />
|
||||
|
||||
@@ -8,6 +8,13 @@ import {
|
||||
} from "@/shared/ui/dialog";
|
||||
|
||||
import { Badge } from "@/shared/ui/badge";
|
||||
import {
|
||||
Circle,
|
||||
Map,
|
||||
Placemark,
|
||||
YMaps,
|
||||
ZoomControl,
|
||||
} from "@pbe/react-yandex-maps";
|
||||
import clsx from "clsx";
|
||||
import { type Dispatch, type SetStateAction } from "react";
|
||||
|
||||
@@ -22,7 +29,7 @@ const PlanDetail = ({ detail, setDetail, plan }: Props) => {
|
||||
|
||||
return (
|
||||
<Dialog open={detail} onOpenChange={setDetail}>
|
||||
<DialogContent className="sm:max-w-lg">
|
||||
<DialogContent className="sm:max-w-lg max-h-[80vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-xl font-semibold">
|
||||
Reja haqida batafsil
|
||||
@@ -30,19 +37,47 @@ const PlanDetail = ({ detail, setDetail, plan }: Props) => {
|
||||
</DialogHeader>
|
||||
|
||||
<DialogDescription className="space-y-4 mt-2 text-md">
|
||||
{/* Reja nomi */}
|
||||
<div>
|
||||
<p className="font-semibold text-gray-900">Reja nomi:</p>
|
||||
<p>{plan.title}</p>
|
||||
</div>
|
||||
|
||||
{/* Reja tavsifi */}
|
||||
<div>
|
||||
<p className="font-semibold text-gray-900">Tavsifi:</p>
|
||||
<p>{plan.description}</p>
|
||||
</div>
|
||||
|
||||
{/* Kimga tegishli */}
|
||||
{plan.comment && (
|
||||
<div>
|
||||
<p className="font-semibold text-gray-900">Qanday bajarildi:</p>
|
||||
<p>{plan.comment}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{plan.doctor && (
|
||||
<div>
|
||||
<p className="font-semibold text-gray-900">
|
||||
Shikorga biriktirgan
|
||||
</p>
|
||||
<p>
|
||||
<span>Shifokot ismi: </span>
|
||||
{plan.doctor.first_name} {plan.doctor.last_name}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{plan.pharmacy && (
|
||||
<div>
|
||||
<p className="font-semibold text-gray-900">
|
||||
Dorixonaga biriktirgan
|
||||
</p>
|
||||
<p>
|
||||
<span>Dorixona nomi: </span>
|
||||
{plan.pharmacy.name}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<p className="font-semibold text-gray-900">Kimga tegishli:</p>
|
||||
<p>
|
||||
@@ -50,21 +85,56 @@ const PlanDetail = ({ detail, setDetail, plan }: Props) => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Reja statusi */}
|
||||
<div>
|
||||
<p className="font-semibold text-gray-900">Reja statusi:</p>
|
||||
|
||||
<Badge
|
||||
className={clsx(
|
||||
plan.is_done
|
||||
plan.comment
|
||||
? "bg-green-100 text-green-700"
|
||||
: "bg-yellow-100 text-yellow-700",
|
||||
"text-sm px-4 py-2 mt-2",
|
||||
)}
|
||||
>
|
||||
{plan.is_done ? "Bajarilgan" : "Bajarilmagan"}
|
||||
{plan.comment ? "Bajarilgan" : "Bajarilmagan"}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{plan.doctor
|
||||
? "Shifokor manzili:"
|
||||
: plan.pharmacy && "Dorixona manzili:"}
|
||||
</div>
|
||||
|
||||
<YMaps query={{ lang: "en_RU" }}>
|
||||
<div className="h-[300px] w-full rounded-md overflow-hidden">
|
||||
<Map
|
||||
state={{
|
||||
center: [plan.latitude, plan.longitude],
|
||||
zoom: 12,
|
||||
}}
|
||||
width="100%"
|
||||
height="100%"
|
||||
>
|
||||
<ZoomControl
|
||||
options={{
|
||||
position: { right: "10px", bottom: "70px" },
|
||||
}}
|
||||
/>
|
||||
|
||||
<Placemark geometry={[plan.latitude, plan.longitude]} />
|
||||
|
||||
<Circle
|
||||
geometry={[[plan.latitude, plan.longitude], 300]}
|
||||
options={{
|
||||
fillColor: "rgba(255, 100, 0, 0.3)",
|
||||
strokeColor: "rgba(255, 100, 0, 0.8)",
|
||||
strokeWidth: 2,
|
||||
}}
|
||||
/>
|
||||
</Map>
|
||||
</div>
|
||||
</YMaps>
|
||||
</DialogDescription>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
18
src/features/support/lib/api.ts
Normal file
18
src/features/support/lib/api.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { SupportListRes } from "@/features/support/lib/data";
|
||||
import httpClient from "@/shared/config/api/httpClient";
|
||||
import { API_URLS } from "@/shared/config/api/URLs";
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
export const support_api = {
|
||||
async list(params: {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
problem?: string;
|
||||
district?: string;
|
||||
user?: string;
|
||||
date?: string;
|
||||
}): Promise<AxiosResponse<SupportListRes>> {
|
||||
const res = await httpClient.get(API_URLS.SUPPORT, { params });
|
||||
return res;
|
||||
},
|
||||
};
|
||||
27
src/features/support/lib/data.ts
Normal file
27
src/features/support/lib/data.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export interface SupportListRes {
|
||||
status_code: number;
|
||||
status: string;
|
||||
message: string;
|
||||
data: {
|
||||
count: number;
|
||||
next: null | string;
|
||||
previous: null | string;
|
||||
results: SupportListData[];
|
||||
};
|
||||
}
|
||||
export interface SupportListData {
|
||||
id: number;
|
||||
problem: string;
|
||||
date: string;
|
||||
type: "PROBLEM" | "HELP";
|
||||
district: {
|
||||
id: number;
|
||||
name: string;
|
||||
} | null;
|
||||
user: {
|
||||
id: number;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
};
|
||||
created_at: string;
|
||||
}
|
||||
70
src/features/support/ui/SupportDetail.tsx
Normal file
70
src/features/support/ui/SupportDetail.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import type { SupportListData } from "@/features/support/lib/data";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/shared/ui/dialog";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
|
||||
const SupportDetail = ({
|
||||
open,
|
||||
setOpen,
|
||||
supportDetail,
|
||||
}: {
|
||||
open: boolean;
|
||||
setOpen: Dispatch<SetStateAction<boolean>>;
|
||||
supportDetail: SupportListData | null;
|
||||
}) => {
|
||||
if (supportDetail === null) return null;
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Batafsil</DialogTitle>
|
||||
<DialogDescription className="mt-5 flex flex-col gap-2">
|
||||
<p className="text-black text-lg font-medium">
|
||||
<span className="text-foreground">Kim jo'natgan:</span>{" "}
|
||||
{supportDetail?.user.first_name} {supportDetail?.user.last_name}
|
||||
</p>
|
||||
{supportDetail?.district && (
|
||||
<p className="text-black text-lg font-medium">
|
||||
<span className="text-foreground">Tuman nomi:</span>{" "}
|
||||
{supportDetail?.district?.name}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-black text-lg font-medium">
|
||||
<span className="text-foreground">Jo'natilgan vaqti:</span>{" "}
|
||||
{supportDetail?.date}
|
||||
</p>
|
||||
<p className="text-black text-lg font-medium">
|
||||
<span className="text-foreground">Xabar turi:</span>{" "}
|
||||
{supportDetail?.type === "PROBLEM"
|
||||
? "Muommo hal qilish"
|
||||
: "Yordam so'rash"}
|
||||
</p>
|
||||
<p className="text-black text-lg font-medium">
|
||||
<span className="text-foreground">Xabar tavsifi:</span>{" "}
|
||||
{supportDetail?.problem}
|
||||
</p>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="mt-5">
|
||||
<Button
|
||||
className="bg-blue-500 hover:bg-blue-500 cursor-pointer w-32 h-12 text-md"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
Yopish
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default SupportDetail;
|
||||
225
src/features/support/ui/SupportList.tsx
Normal file
225
src/features/support/ui/SupportList.tsx
Normal file
@@ -0,0 +1,225 @@
|
||||
import { support_api } from "@/features/support/lib/api";
|
||||
import type { SupportListData } from "@/features/support/lib/data";
|
||||
import SupportDetail from "@/features/support/ui/SupportDetail";
|
||||
import formatDate from "@/shared/lib/formatDate";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import { Calendar } from "@/shared/ui/calendar";
|
||||
import { Input } from "@/shared/ui/input";
|
||||
import Pagination from "@/shared/ui/pagination";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/shared/ui/popover";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/shared/ui/table";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ChevronDownIcon, Eye, Loader2 } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const SupportList = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [nameFilter, setNameFilter] = useState<string>("");
|
||||
const [disctritFilter, setDisctritFilter] = useState<string | null>(null);
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [openDate, setOpenDate] = useState<boolean>(false);
|
||||
const [supportDetail, setSupportDetail] = useState<SupportListData | null>(
|
||||
null,
|
||||
);
|
||||
const [dateFilter, setDateFilter] = useState<Date | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (disctritFilter?.length === 0) {
|
||||
setDisctritFilter(null);
|
||||
}
|
||||
}, [disctritFilter]);
|
||||
|
||||
const limit = 20;
|
||||
const { data, isLoading, isError } = useQuery({
|
||||
queryKey: [
|
||||
"factory_list",
|
||||
currentPage,
|
||||
nameFilter,
|
||||
disctritFilter,
|
||||
dateFilter,
|
||||
],
|
||||
queryFn: () => {
|
||||
const params: {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
problem?: string;
|
||||
district?: string;
|
||||
user?: string;
|
||||
date?: string;
|
||||
} = {
|
||||
limit: limit,
|
||||
offset: (currentPage - 1) * limit,
|
||||
user: nameFilter,
|
||||
};
|
||||
|
||||
if (disctritFilter !== null) {
|
||||
params.district = disctritFilter;
|
||||
}
|
||||
|
||||
if (dateFilter) {
|
||||
params.date = formatDate.format(dateFilter, "YYYY-MM-DD");
|
||||
}
|
||||
|
||||
return support_api.list(params);
|
||||
},
|
||||
select(data) {
|
||||
return data.data.data;
|
||||
},
|
||||
});
|
||||
const totalPages = data ? Math.ceil(data?.count / limit) : 1;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full p-10 w-full">
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-4 gap-4">
|
||||
<h1 className="text-2xl font-bold">Yordam so'rovlari ro'yxati</h1>
|
||||
|
||||
<div className="flex gap-2 mb-4">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Foydalanuvchi nomi"
|
||||
className="h-12"
|
||||
value={nameFilter}
|
||||
onChange={(e) => setNameFilter(e.target.value)}
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Tuman nomi"
|
||||
className="h-12"
|
||||
value={disctritFilter ?? ""}
|
||||
onChange={(e) => setDisctritFilter(e.target.value)}
|
||||
/>
|
||||
|
||||
<Popover open={openDate} onOpenChange={setOpenDate}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
id="date"
|
||||
className="w-48 justify-between font-normal h-12"
|
||||
>
|
||||
{dateFilter ? dateFilter.toDateString() : "Sana"}
|
||||
<ChevronDownIcon />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-auto overflow-hidden p-0"
|
||||
align="start"
|
||||
>
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={dateFilter}
|
||||
captionLayout="dropdown"
|
||||
onSelect={(date) => {
|
||||
setDateFilter(date);
|
||||
setOpenDate(false);
|
||||
}}
|
||||
/>
|
||||
<div className="p-2 border-t bg-white">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
onClick={() => {
|
||||
setDateFilter(undefined);
|
||||
setOpenDate(false);
|
||||
}}
|
||||
>
|
||||
Tozalash
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 overflow-auto">
|
||||
{isLoading && (
|
||||
<div className="h-full flex items-center justify-center bg-white/70 z-10">
|
||||
<span className="text-lg font-medium">
|
||||
<Loader2 className="animate-spin" />
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isError && (
|
||||
<div className="h-full flex items-center justify-center z-10">
|
||||
<span className="text-lg font-medium text-red-600">
|
||||
Ma'lumotlarni olishda xatolik yuz berdi.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{!isLoading && !isError && (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="text-center">
|
||||
<TableHead className="text-start">ID</TableHead>
|
||||
<TableHead className="text-start">Kim jo'natgan</TableHead>
|
||||
<TableHead className="text-start">Habar haqida</TableHead>
|
||||
<TableHead className="text-start">Habar turi</TableHead>
|
||||
<TableHead className="text-start">Tuman</TableHead>
|
||||
<TableHead className="text-start">Jo'natilgan sanasi</TableHead>
|
||||
<TableHead className="text-start">Amallar</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data && data.results.length > 0 ? (
|
||||
data?.results.map((plan) => (
|
||||
<TableRow key={plan.id} className="text-start">
|
||||
<TableCell>{plan.id}</TableCell>
|
||||
<TableCell>
|
||||
{plan.user.first_name} {plan.user.last_name}
|
||||
</TableCell>
|
||||
<TableCell>{plan.problem.slice(0, 50)}...</TableCell>
|
||||
<TableCell>
|
||||
{plan.type === "PROBLEM"
|
||||
? "Muommo hal qilish"
|
||||
: "Yordam so'rash"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{plan.district ? plan.district.name : "-"}
|
||||
</TableCell>
|
||||
<TableCell>{plan.date}</TableCell>
|
||||
<TableCell>
|
||||
<Button
|
||||
className="bg-blue-500 hover:bg-blue-500 cursor-pointer"
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
setSupportDetail(plan);
|
||||
}}
|
||||
>
|
||||
<Eye />
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={7} className="text-center py-4 text-lg">
|
||||
Farmasevtika topilmadi.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)}
|
||||
</div>
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
setCurrentPage={setCurrentPage}
|
||||
totalPages={totalPages}
|
||||
/>
|
||||
|
||||
<SupportDetail
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
supportDetail={supportDetail}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SupportList;
|
||||
12
src/pages/Distributed.tsx
Normal file
12
src/pages/Distributed.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import DistributedList from "@/features/distributed/ui/DistributedList";
|
||||
import SidebarLayout from "@/SidebarLayout";
|
||||
|
||||
const Distributed = () => {
|
||||
return (
|
||||
<SidebarLayout>
|
||||
<DistributedList />
|
||||
</SidebarLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Distributed;
|
||||
12
src/pages/Support.tsx
Normal file
12
src/pages/Support.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import SupportList from "@/features/support/ui/SupportList";
|
||||
import SidebarLayout from "@/SidebarLayout";
|
||||
|
||||
const Support = () => {
|
||||
return (
|
||||
<SidebarLayout>
|
||||
<SupportList />
|
||||
</SidebarLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Support;
|
||||
@@ -1,4 +1,5 @@
|
||||
import LoginLayout from "@/LoginLayout";
|
||||
import Distributed from "@/pages/Distributed";
|
||||
import Districts from "@/pages/Districts";
|
||||
import Doctors from "@/pages/Doctors";
|
||||
import UsersPage from "@/pages/Home";
|
||||
@@ -11,6 +12,7 @@ import Region from "@/pages/Region";
|
||||
import Reports from "@/pages/Reports";
|
||||
import SentLocations from "@/pages/SentLocations";
|
||||
import Specifications from "@/pages/Specifications";
|
||||
import Support from "@/pages/Support";
|
||||
import TourPlan from "@/pages/TourPlan";
|
||||
import routesConfig from "@/providers/routing/config";
|
||||
import { Navigate, useRoutes } from "react-router-dom";
|
||||
@@ -78,6 +80,14 @@ const AppRouter = () => {
|
||||
path: "/dashboard/region",
|
||||
element: <Region />,
|
||||
},
|
||||
{
|
||||
path: "/dashboard/support",
|
||||
element: <Support />,
|
||||
},
|
||||
{
|
||||
path: "/dashboard/distributed-product/",
|
||||
element: <Distributed />,
|
||||
},
|
||||
]);
|
||||
|
||||
return routes;
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
const API_V = "/api/v1/";
|
||||
|
||||
export const API_URLS = {
|
||||
BASE_URL: import.meta.env.VITE_API_URL || "https://api.meridynpharma.com",
|
||||
LOGIN: "/api/v1/authentication/admin_login/",
|
||||
USER: "/api/v1/admin/user/",
|
||||
REGION: "/api/v1/admin/district/",
|
||||
REGIONS: "/api/v1/admin/region/",
|
||||
DISTRICT: "/api/v1/admin/district/",
|
||||
DOCTOR: "/api/v1/admin/doctor/",
|
||||
OBJECT: "/api/v1/admin/place/",
|
||||
PHARMACIES: "/api/v1/admin/pharmacy/",
|
||||
PLANS: "/api/v1/admin/plan/",
|
||||
PILL: "/api/v1/admin/product/",
|
||||
LOCATION: "/api/v1/admin/location/",
|
||||
USER_LOCATION: "/api/v1/admin/user_location/",
|
||||
ORDER: "/api/v1/admin/order/",
|
||||
FACTORY: "/api/v1/admin/factory/",
|
||||
REPORT: "/api/v1/admin/payment/",
|
||||
TOUR_PLAN: "/api/v1/admin/tour_plan/",
|
||||
LOGIN: `${API_V}authentication/admin_login/`,
|
||||
USER: `${API_V}admin/user/`,
|
||||
REGION: `${API_V}admin/district/`,
|
||||
REGIONS: `${API_V}admin/region/`,
|
||||
DISTRICT: `${API_V}admin/district/`,
|
||||
DOCTOR: `${API_V}admin/doctor/`,
|
||||
OBJECT: `${API_V}admin/place/`,
|
||||
PHARMACIES: `${API_V}admin/pharmacy/`,
|
||||
PLANS: `${API_V}admin/plan/`,
|
||||
PILL: `${API_V}admin/product/`,
|
||||
LOCATION: `${API_V}admin/location/`,
|
||||
USER_LOCATION: `${API_V}admin/user_location/`,
|
||||
ORDER: `${API_V}admin/order/`,
|
||||
FACTORY: `${API_V}admin/factory/`,
|
||||
REPORT: `${API_V}admin/payment/`,
|
||||
TOUR_PLAN: `${API_V}admin/tour_plan/`,
|
||||
SUPPORT: `${API_V}admin/support/list/`,
|
||||
DISTRIBUTED: `${API_V}admin/distributed_product/list/`,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
AlertCircle,
|
||||
BriefcaseMedical,
|
||||
Building2,
|
||||
Calendar,
|
||||
@@ -96,6 +97,16 @@ const items = [
|
||||
url: "/dashboard/pharmaceuticals",
|
||||
icon: Microscope,
|
||||
},
|
||||
{
|
||||
title: "Yordam so'rovlari",
|
||||
url: "/dashboard/support",
|
||||
icon: AlertCircle,
|
||||
},
|
||||
{
|
||||
title: "Tarqatilgan dorilar",
|
||||
url: "/dashboard/distributed-product",
|
||||
icon: Pill,
|
||||
},
|
||||
];
|
||||
|
||||
export function AppSidebar() {
|
||||
|
||||
Reference in New Issue
Block a user