363 lines
12 KiB
TypeScript
363 lines
12 KiB
TypeScript
"use client";
|
|
import { district_api } from "@/features/district/lib/api";
|
|
import { doctor_api } from "@/features/doctor/lib/api";
|
|
import { location_api, type CreateLocation } from "@/features/home/lib/api";
|
|
import { object_api } from "@/features/object/lib/api";
|
|
import { pharmacy_api } from "@/features/phamarcy/lib/api";
|
|
import { Button } from "@/shared/ui/button";
|
|
import { Card, CardDescription, CardHeader, CardTitle } from "@/shared/ui/card";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/shared/ui/dialog";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@/shared/ui/table";
|
|
import { DashboardLayout } from "@/widgets/dashboard-layout/ui";
|
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
import {
|
|
flexRender,
|
|
getCoreRowModel,
|
|
getFilteredRowModel,
|
|
getPaginationRowModel,
|
|
getSortedRowModel,
|
|
useReactTable,
|
|
} from "@tanstack/react-table";
|
|
import { AxiosError } from "axios";
|
|
import { Loader2, MapPin, Send } from "lucide-react";
|
|
import React, { useState } from "react";
|
|
import { toast } from "sonner";
|
|
import { columns } from "../lib/column";
|
|
|
|
const MyLocation: React.FC = () => {
|
|
const [showMainModal, setShowMainModal] = useState<boolean>(false);
|
|
const queryClinent = useQueryClient();
|
|
const [showSelectModal, setShowSelectModal] = useState<boolean>(false);
|
|
const [selectType, setSelectType] = useState<
|
|
"district" | "object" | "doctor" | "pharm" | null
|
|
>(null);
|
|
const [loadingButtonId, setLoadingButtonId] = useState<number | null>(null);
|
|
|
|
const mutation = useMutation({
|
|
mutationFn: (body: CreateLocation) => location_api.create_location(body),
|
|
onSuccess: () => {
|
|
toast.success("Lokatsiya jo'natildi");
|
|
setShowSelectModal(false);
|
|
setLoadingButtonId(null);
|
|
queryClinent.refetchQueries({ queryKey: ["location_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: location } = useQuery({
|
|
queryKey: ["location_list"],
|
|
queryFn: () => location_api.list_location(),
|
|
select(data) {
|
|
return data.data.data;
|
|
},
|
|
});
|
|
|
|
const { data: districts, isLoading: isDistrictsLoading } = useQuery({
|
|
queryKey: ["my_disctrict"],
|
|
queryFn: () => district_api.getDiscrict(),
|
|
select: (data) => data.data.data,
|
|
});
|
|
|
|
const { data: objects, isLoading: isObjectsLoading } = useQuery({
|
|
queryKey: ["object_list"],
|
|
queryFn: () => object_api.getAll({}),
|
|
select: (data) => data.data.data,
|
|
});
|
|
|
|
const { data: doctors, isLoading: isDoctorsLoading } = useQuery({
|
|
queryKey: ["doctor_list"],
|
|
queryFn: () => doctor_api.list(),
|
|
select: (data) => data.data.data,
|
|
});
|
|
|
|
const { data: pharm, isLoading: isPharmLoading } = useQuery({
|
|
queryKey: ["pharmacy_list"],
|
|
queryFn: () => pharmacy_api.list(),
|
|
select: (data) => data.data.data,
|
|
});
|
|
|
|
const getOptions = () => {
|
|
if (selectType === "district") return districts;
|
|
if (selectType === "object") return objects;
|
|
if (selectType === "doctor") return doctors;
|
|
if (selectType === "pharm") return pharm;
|
|
return [];
|
|
};
|
|
|
|
const isLoading = () => {
|
|
if (selectType === "district") return isDistrictsLoading;
|
|
if (selectType === "object") return isObjectsLoading;
|
|
if (selectType === "doctor") return isDoctorsLoading;
|
|
if (selectType === "pharm") return isPharmLoading;
|
|
return false;
|
|
};
|
|
|
|
const typeLabel = (type: "district" | "object" | "doctor" | "pharm") => {
|
|
if (type === "district") return "Tuman";
|
|
if (type === "object") return "Obyekt";
|
|
if (type === "doctor") return "Shifokor";
|
|
if (type === "pharm") return "Dorixona";
|
|
};
|
|
|
|
const handleAddLocation = (id: number) => {
|
|
if (!selectType) return;
|
|
|
|
setLoadingButtonId(id); // faqat shu button loading
|
|
|
|
navigator.geolocation.getCurrentPosition(
|
|
(position) => {
|
|
const latitude = position.coords.latitude;
|
|
const longitude = position.coords.longitude;
|
|
|
|
const body: CreateLocation = {
|
|
latitude,
|
|
longitude,
|
|
};
|
|
|
|
switch (selectType) {
|
|
case "district":
|
|
body.district_id = id;
|
|
break;
|
|
case "object":
|
|
body.place_id = id;
|
|
break;
|
|
case "doctor":
|
|
body.doctor_id = id;
|
|
break;
|
|
case "pharm":
|
|
body.pharmacy_id = id;
|
|
break;
|
|
}
|
|
|
|
mutation.mutate(body);
|
|
},
|
|
() => {
|
|
toast.error("Geolokatsiya olinmadi");
|
|
setLoadingButtonId(null);
|
|
},
|
|
{ enableHighAccuracy: true, timeout: 15000, maximumAge: 0 },
|
|
);
|
|
};
|
|
|
|
const table = useReactTable({
|
|
data: location || [],
|
|
columns: columns,
|
|
getCoreRowModel: getCoreRowModel(),
|
|
getPaginationRowModel: getPaginationRowModel(),
|
|
getSortedRowModel: getSortedRowModel(),
|
|
getFilteredRowModel: getFilteredRowModel(),
|
|
});
|
|
|
|
return (
|
|
<DashboardLayout link="/">
|
|
<div className="space-y-4">
|
|
<Card>
|
|
<CardHeader className="flex flex-col">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-12 h-12 bg-blue-500 rounded-full flex items-center justify-center">
|
|
<MapPin className="text-white" size={24} />
|
|
</div>
|
|
<div>
|
|
<CardTitle className="text-2xl">Lokatsiya Tizimi</CardTitle>
|
|
<CardDescription>{"Manzil jo'natish va tarix"}</CardDescription>
|
|
</div>
|
|
</div>
|
|
</CardHeader>
|
|
</Card>
|
|
|
|
{/* Main Modal */}
|
|
<Dialog open={showMainModal} onOpenChange={setShowMainModal}>
|
|
<DialogContent className="sm:max-w-md">
|
|
<DialogHeader>
|
|
<DialogTitle className="text-2xl">
|
|
{"Nimani jo'natasiz?"}
|
|
</DialogTitle>
|
|
</DialogHeader>
|
|
<div className="space-y-3 mt-4">
|
|
{(["district", "object", "doctor", "pharm"] as const).map(
|
|
(type) => (
|
|
<Button
|
|
key={type}
|
|
onClick={() => {
|
|
setSelectType(type);
|
|
setShowSelectModal(true);
|
|
setShowMainModal(false);
|
|
}}
|
|
className="w-full h-12 text-lg"
|
|
variant="outline"
|
|
>
|
|
{typeLabel(type)}
|
|
</Button>
|
|
),
|
|
)}
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* Select Modal */}
|
|
<Dialog open={showSelectModal} onOpenChange={setShowSelectModal}>
|
|
<DialogContent className="sm:max-w-md h-[60%] flex flex-col justify-start overflow-y-auto">
|
|
<DialogHeader>
|
|
<DialogTitle className="text-2xl">
|
|
Mavjud {selectType && typeLabel(selectType)}lar
|
|
</DialogTitle>
|
|
</DialogHeader>
|
|
<div className="space-y-2 flex flex-col flex-1">
|
|
{isLoading() ? (
|
|
<div className="flex flex-col items-center justify-center py-12 gap-3">
|
|
<Loader2 className="h-8 w-8 animate-spin text-blue-500" />
|
|
<p className="text-muted-foreground">Yuklanmoqda...</p>
|
|
</div>
|
|
) : getOptions()?.length === 0 ? (
|
|
<div className="flex items-center justify-center py-12">
|
|
<p className="text-muted-foreground">Ma'lumot topilmadi</p>
|
|
</div>
|
|
) : (
|
|
getOptions()?.map((item) => {
|
|
const id = item.id;
|
|
const label =
|
|
"name" in item
|
|
? item.name
|
|
: `${item.first_name} ${item.last_name}`;
|
|
const isButtonLoading = loadingButtonId === id;
|
|
|
|
return (
|
|
<Button
|
|
key={id}
|
|
onClick={() => handleAddLocation(id)}
|
|
variant="outline"
|
|
className="w-full justify-start h-auto py-3 flex items-center gap-2"
|
|
disabled={isButtonLoading}
|
|
>
|
|
<MapPin className="h-4 w-4" />
|
|
{isButtonLoading ? (
|
|
<>
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
Yuklanmoqda...
|
|
</>
|
|
) : (
|
|
label
|
|
)}
|
|
</Button>
|
|
);
|
|
})
|
|
)}
|
|
</div>
|
|
<Button
|
|
onClick={() => setShowSelectModal(false)}
|
|
variant="outline"
|
|
className="w-full mt-2"
|
|
>
|
|
Bekor qilish
|
|
</Button>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
<div className="flex justify-end">
|
|
<Button onClick={() => setShowMainModal(true)} className="gap-2">
|
|
<Send size={20} />
|
|
<span>{"Lokatsiya Jo'natish"}</span>
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Table */}
|
|
<div className="w-full">
|
|
<div className="overflow-hidden rounded-md border">
|
|
<Table>
|
|
<TableHeader>
|
|
{table.getHeaderGroups().map((headerGroup) => (
|
|
<TableRow key={headerGroup.id}>
|
|
{headerGroup.headers.map((header) => (
|
|
<TableHead
|
|
key={header.id}
|
|
className="border-r text-center"
|
|
>
|
|
{header.isPlaceholder
|
|
? null
|
|
: flexRender(
|
|
header.column.columnDef.header,
|
|
header.getContext(),
|
|
)}
|
|
</TableHead>
|
|
))}
|
|
</TableRow>
|
|
))}
|
|
</TableHeader>
|
|
<TableBody>
|
|
{table.getRowModel().rows.length ? (
|
|
table.getRowModel().rows.map((row) => (
|
|
<TableRow
|
|
key={row.id}
|
|
data-state={row.getIsSelected() && "selected"}
|
|
>
|
|
{row.getVisibleCells().map((cell) => (
|
|
<TableCell
|
|
key={cell.id}
|
|
className="border-r text-center"
|
|
>
|
|
{flexRender(
|
|
cell.column.columnDef.cell,
|
|
cell.getContext(),
|
|
)}
|
|
</TableCell>
|
|
))}
|
|
</TableRow>
|
|
))
|
|
) : (
|
|
<TableRow>
|
|
<TableCell
|
|
colSpan={columns.length}
|
|
className="h-24 text-center"
|
|
>
|
|
Hech qanday lokatsiya jo'natilmagan
|
|
</TableCell>
|
|
</TableRow>
|
|
)}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</DashboardLayout>
|
|
);
|
|
};
|
|
|
|
export default MyLocation;
|