import { useState } from "react"; import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { useToast } from "@/hooks/use-toast"; import { Database, FileArchive, HardDrive, Cloud, RefreshCw, } from "lucide-react"; import { useMutation, useQuery } from "@tanstack/react-query"; import { apiRequest, queryClient } from "@/lib/queryClient"; import { formatDateToHumanReadable } from "@/utils/dateUtils"; export default function DatabaseManagementPage() { const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const { toast } = useToast(); // ----- Database status query ----- const { data: dbStatus, isLoading: isLoadingStatus } = useQuery({ queryKey: ["/db/status"], queryFn: async () => { const res = await apiRequest("GET", "/api/database-management/status"); return res.json(); }, }); // helper: safely extract filename from fetch Response headers function getFileNameFromResponse(res: Response): string { const disposition = res.headers.get("Content-Disposition") || ""; // filename*=UTF-8''encoded%20name.zip const starMatch = disposition.match(/filename\*\s*=\s*([^;]+)/i); if (starMatch && starMatch[1]) { let val = starMatch[1] .trim() .replace(/^UTF-8''/i, "") .replace(/['"]/g, ""); try { return decodeURIComponent(val); } catch { return val; } } // filename="name" OR filename=name const fileNameRegex = /filename\s*=\s*"([^"]+)"|filename\s*=\s*([^;]+)/i; const normalMatch = disposition.match(fileNameRegex); if (normalMatch) { // normalMatch[1] corresponds to the quoted capture, normalMatch[2] to unquoted const candidate = (normalMatch[1] ?? normalMatch[2] ?? "").trim(); if (candidate) return candidate.replace(/['"]/g, ""); } // fallback by content-type const ct = (res.headers.get("Content-Type") || "").toLowerCase(); const iso = new Date().toISOString().replace(/[:.]/g, "-"); if (ct.includes("zip")) return `dental_backup_${iso}.zip`; if (ct.includes("gzip") || ct.includes("x-gzip")) return `dental_backup_${iso}.tar.gz`; return `dental_backup_${iso}.dump`; } // ----- Backup mutation ----- const backupMutation = useMutation({ mutationFn: async () => { const res = await apiRequest("POST", "/api/database-management/backup"); if (!res.ok) { // Try to parse JSON error let errorBody = {}; try { errorBody = await res.json(); } catch {} throw new Error((errorBody as any)?.error || "Backup failed"); } // get filename from Content-Disposition or fallback const fileName = getFileNameFromResponse(res); // Convert response to blob (file) const blob = await res.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = fileName; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); }, onSuccess: () => { toast({ title: "Backup Complete", description: "Database backup downloaded successfully", variant: "default", }); queryClient.invalidateQueries({ queryKey: ["/db/status"] }); }, onError: (error: any) => { console.error("Backup failed:", error); toast({ title: "Error", description: error.message, variant: "destructive", }); }, }); return (
Manage your dental practice database with backup, export capabilities
Create a complete backup of your dental practice database including patients, appointments, claims, and all related data.
Loading status...
) : ({dbStatus?.connected ? "Connected" : "Disconnected"}
{dbStatus?.size ?? "Unknown"}
{dbStatus?.patients ? `${dbStatus.patients} patients` : "N/A"}