task update

This commit is contained in:
Samandar Turgunboyev
2026-01-26 16:40:07 +05:00
parent 30f842c53d
commit 88dc5470d9
13 changed files with 165 additions and 46 deletions

View File

@@ -37,4 +37,11 @@ export const doctor_api = {
const res = await httpClient.delete(`${API_URLS.DOCTOR}${id}/delete/`); const res = await httpClient.delete(`${API_URLS.DOCTOR}${id}/delete/`);
return res; return res;
}, },
async export() {
const res = await httpClient.get(`${API_URLS.DOCTOR_EXPORT}`, {
responseType: "blob",
});
return res;
},
}; };

View File

@@ -1,3 +1,4 @@
import { doctor_api } from "@/features/doctors/lib/api";
import type { DoctorListResData } from "@/features/doctors/lib/data"; import type { DoctorListResData } from "@/features/doctors/lib/data";
import AddedDoctor from "@/features/doctors/ui/AddedDoctor"; import AddedDoctor from "@/features/doctors/ui/AddedDoctor";
import { Button } from "@/shared/ui/button"; import { Button } from "@/shared/ui/button";
@@ -9,8 +10,11 @@ import {
DialogTrigger, DialogTrigger,
} from "@/shared/ui/dialog"; } from "@/shared/ui/dialog";
import { Input } from "@/shared/ui/input"; import { Input } from "@/shared/ui/input";
import { Plus } from "lucide-react"; import { useMutation } from "@tanstack/react-query";
import type { AxiosError } from "axios";
import { CloudDownload, Loader2, Plus } from "lucide-react";
import type { Dispatch, SetStateAction } from "react"; import type { Dispatch, SetStateAction } from "react";
import { toast } from "sonner";
interface Props { interface Props {
searchName: string; searchName: string;
@@ -49,6 +53,45 @@ const FilterDoctor = ({
setEditingPlan, setEditingPlan,
editingPlan, editingPlan,
}: Props) => { }: Props) => {
const { mutate, isPending } = useMutation({
mutationFn: async () => {
const res = await doctor_api.export();
return res.data;
},
onSuccess: (data: Blob) => {
// Blob URL yaratish
const url = window.URL.createObjectURL(
new Blob([data], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
}),
);
// <a> elementi orqali yuklab olish
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", "doctor_export.xlsx"); // Fayl nomi
document.body.appendChild(link);
link.click();
link.remove();
// Blob URL-ni ozod qilish
window.URL.revokeObjectURL(url);
toast.success("Excel muvaffaqiyatli yuklab olindi", {
position: "top-center",
richColors: true,
});
},
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 ( return (
<div className="flex justify-end gap-2 w-full"> <div className="flex justify-end gap-2 w-full">
<Input <Input
@@ -87,6 +130,17 @@ const FilterDoctor = ({
onChange={(e) => setSearchUser(e.target.value)} onChange={(e) => setSearchUser(e.target.value)}
className="w-full md:w-48" className="w-full md:w-48"
/> />
<Button
variant="default"
className="bg-blue-500 cursor-pointer hover:bg-blue-500"
onClick={() => mutate()}
disabled={isPending}
>
<CloudDownload className="!h-5 !w-5" /> Excel formatda yuklash
{isPending && <Loader2 className="animate-spin" />}
</Button>
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}> <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button <Button

View File

@@ -38,4 +38,11 @@ export const pharmacies_api = {
const res = await httpClient.delete(`${API_URLS.PHARMACIES}${id}/delete/`); const res = await httpClient.delete(`${API_URLS.PHARMACIES}${id}/delete/`);
return res; return res;
}, },
async export() {
const res = await httpClient.get(`${API_URLS.PHARMACIES_EXPORT}`, {
responseType: "blob",
});
return res;
},
}; };

View File

@@ -123,9 +123,9 @@ export interface PharmaciesListData {
export interface CreatePharmaciesReq { export interface CreatePharmaciesReq {
name: string; name: string;
inn: string; inn?: string;
owner_phone: string; owner_phone: string;
responsible_phone: string; responsible_phone?: string;
district_id: number; district_id: number;
place_id: number; place_id: number;
user_id: number; user_id: number;
@@ -136,9 +136,9 @@ export interface CreatePharmaciesReq {
export interface UpdatePharmaciesReq { export interface UpdatePharmaciesReq {
name: string; name: string;
inn: string; inn?: string;
owner_phone: string; owner_phone: string;
responsible_phone: string; responsible_phone?: string;
longitude: number; longitude: number;
latitude: number; latitude: number;
extra_location: { longitude: number; latitude: number }; extra_location: { longitude: number; latitude: number };

View File

@@ -2,9 +2,9 @@ import z from "zod";
export const PharmForm = z.object({ export const PharmForm = z.object({
name: z.string().min(1, { message: "Majburiy maydon" }), name: z.string().min(1, { message: "Majburiy maydon" }),
inn: z.string().min(1, { message: "Majburiy maydon" }), inn: z.string().optional(),
phone_number: z.string().min(1, { message: "Majburiy maydon" }), phone_number: z.string().min(1, { message: "Majburiy maydon" }),
additional_phone: z.string().min(1, { message: "Majburiy maydon" }), additional_phone: z.string().optional(),
district: z.string().min(1, { message: "Majburiy maydon" }), district: z.string().min(1, { message: "Majburiy maydon" }),
user: z.string().min(1, { message: "Majburiy maydon" }), user: z.string().min(1, { message: "Majburiy maydon" }),
object: z.string().min(1, { message: "Majburiy maydon" }), object: z.string().min(1, { message: "Majburiy maydon" }),

View File

@@ -249,36 +249,31 @@ const AddedPharmacies = ({ initialValues, setDialogOpen }: Props) => {
}); });
function onSubmit(values: z.infer<typeof PharmForm>) { function onSubmit(values: z.infer<typeof PharmForm>) {
const baseBody = {
extra_location: {
latitude: Number(values.lat),
longitude: Number(values.long),
},
latitude: Number(values.lat),
longitude: Number(values.long),
name: values.name,
owner_phone: onlyNumber(values.phone_number),
...(values.additional_phone && {
responsible_phone: onlyNumber(values.additional_phone),
}),
...(values.inn && { inn: values.inn }), // 👈 faqat bolsa yuboriladi
};
if (initialValues) { if (initialValues) {
edit({ edit({
id: initialValues.id, id: initialValues.id,
body: { body: baseBody,
extra_location: {
latitude: Number(values.lat),
longitude: Number(values.long),
},
latitude: Number(values.lat),
longitude: Number(values.long),
inn: values.inn,
name: values.name,
owner_phone: onlyNumber(values.phone_number),
responsible_phone: onlyNumber(values.additional_phone),
},
}); });
} else { } else {
mutate({ mutate({
...baseBody,
district_id: Number(values.district), district_id: Number(values.district),
extra_location: {
latitude: Number(values.lat),
longitude: Number(values.long),
},
latitude: Number(values.lat),
longitude: Number(values.long),
inn: values.inn,
name: values.name,
owner_phone: onlyNumber(values.phone_number),
place_id: Number(values.object), place_id: Number(values.object),
responsible_phone: onlyNumber(values.additional_phone),
user_id: Number(values.user), user_id: Number(values.user),
}); });
} }
@@ -343,7 +338,7 @@ const AddedPharmacies = ({ initialValues, setDialogOpen }: Props) => {
<Input <Input
placeholder="+998 90 123-45-67" placeholder="+998 90 123-45-67"
{...field} {...field}
value={formatPhone(field.value)} value={field.value && formatPhone(field.value)}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />

View File

@@ -1,3 +1,4 @@
import { pharmacies_api } from "@/features/pharmacies/lib/api";
import type { PharmaciesListData } from "@/features/pharmacies/lib/data"; import type { PharmaciesListData } from "@/features/pharmacies/lib/data";
import AddedPharmacies from "@/features/pharmacies/ui/AddedPharmacies"; import AddedPharmacies from "@/features/pharmacies/ui/AddedPharmacies";
import { Button } from "@/shared/ui/button"; import { Button } from "@/shared/ui/button";
@@ -9,8 +10,11 @@ import {
DialogTrigger, DialogTrigger,
} from "@/shared/ui/dialog"; } from "@/shared/ui/dialog";
import { Input } from "@/shared/ui/input"; import { Input } from "@/shared/ui/input";
import { Plus } from "lucide-react"; import { useMutation } from "@tanstack/react-query";
import type { AxiosError } from "axios";
import { CloudDownload, Loader2, Plus } from "lucide-react";
import type { Dispatch, SetStateAction } from "react"; import type { Dispatch, SetStateAction } from "react";
import { toast } from "sonner";
interface Props { interface Props {
searchName: string; searchName: string;
@@ -41,6 +45,45 @@ const PharmaciesFilter = ({
setEditingPlan, setEditingPlan,
editingPlan, editingPlan,
}: Props) => { }: Props) => {
const { mutate, isPending } = useMutation({
mutationFn: async () => {
const res = await pharmacies_api.export();
return res.data;
},
onSuccess: (data: Blob) => {
// Blob URL yaratish
const url = window.URL.createObjectURL(
new Blob([data], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
}),
);
// <a> elementi orqali yuklab olish
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", "dorixonalar.xlsx"); // Fayl nomi
document.body.appendChild(link);
link.click();
link.remove();
// Blob URL-ni ozod qilish
window.URL.revokeObjectURL(url);
toast.success("Excel muvaffaqiyatli yuklab olindi", {
position: "top-center",
richColors: true,
});
},
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 ( return (
<div className="flex justify-end gap-2 w-full"> <div className="flex justify-end gap-2 w-full">
<Input <Input
@@ -67,6 +110,15 @@ const PharmaciesFilter = ({
onChange={(e) => setSearchUser(e.target.value)} onChange={(e) => setSearchUser(e.target.value)}
className="w-full md:w-48" className="w-full md:w-48"
/> />
<Button
variant="default"
className="bg-blue-500 cursor-pointer hover:bg-blue-500"
onClick={() => mutate()}
disabled={isPending}
>
<CloudDownload className="!h-5 !w-5" /> Excel formatda yuklash
{isPending && <Loader2 className="animate-spin" />}
</Button>
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}> <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button <Button

View File

@@ -51,7 +51,7 @@ export interface PlanListData {
} }
export interface PlanCreateReq { export interface PlanCreateReq {
title: string; // title: string;
description: string; description: string;
date: string; date: string;
user_id: number; user_id: number;
@@ -63,7 +63,7 @@ export interface PlanCreateReq {
} }
export interface PlanUpdateReq { export interface PlanUpdateReq {
title: string; // title: string;
description: string; description: string;
date: string; date: string;
longitude: number; longitude: number;

View File

@@ -1,7 +1,7 @@
import z from "zod"; import z from "zod";
export const createPlanFormData = z.object({ export const createPlanFormData = z.object({
name: z.string().min(1, { message: "Majburiy maydon" }), // name: z.string().min(1, { message: "Majburiy maydon" }),
description: z.string().min(1, { message: "Majburiy maydon" }), description: z.string().min(1, { message: "Majburiy maydon" }),
user: z.string().min(1, { message: "Majburiy maydon" }), user: z.string().min(1, { message: "Majburiy maydon" }),
date: z.string().min(1, { message: "Majburiy maydon" }), date: z.string().min(1, { message: "Majburiy maydon" }),

View File

@@ -27,7 +27,6 @@ import {
FormItem, FormItem,
FormMessage, FormMessage,
} from "@/shared/ui/form"; } from "@/shared/ui/form";
import { Input } from "@/shared/ui/input";
import { Label } from "@/shared/ui/label"; import { Label } from "@/shared/ui/label";
import { Popover, PopoverContent, PopoverTrigger } from "@/shared/ui/popover"; import { Popover, PopoverContent, PopoverTrigger } from "@/shared/ui/popover";
import { Textarea } from "@/shared/ui/textarea"; import { Textarea } from "@/shared/ui/textarea";
@@ -49,7 +48,7 @@ const AddedPlan = ({ initialValues, setDialogOpen }: Props) => {
const form = useForm<z.infer<typeof createPlanFormData>>({ const form = useForm<z.infer<typeof createPlanFormData>>({
resolver: zodResolver(createPlanFormData), resolver: zodResolver(createPlanFormData),
defaultValues: { defaultValues: {
name: initialValues?.title || "", // name: initialValues?.title || "",
description: initialValues?.description || "", description: initialValues?.description || "",
user: initialValues ? String(initialValues.user.id) : "", user: initialValues ? String(initialValues.user.id) : "",
date: initialValues ? initialValues?.date : "", date: initialValues ? initialValues?.date : "",
@@ -180,7 +179,7 @@ const AddedPlan = ({ initialValues, setDialogOpen }: Props) => {
}, },
latitude: initialValues.latitude, latitude: initialValues.latitude,
longitude: initialValues.longitude, longitude: initialValues.longitude,
title: data.name, // title: data.name,
}, },
id: initialValues.id, id: initialValues.id,
}); });
@@ -194,7 +193,7 @@ const AddedPlan = ({ initialValues, setDialogOpen }: Props) => {
}, },
latitude: lat, latitude: lat,
longitude: long, longitude: long,
title: data.name, // title: data.name,
doctor_id: data.doctor_id ? Number(data.doctor_id) : null, doctor_id: data.doctor_id ? Number(data.doctor_id) : null,
pharmacy_id: data.pharmacy_id ? Number(data.pharmacy_id) : null, pharmacy_id: data.pharmacy_id ? Number(data.pharmacy_id) : null,
user_id: Number(data.user), user_id: Number(data.user),
@@ -499,7 +498,7 @@ const AddedPlan = ({ initialValues, setDialogOpen }: Props) => {
/> />
)} )}
<FormField {/* <FormField
control={form.control} control={form.control}
name="name" name="name"
render={({ field }) => ( render={({ field }) => (
@@ -515,7 +514,7 @@ const AddedPlan = ({ initialValues, setDialogOpen }: Props) => {
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> /> */}
<FormField <FormField
control={form.control} control={form.control}

View File

@@ -8,8 +8,10 @@ export const API_URLS = {
REGIONS: `${API_V}admin/region/`, REGIONS: `${API_V}admin/region/`,
DISTRICT: `${API_V}admin/district/`, DISTRICT: `${API_V}admin/district/`,
DOCTOR: `${API_V}admin/doctor/`, DOCTOR: `${API_V}admin/doctor/`,
DOCTOR_EXPORT: `${API_V}admin/doctor/export/`,
OBJECT: `${API_V}admin/place/`, OBJECT: `${API_V}admin/place/`,
PHARMACIES: `${API_V}admin/pharmacy/`, PHARMACIES: `${API_V}admin/pharmacy/`,
PHARMACIES_EXPORT: `${API_V}admin/pharmacy/export/`,
PLANS: `${API_V}admin/plan/`, PLANS: `${API_V}admin/plan/`,
PILL: `${API_V}admin/product/`, PILL: `${API_V}admin/product/`,
LOCATION: `${API_V}admin/location/`, LOCATION: `${API_V}admin/location/`,

View File

@@ -6,7 +6,6 @@ import {
ClipboardList, ClipboardList,
FileText, FileText,
Hospital, Hospital,
ListChecks,
LogOut, LogOut,
Map, Map,
MapPin, MapPin,
@@ -77,11 +76,11 @@ const items = [
url: "/dashboard/reports", url: "/dashboard/reports",
icon: ClipboardList, icon: ClipboardList,
}, },
{ // {
title: "Tur planlar", // title: "Tur planlar",
url: "/dashboard/tour-plan", // url: "/dashboard/tour-plan",
icon: ListChecks, // icon: ListChecks,
}, // },
{ {
title: "Hududlar", title: "Hududlar",
url: "/dashboard/region", url: "/dashboard/region",

View File

@@ -10,4 +10,8 @@ export default defineConfig({
resolve: { resolve: {
alias: [{ find: "@", replacement: path.resolve(__dirname, "src") }], alias: [{ find: "@", replacement: path.resolve(__dirname, "src") }],
}, },
server: {
port: 5175,
host: true, // barcha hostlarga ruxsat
},
}); });