mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 05:05:38 -04:00
Add next backup countdown and download action to backup toasts
- Show "Next backup in X" in scheduled backups description - Add download button to manual backup success toast - Remove redundant schedule description from header - Reorder day-of-week picker above time picker for weekly Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -82,25 +82,65 @@ const DAYS_OF_WEEK = [
|
|||||||
"Saturday",
|
"Saturday",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
function describeSchedule(
|
function getNextBackupDate(
|
||||||
frequency: BackupFrequency,
|
frequency: BackupFrequency,
|
||||||
time: string,
|
time: string,
|
||||||
_dayOfWeek = 0,
|
dayOfWeek: number,
|
||||||
): string {
|
): Date {
|
||||||
|
const now = new Date();
|
||||||
const [h, m] = time.split(":").map(Number);
|
const [h, m] = time.split(":").map(Number);
|
||||||
const d = new Date(2000, 0, 1, h, m);
|
|
||||||
const timeStr = format(d, "h:mm a");
|
|
||||||
|
|
||||||
switch (frequency) {
|
if (frequency === "6h") {
|
||||||
case "6h":
|
const next = new Date(now);
|
||||||
return "Every 6 hours";
|
const currentHour = next.getHours();
|
||||||
case "12h":
|
const nextHour = Math.ceil((currentHour + 1) / 6) * 6;
|
||||||
return `Every 12 hours starting ${timeStr}`;
|
next.setHours(nextHour, m, 0, 0);
|
||||||
case "1d":
|
if (next <= now) next.setHours(next.getHours() + 6);
|
||||||
return `Daily at ${timeStr}`;
|
return next;
|
||||||
case "7d":
|
|
||||||
return "Weekly";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (frequency === "12h") {
|
||||||
|
const next = new Date(now);
|
||||||
|
const h2 = (h + 12) % 24;
|
||||||
|
const candidates = [h, h2].sort((a, b) => a - b);
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
next.setHours(candidate, m, 0, 0);
|
||||||
|
if (next > now) return next;
|
||||||
|
}
|
||||||
|
next.setDate(next.getDate() + 1);
|
||||||
|
next.setHours(candidates[0], m, 0, 0);
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frequency === "7d") {
|
||||||
|
const next = new Date(now);
|
||||||
|
const daysUntil = (dayOfWeek - next.getDay() + 7) % 7;
|
||||||
|
if (daysUntil === 0) {
|
||||||
|
next.setHours(h, m, 0, 0);
|
||||||
|
if (next > now) return next;
|
||||||
|
next.setDate(next.getDate() + 7);
|
||||||
|
next.setHours(h, m, 0, 0);
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
next.setDate(next.getDate() + daysUntil);
|
||||||
|
next.setHours(h, m, 0, 0);
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1d
|
||||||
|
const next = new Date(now);
|
||||||
|
next.setHours(h, m, 0, 0);
|
||||||
|
if (next <= now) next.setDate(next.getDate() + 1);
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatNextBackup(
|
||||||
|
frequency: BackupFrequency,
|
||||||
|
time: string,
|
||||||
|
dayOfWeek: number,
|
||||||
|
): string {
|
||||||
|
const next = getNextBackupDate(frequency, time, dayOfWeek);
|
||||||
|
return `Next backup ${formatDistanceToNow(next, { addSuffix: true })}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BackupSection({
|
export function BackupSection({
|
||||||
@@ -140,7 +180,17 @@ export function BackupSection({
|
|||||||
try {
|
try {
|
||||||
const backup = await createBackupAction();
|
const backup = await createBackupAction();
|
||||||
setBackups((prev) => [backup, ...prev]);
|
setBackups((prev) => [backup, ...prev]);
|
||||||
toast.success("Backup created");
|
toast.success("Backup created", {
|
||||||
|
action: {
|
||||||
|
label: "Download",
|
||||||
|
onClick: () => {
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = `/api/backup/${backup.filename}`;
|
||||||
|
a.download = backup.filename;
|
||||||
|
a.click();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
toast.error("Failed to create backup");
|
toast.error("Failed to create backup");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -229,9 +279,7 @@ export function BackupSection({
|
|||||||
setSavingSchedule(true);
|
setSavingSchedule(true);
|
||||||
try {
|
try {
|
||||||
await setBackupScheduleAction(newFrequency, newTime, newDow);
|
await setBackupScheduleAction(newFrequency, newTime, newDow);
|
||||||
toast.success(
|
toast.success("Schedule updated");
|
||||||
`Schedule updated: ${describeSchedule(newFrequency, newTime, newDow)}`,
|
|
||||||
);
|
|
||||||
} catch {
|
} catch {
|
||||||
setFrequency(prevFreq);
|
setFrequency(prevFreq);
|
||||||
setTime(prevTime);
|
setTime(prevTime);
|
||||||
@@ -411,7 +459,10 @@ export function BackupSection({
|
|||||||
<CardTitle>Scheduled</CardTitle>
|
<CardTitle>Scheduled</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
{scheduledEnabled ? (
|
{scheduledEnabled ? (
|
||||||
<span className="inline-flex items-baseline gap-1">
|
<span className="inline-flex flex-wrap items-baseline gap-1">
|
||||||
|
<span suppressHydrationWarning>
|
||||||
|
{formatNextBackup(frequency, time, dow)}.
|
||||||
|
</span>{" "}
|
||||||
Keeping last{" "}
|
Keeping last{" "}
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger className="inline-flex cursor-pointer items-center gap-0.5 border-b border-dotted border-muted-foreground/50 transition-colors hover:text-foreground">
|
<DropdownMenuTrigger className="inline-flex cursor-pointer items-center gap-0.5 border-b border-dotted border-muted-foreground/50 transition-colors hover:text-foreground">
|
||||||
|
|||||||
Reference in New Issue
Block a user