Add ability to delete results on daily entry page
- Add delete/restore button in action column for each test row - Implement soft delete functionality using deletedResults tracking array - Update save endpoint to process deletedIds for soft deletion - Allow saving when only deletions exist (no new data needed)
This commit is contained in:
parent
01a3e5d5bf
commit
448186bea7
@ -195,11 +195,18 @@ class EntryApiController extends BaseController
|
||||
|
||||
$date = $input['date'];
|
||||
$results = $input['results'];
|
||||
$deletedIds = $input['deletedIds'] ?? [];
|
||||
$savedIds = [];
|
||||
|
||||
// Start transaction
|
||||
$this->resultModel->db->transBegin();
|
||||
|
||||
// Handle deletions first
|
||||
foreach ($deletedIds as $resultId) {
|
||||
$this->resultModel->delete((int) $resultId);
|
||||
}
|
||||
|
||||
// Save/update results
|
||||
foreach ($results as $r) {
|
||||
if (!isset($r['controlId']) || !isset($r['testId']) || !isset($r['value'])) {
|
||||
continue;
|
||||
@ -228,7 +235,7 @@ class EntryApiController extends BaseController
|
||||
|
||||
return $this->respond([
|
||||
'status' => 'success',
|
||||
'message' => 'Saved ' . count($savedIds) . ' results',
|
||||
'message' => 'Saved ' . count($savedIds) . ' results' . (count($deletedIds) > 0 ? ', deleted ' . count($deletedIds) : ''),
|
||||
'data' => ['savedIds' => $savedIds]
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
|
||||
@ -100,8 +100,9 @@
|
||||
<tr>
|
||||
<th>Test</th>
|
||||
<th class="text-center">Mean ± 2SD</th>
|
||||
<th class="w-32">Result</th>
|
||||
<th class="w-40">Result</th>
|
||||
<th class="w-56">Comment</th>
|
||||
<th class="w-16">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -123,18 +124,30 @@
|
||||
step="0.01"
|
||||
:placeholder="'...'"
|
||||
class="input input-bordered input-sm w-full font-mono"
|
||||
:class="getInputClass(test, $el.value)"
|
||||
:class="getInputClass(test, $el.value) + ' ' + (deletedResults.includes(test.testId) ? 'input-disabled' : '')"
|
||||
:disabled="deletedResults.includes(test.testId)"
|
||||
@input.debounce.300ms="updateResult(test.testId, $el.value)"
|
||||
:value="test.existingResult ? test.existingResult.resValue : ''">
|
||||
:value="getResultValue(test.testId)">
|
||||
</td>
|
||||
<td>
|
||||
<textarea
|
||||
:placeholder="'Optional comment...'"
|
||||
rows="1"
|
||||
class="textarea textarea-bordered textarea-xs w-full"
|
||||
:class="deletedResults.includes(test.testId) ? 'textarea-disabled' : ''"
|
||||
:disabled="deletedResults.includes(test.testId)"
|
||||
@input.debounce.300ms="updateComment(test.testId, $el.value)"
|
||||
:value="getComment(test.testId)"></textarea>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<button type="button"
|
||||
@click="toggleDelete(test.testId)"
|
||||
class="btn btn-ghost btn-sm"
|
||||
:class="deletedResults.includes(test.testId) ? 'text-warning' : 'text-error'"
|
||||
:title="deletedResults.includes(test.testId) ? 'Restore' : 'Delete'">
|
||||
<i class="fa-solid" :class="deletedResults.includes(test.testId) ? 'fa-rotate-left' : 'fa-trash'"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
@ -163,6 +176,7 @@ document.addEventListener('alpine:init', () => {
|
||||
saving: false,
|
||||
resultsData: {},
|
||||
commentsData: {},
|
||||
deletedResults: [],
|
||||
deptId: null,
|
||||
departments: null,
|
||||
|
||||
@ -236,9 +250,10 @@ document.addEventListener('alpine:init', () => {
|
||||
const json = await response.json();
|
||||
this.tests = json.data || [];
|
||||
|
||||
// Initialize resultsData and commentsData with existing values
|
||||
// Initialize resultsData, commentsData, and deletedResults
|
||||
this.resultsData = {};
|
||||
this.commentsData = {};
|
||||
this.deletedResults = [];
|
||||
for (const test of this.tests) {
|
||||
if (test.existingResult && test.existingResult.resValue !== null) {
|
||||
this.resultsData[test.testId] = test.existingResult.resValue;
|
||||
@ -263,7 +278,17 @@ document.addEventListener('alpine:init', () => {
|
||||
this.saving = true;
|
||||
try {
|
||||
const results = [];
|
||||
const deletedIds = [];
|
||||
|
||||
for (const test of this.tests) {
|
||||
// Check if this test is marked for deletion
|
||||
if (this.deletedResults.includes(test.testId)) {
|
||||
if (test.existingResult && test.existingResult.resultId) {
|
||||
deletedIds.push(test.existingResult.resultId);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = this.resultsData[test.testId];
|
||||
if (value !== undefined && value !== '') {
|
||||
results.push({
|
||||
@ -279,15 +304,20 @@ document.addEventListener('alpine:init', () => {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
date: this.date,
|
||||
results: results
|
||||
results: results,
|
||||
deletedIds: deletedIds
|
||||
})
|
||||
});
|
||||
|
||||
const json = await response.json();
|
||||
if (json.status === 'success') {
|
||||
// Save comments using the returned result IDs
|
||||
// Save comments for non-deleted tests
|
||||
const savedIds = json.data.savedIds || [];
|
||||
for (const item of savedIds) {
|
||||
// Skip if this test is marked for deletion
|
||||
if (this.deletedResults.includes(item.testId)) {
|
||||
continue;
|
||||
}
|
||||
const comment = this.commentsData[item.testId];
|
||||
if (comment) {
|
||||
await this.saveComment(item.testId, this.date, comment);
|
||||
@ -299,6 +329,7 @@ document.addEventListener('alpine:init', () => {
|
||||
// Refresh data
|
||||
this.resultsData = {};
|
||||
this.commentsData = {};
|
||||
this.deletedResults = [];
|
||||
await this.fetchTests();
|
||||
} else {
|
||||
this.$dispatch('notify', { type: 'error', message: json.message || 'Failed to save' });
|
||||
@ -329,12 +360,51 @@ document.addEventListener('alpine:init', () => {
|
||||
}
|
||||
},
|
||||
|
||||
getResultValue(testId) {
|
||||
// If marked for deletion, show empty
|
||||
if (this.deletedResults.includes(testId)) {
|
||||
return '';
|
||||
}
|
||||
// Return from resultsData if changed
|
||||
if (testId in this.resultsData) {
|
||||
return this.resultsData[testId];
|
||||
}
|
||||
// Return existing result from database
|
||||
const test = this.tests.find(t => t.testId === testId);
|
||||
if (test && test.existingResult && test.existingResult.resValue !== null) {
|
||||
return test.existingResult.resValue;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
||||
updateResult(testId, value) {
|
||||
if (value === '') {
|
||||
delete this.resultsData[testId];
|
||||
} else {
|
||||
this.resultsData[testId] = value;
|
||||
}
|
||||
// Remove from deletedResults if user enters a value
|
||||
if (value !== '' && this.deletedResults.includes(testId)) {
|
||||
this.deletedResults = this.deletedResults.filter(id => id !== testId);
|
||||
}
|
||||
},
|
||||
|
||||
toggleDelete(testId) {
|
||||
const test = this.tests.find(t => t.testId === testId);
|
||||
const hasExistingResult = test && test.existingResult && test.existingResult.resValue !== null;
|
||||
const hasChanges = testId in this.resultsData || testId in this.commentsData;
|
||||
|
||||
if (this.deletedResults.includes(testId)) {
|
||||
// Restore - remove from deleted list
|
||||
this.deletedResults = this.deletedResults.filter(id => id !== testId);
|
||||
} else {
|
||||
// Mark for deletion
|
||||
if (hasExistingResult || hasChanges) {
|
||||
this.deletedResults.push(testId);
|
||||
// Clear any pending changes for this test
|
||||
delete this.resultsData[testId];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
updateComment(testId, value) {
|
||||
@ -384,7 +454,11 @@ document.addEventListener('alpine:init', () => {
|
||||
},
|
||||
|
||||
get canSave() {
|
||||
return this.selectedControl && (Object.keys(this.resultsData).length > 0 || Object.keys(this.commentsData).length > 0) && !this.saving;
|
||||
return this.selectedControl && (
|
||||
Object.keys(this.resultsData).length > 0 ||
|
||||
Object.keys(this.commentsData).length > 0 ||
|
||||
this.deletedResults.length > 0
|
||||
) && !this.saving;
|
||||
},
|
||||
|
||||
setToday() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user