Refactor ShowSchedules component for improved layout and user interaction

- Replaced the table layout with a grid layout for better visual organization of schedules.
- Enhanced the display of schedule details, including status badges and command information.
- Updated action buttons for running schedules and managing them with improved icons and tooltips.
- Streamlined the code for better readability and maintainability.
This commit is contained in:
Mauricio Siu
2025-05-02 16:26:47 -06:00
parent 4ee220c1d8
commit 49e55961db

View File

@@ -1,15 +1,14 @@
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { HandleSchedules } from "./handle-schedules"; import { HandleSchedules } from "./handle-schedules";
import { Clock, Play, Terminal, Trash2 } from "lucide-react"; import {
Clock,
Play,
Terminal,
Trash2,
ClipboardList,
Loader2,
} from "lucide-react";
import { import {
Card, Card,
CardContent, CardContent,
@@ -33,9 +32,10 @@ interface Props {
} }
export const ShowSchedules = ({ applicationId }: Props) => { export const ShowSchedules = ({ applicationId }: Props) => {
const { data: schedules } = api.schedule.list.useQuery({ const { data: schedules, isLoading: isLoadingSchedules } =
applicationId, api.schedule.list.useQuery({
}); applicationId,
});
const utils = api.useUtils(); const utils = api.useUtils();
const { mutateAsync: deleteSchedule, isLoading: isDeleting } = const { mutateAsync: deleteSchedule, isLoading: isDeleting } =
@@ -47,7 +47,7 @@ export const ShowSchedules = ({ applicationId }: Props) => {
return ( return (
<Card className="border px-4 shadow-none bg-transparent"> <Card className="border px-4 shadow-none bg-transparent">
<CardHeader className="px-0"> <CardHeader className="px-0">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<CardTitle className="text-xl font-bold flex items-center gap-2"> <CardTitle className="text-xl font-bold flex items-center gap-2">
Scheduled Tasks Scheduled Tasks
@@ -63,138 +63,145 @@ export const ShowSchedules = ({ applicationId }: Props) => {
</div> </div>
</CardHeader> </CardHeader>
<CardContent className="px-0"> <CardContent className="px-0">
{schedules && schedules.length > 0 ? ( {isLoadingSchedules ? (
<div className="rounded-lg border"> <div className="flex gap-4 min-h-[35vh] w-full items-center justify-center text-center mx-auto">
<Table> <Loader2 className="size-4 text-muted-foreground/70 transition-colors animate-spin self-center" />
<TableHeader> <span className="text-sm text-muted-foreground/70">
<TableRow> Loading scheduled tasks...
<TableHead>Task Name</TableHead> </span>
<TableHead>Schedule</TableHead> </div>
<TableHead>Shell</TableHead> ) : schedules && schedules.length > 0 ? (
<TableHead>Command</TableHead> <div className="grid xl:grid-cols-2 gap-4 grid-cols-1 h-full">
<TableHead>Status</TableHead> {schedules.map((schedule) => {
<TableHead className="text-right">Actions</TableHead> const application = schedule.application;
</TableRow> const deployments = schedule.deployments;
</TableHeader> return (
<TableBody> <div
{schedules.map((schedule) => { key={schedule.scheduleId}
const application = schedule.application; className=" flex items-center justify-between rounded-lg border p-3 transition-colors bg-muted/50"
const deployments = schedule.deployments; >
return ( <div className="flex items-start gap-3">
<TableRow key={schedule.scheduleId}> <div className="flex h-9 w-9 items-center justify-center rounded-full bg-primary/5">
<TableCell className="font-medium"> <Clock className="size-4 text-primary/70" />
{schedule.name} </div>
</TableCell> <div className="space-y-1.5">
<TableCell> <div className="flex items-center gap-2">
<Badge variant="secondary" className="font-mono"> <h3 className="text-sm font-medium leading-none">
{schedule.cronExpression} {schedule.name}
</Badge> </h3>
</TableCell>
<TableCell>
<Badge variant="secondary" className="font-mono">
{schedule.shellType}
</Badge>
</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Terminal className="w-4 h-4 text-muted-foreground" />
<code className="bg-muted px-2 py-1 rounded text-sm">
{schedule.command}
</code>
</div>
</TableCell>
<TableCell>
<Badge <Badge
variant={schedule.enabled ? "default" : "secondary"} variant={schedule.enabled ? "default" : "secondary"}
className="text-[10px] px-1 py-0"
> >
{schedule.enabled ? "Enabled" : "Disabled"} {schedule.enabled ? "Enabled" : "Disabled"}
</Badge> </Badge>
</TableCell> </div>
<TableCell className="text-right"> <div className="flex items-center gap-2 text-sm text-muted-foreground">
<div className="flex justify-end gap-2"> <Badge
<ShowSchedulesLogs variant="outline"
deployments={deployments || []} className="font-mono text-[10px] bg-transparent"
serverId={application.serverId || undefined} >
/> {schedule.cronExpression}
</Badge>
<span className="text-xs text-muted-foreground/50">
</span>
<Badge
variant="outline"
className="font-mono text-[10px] bg-transparent"
>
{schedule.shellType}
</Badge>
</div>
<div className="flex items-center gap-2">
<Terminal className="size-3.5 text-muted-foreground/70" />
<code className="font-mono text-[10px] text-muted-foreground/70">
{schedule.command}
</code>
</div>
</div>
</div>
<TooltipProvider delayDuration={0}> <div className="flex items-center gap-1.5">
<Tooltip> <ShowSchedulesLogs
<TooltipTrigger asChild> deployments={deployments || []}
<Button serverId={application.serverId || undefined}
type="button" >
variant="ghost" <Button variant="ghost" size="icon">
isLoading={isLoading} <ClipboardList className="size-4 transition-colors " />
onClick={async () => { </Button>
await runManually({ </ShowSchedulesLogs>
scheduleId: schedule.scheduleId,
})
.then(() => {
toast.success(
"Schedule run successfully",
);
utils.schedule.list.invalidate({
applicationId,
});
})
.catch((error) => {
console.log(error);
toast.error(
`Error running schedule: ${error}`,
);
});
}}
>
<Play className="size-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
Run Manual Schedule
</TooltipContent>
</Tooltip>
</TooltipProvider>
<HandleSchedules <TooltipProvider delayDuration={0}>
scheduleId={schedule.scheduleId} <Tooltip>
applicationId={applicationId} <TooltipTrigger asChild>
/> <Button
type="button"
<DialogAction variant="ghost"
title="Delete Schedule" size="icon"
description="Are you sure you want to delete this schedule?" isLoading={isLoading}
type="destructive"
onClick={async () => { onClick={async () => {
await deleteSchedule({ await runManually({
scheduleId: schedule.scheduleId, scheduleId: schedule.scheduleId,
}) })
.then(() => { .then(() => {
toast.success("Schedule run successfully");
utils.schedule.list.invalidate({ utils.schedule.list.invalidate({
applicationId, applicationId,
}); });
toast.success(
"Schedule deleted successfully",
);
}) })
.catch(() => { .catch((error) => {
toast.error("Error deleting schedule"); console.log(error);
toast.error(
`Error running schedule: ${error}`,
);
}); });
}} }}
> >
<Button <Play className="size-4 transition-colors" />
variant="ghost" </Button>
size="icon" </TooltipTrigger>
className="group hover:bg-red-500/10 " <TooltipContent>Run Manual Schedule</TooltipContent>
isLoading={isDeleting} </Tooltip>
> </TooltipProvider>
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
</Button> <HandleSchedules
</DialogAction> scheduleId={schedule.scheduleId}
</div> applicationId={applicationId}
</TableCell> />
</TableRow>
); <DialogAction
})} title="Delete Schedule"
</TableBody> description="Are you sure you want to delete this schedule?"
</Table> type="destructive"
onClick={async () => {
await deleteSchedule({
scheduleId: schedule.scheduleId,
})
.then(() => {
utils.schedule.list.invalidate({
applicationId,
});
toast.success("Schedule deleted successfully");
})
.catch(() => {
toast.error("Error deleting schedule");
});
}}
>
<Button
variant="ghost"
size="icon"
className="group hover:bg-red-500/10 "
isLoading={isDeleting}
>
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
</Button>
</DialogAction>
</div>
</div>
);
})}
</div> </div>
) : ( ) : (
<div className="flex flex-col gap-2 items-center justify-center py-12 border rounded-lg"> <div className="flex flex-col gap-2 items-center justify-center py-12 border rounded-lg">