304 lines
11 KiB
TypeScript
304 lines
11 KiB
TypeScript
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, 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 o‘chirildi");
|
||
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;
|
||
}[];
|
||
};
|
||
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 [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 link="/">
|
||
<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 ko‘ring.
|
||
</AlertDescription>
|
||
</Alert>
|
||
)}
|
||
|
||
{!isLoading &&
|
||
!isError &&
|
||
data &&
|
||
Object.keys(grouped).length === 0 && (
|
||
<Alert className="mt-4">
|
||
<AlertTitle>Ma'lumot yo‘q</AlertTitle>
|
||
<AlertDescription>
|
||
Tanlangan sana bo‘yicha 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.comment ? "text-green-500" : "text-foreground",
|
||
)}
|
||
>
|
||
{item.title}
|
||
</h3> */}
|
||
<h3
|
||
className={clsx(
|
||
"font-semibold wrap-break-word",
|
||
item.comment ? "text-green-500" : "text-foreground",
|
||
)}
|
||
>
|
||
{item.description}
|
||
</h3>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 gap-2">
|
||
<Button
|
||
size="sm"
|
||
variant="outline"
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
setTaskEdit(item);
|
||
setIsAddDialogOpen(true);
|
||
}}
|
||
className="p-4"
|
||
disabled={item.comment ? true : false}
|
||
>
|
||
<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 o‘chirish</DialogTitle>
|
||
</DialogHeader>
|
||
<p>
|
||
Siz haqiqatdan ham <strong>{selectedTask?.title}</strong> rejani
|
||
o‘chirmoqchimisiz?
|
||
</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>
|
||
);
|
||
}
|