react
This commit is contained in:
299
src/features/plan/ui/plans.tsx
Normal file
299
src/features/plan/ui/plans.tsx
Normal 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 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;
|
||||
}[];
|
||||
};
|
||||
|
||||
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 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.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 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user