port-nimara-client-portal/components/PDFOptionsModal.vue

309 lines
9.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div v-if="modelValue" class="modal-overlay" @click.self="closeModal">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Configure PDF Export</h3>
<button @click="closeModal" class="btn-close">
<Icon name="mdi:close" class="w-5 h-5" />
</button>
</div>
<form @submit.prevent="handleGenerate" class="modal-body">
<!-- Document Name -->
<div class="form-group">
<label class="label">
<span class="label-text">Document Name <span class="text-red-500">*</span></span>
</label>
<input
v-model="options.documentName"
type="text"
placeholder="e.g., May 2025 Expenses"
class="input input-bordered w-full"
required
/>
</div>
<!-- Subheader -->
<div class="form-group">
<label class="label">
<span class="label-text">Subheader (optional)</span>
</label>
<input
v-model="options.subheader"
type="text"
placeholder="e.g., Port Nimara Business Trip"
class="input input-bordered w-full"
/>
</div>
<!-- Grouping Options -->
<div class="form-group">
<label class="label">
<span class="label-text">Group Expenses By</span>
</label>
<select v-model="options.groupBy" class="select select-bordered w-full">
<option value="none">No Grouping</option>
<option value="payer">Group by Person</option>
<option value="category">Group by Category</option>
<option value="date">Group by Date</option>
</select>
</div>
<!-- Include Options -->
<div class="form-group">
<label class="label">
<span class="label-text">Include in PDF</span>
</label>
<div class="space-y-3">
<label class="flex items-center gap-3 cursor-pointer">
<input
v-model="options.includeReceipts"
type="checkbox"
class="checkbox checkbox-primary"
/>
<div>
<div class="font-medium">Include Receipt Images</div>
<div class="text-sm text-gray-500">Attach receipt photos to the PDF document</div>
</div>
</label>
<label class="flex items-center gap-3 cursor-pointer">
<input
v-model="options.includeSummary"
type="checkbox"
class="checkbox checkbox-primary"
/>
<div>
<div class="font-medium">Include Summary</div>
<div class="text-sm text-gray-500">Add totals and breakdown at the end</div>
</div>
</label>
<label class="flex items-center gap-3 cursor-pointer">
<input
v-model="options.includeDetails"
type="checkbox"
class="checkbox checkbox-primary"
/>
<div>
<div class="font-medium">Include Expense Details</div>
<div class="text-sm text-gray-500">Show establishment name, date, description</div>
</div>
</label>
<label class="flex items-center gap-3 cursor-pointer">
<input
v-model="options.includeProcessingFee"
type="checkbox"
class="checkbox checkbox-primary"
/>
<div>
<div class="font-medium">Include Processing Fee</div>
<div class="text-sm text-gray-500">Add 5% processing fee to totals</div>
</div>
</label>
</div>
</div>
<!-- Page Format -->
<div class="form-group">
<label class="label">
<span class="label-text">Page Format</span>
</label>
<select v-model="options.pageFormat" class="select select-bordered w-full">
<option value="A4">A4 (210 × 297 mm)</option>
<option value="Letter">Letter (8.5 × 11 in)</option>
<option value="Legal">Legal (8.5 × 14 in)</option>
</select>
</div>
<!-- Preview Info -->
<div class="bg-blue-50 dark:bg-blue-950 rounded-lg p-4">
<div class="flex items-start gap-3">
<Icon name="mdi:information" class="w-5 h-5 text-blue-500 mt-0.5 flex-shrink-0" />
<div class="text-sm">
<div class="font-medium text-blue-900 dark:text-blue-100 mb-1">
PDF Preview
</div>
<div class="text-blue-800 dark:text-blue-200">
<div>Selected expenses: <strong>{{ selectedExpenses.length }}</strong></div>
<div>Total amount: <strong>€{{ totalAmount.toFixed(2) }}</strong></div>
<div v-if="options.groupBy !== 'none'">
Grouped by: <strong>{{ groupByLabel }}</strong>
</div>
</div>
</div>
</div>
</div>
</form>
<div class="modal-footer">
<button
type="button"
@click="closeModal"
class="btn btn-ghost"
>
Cancel
</button>
<button
@click="handleGenerate"
:disabled="!options.documentName || generating"
class="btn btn-primary"
>
<span v-if="generating" class="loading loading-spinner loading-sm"></span>
<Icon v-else name="mdi:file-pdf" class="w-4 h-4" />
{{ generating ? 'Generating...' : 'Generate PDF' }}
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue';
// Props
interface Props {
modelValue: boolean;
selectedExpenses: number[];
}
const props = defineProps<Props>();
// Emits
const emit = defineEmits<{
'update:modelValue': [value: boolean];
'generate': [options: PDFOptions];
}>();
// PDF Options interface
interface PDFOptions {
documentName: string;
subheader: string;
groupBy: 'none' | 'payer' | 'category' | 'date';
includeReceipts: boolean;
includeSummary: boolean;
includeDetails: boolean;
includeProcessingFee: boolean;
pageFormat: 'A4' | 'Letter' | 'Legal';
}
// Reactive state
const generating = ref(false);
const options = ref<PDFOptions>({
documentName: '',
subheader: '',
groupBy: 'payer',
includeReceipts: true,
includeSummary: true,
includeDetails: true,
includeProcessingFee: true,
pageFormat: 'A4'
});
// Computed
const totalAmount = computed(() => {
// This would ideally come from the parent component
// For now, we'll use a placeholder
return props.selectedExpenses.length * 25; // Rough estimate
});
const groupByLabel = computed(() => {
switch (options.value.groupBy) {
case 'payer': return 'Person';
case 'category': return 'Category';
case 'date': return 'Date';
default: return 'None';
}
});
// Methods
const closeModal = () => {
emit('update:modelValue', false);
};
const handleGenerate = async () => {
if (!options.value.documentName) return;
generating.value = true;
try {
emit('generate', { ...options.value });
} catch (error) {
console.error('[PDFOptionsModal] Error generating PDF:', error);
} finally {
generating.value = false;
}
};
// Watch for modal open to set default document name
watch(() => props.modelValue, (isOpen) => {
if (isOpen && !options.value.documentName) {
const now = new Date();
const monthName = now.toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
options.value.documentName = `${monthName} Expenses`;
}
});
</script>
<style scoped>
.modal-overlay {
@apply fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50;
}
.modal-content {
@apply bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full max-h-[90vh] overflow-hidden;
}
.modal-header {
@apply flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-700;
}
.modal-title {
@apply text-lg font-semibold text-gray-900 dark:text-white;
}
.btn-close {
@apply p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors;
}
.modal-body {
@apply p-6 space-y-4 overflow-y-auto;
}
.form-group {
@apply space-y-2;
}
.label-text {
@apply text-sm font-medium text-gray-700 dark:text-gray-300;
}
.modal-footer {
@apply flex items-center justify-end gap-3 p-6 border-t border-gray-200 dark:border-gray-700;
}
.btn {
@apply inline-flex items-center gap-2 px-4 py-2 rounded-lg font-medium transition-colors;
}
.btn-ghost {
@apply text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700;
}
.btn-primary {
@apply bg-primary text-white hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed;
}
.input, .select {
@apply block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-primary focus:border-transparent;
}
.checkbox {
@apply rounded border-gray-300 dark:border-gray-600 text-primary focus:ring-primary;
}
</style>