1- import { Plus } from "lucide-react" ;
2- import { Link , Outlet } from "react-router" ;
1+ import { Plus , Upload } from "lucide-react" ;
2+ import { Form , Link , Outlet , useNavigation } from "react-router" ;
33
44import { getReportsSimple } from "~/.server/db/report" ;
55import { PageLayout } from "~/components/PageLayout" ;
66import { ButtonResponsive } from "~/components/ui/button" ;
77import { Typography } from "~/components/ui/typography" ;
88import { ReportType } from "~/types/GataReport.type" ;
99import { getRequiredUser } from "~/utils/auth.server" ;
10+ import { migrateAllReportFilesToBlob } from "~/utils/migrateReportFiles.server" ;
1011import { isAdmin } from "~/utils/roleUtils" ;
1112
1213import type { Route } from "./+types/report" ;
@@ -21,7 +22,36 @@ export const loader = async ({ request }: Route.LoaderArgs) => {
2122 return { reports, loggedInUser } ;
2223} ;
2324
24- export default function ReportPage ( { loaderData : { loggedInUser, reports } } : Route . ComponentProps ) {
25+ export const action = async ( { request } : Route . ActionArgs ) => {
26+ await getRequiredUser ( request ) ;
27+
28+ const formData = await request . formData ( ) ;
29+ const intent = formData . get ( "intent" ) ;
30+
31+ if ( intent === "migrateAllFiles" ) {
32+ try {
33+ const progress = await migrateAllReportFilesToBlob ( ) ;
34+ return {
35+ success : true ,
36+ message : `Migration complete: ${ progress . successful } reports migrated successfully, ${ progress . failed } failed. Total files: ${ progress . totalFiles } ` ,
37+ progress,
38+ } ;
39+ } catch ( error ) {
40+ console . error ( "Migration error:" , error ) ;
41+ return {
42+ success : false ,
43+ message : error instanceof Error ? error . message : "Migration failed with unknown error" ,
44+ } ;
45+ }
46+ }
47+
48+ return { success : false , message : "Unknown intent" } ;
49+ } ;
50+
51+ export default function ReportPage ( { loaderData : { loggedInUser, reports } , actionData } : Route . ComponentProps ) {
52+ const navigation = useNavigation ( ) ;
53+ const isMigrating = navigation . state === "submitting" && navigation . formData ?. get ( "intent" ) === "migrateAllFiles" ;
54+
2555 return (
2656 < PageLayout >
2757 < div className = "flex justify-between items-center" >
@@ -30,6 +60,42 @@ export default function ReportPage({ loaderData: { loggedInUser, reports } }: Ro
3060 </ Typography >
3161 { isAdmin ( loggedInUser ) && < ButtonResponsive as = { Link } to = "new" label = "Opprett" icon = { < Plus /> } /> }
3262 </ div >
63+
64+ { /* Migration Section */ }
65+ < div className = "my-4 p-4 bg-gray-50 rounded-lg border border-gray-200" >
66+ < Typography variant = "h3" className = "mb-2" >
67+ Admin: File Migration
68+ </ Typography >
69+ < Typography className = "text-sm text-gray-600 mb-3" >
70+ Migrate all report files from Cloudinary and oldReportFiles to Azure Blob Storage. This will update all
71+ content references to point directly to Azure URLs.
72+ </ Typography >
73+ < Form method = "post" >
74+ < input type = "hidden" name = "intent" value = "migrateAllFiles" />
75+ < ButtonResponsive
76+ as = "button"
77+ type = "submit"
78+ label = { isMigrating ? "Migrating..." : "Migrate All Files to Azure" }
79+ icon = { < Upload /> }
80+ disabled = { isMigrating }
81+ />
82+ </ Form >
83+ { actionData && (
84+ < div
85+ className = { `mt-3 p-3 rounded ${
86+ actionData . success ? "bg-green-100 text-green-800" : "bg-red-100 text-red-800"
87+ } `}
88+ >
89+ < Typography className = "font-semibold" > { actionData . message } </ Typography >
90+ { actionData . progress && (
91+ < Typography className = "text-sm mt-1" >
92+ Reports processed: { actionData . progress . completed } / { actionData . progress . totalReports }
93+ </ Typography >
94+ ) }
95+ </ div >
96+ ) }
97+ </ div >
98+
3399 < ul aria-labelledby = "report-page-title" className = "divide-y my-4" >
34100 { reports . map ( ( report ) => {
35101 return (
0 commit comments