ui edit
This commit is contained in:
@@ -69,7 +69,7 @@ export function DataTableDistruct<TData, TValue>({
|
|||||||
) : (
|
) : (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||||
No results.
|
Tuman mavjud emas
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -191,12 +191,8 @@ export default function District() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
<div className="space-y-6">
|
<>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
|
||||||
<h1 className="text-3xl font-bold text-foreground">Tumanlar</h1>
|
|
||||||
<p className="text-muted-foreground mt-1">Tumanlarni boshqarish</p>
|
|
||||||
</div>
|
|
||||||
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<AddedButton onClick={() => form.reset({ name: "" })} />
|
<AddedButton onClick={() => form.reset({ name: "" })} />
|
||||||
@@ -260,7 +256,7 @@ export default function District() {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-6 mt-5">
|
<div className="space-y-6">
|
||||||
<h1 className="text-3xl font-bold">Tumanlar ro‘yxati</h1>
|
<h1 className="text-3xl font-bold">Tumanlar ro‘yxati</h1>
|
||||||
|
|
||||||
{/* Loading state */}
|
{/* Loading state */}
|
||||||
@@ -282,7 +278,7 @@ export default function District() {
|
|||||||
<p className="text-gray-500">Tumanlar mavjud emas</p>
|
<p className="text-gray-500">Tumanlar mavjud emas</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
|
|
||||||
<Dialog open={deleteDialog} onOpenChange={setDeleteDialog}>
|
<Dialog open={deleteDialog} onOpenChange={setDeleteDialog}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export function DataTable<TData, TValue>({
|
|||||||
) : (
|
) : (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||||
No results.
|
Shifokor mavjud emas
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -4,16 +4,18 @@ import { useMutation } from "@tanstack/react-query";
|
|||||||
import type { AxiosError } from "axios";
|
import type { AxiosError } from "axios";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import {
|
import {
|
||||||
|
Banknote,
|
||||||
Calendar,
|
Calendar,
|
||||||
Clipboard,
|
|
||||||
FileText,
|
FileText,
|
||||||
HomeIcon,
|
|
||||||
Layers,
|
|
||||||
List,
|
List,
|
||||||
MapPin,
|
Loader2,
|
||||||
Syringe,
|
MapPinCheck,
|
||||||
|
MapPinHouse,
|
||||||
|
MapPinned,
|
||||||
|
Pill,
|
||||||
User,
|
User,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
@@ -31,7 +33,7 @@ const navItems: NavItem[] = [
|
|||||||
id: "lokatsiya",
|
id: "lokatsiya",
|
||||||
title: "Lokatsiya jo'natish",
|
title: "Lokatsiya jo'natish",
|
||||||
link: "/location",
|
link: "/location",
|
||||||
icon: <MapPin className="w-8 h-8" />,
|
icon: <MapPinCheck className="w-8 h-8" />,
|
||||||
description: "Manzilni jo'natish",
|
description: "Manzilni jo'natish",
|
||||||
featured: true,
|
featured: true,
|
||||||
},
|
},
|
||||||
@@ -56,12 +58,14 @@ const navItems: NavItem[] = [
|
|||||||
export default function Home() {
|
export default function Home() {
|
||||||
const featuredItems = navItems.filter((item) => item.featured);
|
const featuredItems = navItems.filter((item) => item.featured);
|
||||||
const router = useNavigate();
|
const router = useNavigate();
|
||||||
|
const [locationLoad, setLocationLoad] = useState<boolean>(false);
|
||||||
|
|
||||||
const { mutate } = useMutation({
|
const { mutate } = useMutation({
|
||||||
mutationFn: (body: SendLocation) => location_api.send_loaction(body),
|
mutationFn: (body: SendLocation) => location_api.send_loaction(body),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success("Lokatsiya jo'natildi");
|
toast.success("Lokatsiya jo'natildi");
|
||||||
router("/location");
|
router("/location");
|
||||||
|
setLocationLoad(false);
|
||||||
},
|
},
|
||||||
onError: (error: AxiosError) => {
|
onError: (error: AxiosError) => {
|
||||||
const data = error.response?.data as { message?: string };
|
const data = error.response?.data as { message?: string };
|
||||||
@@ -77,6 +81,7 @@ export default function Home() {
|
|||||||
name: string[];
|
name: string[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
setLocationLoad(false);
|
||||||
|
|
||||||
const message =
|
const message =
|
||||||
Array.isArray(errorName.data?.name) && errorName.data.name.length
|
Array.isArray(errorName.data?.name) && errorName.data.name.length
|
||||||
@@ -92,6 +97,7 @@ export default function Home() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleLocationClick = () => {
|
const handleLocationClick = () => {
|
||||||
|
setLocationLoad(true);
|
||||||
navigator.geolocation.getCurrentPosition(
|
navigator.geolocation.getCurrentPosition(
|
||||||
(pos) => {
|
(pos) => {
|
||||||
mutate({
|
mutate({
|
||||||
@@ -125,41 +131,48 @@ export default function Home() {
|
|||||||
<div className="px-4 sm:px-6 mt-5 lg:px-4 bg-background">
|
<div className="px-4 sm:px-6 mt-5 lg:px-4 bg-background">
|
||||||
<div className="max-w-6xl mx-auto">
|
<div className="max-w-6xl mx-auto">
|
||||||
<p className="text-md font-bold text-black mb-5 uppercase">Asosiy</p>
|
<p className="text-md font-bold text-black mb-5 uppercase">Asosiy</p>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||||
{featuredItems.map((item) => {
|
{featuredItems.map((item) => {
|
||||||
const isLocation = item.id === "lokatsiya";
|
const isLocation = item.id === "lokatsiya";
|
||||||
|
const specification = item.id === "spetsifikatsiya";
|
||||||
|
const tourPlan = item.id === "tour_plan";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
onClick={isLocation ? handleLocationClick : undefined}
|
onClick={isLocation ? handleLocationClick : undefined}
|
||||||
className="group relative px-4 cursor-pointer"
|
className="group relative cursor-pointer flex justify-start"
|
||||||
>
|
>
|
||||||
{!isLocation ? (
|
{!isLocation ? (
|
||||||
<Link to={item.link} className="absolute inset-0"></Link>
|
<Link
|
||||||
|
to={item.link}
|
||||||
|
className="absolute inset-0 z-50"
|
||||||
|
></Link>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"flex items-start gap-6 pb-2 border-b border-border hover:border-primary/30 transition-colors",
|
"flex rounded-xl shadow-sm items-start gap-2 border-b p-2 pb-2 border-border hover:border-primary/30 transition-colors",
|
||||||
isLocation && "shadow-sm p-2 rounded-xl scale-[115%]",
|
isLocation
|
||||||
|
? "w-full"
|
||||||
|
: specification
|
||||||
|
? "w-[95%]"
|
||||||
|
: tourPlan && "w-[85%]",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"shrink-0 w-12 h-12 rounded-lg text-primary flex items-center justify-center",
|
"shrink-0 w-12 h-12 rounded-lg flex items-center justify-center bg-primary text-white",
|
||||||
isLocation && "bg-primary text-white",
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{item.icon}
|
{isLocation && locationLoad ? (
|
||||||
|
<Loader2 className="animate-spin" />
|
||||||
|
) : (
|
||||||
|
item.icon
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3
|
<h3 className={clsx("text-lg font-bold text-primary")}>
|
||||||
className={clsx(
|
|
||||||
"text-lg font-bold text-foreground",
|
|
||||||
isLocation && "text-primary",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{item.title}
|
{item.title}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
@@ -210,7 +223,7 @@ export default function Home() {
|
|||||||
<Link to={"/district"} className="border border-border rounded-lg">
|
<Link to={"/district"} className="border border-border rounded-lg">
|
||||||
<div className="flex flex-col items-center text-center p-4">
|
<div className="flex flex-col items-center text-center p-4">
|
||||||
<div className="text-primary/60 group-hover:text-primary transition-colors mb-3">
|
<div className="text-primary/60 group-hover:text-primary transition-colors mb-3">
|
||||||
<Layers className="w-6 h-6" />
|
<MapPinned className="w-6 h-6" />
|
||||||
</div>
|
</div>
|
||||||
<h4 className="font-semibold text-foreground text-sm">Tuman</h4>
|
<h4 className="font-semibold text-foreground text-sm">Tuman</h4>
|
||||||
</div>
|
</div>
|
||||||
@@ -218,7 +231,7 @@ export default function Home() {
|
|||||||
<Link to={"/object"} className="border border-border rounded-lg">
|
<Link to={"/object"} className="border border-border rounded-lg">
|
||||||
<div className="flex flex-col items-center text-center p-4">
|
<div className="flex flex-col items-center text-center p-4">
|
||||||
<div className="text-primary/60 group-hover:text-primary transition-colors mb-3">
|
<div className="text-primary/60 group-hover:text-primary transition-colors mb-3">
|
||||||
<HomeIcon className="w-6 h-6" />
|
<MapPinHouse className="w-6 h-6" />
|
||||||
</div>
|
</div>
|
||||||
<h4 className="font-semibold text-foreground text-sm">
|
<h4 className="font-semibold text-foreground text-sm">
|
||||||
Obyekt
|
Obyekt
|
||||||
@@ -242,7 +255,7 @@ export default function Home() {
|
|||||||
<Link to={"/pharmacy"} className="border border-border rounded-lg">
|
<Link to={"/pharmacy"} className="border border-border rounded-lg">
|
||||||
<div className="flex flex-col items-center text-center p-4">
|
<div className="flex flex-col items-center text-center p-4">
|
||||||
<div className="text-primary/60 group-hover:text-primary transition-colors mb-3">
|
<div className="text-primary/60 group-hover:text-primary transition-colors mb-3">
|
||||||
<Syringe className="w-6 h-6" />
|
<Pill className="w-6 h-6" />
|
||||||
</div>
|
</div>
|
||||||
<h4 className="font-semibold text-foreground text-sm">
|
<h4 className="font-semibold text-foreground text-sm">
|
||||||
Dorixona
|
Dorixona
|
||||||
@@ -257,10 +270,10 @@ export default function Home() {
|
|||||||
>
|
>
|
||||||
<div className="flex flex-col items-center text-center p-4">
|
<div className="flex flex-col items-center text-center p-4">
|
||||||
<div className="text-primary/60 group-hover:text-primary transition-colors mb-3">
|
<div className="text-primary/60 group-hover:text-primary transition-colors mb-3">
|
||||||
<Clipboard className="w-14 h-14" />
|
<Banknote className="w-14 h-14" />
|
||||||
</div>
|
</div>
|
||||||
<h4 className="font-semibold text-foreground text-lg">
|
<h4 className="font-semibold text-foreground text-lg">
|
||||||
Hisobotlar
|
To'lovlar
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -346,7 +346,7 @@ const MyLocation: React.FC = () => {
|
|||||||
colSpan={columns.length}
|
colSpan={columns.length}
|
||||||
className="h-24 text-center"
|
className="h-24 text-center"
|
||||||
>
|
>
|
||||||
No results.
|
Hech qanday lokatsiya jo'natilmagan
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export function DataTableObject<ObjectAllData, TValue>({
|
|||||||
) : (
|
) : (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||||
No results.
|
Obyekt mavjud emas
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/shared/ui/dialog";
|
} from "@/shared/ui/dialog";
|
||||||
|
import { Skeleton } from "@/shared/ui/skeleton";
|
||||||
import { DashboardLayout } from "@/widgets/dashboard-layout/ui/DashboardLayout";
|
import { DashboardLayout } from "@/widgets/dashboard-layout/ui/DashboardLayout";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -80,16 +81,22 @@ const ObjectList = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isLoading && !isError && objects && objects.length > 0 && (
|
{isLoading ? (
|
||||||
|
<div className="space-y-3">
|
||||||
|
{[1, 2, 3].map((i) => (
|
||||||
|
<Skeleton key={i} className="h-12 w-full rounded-md" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : isError ? (
|
||||||
|
<p className="text-red-500">
|
||||||
|
Tumanlar yuklanmadi. Qayta urinib ko‘ring.
|
||||||
|
</p>
|
||||||
|
) : objects ? (
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<DataTableObject columns={columns} data={objects} />
|
<DataTableObject columns={columns} data={objects} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
) : (
|
||||||
|
<p className="text-gray-500">Tumanlar mavjud emas</p>
|
||||||
{!isLoading && !isError && objects && objects.length === 0 && (
|
|
||||||
<div className="flex justify-center items-center h-64">
|
|
||||||
<span className="text-gray-500">Hech qanday obyekt topilmadi.</span>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ const PharmacyList = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{!isLoading && !isError && data?.length === 0 && (
|
{!isLoading && !isError && data?.length === 0 && (
|
||||||
<div className="flex justify-center items-center py-20">
|
<div className="h-[80vh] flex justify-center items-center w-[90%] fixed">
|
||||||
<p className="text-gray-500">Hech qanday dorixona topilmadi</p>
|
<p>Dorixona mavjud emas</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -92,10 +92,10 @@ const PlanTour = () => {
|
|||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
<div className="max-w-7xl mx-auto">
|
<div className="max-w-7xl mx-auto">
|
||||||
<h1 className="text-4xl font-bold text-gray-900 mb-2">
|
<h1 className="text-4xl font-bold text-gray-900 mb-2">
|
||||||
Oylik hisobotlar
|
Oylik to'lovlar
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600">
|
||||||
Dorixonalar uchun oylik summalarni boshqaring
|
Dorixonalar uchun oylik to'lovlarni boshqaring
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
@@ -110,6 +110,12 @@ const PlanTour = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!isLoading && !isError && data && data.length === 0 ? (
|
||||||
|
<div className="h-[80vh] flex justify-center items-center w-[90%] fixed">
|
||||||
|
<p>Hech qanday to'lovlar yo'q</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<Card className="mb-4 border-0 p-0 mt-5">
|
<Card className="mb-4 border-0 p-0 mt-5">
|
||||||
<CardHeader className="bg-blue-500 p-3 text-white">
|
<CardHeader className="bg-blue-500 p-3 text-white">
|
||||||
<CardTitle className="flex items-center gap-2 justify-center">
|
<CardTitle className="flex items-center gap-2 justify-center">
|
||||||
@@ -144,6 +150,8 @@ const PlanTour = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<PlanPrice selectedMonth={selectedMonth} pharmacies={pharmacies} />
|
<PlanPrice selectedMonth={selectedMonth} pharmacies={pharmacies} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</DashboardLayout>
|
</DashboardLayout>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -125,9 +125,9 @@ export function HistoryListPage() {
|
|||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<p className="text-center text-gray-500 py-10">
|
<div className="fixed h-[70vh] flex justify-center items-center w-full">
|
||||||
{"Hozircha ma'lumot yo‘q."}
|
<p className="text-gray-500">{"Hozircha ma'lumot yo‘q."}</p>
|
||||||
</p>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export function DataTable({ columns, data }: DataTableProps<TourItemData>) {
|
|||||||
) : (
|
) : (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||||
No results.
|
Bu oy uchun tur plan mavjud emas
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const rippleVariants = {
|
|||||||
|
|
||||||
const AddedButton: React.FC<AddedButtonProps> = ({ onClick }) => {
|
const AddedButton: React.FC<AddedButtonProps> = ({ onClick }) => {
|
||||||
return (
|
return (
|
||||||
<div className="fixed bottom-8 right-8 flex items-center justify-center w-16 h-16">
|
<div className="fixed bottom-8 right-8 flex items-center justify-center w-16 h-16 z-50">
|
||||||
{[0.5, 1, 1.5].map((delay, i) => (
|
{[0.5, 1, 1.5].map((delay, i) => (
|
||||||
<motion.span
|
<motion.span
|
||||||
key={i}
|
key={i}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
||||||
import { XIcon } from "lucide-react";
|
import { XIcon } from "lucide-react";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/shared/lib/utils";
|
import { cn } from "@/shared/lib/utils";
|
||||||
|
|
||||||
@@ -47,6 +47,7 @@ function SheetOverlay({
|
|||||||
function SheetContent({
|
function SheetContent({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
|
closedButton,
|
||||||
side = "right",
|
side = "right",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
||||||
@@ -72,10 +73,12 @@ function SheetContent({
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
{closedButton && (
|
||||||
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
|
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
|
||||||
<XIcon className="size-4" />
|
<XIcon className="size-4" />
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
</SheetPrimitive.Close>
|
</SheetPrimitive.Close>
|
||||||
|
)}
|
||||||
</SheetPrimitive.Content>
|
</SheetPrimitive.Content>
|
||||||
</SheetPortal>
|
</SheetPortal>
|
||||||
);
|
);
|
||||||
@@ -129,11 +132,11 @@ function SheetDescription({
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
Sheet,
|
Sheet,
|
||||||
SheetTrigger,
|
|
||||||
SheetClose,
|
SheetClose,
|
||||||
SheetContent,
|
SheetContent,
|
||||||
SheetHeader,
|
|
||||||
SheetFooter,
|
|
||||||
SheetTitle,
|
|
||||||
SheetDescription,
|
SheetDescription,
|
||||||
|
SheetFooter,
|
||||||
|
SheetHeader,
|
||||||
|
SheetTitle,
|
||||||
|
SheetTrigger,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,22 +1,30 @@
|
|||||||
import Logo from "@/assets/logo.png";
|
import Logo from "@/assets/logo.png";
|
||||||
|
import { location_api, type SendLocation } from "@/features/home/lib/api";
|
||||||
|
|
||||||
import { cn } from "@/shared/lib/utils";
|
import { cn } from "@/shared/lib/utils";
|
||||||
import { Button } from "@/shared/ui/button";
|
import { Button } from "@/shared/ui/button";
|
||||||
import { Sheet, SheetContent } from "@/shared/ui/sheet";
|
import { Sheet, SheetClose, SheetContent } from "@/shared/ui/sheet";
|
||||||
|
import { useMutation } from "@tanstack/react-query";
|
||||||
|
import type { AxiosError } from "axios";
|
||||||
import {
|
import {
|
||||||
Building,
|
Banknote,
|
||||||
Building2,
|
|
||||||
Calendar,
|
Calendar,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
FileText,
|
FileText,
|
||||||
MapPin,
|
Home,
|
||||||
|
List,
|
||||||
|
Loader2,
|
||||||
|
MapPinCheck,
|
||||||
|
MapPinHouse,
|
||||||
|
MapPinned,
|
||||||
Menu,
|
Menu,
|
||||||
Navigation,
|
Pill,
|
||||||
Truck,
|
|
||||||
User,
|
User,
|
||||||
|
X,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useState, type ReactNode } from "react";
|
import { useState, type ReactNode } from "react";
|
||||||
import { Link, useLocation, useNavigate, useParams } from "react-router-dom";
|
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
interface NavItem {
|
interface NavItem {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -25,24 +33,63 @@ interface NavItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const navItems: NavItem[] = [
|
const navItems: NavItem[] = [
|
||||||
{ title: "Reja", href: "/plan", icon: Calendar },
|
{ title: "Asosiy sahifa", href: "/", icon: Home },
|
||||||
{ title: "Tuman", href: "/district", icon: MapPin },
|
{ title: "Lokatsiya jo'natish", href: "/location", icon: MapPinCheck },
|
||||||
{ title: "Obyekt", href: "/object", icon: Building2 },
|
|
||||||
{ title: "Shifokor", href: "/physician", icon: User },
|
|
||||||
{ title: "Dorixona", href: "/pharmacy", icon: Building },
|
|
||||||
{ title: "Tur Plan", href: "/type-plan", icon: Truck },
|
|
||||||
{ title: "Lokatsiya", href: "/location", icon: Navigation },
|
|
||||||
{ title: "Spetsifikatsiya", href: "/specification", icon: FileText },
|
{ title: "Spetsifikatsiya", href: "/specification", icon: FileText },
|
||||||
|
{ title: "Tur Plan", href: "/tour-plan", icon: List },
|
||||||
|
{ title: "Reja", href: "/plan", icon: Calendar },
|
||||||
|
{ title: "Tuman", href: "/district", icon: MapPinned },
|
||||||
|
{ title: "Obyekt", href: "/object", icon: MapPinHouse },
|
||||||
|
{ title: "Shifokor", href: "/physician", icon: User },
|
||||||
|
{ title: "Dorixona", href: "/pharmacy", icon: Pill },
|
||||||
|
{ title: "To'lovlar", href: "/type-plan", icon: Banknote },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function DashboardLayout({ children }: { children: ReactNode }) {
|
export function DashboardLayout({ children }: { children: ReactNode }) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const pathname = location.pathname;
|
const pathname = location.pathname;
|
||||||
const router = useNavigate();
|
const router = useNavigate();
|
||||||
const params = useParams();
|
|
||||||
const locale = params.locale as string;
|
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||||
|
|
||||||
|
const [locationLoad, setLocationLoad] = useState(false);
|
||||||
|
|
||||||
|
const { mutate: sendLocation } = useMutation({
|
||||||
|
mutationFn: (body: SendLocation) => location_api.send_loaction(body),
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success("Lokatsiya jo'natildi");
|
||||||
|
setLocationLoad(false);
|
||||||
|
router("/location");
|
||||||
|
},
|
||||||
|
onError: (error: AxiosError) => {
|
||||||
|
const data = error.response?.data as { message?: string };
|
||||||
|
toast.error(data?.message || "Xatolik yuz berdi");
|
||||||
|
setLocationLoad(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSidebarLocationClick = () => {
|
||||||
|
setLocationLoad(true);
|
||||||
|
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
(pos) => {
|
||||||
|
sendLocation({
|
||||||
|
latitude: pos.coords.latitude,
|
||||||
|
longitude: pos.coords.longitude,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
toast.error("Lokatsiya olishda xatolik");
|
||||||
|
console.error(err);
|
||||||
|
setLocationLoad(false);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enableHighAccuracy: true,
|
||||||
|
timeout: 15000,
|
||||||
|
maximumAge: 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<div className="min-h-screen bg-background">
|
||||||
{/* Sidebar - Desktop */}
|
{/* Sidebar - Desktop */}
|
||||||
@@ -55,11 +102,35 @@ export function DashboardLayout({ children }: { children: ReactNode }) {
|
|||||||
<ul role="list" className="flex flex-1 flex-col gap-y-1">
|
<ul role="list" className="flex flex-1 flex-col gap-y-1">
|
||||||
{navItems.map((item) => {
|
{navItems.map((item) => {
|
||||||
const Icon = item.icon;
|
const Icon = item.icon;
|
||||||
const isActive = pathname?.includes(item.href);
|
const isActive =
|
||||||
|
item.href === "/"
|
||||||
|
? pathname === "/"
|
||||||
|
: pathname.startsWith(item.href);
|
||||||
|
|
||||||
|
const isLocationItem = item.href === "/location";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={item.href}>
|
<li key={item.href}>
|
||||||
|
{isLocationItem ? (
|
||||||
|
<button
|
||||||
|
onClick={handleSidebarLocationClick}
|
||||||
|
className={cn(
|
||||||
|
"group flex gap-x-3 p-3 text-left w-full text-sm font-semibold leading-6 transition-colors",
|
||||||
|
isActive
|
||||||
|
? "bg-accent-gradient-soft text-primary rounded-xl-soft shadow-sm"
|
||||||
|
: "text-sidebar-foreground hover:bg-accent-gradient-soft hover:text-primary rounded-lg",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{locationLoad ? (
|
||||||
|
<Loader2 className="h-5 w-5 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Icon className="h-5 w-5 shrink-0" />
|
||||||
|
)}
|
||||||
|
{item.title}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
<Link
|
<Link
|
||||||
to={`/${locale}${item.href}`}
|
to={item.href}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group flex gap-x-3 p-3 text-sm font-semibold leading-6 transition-colors",
|
"group flex gap-x-3 p-3 text-sm font-semibold leading-6 transition-colors",
|
||||||
isActive
|
isActive
|
||||||
@@ -70,6 +141,7 @@ export function DashboardLayout({ children }: { children: ReactNode }) {
|
|||||||
<Icon className="h-5 w-5 shrink-0" />
|
<Icon className="h-5 w-5 shrink-0" />
|
||||||
{item.title}
|
{item.title}
|
||||||
</Link>
|
</Link>
|
||||||
|
)}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -105,20 +177,48 @@ export function DashboardLayout({ children }: { children: ReactNode }) {
|
|||||||
|
|
||||||
{/* Mobile Sidebar */}
|
{/* Mobile Sidebar */}
|
||||||
<Sheet open={sidebarOpen} onOpenChange={setSidebarOpen}>
|
<Sheet open={sidebarOpen} onOpenChange={setSidebarOpen}>
|
||||||
<SheetContent side="left" className="w-64 p-0">
|
<SheetContent side="left" className="w-64 p-0" closedButton={false}>
|
||||||
<div className="flex h-16 shrink-0 items-center border-b border-sidebar-border">
|
<div className="flex justify-between h-16 px-2 shrink-0 items-center border-b border-sidebar-border">
|
||||||
<img src={Logo} alt="logo" className="w-32 h-12 ml-2" />
|
<img src={Logo} alt="logo" className="w-32 h-10 ml-2" />
|
||||||
|
<SheetClose asChild>
|
||||||
|
<Button size={"icon"} variant={"ghost"}>
|
||||||
|
<X className="size-5 text-gray-600" />
|
||||||
|
</Button>
|
||||||
|
</SheetClose>
|
||||||
</div>
|
</div>
|
||||||
<nav className="flex flex-1 flex-col p-6">
|
<nav className="flex flex-1 flex-col px-2">
|
||||||
<ul role="list" className="flex flex-1 flex-col gap-y-1">
|
<ul role="list" className="flex flex-1 flex-col">
|
||||||
{navItems.map((item) => {
|
{navItems.map((item) => {
|
||||||
const Icon = item.icon;
|
const Icon = item.icon;
|
||||||
const isActive = pathname?.includes(item.href);
|
const isActive =
|
||||||
|
item.href === "/"
|
||||||
|
? pathname === "/"
|
||||||
|
: pathname.startsWith(item.href);
|
||||||
|
|
||||||
|
const isLocationItem = item.href === "/location";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={item.href}>
|
<li key={item.href}>
|
||||||
|
{isLocationItem ? (
|
||||||
|
<button
|
||||||
|
onClick={handleSidebarLocationClick}
|
||||||
|
className={cn(
|
||||||
|
"group flex gap-x-3 p-3 text-left w-full text-sm font-semibold leading-6 transition-colors",
|
||||||
|
isActive
|
||||||
|
? "bg-accent-gradient-soft text-primary rounded-xl-soft shadow-sm"
|
||||||
|
: "text-sidebar-foreground hover:bg-accent-gradient-soft hover:text-primary rounded-lg",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{locationLoad ? (
|
||||||
|
<Loader2 className="h-5 w-5 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Icon className="h-5 w-5 shrink-0" />
|
||||||
|
)}
|
||||||
|
{item.title}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
<Link
|
<Link
|
||||||
to={`/$${item.href}`}
|
to={item.href}
|
||||||
onClick={() => setSidebarOpen(false)}
|
|
||||||
className={cn(
|
className={cn(
|
||||||
"group flex gap-x-3 p-3 text-sm font-semibold leading-6 transition-colors",
|
"group flex gap-x-3 p-3 text-sm font-semibold leading-6 transition-colors",
|
||||||
isActive
|
isActive
|
||||||
@@ -129,6 +229,7 @@ export function DashboardLayout({ children }: { children: ReactNode }) {
|
|||||||
<Icon className="h-5 w-5 shrink-0" />
|
<Icon className="h-5 w-5 shrink-0" />
|
||||||
{item.title}
|
{item.title}
|
||||||
</Link>
|
</Link>
|
||||||
|
)}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export default defineConfig({
|
|||||||
port: 5174,
|
port: 5174,
|
||||||
host: true, // barcha hostlarga ruxsat
|
host: true, // barcha hostlarga ruxsat
|
||||||
allowedHosts: [
|
allowedHosts: [
|
||||||
"factory-epa-announced-adapter.trycloudflare.com", // ngrok host qo'shildi
|
"worth-fathers-turned-diamonds.trycloudflare.com", // ngrok host qo'shildi
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user