gdc_cmod/app/Views/superuser/pdf_batch.php
mahdahar 0fad3baab7 feat: add PDF batch generation page for superuser
- Add /superuser/pdf-batch page with textarea input for access numbers
- Create API endpoint /api/superuser/pdf-batch to process batch PDF generation
- Use report/{id}/pdf endpoint for authenticated PDF generation
- Show results table with success/failed status, language, and type (new/regen)
- Remove deprecated /batch/pdf endpoint
- Add PDF Batch menu item to superuser navigation
2026-03-11 09:34:11 +07:00

171 lines
7.7 KiB
PHP

<?php $config = require APPPATH . 'Views/shared/config.php'; ?>
<?= $this->extend('shared/layout'); ?>
<?= $this->section('content') ?>
<div x-data="pdfBatch" class="contents">
<main class="p-4 flex-1 flex flex-col gap-4 max-w-4xl w-full mx-auto overflow-hidden min-h-0">
<div class="card bg-base-100 shadow-xl border border-base-200 h-full">
<div class="card-body p-4 overflow-y-auto">
<div class="flex justify-between items-center mb-4">
<h2 class="text-2xl font-bold flex items-center gap-2 text-base-content">
<i class="fa fa-file-pdf text-primary"></i> PDF Batch Generation
</h2>
</div>
<div class="form-control w-full mb-4">
<label class="label">
<span class="label-text font-medium">Access Numbers</span>
<span class="label-text-alt">One per line or comma-separated</span>
</label>
<textarea
x-model="accessNumbersInput"
class="textarea textarea-bordered w-full font-mono text-sm h-48"
placeholder="24000001&#10;24000002&#10;24000003"
:disabled="isProcessing"
></textarea>
</div>
<div class="flex justify-between items-center mb-4">
<div class="text-sm text-base-content/60">
<span x-show="!isProcessing" x-text="`${getAccessNumberCount()} access numbers`"></span>
<span x-show="isProcessing" x-text="`Processing ${currentIndex} of ${totalCount}...`"></span>
</div>
<button
class="btn btn-primary"
@click="processBatch()"
:disabled="isProcessing || getAccessNumberCount() === 0"
>
<span x-show="isProcessing" class="loading loading-spinner loading-sm"></span>
<i x-show="!isProcessing" class="fa fa-play mr-2"></i>
<span x-text="isProcessing ? 'Processing...' : 'Generate PDFs'"></span>
</button>
</div>
<div x-show="results.length > 0" class="mt-4">
<div class="flex justify-between items-center mb-2">
<h3 class="text-lg font-semibold">Results</h3>
<div class="flex gap-4 text-sm">
<span class="text-success">
<i class="fa fa-check-circle mr-1"></i>
<span x-text="successfulCount"></span> Success
</span>
<span class="text-error">
<i class="fa fa-times-circle mr-1"></i>
<span x-text="failedCount"></span> Failed
</span>
</div>
</div>
<div class="overflow-x-auto border rounded-lg">
<table class="table table-zebra w-full">
<thead>
<tr>
<th class="w-16">Status</th>
<th>Access Number</th>
<th>Language</th>
<th>Type</th>
<th>Error</th>
</tr>
</thead>
<tbody>
<template x-for="(result, index) in results" :key="index">
<tr :class="result.success ? 'bg-success/5' : 'bg-error/5'">
<td>
<i
:class="result.success ? 'fa fa-check-circle text-success' : 'fa fa-times-circle text-error'"
class="text-lg"
></i>
</td>
<td class="font-mono font-bold" x-text="result.accessnumber"></td>
<td x-text="result.lang || '-'" :class="result.success ? 'text-success' : ''"></td>
<td>
<span
x-show="result.success"
class="badge badge-sm"
:class="result.isRegen ? 'badge-warning' : 'badge-success'"
x-text="result.isRegen ? 'Regenerated' : 'New'"
></span>
<span x-show="!result.success" class="text-error">-</span>
</td>
<td class="text-error text-sm" x-text="result.error || '-'" :class="result.error ? '' : 'text-base-content/30'"></td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
</div>
</main>
</div>
<?= $this->endSection(); ?>
<?= $this->section('script') ?>
<script type="module">
import Alpine from '<?= base_url("js/app.js"); ?>';
document.addEventListener('alpine:init', () => {
Alpine.data("pdfBatch", () => ({
accessNumbersInput: '',
isProcessing: false,
results: [],
currentIndex: 0,
totalCount: 0,
successfulCount: 0,
failedCount: 0,
getAccessNumberCount() {
const numbers = this.parseAccessNumbers();
return numbers.length;
},
parseAccessNumbers() {
return this.accessNumbersInput
.split(/[\n,]+/)
.map(n => n.trim())
.filter(n => n.length > 0);
},
async processBatch() {
const accessNumbers = this.parseAccessNumbers();
if (accessNumbers.length === 0) {
alert('Please enter at least one access number');
return;
}
this.isProcessing = true;
this.results = [];
this.totalCount = accessNumbers.length;
this.currentIndex = 0;
this.successfulCount = 0;
this.failedCount = 0;
try {
const res = await fetch(`${BASEURL}/api/superuser/pdf-batch`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ accessNumbers })
});
const data = await res.json();
if (data.success) {
this.results = data.results;
this.successfulCount = data.successful;
this.failedCount = data.failed;
} else {
alert(data.message || 'Failed to process batch');
}
} catch (err) {
alert('Error processing batch: ' + err.message);
} finally {
this.isProcessing = false;
}
}
}));
});
Alpine.start();
</script>
<?= $this->endSection(); ?>