US-017: Implement Daily Comments per day per test
- Add per-day comment input in monthly entry grid (comment icon per cell) - Update saveMonthly() to save rescomment field for each result - Update getMonthlyData() to return per-day comments - Store comments in results.rescomment field with soft deletes support
This commit is contained in:
parent
1374c8769f
commit
2e23fc38c3
@ -98,6 +98,7 @@ class EntryApiController extends BaseController
|
|||||||
foreach ($tests as $testData) {
|
foreach ($tests as $testData) {
|
||||||
$testId = $testData['testId'] ?? 0;
|
$testId = $testData['testId'] ?? 0;
|
||||||
$resvalues = $testData['resvalue'] ?? [];
|
$resvalues = $testData['resvalue'] ?? [];
|
||||||
|
$rescomments = $testData['rescomment'] ?? [];
|
||||||
|
|
||||||
$controlTest = $this->controlTestModel->getByControlAndTest($controlId, $testId);
|
$controlTest = $this->controlTestModel->getByControlAndTest($controlId, $testId);
|
||||||
$mean = $controlTest['mean'] ?? 0;
|
$mean = $controlTest['mean'] ?? 0;
|
||||||
@ -111,12 +112,15 @@ class EntryApiController extends BaseController
|
|||||||
|
|
||||||
foreach ($resvalues as $day => $value) {
|
foreach ($resvalues as $day => $value) {
|
||||||
if (!empty($value)) {
|
if (!empty($value)) {
|
||||||
|
$resdate = $yearMonth . '-' . str_pad($day, 2, '0', STR_PAD_LEFT);
|
||||||
|
$rescomment = $rescomments[$day] ?? '';
|
||||||
|
|
||||||
$resultData = [
|
$resultData = [
|
||||||
'control_ref_id' => $controlId,
|
'control_ref_id' => $controlId,
|
||||||
'test_ref_id' => $testId,
|
'test_ref_id' => $testId,
|
||||||
'resdate' => $yearMonth . '-' . str_pad($day, 2, '0', STR_PAD_LEFT),
|
'resdate' => $resdate,
|
||||||
'resvalue' => $value,
|
'resvalue' => $value,
|
||||||
'rescomment' => '',
|
'rescomment' => $rescomment,
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($operation === 'replace') {
|
if ($operation === 'replace') {
|
||||||
@ -223,9 +227,13 @@ class EntryApiController extends BaseController
|
|||||||
$comment = $this->commentModel->getByControlTestMonth($controlId, $testId, $yearMonth);
|
$comment = $this->commentModel->getByControlTestMonth($controlId, $testId, $yearMonth);
|
||||||
|
|
||||||
$formValues = [];
|
$formValues = [];
|
||||||
|
$comments = [];
|
||||||
foreach ($results as $row) {
|
foreach ($results as $row) {
|
||||||
$day = (int)date('j', strtotime($row['resdate']));
|
$day = (int)date('j', strtotime($row['resdate']));
|
||||||
$formValues[$day] = $row['resvalue'];
|
$formValues[$day] = $row['resvalue'];
|
||||||
|
if (!empty($row['rescomment'])) {
|
||||||
|
$comments[$day] = $row['rescomment'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->respond([
|
return $this->respond([
|
||||||
@ -233,6 +241,7 @@ class EntryApiController extends BaseController
|
|||||||
'message' => 'fetch success',
|
'message' => 'fetch success',
|
||||||
'data' => [
|
'data' => [
|
||||||
'formValues' => $formValues,
|
'formValues' => $formValues,
|
||||||
|
'comments' => $comments,
|
||||||
'comment' => $comment ? $comment['comtext'] : ''
|
'comment' => $comment ? $comment['comtext'] : ''
|
||||||
]
|
]
|
||||||
], 200);
|
], 200);
|
||||||
|
|||||||
@ -108,15 +108,38 @@
|
|||||||
<span x-text="getTestName(testId)"></span>
|
<span x-text="getTestName(testId)"></span>
|
||||||
</td>
|
</td>
|
||||||
<template x-for="day in daysInMonth" :key="day">
|
<template x-for="day in daysInMonth" :key="day">
|
||||||
<td class="py-1 px-1" :class="isWeekend(day) ? 'bg-red-50' : ''">
|
<td class="py-1 px-1 relative" :class="isWeekend(day) ? 'bg-red-50' : ''">
|
||||||
<input
|
<div class="flex items-center gap-1">
|
||||||
type="number"
|
<input
|
||||||
step="0.01"
|
type="number"
|
||||||
class="w-full px-2 py-1.5 text-center text-sm bg-white border border-slate-200 rounded focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500"
|
step="0.01"
|
||||||
:placeholder="'-'"
|
class="w-full px-2 py-1.5 text-center text-sm bg-white border border-slate-200 rounded focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500"
|
||||||
x-model="monthlyData[testId + '_" + day + "']"
|
:placeholder="'-'"
|
||||||
@change="markDirty(testId, day)"
|
x-model="monthlyData[testId + '_' + day]"
|
||||||
|
@change="markDirty(testId, day)"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="p-1 text-slate-400 hover:text-blue-600 hover:bg-blue-50 rounded transition-colors"
|
||||||
|
:title="monthlyData[testId + '_' + day + '_comment'] || 'Add comment'"
|
||||||
|
@click.stop="toggleComment(testId, day)"
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-comment text-xs" :class="monthlyData[testId + '_' + day + '_comment'] ? 'text-blue-500' : ''"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
x-show="showCommentId === testId + '_' + day"
|
||||||
|
x-transition
|
||||||
|
class="absolute z-10 top-full left-0 mt-1 w-48"
|
||||||
|
@click.outside="showCommentId = null"
|
||||||
>
|
>
|
||||||
|
<textarea
|
||||||
|
class="w-full px-2 py-1.5 text-xs bg-white border border-slate-200 rounded shadow-lg focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 resize-none"
|
||||||
|
rows="2"
|
||||||
|
placeholder="Add comment..."
|
||||||
|
x-model="monthlyData[testId + '_' + day + '_comment']"
|
||||||
|
@keydown.enter.prevent
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</template>
|
</template>
|
||||||
</tr>
|
</tr>
|
||||||
@ -191,6 +214,7 @@ document.addEventListener('alpine:init', () => {
|
|||||||
dirtyCells: new Set(),
|
dirtyCells: new Set(),
|
||||||
operation: 'replace',
|
operation: 'replace',
|
||||||
saveResult: null,
|
saveResult: null,
|
||||||
|
showCommentId: null,
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.loadDraft();
|
this.loadDraft();
|
||||||
@ -230,6 +254,15 @@ document.addEventListener('alpine:init', () => {
|
|||||||
this.dirtyCells.add(testId + '_' + day);
|
this.dirtyCells.add(testId + '_' + day);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
toggleComment(testId, day) {
|
||||||
|
const id = testId + '_' + day;
|
||||||
|
if (this.showCommentId === id) {
|
||||||
|
this.showCommentId = null;
|
||||||
|
} else {
|
||||||
|
this.showCommentId = id;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async loadControls() {
|
async loadControls() {
|
||||||
this.errors = {};
|
this.errors = {};
|
||||||
this.controls = [];
|
this.controls = [];
|
||||||
@ -311,9 +344,13 @@ document.addEventListener('alpine:init', () => {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
const formValues = data.data.formValues || {};
|
const formValues = data.data.formValues || {};
|
||||||
|
const comments = data.data.comments || {};
|
||||||
for (const [day, value] of Object.entries(formValues)) {
|
for (const [day, value] of Object.entries(formValues)) {
|
||||||
this.monthlyData[testId + '_' + day] = value;
|
this.monthlyData[testId + '_' + day] = value;
|
||||||
}
|
}
|
||||||
|
for (const [day, comment] of Object.entries(comments)) {
|
||||||
|
this.monthlyData[testId + '_' + day + '_comment'] = comment;
|
||||||
|
}
|
||||||
if (!this.comment && data.data.comment) {
|
if (!this.comment && data.data.comment) {
|
||||||
this.comment = data.data.comment;
|
this.comment = data.data.comment;
|
||||||
}
|
}
|
||||||
@ -354,17 +391,23 @@ document.addEventListener('alpine:init', () => {
|
|||||||
const tests = [];
|
const tests = [];
|
||||||
for (const testId of this.selectedTests) {
|
for (const testId of this.selectedTests) {
|
||||||
const resvalue = {};
|
const resvalue = {};
|
||||||
|
const rescomment = {};
|
||||||
let hasData = false;
|
let hasData = false;
|
||||||
for (let day = 1; day <= this.daysInMonth; day++) {
|
for (let day = 1; day <= this.daysInMonth; day++) {
|
||||||
const key = testId + '_' + day;
|
const key = testId + '_' + day;
|
||||||
const value = this.monthlyData[key];
|
const value = this.monthlyData[key];
|
||||||
|
const commentKey = testId + '_' + day + '_comment';
|
||||||
|
const comment = this.monthlyData[commentKey];
|
||||||
if (value !== undefined && value !== null && value !== '') {
|
if (value !== undefined && value !== null && value !== '') {
|
||||||
resvalue[day] = value;
|
resvalue[day] = value;
|
||||||
hasData = true;
|
hasData = true;
|
||||||
}
|
}
|
||||||
|
if (comment !== undefined && comment !== null && comment !== '') {
|
||||||
|
rescomment[day] = comment;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (hasData) {
|
if (hasData) {
|
||||||
tests.push({ testId, resvalue });
|
tests.push({ testId, resvalue, rescomment });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user