Files
meridyn-bot/src/features/phamarcy/ui/CreatePharmacy.tsx
Samandar Turgunboyev 2fb567d93f back page update
2025-12-03 11:00:57 +05:00

476 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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[];
};
};
const message =
Array.isArray(errorName.data?.name) && errorName.data.name.length
? errorName.data.name[0]
: data?.message ||
(Array.isArray(errorData?.messages) && errorData.messages.length
? errorData.messages[0].message
: undefined) ||
"Xatolik yuz berdi";
toast.error(message);
},
});
const { 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 link="/pharmacy">
<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>{"Obyekt"}</FormLabel>
<FormControl>
<Select
key={field.value}
value={field.value}
onValueChange={(value) => {
field.onChange(value);
handleStreetChange(value);
}}
>
<SelectTrigger className="w-full h-12">
<SelectValue placeholder="Obyektlar" />
</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;