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,299 @@
import { plans_api } from "@/features/plan/lib/api";
import type { Task } from "@/features/plan/lib/data";
import { groupByDate } from "@/features/plan/lib/filteDareHelper";
import { AddPlans } from "@/features/plan/ui/addPlans";
import { PlanDetailsDialog } from "@/features/plan/ui/PlanDetailsDialogProps";
import formatDate from "@/shared/lib/formatDate";
import { Alert, AlertDescription, AlertTitle } from "@/shared/ui/alert";
import { Button } from "@/shared/ui/button";
import { Calendar } from "@/shared/ui/calendar";
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/ui/card";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/shared/ui/dialog";
import { Skeleton } from "@/shared/ui/skeleton";
import { DashboardLayout } from "@/widgets/dashboard-layout/ui";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import type { AxiosError } from "axios";
import clsx from "clsx";
import {
CalendarIcon,
Loader2,
Pencil,
Trash,
TriangleAlert,
} from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
export default function Plans() {
const [taskEdit, setTaskEdit] = useState<Task | null>(null);
const queryClient = useQueryClient();
const [selectedDate, setSelectedDate] = useState<Date | null>(null);
const [tempDate, setTempDate] = useState<Date | null>(null);
const { data, isLoading, isError } = useQuery({
queryKey: ["my_plans", selectedDate],
queryFn: () =>
plans_api.getMyPloans({
date: selectedDate ? formatDate.format(selectedDate, "YYYY-MM-DD") : "",
}),
});
const { mutate, isPending } = useMutation({
mutationFn: (id: number) => plans_api.delete(id),
onSuccess: () => {
toast.success("Reja ochirildi");
queryClient.refetchQueries({ queryKey: ["my_plans", selectedDate] });
setDeleteDialogOpen(false);
},
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;
}[];
};
toast.error(
data.message || errorData?.messages?.[0].message || "Xatolik yuz berdi",
);
},
});
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [isPlanDetailsOpen, setIsPlanDetailsOpen] = useState(false);
const [isDateDialogOpen, setIsDateDialogOpen] = useState(false);
const [newTask, setNewTask] = useState({
title: "",
description: "",
date: new Date(),
});
const [selectedTask, setSelectedTask] = useState<Task | null>(null);
const openTaskDetails = (task: Task) => {
setSelectedTask(task);
setIsPlanDetailsOpen(true);
};
const handleDeleteTask = (task: Task) => {
setSelectedTask(task);
setDeleteDialogOpen(true);
};
const grouped = groupByDate(data?.data.data || []);
return (
<DashboardLayout>
<div className="space-y-4">
<div className="flex items-center justify-between">
<h1 className="text-3xl font-bold text-foreground">Kunlik Reja</h1>
<Dialog open={isDateDialogOpen} onOpenChange={setIsDateDialogOpen}>
<DialogTrigger asChild>
<Button variant="outline" className="bg-transparent font-normal">
<CalendarIcon className="mr-2 h-4 w-4" />
{selectedDate
? formatDate.format(selectedDate, "dd.MM.yyyy")
: "Barchasi"}
</Button>
</DialogTrigger>
<DialogContent className="w-auto p-4">
<Calendar
mode="single"
selected={tempDate ?? selectedDate ?? undefined}
onSelect={(date) => setTempDate(date ?? null)}
initialFocus
/>
<div className="mt-4 flex justify-end gap-2">
<Button
variant="outline"
onClick={() => {
setIsDateDialogOpen(false);
setTempDate(null);
setSelectedDate(null);
}}
>
Bekor qilish
</Button>
<Button
onClick={() => {
if (tempDate) setSelectedDate(tempDate);
setIsDateDialogOpen(false);
}}
>
Tanlash
</Button>
</div>
</DialogContent>
</Dialog>
</div>
{isLoading && (
<div className="space-y-4">
{[1, 2, 3].map((i) => (
<Card key={i} className="px-0 py-3 gap-0">
<CardHeader className="px-3 py-0">
<Skeleton className="h-5 w-32" />
</CardHeader>
<CardContent className="space-y-3 px-2 py-0">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-3/4" />
<div className="grid grid-cols-2 gap-2">
<Skeleton className="h-10 w-full" />
<Skeleton className="h-10 w-full" />
</div>
</CardContent>
</Card>
))}
</div>
)}
{isError && (
<Alert variant="destructive">
<TriangleAlert className="h-4 w-4" />
<AlertTitle>Xatolik!</AlertTitle>
<AlertDescription>
Ma'lumotlarni yuklashda muammo yuz berdi. Qayta urinib koring.
</AlertDescription>
</Alert>
)}
{!isLoading &&
!isError &&
data &&
Object.keys(grouped).length === 0 && (
<Alert className="mt-4">
<AlertTitle>Ma'lumot yoq</AlertTitle>
<AlertDescription>
Tanlangan sana boyicha reja topilmadi.
</AlertDescription>
</Alert>
)}
{data &&
Object.entries(grouped).map(([date, items]) => (
<Card key={date} className="px-0 py-3 gap-0">
<CardHeader className="px-3 py-0">
<CardTitle className="text-lg">
{formatDate.format(new Date(date), "DD.MM.YYYY")}
</CardTitle>
</CardHeader>
<CardContent className="space-y-2 px-2 py-0">
{items.map((item) => (
<div
key={item.id}
onClick={() => openTaskDetails(item)}
className="flex flex-col gap-3 p-3 rounded-md border bg-white group hover:bg-gray-50 transition-colors"
>
<div className="flex gap-3">
<div className="flex-1 flex flex-col">
<h3
className={clsx(
"font-semibold wrap-break-word",
item.is_done ? "text-green-500" : "text-foreground",
)}
>
{item.title}
</h3>
<p
className={clsx(
"text-sm wrap-break-word",
item.is_done
? "text-green-500"
: "text-muted-foreground",
)}
>
{item.description}
</p>
</div>
</div>
<div className="grid grid-cols-2 gap-2">
<Button
size="sm"
variant="outline"
onClick={(e) => {
e.stopPropagation();
setTaskEdit(item);
setIsAddDialogOpen(true);
}}
className="p-4"
disabled={item.is_done}
>
<Pencil className="h-4 w-4" />
<p>Tahrirlash</p>
</Button>
<Button
size="sm"
variant="destructive"
onClick={(e) => {
e.stopPropagation();
handleDeleteTask(item);
}}
className="p-4"
disabled={item.is_done}
>
<Trash className="h-4 w-4" />
<p>{"O'chirish"}</p>
</Button>
</div>
</div>
))}
</CardContent>
</Card>
))}
<AddPlans
isDialogOpen={isAddDialogOpen}
setIsDialogOpen={setIsAddDialogOpen}
newTask={newTask}
setNewTask={setNewTask}
setTaskEdit={setTaskEdit}
taskEdit={taskEdit}
/>
<PlanDetailsDialog
isOpen={isPlanDetailsOpen}
onOpenChange={setIsPlanDetailsOpen}
task={selectedTask}
/>
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Rejani ochirish</DialogTitle>
</DialogHeader>
<p>
Siz haqiqatdan ham <strong>{selectedTask?.title}</strong> rejani
ochirmoqchimisiz?
</p>
<DialogFooter className="flex justify-end gap-2">
<Button
variant="outline"
onClick={() => setDeleteDialogOpen(false)}
>
Bekor qilish
</Button>
<Button
variant="destructive"
disabled={isPending}
onClick={() => selectedTask && mutate(selectedTask.id)}
>
{isPending ? <Loader2 className="animate-spin" /> : "O'chirish"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</DashboardLayout>
);
}