- 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
171 lines
7.7 KiB
PHP
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 24000002 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(); ?>
|