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

321 lines
8.9 KiB
Vue
Raw Normal View History

<template>
<v-dialog
v-model="dialog"
max-width="600"
persistent
scrollable
>
<v-card>
<v-card-title class="d-flex align-center">
<v-icon class="mr-2">mdi-file-pdf</v-icon>
<span>Configure PDF Export</span>
<v-spacer />
<v-btn
@click="closeModal"
icon="mdi-close"
variant="text"
size="small"
/>
</v-card-title>
<v-card-text>
<v-form ref="form" @submit.prevent="handleGenerate">
<v-row>
<!-- Document Name -->
<v-col cols="12">
<v-text-field
v-model="options.documentName"
label="Document Name"
variant="outlined"
:rules="[rules.required]"
required
placeholder="e.g., May 2025 Expenses"
/>
</v-col>
<!-- Subheader -->
<v-col cols="12">
<v-text-field
v-model="options.subheader"
label="Subheader (optional)"
variant="outlined"
placeholder="e.g., Port Nimara Business Trip"
/>
</v-col>
<!-- Grouping Options -->
<v-col cols="12">
<v-select
v-model="options.groupBy"
:items="groupByOptions"
label="Group Expenses By"
variant="outlined"
item-title="text"
item-value="value"
/>
</v-col>
<!-- Include Options -->
<v-col cols="12">
<v-card variant="tonal" class="pa-4">
<v-card-subtitle class="px-0 pb-2">
<v-icon class="mr-2">mdi-checkbox-marked-circle</v-icon>
Include in PDF
</v-card-subtitle>
<div class="include-options">
<v-checkbox
v-model="options.includeReceipts"
color="primary"
hide-details
>
<template #label>
<div>
<div class="font-weight-medium">Include Receipt Images</div>
<div class="text-caption text-grey-darken-1">Attach receipt photos to the PDF document</div>
</div>
</template>
</v-checkbox>
<v-checkbox
v-model="options.includeSummary"
color="primary"
hide-details
>
<template #label>
<div>
<div class="font-weight-medium">Include Summary</div>
<div class="text-caption text-grey-darken-1">Add totals and breakdown at the end</div>
</div>
</template>
</v-checkbox>
<v-checkbox
v-model="options.includeDetails"
color="primary"
hide-details
>
<template #label>
<div>
<div class="font-weight-medium">Include Expense Details</div>
<div class="text-caption text-grey-darken-1">Show establishment name, date, description</div>
</div>
</template>
</v-checkbox>
<v-checkbox
v-model="options.includeProcessingFee"
color="primary"
hide-details
>
<template #label>
<div>
<div class="font-weight-medium">Include Processing Fee</div>
<div class="text-caption text-grey-darken-1">Add 5% processing fee to totals</div>
</div>
</template>
</v-checkbox>
</div>
</v-card>
</v-col>
<!-- Page Format -->
<v-col cols="12">
<v-select
v-model="options.pageFormat"
:items="pageFormatOptions"
label="Page Format"
variant="outlined"
item-title="text"
item-value="value"
/>
</v-col>
<!-- Preview Info -->
<v-col cols="12">
<v-alert
type="info"
variant="tonal"
class="mb-0"
>
<template #prepend>
<v-icon>mdi-information</v-icon>
</template>
<div class="font-weight-medium mb-2">PDF Preview</div>
<div class="text-body-2">
<div><strong>Selected expenses:</strong> {{ selectedExpenses.length }}</div>
<div><strong>Total amount:</strong> {{ totalAmount.toFixed(2) }}</div>
<div v-if="options.groupBy !== 'none'">
<strong>Grouped by:</strong> {{ groupByLabel }}
</div>
</div>
</v-alert>
</v-col>
</v-row>
</v-form>
</v-card-text>
<v-card-actions class="px-6 pb-4">
<v-spacer />
<v-btn
@click="closeModal"
variant="text"
:disabled="generating"
>
Cancel
</v-btn>
<v-btn
@click="handleGenerate"
:disabled="!options.documentName || generating"
color="primary"
:loading="generating"
>
<v-icon v-if="!generating" class="mr-1">mdi-file-pdf</v-icon>
Generate PDF
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</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';
}
// Computed dialog model
const dialog = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
});
// Reactive state
const form = ref();
const generating = ref(false);
const options = ref<PDFOptions>({
documentName: '',
subheader: '',
groupBy: 'payer',
includeReceipts: true,
includeSummary: true,
includeDetails: true,
includeProcessingFee: true,
pageFormat: 'A4'
});
// Form options
const groupByOptions = [
{ text: 'No Grouping', value: 'none' },
{ text: 'Group by Person', value: 'payer' },
{ text: 'Group by Category', value: 'category' },
{ text: 'Group by Date', value: 'date' }
];
const pageFormatOptions = [
{ text: 'A4 (210 × 297 mm)', value: 'A4' },
{ text: 'Letter (8.5 × 11 in)', value: 'Letter' },
{ text: 'Legal (8.5 × 14 in)', value: 'Legal' }
];
// Validation rules
const rules = {
required: (value: string) => !!value || 'This field is required'
};
// 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 = () => {
dialog.value = false;
};
const handleGenerate = async () => {
if (!form.value) return;
const { valid } = await form.value.validate();
if (!valid) return;
generating.value = true;
try {
emit('generate', { ...options.value });
// Close modal on successful generation
dialog.value = false;
} catch (error) {
console.error('[PDFOptionsModal] Error generating PDF:', error);
} finally {
generating.value = false;
}
};
// Watch for modal open to set default document name
watch(dialog, (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>
.include-options {
display: flex;
flex-direction: column;
gap: 12px;
}
.include-options :deep(.v-checkbox) {
align-items: flex-start;
}
.include-options :deep(.v-checkbox .v-selection-control__wrapper) {
margin-top: 2px;
}
.v-card-actions {
border-top: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
}
</style>