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'];
|
$date = $input['date'];
|
||||||
$results = $input['results'];
|
$results = $input['results'];
|
||||||
|
$deletedIds = $input['deletedIds'] ?? [];
|
||||||
$savedIds = [];
|
$savedIds = [];
|
||||||
|
|
||||||
// Start transaction
|
// Start transaction
|
||||||
$this->resultModel->db->transBegin();
|
$this->resultModel->db->transBegin();
|
||||||
|
|
||||||
|
// Handle deletions first
|
||||||
|
foreach ($deletedIds as $resultId) {
|
||||||
|
$this->resultModel->delete((int) $resultId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save/update results
|
||||||
foreach ($results as $r) {
|
foreach ($results as $r) {
|
||||||
if (!isset($r['controlId']) || !isset($r['testId']) || !isset($r['value'])) {
|
if (!isset($r['controlId']) || !isset($r['testId']) || !isset($r['value'])) {
|
||||||
continue;
|
continue;
|
||||||
@ -228,7 +235,7 @@ class EntryApiController extends BaseController
|
|||||||
|
|
||||||
return $this->respond([
|
return $this->respond([
|
||||||
'status' => 'success',
|
'status' => 'success',
|
||||||
'message' => 'Saved ' . count($savedIds) . ' results',
|
'message' => 'Saved ' . count($savedIds) . ' results' . (count($deletedIds) > 0 ? ', deleted ' . count($deletedIds) : ''),
|
||||||
'data' => ['savedIds' => $savedIds]
|
'data' => ['savedIds' => $savedIds]
|
||||||
], 200);
|
], 200);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|||||||
@ -100,8 +100,9 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Test</th>
|
<th>Test</th>
|
||||||
<th class="text-center">Mean ± 2SD</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-56">Comment</th>
|
||||||
|
<th class="w-16">Action</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -123,18 +124,30 @@
|
|||||||
step="0.01"
|
step="0.01"
|
||||||
:placeholder="'...'"
|
:placeholder="'...'"
|
||||||
class="input input-bordered input-sm w-full font-mono"
|
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)"
|
@input.debounce.300ms="updateResult(test.testId, $el.value)"
|
||||||
:value="test.existingResult ? test.existingResult.resValue : ''">
|
:value="getResultValue(test.testId)">
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<textarea
|
<textarea
|
||||||
:placeholder="'Optional comment...'"
|
:placeholder="'Optional comment...'"
|
||||||
rows="1"
|
rows="1"
|
||||||
class="textarea textarea-bordered textarea-xs w-full"
|
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)"
|
@input.debounce.300ms="updateComment(test.testId, $el.value)"
|
||||||
:value="getComment(test.testId)"></textarea>
|
:value="getComment(test.testId)"></textarea>
|
||||||
</td>
|
</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>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -163,6 +176,7 @@ document.addEventListener('alpine:init', () => {
|
|||||||
saving: false,
|
saving: false,
|
||||||
resultsData: {},
|
resultsData: {},
|
||||||
commentsData: {},
|
commentsData: {},
|
||||||
|
deletedResults: [],
|
||||||
deptId: null,
|
deptId: null,
|
||||||
departments: null,
|
departments: null,
|
||||||
|
|
||||||
@ -236,9 +250,10 @@ document.addEventListener('alpine:init', () => {
|
|||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
this.tests = json.data || [];
|
this.tests = json.data || [];
|
||||||
|
|
||||||
// Initialize resultsData and commentsData with existing values
|
// Initialize resultsData, commentsData, and deletedResults
|
||||||
this.resultsData = {};
|
this.resultsData = {};
|
||||||
this.commentsData = {};
|
this.commentsData = {};
|
||||||
|
this.deletedResults = [];
|
||||||
for (const test of this.tests) {
|
for (const test of this.tests) {
|
||||||
if (test.existingResult && test.existingResult.resValue !== null) {
|
if (test.existingResult && test.existingResult.resValue !== null) {
|
||||||
this.resultsData[test.testId] = test.existingResult.resValue;
|
this.resultsData[test.testId] = test.existingResult.resValue;
|
||||||
@ -263,7 +278,17 @@ document.addEventListener('alpine:init', () => {
|
|||||||
this.saving = true;
|
this.saving = true;
|
||||||
try {
|
try {
|
||||||
const results = [];
|
const results = [];
|
||||||
|
const deletedIds = [];
|
||||||
|
|
||||||
for (const test of this.tests) {
|
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];
|
const value = this.resultsData[test.testId];
|
||||||
if (value !== undefined && value !== '') {
|
if (value !== undefined && value !== '') {
|
||||||
results.push({
|
results.push({
|
||||||
@ -279,15 +304,20 @@ document.addEventListener('alpine:init', () => {
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
date: this.date,
|
date: this.date,
|
||||||
results: results
|
results: results,
|
||||||
|
deletedIds: deletedIds
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
if (json.status === 'success') {
|
if (json.status === 'success') {
|
||||||
// Save comments using the returned result IDs
|
// Save comments for non-deleted tests
|
||||||
const savedIds = json.data.savedIds || [];
|
const savedIds = json.data.savedIds || [];
|
||||||
for (const item of 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];
|
const comment = this.commentsData[item.testId];
|
||||||
if (comment) {
|
if (comment) {
|
||||||
await this.saveComment(item.testId, this.date, comment);
|
await this.saveComment(item.testId, this.date, comment);
|
||||||
@ -299,6 +329,7 @@ document.addEventListener('alpine:init', () => {
|
|||||||
// Refresh data
|
// Refresh data
|
||||||
this.resultsData = {};
|
this.resultsData = {};
|
||||||
this.commentsData = {};
|
this.commentsData = {};
|
||||||
|
this.deletedResults = [];
|
||||||
await this.fetchTests();
|
await this.fetchTests();
|
||||||
} else {
|
} else {
|
||||||
this.$dispatch('notify', { type: 'error', message: json.message || 'Failed to save' });
|
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) {
|
updateResult(testId, value) {
|
||||||
if (value === '') {
|
if (value === '') {
|
||||||
delete this.resultsData[testId];
|
delete this.resultsData[testId];
|
||||||
} else {
|
} else {
|
||||||
this.resultsData[testId] = value;
|
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) {
|
updateComment(testId, value) {
|
||||||
@ -384,7 +454,11 @@ document.addEventListener('alpine:init', () => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
get canSave() {
|
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() {
|
setToday() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user