Add schema reconciliation migration and file removal in bulk upload
Migration: - Add standalone hasConflict index on ConflictOfInterest - Ensure roundId is nullable on ConflictOfInterest - Drop stale composite roundId_hasConflict index Bulk upload: - Add trash icon button to remove uploaded files - Uses existing file.delete endpoint with audit logging Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
85a0fa5016
commit
5e0c8b2dfe
|
|
@ -0,0 +1,19 @@
|
||||||
|
-- =============================================================================
|
||||||
|
-- Schema Reconciliation: Fill remaining gaps between migrations and schema.prisma
|
||||||
|
-- =============================================================================
|
||||||
|
-- All statements are idempotent (safe to re-run on any database state).
|
||||||
|
|
||||||
|
-- 1. ConflictOfInterest: add standalone hasConflict index (schema has @@index([hasConflict]))
|
||||||
|
-- Migration 20260205223133 only created composite (roundId, hasConflict) index.
|
||||||
|
CREATE INDEX IF NOT EXISTS "ConflictOfInterest_hasConflict_idx" ON "ConflictOfInterest"("hasConflict");
|
||||||
|
|
||||||
|
-- 2. Ensure ConflictOfInterest.roundId is nullable (schema says String?)
|
||||||
|
-- Pipeline migration (20260213) makes it nullable, but guard for safety.
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "ConflictOfInterest" ALTER COLUMN "roundId" DROP NOT NULL;
|
||||||
|
EXCEPTION WHEN others THEN NULL;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- 3. Drop stale composite index that no longer matches schema
|
||||||
|
-- Schema only has @@index([hasConflict]) and @@index([userId]), not (roundId, hasConflict).
|
||||||
|
DROP INDEX IF EXISTS "ConflictOfInterest_roundId_hasConflict_idx";
|
||||||
|
|
@ -47,6 +47,7 @@ import {
|
||||||
FileUp,
|
FileUp,
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
|
Trash2,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { cn, formatFileSize } from '@/lib/utils'
|
import { cn, formatFileSize } from '@/lib/utils'
|
||||||
import { Pagination } from '@/components/shared/pagination'
|
import { Pagination } from '@/components/shared/pagination'
|
||||||
|
|
@ -155,6 +156,26 @@ export default function BulkUploadPage() {
|
||||||
[utils, refetch]
|
[utils, refetch]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Delete a file
|
||||||
|
const deleteMutation = trpc.file.delete.useMutation({
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success('File removed')
|
||||||
|
refetch()
|
||||||
|
},
|
||||||
|
onError: (err) => {
|
||||||
|
toast.error(`Failed to remove file: ${err.message}`)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleDeleteFile = useCallback(
|
||||||
|
(fileId: string) => {
|
||||||
|
if (confirm('Remove this uploaded file?')) {
|
||||||
|
deleteMutation.mutate({ id: fileId })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[deleteMutation]
|
||||||
|
)
|
||||||
|
|
||||||
const uploadMutation = trpc.file.adminUploadForRoundRequirement.useMutation()
|
const uploadMutation = trpc.file.adminUploadForRoundRequirement.useMutation()
|
||||||
|
|
||||||
// Upload a single file for a project requirement
|
// Upload a single file for a project requirement
|
||||||
|
|
@ -513,7 +534,20 @@ export default function BulkUploadPage() {
|
||||||
</div>
|
</div>
|
||||||
) : req.file || uploadState?.status === 'complete' ? (
|
) : req.file || uploadState?.status === 'complete' ? (
|
||||||
<div className="flex flex-col items-center gap-1">
|
<div className="flex flex-col items-center gap-1">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
<CheckCircle2 className="h-4 w-4 text-green-600" />
|
<CheckCircle2 className="h-4 w-4 text-green-600" />
|
||||||
|
{req.file && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="text-muted-foreground hover:text-destructive transition-colors cursor-pointer"
|
||||||
|
title="Remove file"
|
||||||
|
onClick={() => handleDeleteFile(req.file!.id)}
|
||||||
|
disabled={deleteMutation.isPending}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
{req.file?.bucket && req.file?.objectKey ? (
|
{req.file?.bucket && req.file?.objectKey ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue