mad-lawsuit/resources/js/Pages/Admin/DocketEntries/Show.vue
TheMaddax b7125cf2b7 Phase 4: Production deployment configuration
- Add Docker configuration (Dockerfile, docker-compose.yml)
- Add Nginx and Supervisor configuration
- Add deployment documentation (DEPLOYMENT.md)
- Complete Phase 3 data migration (63 entries, 63 docs, 28 subs)
- Add responsive PDF viewer component
- Fix date format to American (MM/DD/YYYY)
- Update typography to match v1.0
- Add admin dashboard with full CRUD operations
- Configure PostgreSQL with secure password
- Connect to caddy_network for reverse proxy
- Ready for production deployment
2025-12-17 19:12:32 -07:00

249 lines
11 KiB
Vue

<script setup lang="ts">
import { Head, Link, router, useForm } from '@inertiajs/vue3';
import { ref } from 'vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import DangerButton from '@/Components/DangerButton.vue';
interface Document {
id: number;
title: string;
original_filename: string;
file_size: number;
summary: string | null;
display_order: number;
}
interface Entry {
id: number;
date: string;
title: string;
summary: string;
notes: string | null;
documents: Document[];
}
interface Props {
entry: Entry;
}
const props = defineProps<Props>();
const uploadForm = useForm({
file: null as File | null,
title: '',
summary: '',
});
const fileInput = ref<HTMLInputElement | null>(null);
const handleFileSelect = (event: Event) => {
const target = event.target as HTMLInputElement;
if (target.files && target.files[0]) {
uploadForm.file = target.files[0];
uploadForm.title = target.files[0].name.replace('.pdf', '');
}
};
const uploadDocument = () => {
if (!uploadForm.file) return;
uploadForm.post(route('admin.documents.store', props.entry.id), {
onSuccess: () => {
uploadForm.reset();
if (fileInput.value) {
fileInput.value.value = '';
}
},
});
};
const deleteDocument = (docId: number, title: string) => {
if (confirm(`Are you sure you want to delete "${title}"?`)) {
router.delete(route('admin.documents.destroy', docId));
}
};
const deleteEntry = () => {
if (confirm(`Are you sure you want to delete this entry and all ${props.entry.documents.length} associated documents?`)) {
router.delete(route('admin.docket-entries.destroy', props.entry.id));
}
};
const formatFileSize = (bytes: number): string => {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
};
</script>
<template>
<div class="min-h-screen bg-gray-100">
<Head :title="`View Entry: ${props.entry.title}`" />
<!-- Header -->
<header class="bg-white shadow">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div class="flex justify-between items-center">
<div>
<h1 class="text-3xl font-bold text-gray-900">
{{ props.entry.title }}
</h1>
<p class="text-sm text-gray-600 mt-1">
Filed on {{ props.entry.date }}
</p>
</div>
<div class="flex gap-4">
<Link
:href="route('admin.docket-entries.index')"
class="px-4 py-2 text-sm text-gray-700 hover:text-gray-900 transition-colors"
>
Back to Entries
</Link>
<Link
:href="route('admin.docket-entries.edit', props.entry.id)"
class="px-4 py-2 bg-indigo-600 text-white text-sm rounded-lg hover:bg-indigo-700 transition-colors"
>
Edit Entry
</Link>
</div>
</div>
</div>
</header>
<!-- Main Content -->
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Entry Details -->
<div class="lg:col-span-2 space-y-6">
<!-- Summary -->
<div class="bg-white rounded-lg shadow p-6">
<h2 class="text-xl font-bold text-gray-900 mb-4">Summary</h2>
<p class="text-gray-700 whitespace-pre-wrap">{{ props.entry.summary }}</p>
</div>
<!-- Notes -->
<div v-if="props.entry.notes" class="bg-white rounded-lg shadow p-6">
<h2 class="text-xl font-bold text-gray-900 mb-4">Internal Notes</h2>
<p class="text-gray-700 whitespace-pre-wrap">{{ props.entry.notes }}</p>
</div>
<!-- Documents -->
<div class="bg-white rounded-lg shadow p-6">
<h2 class="text-xl font-bold text-gray-900 mb-4">
Documents ({{ props.entry.documents.length }})
</h2>
<div v-if="props.entry.documents.length > 0" class="space-y-4">
<div
v-for="doc in props.entry.documents"
:key="doc.id"
class="border border-gray-200 rounded-lg p-4 hover:bg-gray-50 transition-colors"
>
<div class="flex justify-between items-start">
<div class="flex-1">
<h3 class="font-semibold text-gray-900">{{ doc.title }}</h3>
<p class="text-sm text-gray-600 mt-1">
{{ doc.original_filename }} {{ formatFileSize(doc.file_size) }}
</p>
<p v-if="doc.summary" class="text-sm text-gray-700 mt-2">
{{ doc.summary }}
</p>
</div>
<div class="flex gap-2 ml-4">
<a
:href="route('api.documents.download', doc.id)"
target="_blank"
class="text-blue-600 hover:text-blue-900 text-sm transition-colors"
>
Download
</a>
<button
@click="deleteDocument(doc.id, doc.title)"
class="text-red-600 hover:text-red-900 text-sm transition-colors"
>
Delete
</button>
</div>
</div>
</div>
</div>
<div v-else class="text-center py-8 text-gray-500">
No documents uploaded yet.
</div>
</div>
</div>
<!-- Sidebar -->
<div class="space-y-6">
<!-- Upload Document -->
<div class="bg-white rounded-lg shadow p-6">
<h2 class="text-lg font-bold text-gray-900 mb-4">Upload Document</h2>
<form @submit.prevent="uploadDocument" class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
PDF File *
</label>
<input
ref="fileInput"
type="file"
accept=".pdf,application/pdf"
@change="handleFileSelect"
required
class="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Title *
</label>
<input
v-model="uploadForm.title"
type="text"
required
class="block w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Summary (Optional)
</label>
<textarea
v-model="uploadForm.summary"
rows="3"
class="block w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
></textarea>
</div>
<PrimaryButton
type="submit"
class="w-full justify-center"
:class="{ 'opacity-25': uploadForm.processing }"
:disabled="uploadForm.processing || !uploadForm.file"
>
Upload Document
</PrimaryButton>
</form>
</div>
<!-- Danger Zone -->
<div class="bg-red-50 border border-red-200 rounded-lg p-6">
<h2 class="text-lg font-bold text-red-900 mb-2">Danger Zone</h2>
<p class="text-sm text-red-700 mb-4">
Deleting this entry will also delete all {{ props.entry.documents.length }} associated documents. This action cannot be undone.
</p>
<DangerButton
@click="deleteEntry"
class="w-full justify-center"
>
Delete Entry
</DangerButton>
</div>
</div>
</div>
</main>
</div>
</template>