- Removed automatic email sending on docket entry creation - Added sendNotification() method to DocketEntryController - Added route for manual notification trigger - Added 'Send Notification' button to Show page - Email now only sends when admin explicitly clicks button - Still in testing mode (chris@deafgain.org only)
269 lines
12 KiB
Vue
269 lines
12 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 sendNotification = () => {
|
|
if (confirm('Send email notification to all subscribers about this docket entry?')) {
|
|
router.post(route('admin.docket-entries.send-notification', 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>
|
|
|
|
<!-- Send Notification -->
|
|
<div class="bg-blue-50 border border-blue-200 rounded-lg p-6">
|
|
<h2 class="text-lg font-bold text-blue-900 mb-2">Email Notification</h2>
|
|
<p class="text-sm text-blue-700 mb-4">
|
|
Send email notification to all active subscribers about this docket entry.
|
|
</p>
|
|
<PrimaryButton
|
|
@click="sendNotification"
|
|
class="w-full justify-center bg-blue-600 hover:bg-blue-700"
|
|
>
|
|
Send Notification
|
|
</PrimaryButton>
|
|
</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>
|