系统化待办时间支持待定 #655
@ -685,7 +685,9 @@ class SystemizationTodos extends Controller
|
||||
'owner' => ['nullable', 'string', 'max:255'],
|
||||
'assignee' => ['nullable', 'string', 'max:255'],
|
||||
'start_date' => ['nullable', 'date_format:Y-m-d'],
|
||||
'start_note' => ['nullable', 'string', 'max:50'],
|
||||
'due_date' => ['nullable', 'date_format:Y-m-d', 'after_or_equal:start_date'],
|
||||
'due_note' => ['nullable', 'string', 'max:50'],
|
||||
'status' => ['required', Rule::in(SystemizationTodo::STATUSES)],
|
||||
'completed_at' => ['nullable', 'date_format:Y-m-d'],
|
||||
'progress' => ['nullable', 'string', 'max:10000'],
|
||||
|
||||
@ -104,6 +104,13 @@
|
||||
.sys-todo-field input, .sys-todo-field select, .sys-todo-field textarea { width:100%; padding:.5rem .6rem; border:1px solid var(--sys-border); border-radius:.45rem; background:#fff; color:#171717; font:inherit; font-size:.8125rem; }
|
||||
.sys-todo-field input, .sys-todo-field select { min-height:2.15rem; }
|
||||
.sys-todo-field textarea { min-height:5.5rem; resize:vertical; line-height:1.55; overflow:hidden; }
|
||||
.sys-todo-date-control { display:flex; align-items:center; gap:.45rem; min-width:0; }
|
||||
.sys-todo-date-control input[type="date"] { flex:1 1 auto; min-width:0; }
|
||||
.sys-todo-date-control.is-pending input[type="date"] { background:#f7f0de; color:#777; }
|
||||
.sys-todo-date-pending { display:inline-flex; flex:0 0 auto; align-items:center; gap:.3rem; min-height:2.15rem; padding:0 .55rem; border:1px solid var(--sys-border); border-radius:.45rem; background:#fcfaf5; color:#51483c; font-size:.75rem; white-space:nowrap; cursor:pointer; }
|
||||
.sys-todo-date-pending input[type="checkbox"] { width:1rem; height:1rem; min-height:0; padding:0; accent-color:var(--sys-gold); }
|
||||
.sys-todo-date-note { color:#856632; font-size:.68rem; line-height:1.4; }
|
||||
.sys-todo-date-note[hidden] { display:none; }
|
||||
.sys-todo-new-category { margin-top:.35rem; }
|
||||
.sys-todo-new-category[hidden] { display:none; }
|
||||
.sys-todo-owner-checks { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:.2rem .45rem; max-height:7rem; padding:.4rem; border:1px solid var(--sys-border); border-radius:.45rem; background:#fff; overflow:auto; }
|
||||
@ -362,7 +369,9 @@
|
||||
'owner' => $item->owner ?? '',
|
||||
'assignee' => $item->assignee ?? '',
|
||||
'start_date' => $item->start_date?->format('Y-m-d') ?? '',
|
||||
'start_note' => $item->start_note ?? '',
|
||||
'due_date' => $item->due_date?->format('Y-m-d') ?? '',
|
||||
'due_note' => $item->due_note ?? '',
|
||||
'status' => $item->status,
|
||||
'completed_at' => $item->completed_at?->format('Y-m-d') ?? '',
|
||||
'progress' => $item->progress ?? '',
|
||||
@ -525,8 +534,30 @@
|
||||
<input type="hidden" name="owner" data-sys-todo-owner-value>
|
||||
</div>
|
||||
<label class="sys-todo-field sys-todo-field--3">责任人<input name="assignee" maxlength="255" list="sysTodoAssignees"></label>
|
||||
<label class="sys-todo-field sys-todo-field--3">开始时间<input type="date" name="start_date"></label>
|
||||
<label class="sys-todo-field sys-todo-field--3">完成时间<input type="date" name="due_date"></label>
|
||||
<div class="sys-todo-field sys-todo-field--3">
|
||||
<label for="sysTodoStartDate">开始时间</label>
|
||||
<div class="sys-todo-date-control" data-sys-todo-date-control="start">
|
||||
<input id="sysTodoStartDate" type="date" name="start_date">
|
||||
<label class="sys-todo-date-pending">
|
||||
<input type="checkbox" data-sys-todo-date-pending="start">
|
||||
<span>待定</span>
|
||||
</label>
|
||||
</div>
|
||||
<input type="hidden" name="start_note" data-sys-todo-date-note="start">
|
||||
<span class="sys-todo-date-note" data-sys-todo-date-note-caption="start" hidden></span>
|
||||
</div>
|
||||
<div class="sys-todo-field sys-todo-field--3">
|
||||
<label for="sysTodoDueDate">完成时间</label>
|
||||
<div class="sys-todo-date-control" data-sys-todo-date-control="due">
|
||||
<input id="sysTodoDueDate" type="date" name="due_date">
|
||||
<label class="sys-todo-date-pending">
|
||||
<input type="checkbox" data-sys-todo-date-pending="due">
|
||||
<span>待定</span>
|
||||
</label>
|
||||
</div>
|
||||
<input type="hidden" name="due_note" data-sys-todo-date-note="due">
|
||||
<span class="sys-todo-date-note" data-sys-todo-date-note-caption="due" hidden></span>
|
||||
</div>
|
||||
<label class="sys-todo-field sys-todo-field--6">待办事项<textarea name="title" maxlength="5000" required data-sys-todo-auto-grow></textarea></label>
|
||||
<label class="sys-todo-field sys-todo-field--6">完成情况<textarea name="progress" maxlength="10000" data-sys-todo-auto-grow></textarea></label>
|
||||
<label class="sys-todo-field sys-todo-field--6">备注<textarea name="notes" maxlength="10000" data-sys-todo-auto-grow></textarea></label>
|
||||
@ -627,6 +658,20 @@
|
||||
var sectionValue = dialog.querySelector('[data-sys-todo-section-value]');
|
||||
var newSectionWrap = dialog.querySelector('[data-sys-todo-new-section]');
|
||||
var newSectionInput = dialog.querySelector('[data-sys-todo-new-section-input]');
|
||||
var startDateControl = {
|
||||
input: form.querySelector('[name="start_date"]'),
|
||||
pending: dialog.querySelector('[data-sys-todo-date-pending="start"]'),
|
||||
note: form.querySelector('[data-sys-todo-date-note="start"]'),
|
||||
caption: dialog.querySelector('[data-sys-todo-date-note-caption="start"]'),
|
||||
wrap: dialog.querySelector('[data-sys-todo-date-control="start"]'),
|
||||
};
|
||||
var dueDateControl = {
|
||||
input: form.querySelector('[name="due_date"]'),
|
||||
pending: dialog.querySelector('[data-sys-todo-date-pending="due"]'),
|
||||
note: form.querySelector('[data-sys-todo-date-note="due"]'),
|
||||
caption: dialog.querySelector('[data-sys-todo-date-note-caption="due"]'),
|
||||
wrap: dialog.querySelector('[data-sys-todo-date-control="due"]'),
|
||||
};
|
||||
var positionField = dialog.querySelector('[data-sys-todo-position-field]');
|
||||
var positionSelect = dialog.querySelector('[data-sys-todo-position]');
|
||||
var ownerValue = dialog.querySelector('[data-sys-todo-owner-value]');
|
||||
@ -671,6 +716,47 @@
|
||||
if (field) field.value = value || '';
|
||||
}
|
||||
|
||||
function updateDateNoteCaption(control) {
|
||||
var note = control.note.value.trim();
|
||||
var showPreserved = !control.pending.checked
|
||||
&& control.input.value === ''
|
||||
&& note !== ''
|
||||
&& note !== '待定';
|
||||
control.caption.hidden = !showPreserved;
|
||||
control.caption.textContent = showPreserved ? '原时间备注:' + note : '';
|
||||
}
|
||||
|
||||
function syncDateControl(control, fromToggle) {
|
||||
control.wrap.classList.toggle('is-pending', control.pending.checked);
|
||||
if (control.pending.checked) {
|
||||
control.input.value = '';
|
||||
control.input.disabled = true;
|
||||
control.note.value = '待定';
|
||||
updateDateNoteCaption(control);
|
||||
return;
|
||||
}
|
||||
|
||||
control.input.disabled = false;
|
||||
if (control.input.value !== '') {
|
||||
control.note.value = '';
|
||||
control.input.dataset.originalNote = '';
|
||||
} else if (fromToggle) {
|
||||
var originalNote = control.input.dataset.originalNote || '';
|
||||
control.note.value = originalNote !== '待定' ? originalNote : '';
|
||||
}
|
||||
updateDateNoteCaption(control);
|
||||
}
|
||||
|
||||
function applyDateControl(control, dateValue, noteValue) {
|
||||
var normalizedDate = String(dateValue || '');
|
||||
var normalizedNote = String(noteValue || '');
|
||||
control.input.value = normalizedDate;
|
||||
control.input.dataset.originalNote = normalizedNote;
|
||||
control.note.value = normalizedNote;
|
||||
control.pending.checked = normalizedDate === '' && normalizedNote === '待定';
|
||||
syncDateControl(control, false);
|
||||
}
|
||||
|
||||
function resizeTextarea(field) {
|
||||
if (!field || field.offsetParent === null) return;
|
||||
field.style.height = 'auto';
|
||||
@ -974,8 +1060,8 @@
|
||||
setValue('title', payload.title);
|
||||
setOwnerValue(payload.owner);
|
||||
setValue('assignee', payload.assignee);
|
||||
setValue('start_date', payload.start_date);
|
||||
setValue('due_date', payload.due_date);
|
||||
applyDateControl(startDateControl, payload.start_date, payload.start_note);
|
||||
applyDateControl(dueDateControl, payload.due_date, payload.due_note);
|
||||
setValue('status', payload.status);
|
||||
setValue('completed_at', payload.completed_at);
|
||||
setValue('progress', payload.progress);
|
||||
@ -984,6 +1070,8 @@
|
||||
setCategoryValue('');
|
||||
refreshSectionOptions('');
|
||||
setOwnerValue('');
|
||||
applyDateControl(startDateControl, '', '');
|
||||
applyDateControl(dueDateControl, '', '');
|
||||
}
|
||||
if (!currentTodoId && !(payload && payload.status)) {
|
||||
setValue('status', '待办');
|
||||
@ -1155,6 +1243,19 @@
|
||||
populatePositionOptions();
|
||||
}
|
||||
});
|
||||
[startDateControl, dueDateControl].forEach(function (control) {
|
||||
control.pending.addEventListener('change', function () {
|
||||
syncDateControl(control, true);
|
||||
});
|
||||
['input', 'change'].forEach(function (eventName) {
|
||||
control.input.addEventListener(eventName, function () {
|
||||
if (control.input.value !== '') {
|
||||
control.pending.checked = false;
|
||||
}
|
||||
syncDateControl(control, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
dialog.addEventListener('input', function (event) {
|
||||
if (event.target.matches('[data-sys-todo-auto-grow]')) resizeTextarea(event.target);
|
||||
});
|
||||
|
||||
@ -32,7 +32,10 @@ class SystemizationTodosTest extends FeatureTestCase
|
||||
->assertSeeText('已逾期')
|
||||
->assertSeeText('开始时间')
|
||||
->assertSeeText('完成时间')
|
||||
->assertSeeText('待定')
|
||||
->assertSee('data-sys-todo-create', false)
|
||||
->assertSee('data-sys-todo-date-pending="start"', false)
|
||||
->assertSee('data-sys-todo-date-pending="due"', false)
|
||||
->assertSee('data-sys-todo-owner-filter', false)
|
||||
->assertSee('data-sys-todo-assignee-filter', false)
|
||||
->assertSee('name="owner[]"', false)
|
||||
@ -358,6 +361,52 @@ class SystemizationTodosTest extends FeatureTestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function test_manual_edit_saves_and_preserves_pending_date_notes(): void
|
||||
{
|
||||
$this->loginAs();
|
||||
|
||||
$this->post(route('business.systemization-todos.store'), [
|
||||
'category' => '收入',
|
||||
'section' => '平台收入核对',
|
||||
'title' => '待确认开始与完成日期',
|
||||
'owner' => '晨曦',
|
||||
'assignee' => '栎阳',
|
||||
'start_date' => null,
|
||||
'start_note' => '待定',
|
||||
'due_date' => null,
|
||||
'due_note' => '待定',
|
||||
'status' => SystemizationTodo::STATUS_PENDING,
|
||||
'progress' => '',
|
||||
'notes' => '',
|
||||
])->assertRedirect();
|
||||
|
||||
$todo = SystemizationTodo::query()->firstOrFail();
|
||||
$this->assertNull($todo->start_date);
|
||||
$this->assertSame('待定', $todo->start_note);
|
||||
$this->assertNull($todo->due_date);
|
||||
$this->assertSame('待定', $todo->due_note);
|
||||
|
||||
$this->patch(route('business.systemization-todos.update', ['todo' => $todo->id]), [
|
||||
'category' => '收入',
|
||||
'section' => '平台收入核对',
|
||||
'title' => '待确认开始与完成日期(已更新)',
|
||||
'owner' => '晨曦',
|
||||
'assignee' => '栎阳',
|
||||
'start_date' => null,
|
||||
'start_note' => '待定',
|
||||
'due_date' => null,
|
||||
'due_note' => '11月',
|
||||
'status' => SystemizationTodo::STATUS_IN_PROGRESS,
|
||||
'progress' => '等待平台确认',
|
||||
'notes' => '',
|
||||
])->assertRedirect();
|
||||
|
||||
$todo->refresh();
|
||||
$this->assertSame('待确认开始与完成日期(已更新)', $todo->title);
|
||||
$this->assertSame('待定', $todo->start_note);
|
||||
$this->assertSame('11月', $todo->due_note);
|
||||
}
|
||||
|
||||
public function test_page_filters_by_date_and_merges_adjacent_category_and_section_cells(): void
|
||||
{
|
||||
$this->loginAs();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user