571 lines
20 KiB
PHP
571 lines
20 KiB
PHP
<?= $this->extend('superuser/main'); ?>
|
|
|
|
<?= $this->section('content') ?>
|
|
<main class="p-4 flex-1 flex flex-col gap-2" x-data="dashboard">
|
|
<div class="card bg-base-100 shadow-xl h-full border border-base-200 overflow-hidden">
|
|
<div class="card-body p-0 h-full flex flex-col">
|
|
|
|
<!-- Header & Filters -->
|
|
<div class="p-4 border-b border-base-200 bg-base-50">
|
|
<div class="flex flex-col md:flex-row justify-between items-center gap-4 mb-4">
|
|
<div class="flex-1">
|
|
<h2 class="text-2xl font-bold flex items-center gap-2 text-base-content">
|
|
<i class="fa fa-chart-bar text-primary"></i> Requests Overview
|
|
</h2>
|
|
</div>
|
|
|
|
<!-- Status Filters -->
|
|
<div class="join shadow-sm bg-base-100 rounded-lg">
|
|
<button @click="filterKey = 'Total'"
|
|
:class="filterKey === 'Total' ? 'btn-active btn-neutral text-white' : 'btn-ghost'"
|
|
class="btn btn-sm join-item">
|
|
All <span class="badge badge-sm badge-ghost ml-1" x-text="counters.Total"></span>
|
|
</button>
|
|
<button @click="filterKey = 'Pend'"
|
|
:class="filterKey === 'Pend' ? 'btn-active btn-neutral text-white' : 'btn-ghost'"
|
|
class="btn btn-sm join-item">
|
|
Pending <span class="badge badge-sm ml-1" x-text="counters.Pend"></span>
|
|
</button>
|
|
<button @click="filterKey = 'Coll'"
|
|
:class="filterKey === 'Coll' ? 'btn-active btn-warning text-white' : 'btn-ghost'"
|
|
class="btn btn-sm join-item">
|
|
Coll <span class="badge badge-sm badge-warning ml-1" x-text="counters.Coll"></span>
|
|
</button>
|
|
<button @click="filterKey = 'Recv'"
|
|
:class="filterKey === 'Recv' ? 'btn-active btn-info text-white' : 'btn-ghost'"
|
|
class="btn btn-sm join-item">
|
|
Recv <span class="badge badge-sm badge-info ml-1" x-text="counters.Recv"></span>
|
|
</button>
|
|
<button @click="filterKey = 'Inc'"
|
|
:class="filterKey === 'Inc' ? 'btn-active btn-error text-white' : 'btn-ghost'"
|
|
class="btn btn-sm join-item">
|
|
Inc <span class="badge badge-sm badge-error ml-1" x-text="counters.Inc"></span>
|
|
</button>
|
|
<button @click="filterKey = 'Fin'"
|
|
:class="filterKey === 'Fin' ? 'btn-active btn-success text-white' : 'btn-ghost'"
|
|
class="btn btn-sm join-item">
|
|
Fin <span class="badge badge-sm badge-success ml-1" x-text="counters.Fin"></span>
|
|
</button>
|
|
<button @click="filterKey = 'Validated'"
|
|
:class="filterKey === 'Validated' ? 'btn-active btn-primary text-white' : 'btn-ghost'"
|
|
class="btn btn-sm join-item">
|
|
Val <span class="badge badge-sm badge-primary ml-1" x-text="validatedCount"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Search & Date Filter -->
|
|
<div
|
|
class="flex flex-col md:flex-row gap-3 items-end bg-base-100 p-3 rounded-lg border border-base-200 shadow-sm">
|
|
<div class="form-control">
|
|
<label class="label text-xs font-bold py-1 text-base-content/60">Date Range</label>
|
|
<div class="join">
|
|
<input type="date" class="input input-sm input-bordered join-item" x-model="filter.date1" />
|
|
<span class="join-item btn btn-sm btn-ghost no-animation bg-base-200 font-normal px-2">-</span>
|
|
<input type="date" class="input input-sm input-bordered join-item" x-model="filter.date2" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex gap-2">
|
|
<button class="btn btn-sm btn-primary" @click='fetchList()'><i class='fa fa-search'></i> Search</button>
|
|
<button class="btn btn-sm btn-neutral" @click='reset()'><i class='fa fa-sync-alt'></i> Reset</button>
|
|
</div>
|
|
|
|
<span class="flex-1"></span>
|
|
|
|
<div class="form-control w-full md:w-auto">
|
|
<label class='input input-sm input-bordered'>
|
|
<i class="fa fa-filter"></i>
|
|
<input type="text" placeholder="Type to filter..." x-model="filterTable" />
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex-1 overflow-y-auto px-4 pb-4">
|
|
<template x-if="isLoading">
|
|
<table class="table table-xs table-zebra w-full">
|
|
<thead class="bg-base-100 sticky top-0 z-10">
|
|
<tr>
|
|
<th style='width:7%;'>
|
|
<div class="skeleton h-4 w-20"></div>
|
|
</th>
|
|
<th style='width:15%;'>
|
|
<div class="skeleton h-4 w-32"></div>
|
|
</th>
|
|
<th style='width:7%;'>
|
|
<div class="skeleton h-4 w-16"></div>
|
|
</th>
|
|
<th style='width:7%;'>
|
|
<div class="skeleton h-4 w-16"></div>
|
|
</th>
|
|
<th style='width:8%;'>
|
|
<div class="skeleton h-4 w-16"></div>
|
|
</th>
|
|
<th style='width:8%;'>
|
|
<div class="skeleton h-4 w-20"></div>
|
|
</th>
|
|
<th style='width:15%;'>
|
|
<div class="skeleton h-4 w-32"></div>
|
|
</th>
|
|
<th style='width:3%;'>
|
|
<div class="skeleton h-4 w-12"></div>
|
|
</th>
|
|
<th style='width:5%;'>
|
|
<div class="skeleton h-4 w-16"></div>
|
|
</th>
|
|
<th style='width:5%;'>
|
|
<div class="skeleton h-4 w-16"></div>
|
|
</th>
|
|
<th style='width:4%;'>
|
|
<div class="skeleton h-4 w-12"></div>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<template x-for="i in 5" :key="i">
|
|
<tr>
|
|
<td colspan="11">
|
|
<div class="skeleton h-4 w-full"></div>
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
</tbody>
|
|
</table>
|
|
</template>
|
|
<template x-if="!isLoading && !list.length">
|
|
<div class="text-center py-10">
|
|
<i class="fa fa-inbox text-4xl mb-2 opacity-50"></i>
|
|
<p>No records found</p>
|
|
</div>
|
|
</template>
|
|
<template x-if="!isLoading && list.length">
|
|
<table class="table table-xs table-zebra w-full">
|
|
<thead class="bg-base-100 sticky top-0 z-10">
|
|
<tr>
|
|
<th style='width:7%;' @click="sort('REQDATE')"
|
|
class="cursor-pointer hover:bg-base-200 transition-colors select-none">
|
|
<div class="flex items-center gap-1">
|
|
Order Datetime
|
|
<i class="fa text-xs"
|
|
:class="sortCol === 'REQDATE' ? (sortAsc ? 'fa-sort-up' : 'fa-sort-down') : 'fa-sort opacity-20'"></i>
|
|
</div>
|
|
</th>
|
|
<th style='width:15%;' @click="sort('Name')"
|
|
class="cursor-pointer hover:bg-base-200 transition-colors select-none">
|
|
<div class="flex items-center gap-1">
|
|
Patient Name
|
|
<i class="fa text-xs"
|
|
:class="sortCol === 'Name' ? (sortAsc ? 'fa-sort-up' : 'fa-sort-down') : 'fa-sort opacity-20'"></i>
|
|
</div>
|
|
</th>
|
|
<th style='width:7%;' @click="sort('SP_ACCESSNUMBER')"
|
|
class="cursor-pointer hover:bg-base-200 transition-colors select-none">
|
|
<div class="flex items-center gap-1">
|
|
No Lab
|
|
<i class="fa text-xs"
|
|
:class="sortCol === 'SP_ACCESSNUMBER' ? (sortAsc ? 'fa-sort-up' : 'fa-sort-down') : 'fa-sort opacity-20'"></i>
|
|
</div>
|
|
</th>
|
|
<th style='width:7%;' @click="sort('HOSTORDERNUMBER')"
|
|
class="cursor-pointer hover:bg-base-200 transition-colors select-none">
|
|
<div class="flex items-center gap-1">
|
|
No Register
|
|
<i class="fa text-xs"
|
|
:class="sortCol === 'HOSTORDERNUMBER' ? (sortAsc ? 'fa-sort-up' : 'fa-sort-down') : 'fa-sort opacity-20'"></i>
|
|
</div>
|
|
</th>
|
|
<th style='width:8%;' @click="sort('REFF')"
|
|
class="cursor-pointer hover:bg-base-200 transition-colors select-none">
|
|
<div class="flex items-center gap-1">
|
|
Reff
|
|
<i class="fa text-xs"
|
|
:class="sortCol === 'REFF' ? (sortAsc ? 'fa-sort-up' : 'fa-sort-down') : 'fa-sort opacity-20'"></i>
|
|
</div>
|
|
</th>
|
|
<th style='width:8%;' @click="sort('DOC')"
|
|
class="cursor-pointer hover:bg-base-200 transition-colors select-none">
|
|
<div class="flex items-center gap-1">
|
|
Doctor
|
|
<i class="fa text-xs"
|
|
:class="sortCol === 'DOC' ? (sortAsc ? 'fa-sort-up' : 'fa-sort-down') : 'fa-sort opacity-20'"></i>
|
|
</div>
|
|
</th>
|
|
<th style='width:15%;'>Tests</th>
|
|
<th style='width:3%;'>ResTo</th>
|
|
<th style='width:5%;'>Val</th>
|
|
<th style='width:5%;'>Result</th>
|
|
<th style='width:4%;' @click="sort('STATS')"
|
|
class="cursor-pointer hover:bg-base-200 transition-colors select-none">
|
|
<div class="flex items-center gap-1">
|
|
Status
|
|
<i class="fa text-xs"
|
|
:class="sortCol === 'STATS' ? (sortAsc ? 'fa-sort-up' : 'fa-sort-down') : 'fa-sort opacity-20'"></i>
|
|
</div>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tbody>
|
|
<template x-for="req in paginated" :key="req.SP_ACCESSNUMBER">
|
|
<tr class="hover:bg-base-300">
|
|
<td x-text="req.REQDATE"></td>
|
|
<td x-text="req.Name"></td>
|
|
<td x-text="req.SP_ACCESSNUMBER" class="font-bold cursor-pointer" :class="statusColor[req.STATS]"
|
|
@click="openSampleDialog(req.SP_ACCESSNUMBER)"></td>
|
|
<td x-text="req.HOSTORDERNUMBER" class="font-bold cursor-pointer" :class="statusColor[req.STATS]"
|
|
@click="openSampleDialog(req.SP_ACCESSNUMBER)"></td>
|
|
<td x-text="req.REFF"></td>
|
|
<td x-text="req.DOC"></td>
|
|
<td x-text="req.TESTS"></td>
|
|
<td x-text="req.ODR_CRESULT_TO"></td>
|
|
<td>
|
|
<div class='flex gap-1 items-center'>
|
|
<div class='w-15'>
|
|
<p>1: <span x-text="req.VAL1USER"></span></p>
|
|
<p>2: <span x-text="req.VAL2USER"></span></p>
|
|
</div>
|
|
<template x-if="req.ISVAL == 1 && req.ISPENDING != 1">
|
|
<div class='text-center'>
|
|
<template
|
|
x-if="req.VAL1USER == '<?= session('userid'); ?>' || req.VAL2USER == '<?= session('userid'); ?>'">
|
|
<button class="btn btn-xs btn-outline btn-secondary"
|
|
@click="openUnvalDialog(req.SP_ACCESSNUMBER)"><i
|
|
class="fa-solid fa-rotate-right"></i></button>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<template x-if="req.STATS !== 'PartColl' && req.STATS !== 'Coll' && req.STATS !== 'Pend'">
|
|
<button class="btn btn-xs btn-outline btn-primary"
|
|
@click="openPreviewDialog(req.SP_ACCESSNUMBER, 'preview', req)">Preview</button>
|
|
</template>
|
|
</td>
|
|
<td x-text="req.STATS === 'Fin' ? 'Final' : req.STATS" class="font-bold cursor-pointer"
|
|
:class="statusColor[req.STATS]" @click="openSampleDialog(req.SP_ACCESSNUMBER)"></td>
|
|
</tr>
|
|
</template>
|
|
</tbody>
|
|
</table>
|
|
</template>
|
|
</div>
|
|
|
|
<!-- Pagination Control -->
|
|
<div class="p-2 border-t border-base-200 bg-base-50 flex justify-between items-center"
|
|
x-show="!isLoading && list.length > 0">
|
|
<div class="text-xs text-base-content/60">
|
|
Showing <span class="font-bold" x-text="((currentPage - 1) * pageSize) + 1"></span> to
|
|
<span class="font-bold" x-text="Math.min(currentPage * pageSize, filtered.length)"></span> of
|
|
<span class="font-bold" x-text="filtered.length"></span> entries
|
|
</div>
|
|
<div class="join">
|
|
<button class="join-item btn btn-sm" @click="prevPage()" :disabled="currentPage === 1">
|
|
<i class="fa fa-chevron-left"></i>
|
|
</button>
|
|
<button class="join-item btn btn-sm no-animation bg-base-100 cursor-default">
|
|
Page <span x-text="currentPage"></span> / <span x-text="totalPages"></span>
|
|
</button>
|
|
<button class="join-item btn btn-sm" @click="nextPage()" :disabled="currentPage === totalPages">
|
|
<i class="fa fa-chevron-right"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<?php echo $this->include('superuser/dialog_sample'); ?>
|
|
<?php echo $this->include('superuser/dialog_unval'); ?>
|
|
<?php echo $this->include('superuser/dialog_preview'); ?>
|
|
|
|
</main>
|
|
<?= $this->endSection(); ?>
|
|
|
|
<?= $this->section('script') ?>
|
|
<script type="module">
|
|
import Alpine from '<?= base_url("js/app.js"); ?>';
|
|
|
|
document.addEventListener('alpine:init', () => {
|
|
Alpine.data("dashboard", () => ({
|
|
// dashboard
|
|
today: "",
|
|
filter: { date1: "", date2: "" },
|
|
list: [],
|
|
isLoading: false,
|
|
counters: { Pend: 0, Coll: 0, Recv: 0, Inc: 0, Fin: 0, Total: 0 },
|
|
statusColor: {
|
|
Pend: 'bg-white text-black font-bold',
|
|
PartColl: 'bg-orange-300 text-white font-bold',
|
|
Coll: 'bg-orange-500 text-white font-bold',
|
|
PartRecv: 'bg-blue-200 text-black font-bold',
|
|
Recv: 'bg-blue-500 text-white font-bold',
|
|
Inc: 'bg-yellow-500 text-white font-bold',
|
|
Fin: 'bg-green-500 text-white font-bold',
|
|
},
|
|
filterTable: "",
|
|
filterKey: 'Total',
|
|
filterKey: 'Total',
|
|
statusMap: {
|
|
Total: [],
|
|
Pend: ['Pend'],
|
|
Coll: ['Coll', 'PartColl'],
|
|
Recv: ['Recv', 'PartRecv'],
|
|
Inc: ['Inc'],
|
|
Fin: ['Fin'],
|
|
},
|
|
|
|
// Sorting & Pagination
|
|
sortCol: 'REQDATE',
|
|
sortAsc: false,
|
|
currentPage: 1,
|
|
pageSize: 15,
|
|
|
|
sort(col) {
|
|
if (this.sortCol === col) {
|
|
this.sortAsc = !this.sortAsc;
|
|
} else {
|
|
this.sortCol = col;
|
|
this.sortAsc = true;
|
|
}
|
|
},
|
|
|
|
nextPage() {
|
|
if (this.currentPage < this.totalPages) this.currentPage++;
|
|
},
|
|
|
|
prevPage() {
|
|
if (this.currentPage > 1) this.currentPage--;
|
|
},
|
|
|
|
get totalPages() {
|
|
return Math.ceil(this.filtered.length / this.pageSize) || 1;
|
|
},
|
|
|
|
get sorted() {
|
|
return this.filtered.slice().sort((a, b) => {
|
|
let modifier = this.sortAsc ? 1 : -1;
|
|
if (a[this.sortCol] < b[this.sortCol]) return -1 * modifier;
|
|
if (a[this.sortCol] > b[this.sortCol]) return 1 * modifier;
|
|
return 0;
|
|
});
|
|
},
|
|
|
|
get paginated() {
|
|
const start = (this.currentPage - 1) * this.pageSize;
|
|
const end = start + this.pageSize;
|
|
return this.sorted.slice(start, end);
|
|
},
|
|
|
|
init() {
|
|
this.today = new Date().toISOString().slice(0, 10);
|
|
this.filter.date1 = this.today;
|
|
this.filter.date2 = this.today;
|
|
this.$watch('filterTable', () => {
|
|
this.currentPage = 1;
|
|
});
|
|
this.fetchList();
|
|
},
|
|
|
|
fetchList() {
|
|
this.isLoading = true;
|
|
this.list = [];
|
|
let statusOrder = { Pend: 1, PartColl: 2, Coll: 3, PartRecv: 4, Recv: 5, Inc: 6, Fin: 7 };
|
|
let param = new URLSearchParams(this.filter).toString();
|
|
for (let k in this.counters) { this.counters[k] = 0; }
|
|
fetch(`${BASEURL}/api/requests?${param}`, {
|
|
method: 'GET',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}).then(res => res.json()).then(data => {
|
|
this.list = data.data ?? [];
|
|
this.filterKey = 'Total';
|
|
this.list.forEach(item => {
|
|
if (this.counters[item.STATS] !== undefined) { this.counters[item.STATS]++; this.counters.Total++; }
|
|
else {
|
|
if (item.STATS == 'PartColl') { this.counters.Coll++; }
|
|
else if (item.STATS == 'PartRecv') { this.counters.Recv++; }
|
|
this.counters.Total++;
|
|
}
|
|
});
|
|
this.list.sort((a, b) => {
|
|
let codeA = statusOrder[a.STATS] ?? 0;
|
|
let codeB = statusOrder[b.STATS] ?? 0;
|
|
return codeA - codeB;
|
|
});
|
|
}).finally(() => {
|
|
this.isLoading = false;
|
|
});
|
|
},
|
|
|
|
reset() {
|
|
this.filter.date1 = this.today;
|
|
this.filter.date2 = this.today;
|
|
this.fetchList();
|
|
},
|
|
|
|
isValidated(item) {
|
|
return item.ISVAL == 1 && item.ISPENDING != 1;
|
|
},
|
|
get filtered() {
|
|
// Reset pagination when filter changes (implied by this getter being accessed if dependencies change)
|
|
// However, side-effects in getters are tricky.
|
|
// Better to just let the user navigate back, or watch variables.
|
|
// For now, let's keep it pure.
|
|
let filteredList = this.list;
|
|
if (this.filterKey === 'Validated') {
|
|
filteredList = filteredList.filter(item => this.isValidated(item));
|
|
} else {
|
|
const validStatuses = this.statusMap[this.filterKey];
|
|
if (validStatuses.length > 0) {
|
|
filteredList = filteredList.filter(item => validStatuses.includes(item.STATS));
|
|
}
|
|
}
|
|
if (this.filterTable) {
|
|
const searchTerm = this.filterTable.toLowerCase();
|
|
filteredList = filteredList.filter(item =>
|
|
Object.values(item).some(value =>
|
|
String(value).toLowerCase().includes(searchTerm)
|
|
)
|
|
);
|
|
}
|
|
return filteredList;
|
|
},
|
|
get validatedCount() {
|
|
return this.list.filter(r => this.isValidated(r)).length;
|
|
},
|
|
|
|
/*
|
|
sample dialog
|
|
*/
|
|
item: '',
|
|
isDialogSampleOpen: false,
|
|
isSampleLoading: false,
|
|
|
|
openSampleDialog(accessnumber) {
|
|
this.isDialogSampleOpen = true;
|
|
this.fetchItem(accessnumber)
|
|
},
|
|
|
|
closeSampleDialog() {
|
|
this.isDialogSampleOpen = false;
|
|
},
|
|
|
|
fetchItem(accessnumber) {
|
|
this.isSampleLoading = true;
|
|
this.item = [];
|
|
fetch(`${BASEURL}/api/samples/${accessnumber}`, { method: 'GET', headers: { 'Content-Type': 'application/json' } })
|
|
.then(res => res.json()).then(data => {
|
|
this.item = data.data ?? {};
|
|
if (!Array.isArray(this.item.samples)) this.item.samples = [];
|
|
}).finally(() => {
|
|
this.isSampleLoading = false;
|
|
});
|
|
},
|
|
|
|
collect(sampcode, accessnumber) {
|
|
fetch(`${BASEURL}/api/samples/collect/${accessnumber}`, {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ samplenumber: sampcode, userid: '<?= session('userid'); ?>' })
|
|
})
|
|
.then(res => res.json()).then(data => {
|
|
this.fetchItem(accessnumber);
|
|
});
|
|
},
|
|
|
|
uncollect(sampcode, accessnumber) {
|
|
if (!confirm(`Uncollect sample ${sampcode} from request ${accessnumber}?`)) { return; }
|
|
fetch(`${BASEURL}/api/samples/collect/${accessnumber}`, {
|
|
method: 'DELETE', headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ samplenumber: sampcode, userid: '<?= session('userid'); ?>' })
|
|
})
|
|
.then(res => res.json()).then(data => {
|
|
this.fetchItem(accessnumber);
|
|
});
|
|
},
|
|
|
|
unreceive(sampcode, accessnumber) {
|
|
if (!confirm(`Unreceive sample ${sampcode} from request ${accessnumber}?`)) { return; }
|
|
fetch(`${BASEURL}/api/samples/unreceive/${accessnumber}`, {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ samplenumber: sampcode, userid: '<?= session('userid'); ?>' })
|
|
})
|
|
.then(res => res.json()).then(data => {
|
|
this.fetchItem(accessnumber);
|
|
});
|
|
},
|
|
|
|
/*
|
|
preview dialog
|
|
*/
|
|
isDialogPreviewOpen: false,
|
|
reviewed: false,
|
|
previewItem: null,
|
|
openPreviewDialog(accessnumber, type, item) {
|
|
this.previewAccessnumber = accessnumber;
|
|
this.previewItem = item;
|
|
this.previewType = type;
|
|
this.isDialogPreviewOpen = true;
|
|
this.reviewed = false;
|
|
},
|
|
closePreviewDialog() {
|
|
this.isDialogPreviewOpen = false;
|
|
this.previewItem = null;
|
|
},
|
|
setPreviewType(type) {
|
|
this.previewType = type;
|
|
},
|
|
getPreviewUrl() {
|
|
let base = 'http://glenlis/spooler_db/main_dev.php';
|
|
let url = `${base}?acc=${this.previewAccessnumber}`;
|
|
if (this.previewType === 'ind') url += '&lang=ID';
|
|
if (this.previewType === 'eng') url += '&lang=EN';
|
|
if (this.previewType === 'pdf') url += '&output=pdf';
|
|
|
|
// Keep fallback for local dev if needed, but the above is the expected logic
|
|
// return "http://localhost/application.html";
|
|
return url;
|
|
},
|
|
validate(accessnumber, userid) {
|
|
fetch(`${BASEURL}/api/requests/validate/${accessnumber}`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ userid: `${userid}` })
|
|
}).then(response => {
|
|
this.closePreviewDialog();
|
|
this.fetchList();
|
|
console.log('Validate clicked for', this.previewAccessnumber, 'by user', userid);
|
|
});
|
|
},
|
|
|
|
/*
|
|
unvalidate dialog
|
|
*/
|
|
isDialogUnvalOpen: false,
|
|
unvalReason: '',
|
|
unvalAccessnumber: null,
|
|
openUnvalDialog(accessnumber) {
|
|
this.unvalReason = '';
|
|
this.isDialogUnvalOpen = true;
|
|
this.unvalAccessnumber = accessnumber;
|
|
},
|
|
unvalidate(accessnumber, userid) {
|
|
if (!confirm(`Unvalidate request ${accessnumber}?`)) { return; }
|
|
fetch(`${BASEURL}/api/requests/validate/${accessnumber}`, {
|
|
method: "DELETE",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ userid: `${userid}`, comment: this.unvalReason.trim() })
|
|
}).then(response => {
|
|
this.closeUnvalDialog();
|
|
this.fetchList();
|
|
console.log(`Unvalidate clicked for ${accessnumber}, by user ${userid}`);
|
|
});
|
|
},
|
|
closeUnvalDialog() {
|
|
this.isDialogUnvalOpen = false;
|
|
},
|
|
}));
|
|
});
|
|
|
|
Alpine.start();
|
|
</script>
|
|
<?= $this->endSection(); ?>
|