fix: project details table refresh

This commit is contained in:
Théo LAGACHE
2026-02-10 11:44:45 +01:00
parent a49b6d6573
commit 921a355ae0
48 changed files with 808 additions and 566 deletions
@@ -33,7 +33,7 @@ export function DatabaseActionsCell({backup, activeMember, isAlreadyRestore}: Da
<div className={cn("flex items-center space-x-2")}>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<Button variant="ghost" size="icon" type="button" onClick={(e) => e.stopPropagation()}>
<span className="sr-only">Open menu</span>
<MoreHorizontal className="w-4 h-4"/>
</Button>
@@ -1,7 +1,7 @@
"use client"
import {Backup, BackupWith, Restoration} from "@/db/schema/07_database";
import React from "react";
import {Swiper, SwiperSlide} from "swiper/react";
//@ts-ignore
import "swiper/css";
import "swiper/css/pagination";
import {Pagination, Mousewheel} from "swiper/modules";
@@ -12,7 +12,7 @@ import {
BackupActionsType
} from "@/components/wrappers/dashboard/database/backup/actions/backup-actions.schema";
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
import {useMutation} from "@tanstack/react-query";
import {useMutation, useQueryClient} from "@tanstack/react-query";
import {BackupStorageWith} from "@/db/schema/14_storage-backup";
import {TooltipProvider} from "@/components/ui/tooltip";
import {getChannelIcon} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
@@ -29,8 +29,7 @@ import {SafeActionResult} from "next-safe-action";
import {ServerActionResult} from "@/types/action-type";
import {ZodString} from "zod";
import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert";
import {AlertCircleIcon, Trash2} from "lucide-react";
import {ButtonWithConfirm} from "@/components/wrappers/common/button/button-with-confirm";
import {AlertCircleIcon} from "lucide-react";
type BackupActionsFormProps = {
backup: BackupWith;
@@ -42,6 +41,7 @@ export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
const filteredBackupStorages = backup.storages?.filter((storage) => storage.deletedAt === null) ?? []
const isMobile = useIsMobile();
const {closeModal} = useBackupModal();
const queryClient = useQueryClient();
const form = useZodForm({
schema: BackupActionsSchema,
@@ -74,8 +74,8 @@ export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
if (inner?.success) {
toast.success(inner.actionSuccess?.message);
queryClient.invalidateQueries({queryKey: ["database-data", backup.databaseId]});
if (action === "download") {
console.log(inner.value)
const url = inner.value
if (typeof url === "string") {
window.open(url, "_self");
@@ -91,6 +91,7 @@ export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
} else {
if (action === "delete") {
toast.success("Backup deleted successfully.")
queryClient.invalidateQueries({queryKey: ["database-data", backup.databaseId]});
closeModal()
} else {
toast.error(inner?.actionError?.message ?? "An error occurred.");
@@ -111,6 +112,7 @@ export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
if (inner?.success) {
toast.success(inner.actionSuccess?.message);
queryClient.invalidateQueries({queryKey: ["database-data", backup.databaseId]});
closeModal()
} else {
toast.error(inner?.actionError?.message);
@@ -130,10 +132,7 @@ export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
}}
>
{filteredBackupStorages.length > 0 ?
<FormField
control={form.control}
name="backupStorageId"
@@ -230,35 +229,45 @@ export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
// >
// Delete entire backup
// </ButtonWithLoading>
<ButtonWithConfirm
title={"Delete entire backup"}
description={"Are you sure you want to delete this entire backup?"}
button={{
main: {
type: "button",
variant: "destructive",
text: "Delete entire backup",
},
confirm: {
className: "w-full",
text: "Delete",
icon: <Trash2/>,
variant: "destructive",
onClick: async () => {
mutationDeleteEntireBackup.mutateAsync()
},
},
cancel: {
className: "w-full",
text: "Cancel",
icon: <Trash2/>,
variant: "outline",
},
}}
<ButtonWithLoading
type="button"
variant="destructive"
onClick={() => mutationDeleteEntireBackup.mutateAsync()}
isPending={mutationDeleteEntireBackup.isPending}
/>
disabled={mutationDeleteEntireBackup.isPending}
>
Delete entire backup
</ButtonWithLoading>
// <ButtonWithConfirm
// title={"Delete entire backup"}
// description={"Are you sure you want to delete this entire backup?"}
// button={{
// main: {
// type: "button",
// variant: "destructive",
// text: "Delete entire backup",
// },
// confirm: {
// className: "w-full",
// text: "Delete",
// icon: <Trash2/>,
// variant: "destructive",
// onClick: async () => {
// mutationDeleteEntireBackup.mutateAsync()
// },
// },
// cancel: {
// className: "w-full",
// text: "Cancel",
// icon: <Trash2/>,
// variant: "outline",
// },
// }}
// isPending={mutationDeleteEntireBackup.isPending}
// />
)}
@@ -57,10 +57,8 @@ export const downloadBackupAction = userAction.schema(
}
};
console.log(input)
const result = await dispatchStorage(input, undefined, backupStorage.storageChannelId);
console.log(result);
return {
success: result.success,
value: result.url,
@@ -0,0 +1,58 @@
"use server"
import {userAction} from "@/lib/safe-actions/actions";
import {z} from "zod";
import {db} from "@/db";
import {eq} from "drizzle-orm";
import * as drizzleDb from "@/db";
import {BackupWith, Restoration} from "@/db/schema/07_database";
export const getDatabaseDataAction = userAction
.schema(
z.object({
databaseId: z.string(),
})
)
.action(async ({parsedInput}) => {
const {databaseId} = parsedInput;
const database = await db.query.database.findFirst({
where: eq(drizzleDb.schemas.database.id, databaseId),
with: {
project: true,
}
});
const backups = await db.query.backup.findMany({
where: eq(drizzleDb.schemas.backup.databaseId, databaseId),
with: {
restorations: true,
storages: {
with: {
storageChannel: true
}
}
},
orderBy: (b, {desc}) => [desc(b.createdAt)],
}) as BackupWith[];
const restorations = await db.query.restoration.findMany({
where: eq(drizzleDb.schemas.restoration.databaseId, databaseId),
orderBy: (r, {desc}) => [desc(r.createdAt)],
}) as Restoration[];
const totalBackups = backups.length;
const availableBackups = backups.filter(b => !b.deletedAt).length;
const successfulBackups = backups.filter(b => b.status === "success").length;
const successRate = totalBackups > 0 ? (successfulBackups / totalBackups) * 100 : null;
return {
database,
backups,
restorations,
stats: {
totalBackups,
availableBackups,
successRate
}
};
});
@@ -2,7 +2,6 @@
import {createContext, useContext, useState, ReactNode} from "react";
import {BackupWith} from "@/db/schema/07_database";
import {useRouter} from "next/navigation";
export type DatabaseActionKind = "restore" | "download" | "delete";
@@ -32,7 +31,6 @@ const BackupModalContext = createContext<BackupModalContextType | undefined>(und
export const BackupModalProvider = ({children}: { children: ReactNode }) => {
const [open, setOpen] = useState(false);
const router = useRouter();
const [action, setAction] = useState<DatabaseActionKind | null>(null);
const [backup, setBackup] = useState<BackupWith | null>(null);
@@ -46,7 +44,6 @@ export const BackupModalProvider = ({children}: { children: ReactNode }) => {
setOpen(false);
setAction(null);
setBackup(null);
router.refresh()
};
return (