This commit is contained in:
Samandar Turgunboyev
2025-11-27 15:57:26 +05:00
parent 980fb1dd13
commit 969e32be09
177 changed files with 17023 additions and 995 deletions

View File

@@ -0,0 +1,471 @@
"use client";
import { district_api } from "@/features/district/lib/api";
import { object_api } from "@/features/object/lib/api";
import type { CreatePharmacyReq } from "@/features/phamarcy/lib/data";
import formatPhone from "@/shared/lib/formatPhone";
import onlyNumber from "@/shared/lib/onlyNumber";
import { Button } from "@/shared/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/shared/ui/form";
import { Input } from "@/shared/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/shared/ui/select";
import { DashboardLayout } from "@/widgets/dashboard-layout/ui/DashboardLayout";
import { zodResolver } from "@hookform/resolvers/zod";
import {
Circle,
Map,
Placemark,
Polygon,
YMaps,
ZoomControl,
} from "@pbe/react-yandex-maps";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { AxiosError } from "axios";
import { Loader2, LocateFixed } from "lucide-react";
import { useRef, useState } from "react";
import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom";
import { toast } from "sonner";
import z from "zod";
import { pharmacy_api } from "../lib/api";
import { objectForm } from "../lib/form";
interface CoordsData {
lat: number;
lon: number;
polygon: [number, number][][];
}
const CreatePharmacy = () => {
const queryClinent = useQueryClient();
const router = useNavigate();
const mapRef = useRef<ymaps.Map | null>(null);
const form = useForm<z.infer<typeof objectForm>>({
resolver: zodResolver(objectForm),
defaultValues: {
districts: "",
name: "",
inn: "",
phoneDirector: "+998",
phonePharmacy: "+998",
},
});
const { mutate, isPending } = useMutation({
mutationFn: (body: CreatePharmacyReq) => pharmacy_api.create(body),
onSuccess: () => {
router("/pharmacy");
queryClinent.refetchQueries({ queryKey: ["pharmacy_list"] });
},
onError: (error: AxiosError) => {
const data = error.response?.data as { message?: string };
const errorData = error.response?.data as {
messages?: {
token_class: string;
token_type: string;
message: string;
}[];
};
const errorName = error.response?.data as {
data?: {
name: string[];
};
};
toast.error(
errorName.data?.name[0] ||
data.message ||
errorData?.messages?.[0].message ||
"Xatolik yuz berdi",
);
},
});
const { data: districts } = useQuery({
queryKey: ["my_disctrict"],
queryFn: () => district_api.getDiscrict(),
select(data) {
return data.data.data;
},
});
const district_id = form.watch("districts");
const { data: streets } = useQuery({
queryKey: ["object_list", district_id],
queryFn: () => object_api.getAll({ district_id: Number(district_id) }),
select(data) {
return data.data.data;
},
});
const [coords, setCoords] = useState({
latitude: 41.311081,
longitude: 69.240562,
});
const getCoords = async (name: string): Promise<CoordsData | null> => {
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 > 0 && data[0].geojson) {
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: [number, number][]) =>
ring.map((coord: [number, number]) => [coord[1], coord[0]]),
);
} else if (data[0].geojson.type === "MultiPolygon") {
polygon = data[0].geojson.coordinates.map(
(poly: [number, number][][]) =>
poly[0].map((coord: [number, number]) => [coord[1], coord[0]]),
);
}
return { lat, lon, polygon };
}
return null;
};
const [polygonCoords, setPolygonCoords] = useState<
[number, number][][] | null
>(null);
const [circleCoords, setCircleCoords] = useState<[number, number] | null>(
null,
);
const handleStreetChange = (streetId: string) => {
form.setValue("streets", streetId);
const selectedStreet = streets?.find((s) => s.id === Number(streetId));
if (!selectedStreet) return;
setCoords({
latitude: selectedStreet.latitude,
longitude: selectedStreet.longitude,
});
form.setValue("latitude", selectedStreet.latitude);
form.setValue("longitude", selectedStreet.longitude);
setCircleCoords([selectedStreet.latitude, selectedStreet.longitude]);
if (mapRef.current) {
mapRef.current.setCenter(
[selectedStreet.latitude, selectedStreet.longitude],
16,
);
}
};
const handleShowMyLocation = () => {
if (!navigator.geolocation) {
alert("Sizning brauzeringiz geolokatsiyani qollab-quvvatlamaydi");
return;
}
navigator.geolocation.getCurrentPosition(
(position) => {
const lat = position.coords.latitude;
const lon = position.coords.longitude;
setCoords({ latitude: lat, longitude: lon });
form.setValue("latitude", lat);
form.setValue("longitude", lon);
if (mapRef.current) {
mapRef.current.setCenter([lat, lon], 20);
}
},
(error) => {
alert("Joylashuv aniqlanmadi: " + error.message);
},
);
};
const handleMapClick = (
e: ymaps.IEvent<MouseEvent, { coords: [number, number] }>,
) => {
const [lat, lon] = e.get("coords");
setCoords({ latitude: lat, longitude: lon });
form.setValue("latitude", lat);
form.setValue("longitude", lon);
};
const onSubmit = (values: z.infer<typeof objectForm>) => {
mutate({
district_id: Number(values.districts),
extra_location: {
latitude: values.latitude,
longitude: values.longitude,
},
inn: values.inn,
latitude: values.latitude,
longitude: values.longitude,
name: values.name,
owner_phone: onlyNumber(values.phoneDirector),
place_id: Number(values.streets),
responsible_phone: onlyNumber(values.phonePharmacy),
});
};
return (
<DashboardLayout>
<div className="max-w-3xl mx-auto space-y-6">
<h1 className="text-3xl font-bold">{"Qo'shish"}</h1>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Nomi</FormLabel>
<FormControl>
<Input {...field} placeholder="Nomi" className="h-12" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="inn"
render={({ field }) => (
<FormItem>
<FormLabel>INN</FormLabel>
<FormControl>
<Input {...field} placeholder="INN" className="h-12" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="phoneDirector"
render={({ field }) => (
<FormItem>
<FormLabel>Dorixona egasining nomeri</FormLabel>
<FormControl>
<Input
{...field}
placeholder="+998 90 123-45-67"
className="h-12"
onChange={(e) => {
const formatted = formatPhone(e.target.value);
field.onChange(formatted);
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="phonePharmacy"
render={({ field }) => (
<FormItem>
<FormLabel>Masul shaxsning nomeri</FormLabel>
<FormControl>
<Input
{...field}
placeholder="+998 90 123-45-67"
className="h-12"
onChange={(e) => {
const formatted = formatPhone(e.target.value);
field.onChange(formatted);
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="districts"
render={({ field }) => (
<FormItem>
<FormLabel>Tuman</FormLabel>
<FormControl>
<Select
key={field.value}
onValueChange={async (id) => {
field.onChange(id);
const selectedDistrict = districts?.find(
(d) => d.id === Number(id),
);
if (!selectedDistrict) return;
const coordsData = await getCoords(
selectedDistrict.name,
);
if (!coordsData) return;
setCoords({
latitude: coordsData.lat,
longitude: coordsData.lon,
});
setPolygonCoords(coordsData.polygon);
}}
value={field.value}
>
<SelectTrigger className="w-full h-12">
<SelectValue placeholder="Tumanlar" />
</SelectTrigger>
<SelectContent>
{districts?.map((d) => (
<SelectItem key={d.id} value={String(d.id)}>
{d.name}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="streets"
render={({ field }) => (
<FormItem>
<FormLabel>{"Ko'cha"}</FormLabel>
<FormControl>
<Select
key={field.value}
value={field.value}
onValueChange={(value) => {
field.onChange(value);
handleStreetChange(value);
}}
>
<SelectTrigger className="w-full h-12">
<SelectValue placeholder="Ko'chalar" />
</SelectTrigger>
<SelectContent>
{streets?.map((s) => (
<SelectItem key={s.id} value={String(s.id)}>
{s.name}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="latitude"
render={() => (
<FormItem>
<FormLabel>Xarita</FormLabel>
<FormControl>
<div className="relative h-80 border rounded-lg overflow-hidden">
<YMaps query={{ lang: "en_RU" }}>
<Map
defaultState={{
center: [coords.latitude, coords.longitude],
zoom: 12,
}}
width="100%"
height="100%"
onClick={handleMapClick}
>
<ZoomControl
options={{
position: { right: "10px", bottom: "70px" },
}}
/>
<Placemark
geometry={[coords.latitude, coords.longitude]}
/>
{polygonCoords && (
<Polygon
geometry={polygonCoords}
options={{
fillColor: "rgba(0, 150, 255, 0.2)",
strokeColor: "rgba(0, 150, 255, 0.8)",
strokeWidth: 2,
interactivityModel: "default#transparent",
}}
/>
)}
{circleCoords && (
<Circle
geometry={[circleCoords, 500]}
options={{
fillColor: "rgba(255, 100, 0, 0.3)",
strokeColor: "rgba(255, 100, 0, 0.8)",
strokeWidth: 2,
interactivityModel: "default#transparent",
}}
/>
)}
</Map>
</YMaps>
<Button
type="button"
size="sm"
onClick={handleShowMyLocation}
className="absolute bottom-3 right-2.5 shadow-md bg-white text-black hover:bg-gray-100"
>
<LocateFixed className="w-4 h-4 mr-1" />
</Button>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex gap-2 justify-end">
<Button
variant="outline"
type="button"
className="h-12 p-3"
onClick={() => router("/pharmacy")}
>
Bekor qilish
</Button>
<Button type="submit" className="h-12 p-5">
{isPending ? <Loader2 className="animate-spin" /> : "Qo'shish"}
</Button>
</div>
</form>
</Form>
</div>
</DashboardLayout>
);
};
export default CreatePharmacy;