backup page - working 1 done
This commit is contained in:
@@ -17,8 +17,13 @@ const PatientsPage = lazy(() => import("./pages/patients-page"));
|
||||
const SettingsPage = lazy(() => import("./pages/settings-page"));
|
||||
const ClaimsPage = lazy(() => import("./pages/claims-page"));
|
||||
const PaymentsPage = lazy(() => import("./pages/payments-page"));
|
||||
const InsuranceEligibilityPage = lazy(()=> import("./pages/insurance-eligibility-page"))
|
||||
const InsuranceEligibilityPage = lazy(
|
||||
() => import("./pages/insurance-eligibility-page")
|
||||
);
|
||||
const DocumentPage = lazy(() => import("./pages/documents-page"));
|
||||
const DatabaseManagementPage = lazy(
|
||||
() => import("./pages/database-management-page")
|
||||
);
|
||||
const NotFound = lazy(() => import("./pages/not-found"));
|
||||
|
||||
function Router() {
|
||||
@@ -32,9 +37,16 @@ function Router() {
|
||||
<ProtectedRoute path="/patients" component={() => <PatientsPage />} />
|
||||
<ProtectedRoute path="/settings" component={() => <SettingsPage />} />
|
||||
<ProtectedRoute path="/claims" component={() => <ClaimsPage />} />
|
||||
<ProtectedRoute path="/insurance-eligibility" component={()=><InsuranceEligibilityPage/>}/>
|
||||
<ProtectedRoute
|
||||
path="/insurance-eligibility"
|
||||
component={() => <InsuranceEligibilityPage />}
|
||||
/>
|
||||
<ProtectedRoute path="/payments" component={() => <PaymentsPage />} />
|
||||
<ProtectedRoute path="/documents" component={() => <DocumentPage />} />
|
||||
<ProtectedRoute
|
||||
path="/database-management"
|
||||
component={() => <DatabaseManagementPage />}
|
||||
/>
|
||||
<Route path="/auth" component={() => <AuthPage />} />
|
||||
<Route component={() => <NotFound />} />
|
||||
</Switch>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Shield,
|
||||
CreditCard,
|
||||
FolderOpen,
|
||||
Database,
|
||||
} from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -56,6 +57,11 @@ export function Sidebar({ isMobileOpen, setIsMobileOpen }: SidebarProps) {
|
||||
path: "/documents",
|
||||
icon: <FolderOpen className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
name: "Backup Database",
|
||||
path: "/database-management",
|
||||
icon: <Database className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
path: "/settings",
|
||||
|
||||
203
apps/Frontend/src/pages/database-management-page.tsx
Normal file
203
apps/Frontend/src/pages/database-management-page.tsx
Normal file
@@ -0,0 +1,203 @@
|
||||
import { useState } from "react";
|
||||
import { TopAppBar } from "@/components/layout/top-app-bar";
|
||||
import { Sidebar } from "@/components/layout/sidebar";
|
||||
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";
|
||||
|
||||
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();
|
||||
},
|
||||
});
|
||||
|
||||
// ----- 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");
|
||||
}
|
||||
|
||||
// Convert response to blob (file)
|
||||
const blob = await res.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
const disposition = res.headers.get("Content-Disposition");
|
||||
const fileName =
|
||||
disposition?.split("filename=")[1]?.replace(/"/g, "") ||
|
||||
`dental_backup_${new Date().toISOString()}.dump`;
|
||||
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 (
|
||||
<div className="flex h-screen overflow-hidden bg-gray-100">
|
||||
<Sidebar
|
||||
isMobileOpen={isMobileMenuOpen}
|
||||
setIsMobileOpen={setIsMobileMenuOpen}
|
||||
/>
|
||||
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
<TopAppBar
|
||||
toggleMobileMenu={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
||||
/>
|
||||
|
||||
<main className="flex-1 overflow-x-hidden overflow-y-auto bg-gray-100 p-6">
|
||||
<div className="max-w-6xl mx-auto space-y-6">
|
||||
{/* Page Header */}
|
||||
<div className="bg-white rounded-lg shadow-sm p-6 border">
|
||||
<h1 className="text-2xl font-bold text-gray-900 flex items-center space-x-3">
|
||||
<Database className="h-8 w-8 text-blue-600" />
|
||||
<span>Database Management</span>
|
||||
</h1>
|
||||
<p className="text-gray-600 mt-2">
|
||||
Manage your dental practice database with backup, export
|
||||
capabilities
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Database Backup Section */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<HardDrive className="h-5 w-5" />
|
||||
<span>Database Backup</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<p className="text-gray-600">
|
||||
Create a complete backup of your dental practice database
|
||||
including patients, appointments, claims, and all related
|
||||
data.
|
||||
</p>
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
<Button
|
||||
onClick={() => backupMutation.mutate()}
|
||||
disabled={backupMutation.isPending}
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
{backupMutation.isPending ? (
|
||||
<RefreshCw className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<FileArchive className="h-4 w-4" />
|
||||
)}
|
||||
<span>
|
||||
{backupMutation.isPending
|
||||
? "Creating Backup..."
|
||||
: "Create Backup"}
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
<div className="text-sm text-gray-500">
|
||||
Last backup:{" "}
|
||||
{dbStatus?.lastBackup
|
||||
? new Date(dbStatus.lastBackup).toLocaleString()
|
||||
: "Never"}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Database Status Section */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<Database className="h-5 w-5" />
|
||||
<span>Database Status</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{isLoadingStatus ? (
|
||||
<p className="text-gray-500">Loading status...</p>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="p-4 bg-green-50 rounded-lg">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 bg-green-500 rounded-full"></div>
|
||||
<span className="font-medium text-green-800">
|
||||
Status
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-green-600 mt-1">
|
||||
{dbStatus?.connected ? "Connected" : "Disconnected"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-blue-50 rounded-lg">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Database className="h-4 w-4 text-blue-500" />
|
||||
<span className="font-medium text-blue-800">Size</span>
|
||||
</div>
|
||||
<p className="text-blue-600 mt-1">
|
||||
{dbStatus?.size ?? "Unknown"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-purple-50 rounded-lg">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Cloud className="h-4 w-4 text-purple-500" />
|
||||
<span className="font-medium text-purple-800">
|
||||
Records
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-purple-600 mt-1">
|
||||
{dbStatus?.patients
|
||||
? `${dbStatus.patients} patients`
|
||||
: "N/A"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { TopAppBar } from "@/components/layout/top-app-bar";
|
||||
import { Sidebar } from "@/components/layout/sidebar";
|
||||
import {
|
||||
@@ -7,32 +6,16 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
CardDescription,
|
||||
} from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
AlertCircle,
|
||||
DollarSign,
|
||||
ArrowDown,
|
||||
Upload,
|
||||
Image,
|
||||
X,
|
||||
Trash2,
|
||||
Save,
|
||||
} from "lucide-react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableRow,
|
||||
TableHead,
|
||||
TableCell,
|
||||
} from "@/components/ui/table";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -40,13 +23,6 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import PaymentsRecentTable from "@/components/payments/payments-recent-table";
|
||||
import PaymentsOfPatientModal from "@/components/payments/payments-of-patient-table";
|
||||
|
||||
@@ -56,7 +32,6 @@ export default function PaymentsPage() {
|
||||
const [uploadedImage, setUploadedImage] = useState<File | null>(null);
|
||||
const [isExtracting, setIsExtracting] = useState(false);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [extractedPaymentData, setExtractedPaymentData] = useState<any[]>([]);
|
||||
const [editableData, setEditableData] = useState<any[]>([]);
|
||||
|
||||
const { toast } = useToast();
|
||||
|
||||
Reference in New Issue
Block a user