edit filter time
This commit is contained in:
@@ -93,7 +93,54 @@ $state = [
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
|
||||
.tasks-filter select {
|
||||
.tasks-filters {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.tasks-filters .tasks-filter {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.tasks-filter-date {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 0.65rem;
|
||||
}
|
||||
|
||||
.tasks-filter-date-head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.tasks-filter-date-head select {
|
||||
flex: 1;
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.tasks-filter-date-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.45rem;
|
||||
}
|
||||
|
||||
.tasks-filter-date-controls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tasks-filter select,
|
||||
.tasks-filter input[type="text"] {
|
||||
border-radius: 0.875rem;
|
||||
border: 1px solid var(--border-input);
|
||||
background: var(--surface-input);
|
||||
@@ -101,7 +148,38 @@ $state = [
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.tasks-filter select:focus {
|
||||
.tasks-filter-date-field {
|
||||
flex: 1 1 7rem;
|
||||
min-width: 7rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.tasks-filter-date-type {
|
||||
min-width: 140px;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.tasks-filter-clear {
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.8125rem;
|
||||
border-radius: 0.875rem;
|
||||
border: 1px solid var(--border-input);
|
||||
background: var(--surface-tabs);
|
||||
color: var(--text-secondary);
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
.tasks-filter-clear:hover {
|
||||
background: var(--surface-tab-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.tasks-filter select:focus,
|
||||
.tasks-filter input[type="text"]:focus {
|
||||
outline: none;
|
||||
border-color: color-mix(in srgb, var(--primary-color) 55%, white);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 22%, transparent);
|
||||
@@ -344,19 +422,45 @@ $state = [
|
||||
});
|
||||
}
|
||||
|
||||
function filterTasksByAssignee(assigneeId) {
|
||||
function applyTaskFilters() {
|
||||
const assigneeId = document.getElementById('assigneeFilter')?.value || '';
|
||||
const dateField = document.getElementById('dateFilterField')?.value || 'target_date';
|
||||
const fromDate = document.getElementById('dateFilterFromGregorian')?.value || '';
|
||||
const toDate = document.getElementById('dateFilterToGregorian')?.value || '';
|
||||
const hasDateFilter = Boolean(fromDate || toDate);
|
||||
const dateAttrMap = {
|
||||
target_date: 'targetDate',
|
||||
start_date: 'startDate',
|
||||
finished_at: 'finishedDate',
|
||||
created_at: 'createdDate',
|
||||
};
|
||||
const dateAttr = dateAttrMap[dateField] || 'targetDate';
|
||||
const hasAnyFilter = Boolean(assigneeId || hasDateFilter);
|
||||
|
||||
document.querySelectorAll('.task-row').forEach(row => {
|
||||
if (!assigneeId) {
|
||||
row.classList.remove('hidden');
|
||||
return;
|
||||
let visible = true;
|
||||
|
||||
if (assigneeId) {
|
||||
let ids = [];
|
||||
try {
|
||||
ids = JSON.parse(row.dataset.assigneeIds || '[]');
|
||||
} catch (e) {
|
||||
ids = [];
|
||||
}
|
||||
visible = ids.includes(String(assigneeId));
|
||||
}
|
||||
let ids = [];
|
||||
try {
|
||||
ids = JSON.parse(row.dataset.assigneeIds || '[]');
|
||||
} catch (e) {
|
||||
ids = [];
|
||||
|
||||
if (visible && hasDateFilter) {
|
||||
const taskDate = row.dataset[dateAttr] || '';
|
||||
if (!taskDate) {
|
||||
visible = false;
|
||||
} else {
|
||||
if (fromDate && taskDate < fromDate) visible = false;
|
||||
if (toDate && taskDate > toDate) visible = false;
|
||||
}
|
||||
}
|
||||
row.classList.toggle('hidden', !ids.includes(String(assigneeId)));
|
||||
|
||||
row.classList.toggle('hidden', !visible);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.task-table-container').forEach(panel => {
|
||||
@@ -364,32 +468,90 @@ $state = [
|
||||
const visibleRows = panel.querySelectorAll('.task-row:not(.hidden)').length;
|
||||
const hasRows = panel.querySelectorAll('.task-row').length > 0;
|
||||
if (emptyMsg) {
|
||||
emptyMsg.classList.toggle('hidden', !assigneeId || !hasRows || visibleRows > 0);
|
||||
emptyMsg.classList.toggle('hidden', !hasAnyFilter || !hasRows || visibleRows > 0);
|
||||
}
|
||||
});
|
||||
|
||||
updateTaskTabCounts();
|
||||
}
|
||||
|
||||
function filterTasksByAssignee() {
|
||||
applyTaskFilters();
|
||||
}
|
||||
|
||||
function clearTaskDateFilter() {
|
||||
const fromInput = document.getElementById('dateFilterFrom');
|
||||
const toInput = document.getElementById('dateFilterTo');
|
||||
const fromHidden = document.getElementById('dateFilterFromGregorian');
|
||||
const toHidden = document.getElementById('dateFilterToGregorian');
|
||||
|
||||
if (fromInput) fromInput.value = '';
|
||||
if (toInput) toInput.value = '';
|
||||
if (fromHidden) fromHidden.value = '';
|
||||
if (toHidden) toHidden.value = '';
|
||||
|
||||
applyTaskFilters();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container tasks-page w-full flex justify-center items-center flex-col">
|
||||
@if($count > 0)
|
||||
<div class="w-full max-w-6xl mx-auto mt-4">
|
||||
@if(!empty($assigneeFilterOptions))
|
||||
<div class="tasks-filter flex flex-wrap items-center gap-3">
|
||||
<label for="assigneeFilter" class="text-sm font-medium text-gray-700 whitespace-nowrap">
|
||||
فیلتر بر اساس مسئول:
|
||||
</label>
|
||||
<select id="assigneeFilter"
|
||||
onchange="filterTasksByAssignee(this.value)"
|
||||
class="flex-1 min-w-[180px] px-3 py-2 text-sm text-gray-800">
|
||||
<option value="">همه مسئولین</option>
|
||||
@foreach($assigneeFilterOptions as $option)
|
||||
<option value="{{ $option['id'] }}">{{ $option['name'] }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<div class="tasks-filters">
|
||||
@if(!empty($assigneeFilterOptions))
|
||||
<div class="tasks-filter flex flex-wrap items-center gap-3">
|
||||
<label for="assigneeFilter" class="text-sm font-medium text-gray-700 whitespace-nowrap">
|
||||
فیلتر بر اساس مسئول:
|
||||
</label>
|
||||
<select id="assigneeFilter"
|
||||
onchange="applyTaskFilters()"
|
||||
class="flex-1 min-w-[180px] px-3 py-2 text-sm text-gray-800">
|
||||
<option value="">همه مسئولین</option>
|
||||
@foreach($assigneeFilterOptions as $option)
|
||||
<option value="{{ $option['id'] }}">{{ $option['name'] }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="tasks-filter tasks-filter-date">
|
||||
<div class="tasks-filter-date-head">
|
||||
<label for="dateFilterField" class="text-sm font-medium text-gray-700 whitespace-nowrap">
|
||||
فیلتر بر اساس تاریخ:
|
||||
</label>
|
||||
<select id="dateFilterField"
|
||||
onchange="applyTaskFilters()"
|
||||
class="tasks-filter-date-type">
|
||||
<option value="target_date">مهلت</option>
|
||||
<option value="start_date">تاریخ شروع</option>
|
||||
<option value="finished_at">تاریخ اتمام</option>
|
||||
<option value="created_at">تاریخ ایجاد</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="tasks-filter-date-row">
|
||||
<input type="text"
|
||||
id="dateFilterFrom"
|
||||
class="tasks-filter-date-field"
|
||||
placeholder="از تاریخ"
|
||||
autocomplete="off"
|
||||
readonly>
|
||||
<input type="text"
|
||||
id="dateFilterTo"
|
||||
class="tasks-filter-date-field"
|
||||
placeholder="تا تاریخ"
|
||||
autocomplete="off"
|
||||
readonly>
|
||||
<button type="button"
|
||||
id="dateFilterClear"
|
||||
class="tasks-filter-clear"
|
||||
onclick="clearTaskDateFilter()">
|
||||
پاک کردن
|
||||
</button>
|
||||
<input type="hidden" id="dateFilterFromGregorian">
|
||||
<input type="hidden" id="dateFilterToGregorian">
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="tasks-card mt-3">
|
||||
<div class="tasks-tabs">
|
||||
@@ -422,6 +584,10 @@ $state = [
|
||||
<article class="task-card task-row {{ $task->active ? 'is-active' : '' }}"
|
||||
data-task-id="{{ $task->id }}"
|
||||
data-assignee-ids='@json($task->assignee_ids ?? [])'
|
||||
data-start-date="{{ $task->start_date ? \Carbon\Carbon::parse($task->start_date)->format('Y-m-d') : '' }}"
|
||||
data-target-date="{{ $task->target_date ? \Carbon\Carbon::parse($task->target_date)->format('Y-m-d') : '' }}"
|
||||
data-finished-date="{{ $task->finished_at ? \Carbon\Carbon::parse($task->finished_at)->format('Y-m-d') : '' }}"
|
||||
data-created-date="{{ $task->created_at ? \Carbon\Carbon::parse($task->created_at)->format('Y-m-d') : '' }}"
|
||||
onclick="openTaskDetailsModal({{ $task->id }})"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@@ -475,7 +641,7 @@ $state = [
|
||||
@endforeach
|
||||
</div>
|
||||
<div class="task-filter-empty hidden tasks-empty">
|
||||
تسکی برای این مسئول در این وضعیت وجود ندارد.
|
||||
وظیفهای با این فیلترها در این وضعیت وجود ندارد.
|
||||
</div>
|
||||
@else
|
||||
<div class="tasks-empty">
|
||||
@@ -584,6 +750,10 @@ $state = [
|
||||
placeholder="مهلت" autocomplete="off" readonly>
|
||||
<input id="modalTaskTargetDateGregorian" type="hidden">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-1">تاریخ اتمام</h3>
|
||||
<p class="text-gray-800 text-sm" id="modalTaskFinishedAtView">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-1">زمان صرف شده</h3>
|
||||
<p class="text-gray-800 text-sm font-mono" id="modalTaskDuration">00:00:00</p>
|
||||
@@ -804,6 +974,7 @@ $state = [
|
||||
window.tasksById = tasksById;
|
||||
|
||||
updateModalInfoFields(currentTaskDetails);
|
||||
updateTaskFinishedAtView(currentTaskDetails);
|
||||
renderTaskComments(currentTaskDetails);
|
||||
renderTaskDurations(currentTaskDetails);
|
||||
updateModalTotalDuration(currentTaskDetails);
|
||||
@@ -1056,6 +1227,30 @@ $state = [
|
||||
});
|
||||
}
|
||||
|
||||
function resolveFinishedAtFromTask(task) {
|
||||
if (task?.finished_at) {
|
||||
return task.finished_at;
|
||||
}
|
||||
|
||||
const durs = Array.isArray(task?.duration) ? task.duration : [];
|
||||
const ends = durs
|
||||
.map(d => d.end)
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => new Date(b) - new Date(a));
|
||||
|
||||
return ends[0] || null;
|
||||
}
|
||||
|
||||
function updateTaskFinishedAtView(task) {
|
||||
const finishedView = document.getElementById('modalTaskFinishedAtView');
|
||||
if (!finishedView) {
|
||||
return;
|
||||
}
|
||||
|
||||
const finishedAt = resolveFinishedAtFromTask(task);
|
||||
finishedView.textContent = finishedAt ? formatDateTime(finishedAt) : '-';
|
||||
}
|
||||
|
||||
function formatAssigneeNames(task) {
|
||||
const names = Array.isArray(task?.assignee_names) ? task.assignee_names.filter(Boolean) : [];
|
||||
return names.length ? names.join('، ') : '-';
|
||||
@@ -1087,6 +1282,7 @@ $state = [
|
||||
document.getElementById('modalTaskDescription').innerHTML = task.description || 'بدون توضیحات';
|
||||
document.getElementById('modalTaskStartDateView').textContent = task.start_date ? formatJalaliDate(task.start_date) : '-';
|
||||
document.getElementById('modalTaskTargetDateView').textContent = task.target_date ? formatJalaliDate(task.target_date) : '-';
|
||||
updateTaskFinishedAtView(task);
|
||||
updateModalInfoFields(task);
|
||||
|
||||
// اپ
|
||||
@@ -1173,11 +1369,22 @@ $state = [
|
||||
break;
|
||||
}
|
||||
}
|
||||
currentTaskDetails.finished_at = data.finished_at || data.end || currentTaskDetails.finished_at;
|
||||
}
|
||||
|
||||
currentTaskDetails.duration = durs;
|
||||
renderTaskDurations(currentTaskDetails);
|
||||
updateModalTotalDuration(currentTaskDetails);
|
||||
updateTaskFinishedAtView(currentTaskDetails);
|
||||
|
||||
const row = document.querySelector(`.task-row[data-task-id="${currentTaskDetails.id}"]`);
|
||||
if (row && currentTaskDetails.finished_at) {
|
||||
row.dataset.finishedDate = String(currentTaskDetails.finished_at).slice(0, 10);
|
||||
}
|
||||
|
||||
tasksById[currentTaskDetails.id] = currentTaskDetails;
|
||||
tasksById[String(currentTaskDetails.id)] = currentTaskDetails;
|
||||
window.tasksById = tasksById;
|
||||
|
||||
const nextAction = currentTaskDetails.active ? 'end' : 'start';
|
||||
durationBtn.textContent = nextAction === 'start' ? 'شروع' : 'پایان';
|
||||
@@ -1301,18 +1508,22 @@ $state = [
|
||||
const sdEdit = $('#modalTaskStartDateEdit');
|
||||
const tdEdit = $('#modalTaskTargetDateEdit');
|
||||
|
||||
const pickerOptions = {
|
||||
format: 'YYYY/MM/DD',
|
||||
autoClose: true,
|
||||
toolbox: {
|
||||
calendarSwitch: {enabled: false},
|
||||
todayButton: {enabled: true, text: {fa: 'امروز'}}
|
||||
}
|
||||
};
|
||||
|
||||
if (sdEdit.length) {
|
||||
try {
|
||||
sdEdit.persianDatepicker('destroy');
|
||||
} catch (e) {
|
||||
}
|
||||
sdEdit.persianDatepicker({
|
||||
format: 'YYYY/MM/DD',
|
||||
autoClose: true,
|
||||
toolbox: {
|
||||
calendarSwitch: {enabled: false},
|
||||
todayButton: {enabled: true, text: {fa: 'امروز'}}
|
||||
},
|
||||
...pickerOptions,
|
||||
onSelect: function (unix) {
|
||||
let gregorian = new persianDate(unix).toCalendar('gregorian').toLocale('en');
|
||||
let dateStr = gregorian.format('YYYY-MM-DD');
|
||||
@@ -1328,12 +1539,7 @@ $state = [
|
||||
} catch (e) {
|
||||
}
|
||||
tdEdit.persianDatepicker({
|
||||
format: 'YYYY/MM/DD',
|
||||
autoClose: true,
|
||||
toolbox: {
|
||||
calendarSwitch: {enabled: false},
|
||||
todayButton: {enabled: true, text: {fa: 'امروز'}}
|
||||
},
|
||||
...pickerOptions,
|
||||
onSelect: function (unix) {
|
||||
let gregorian = new persianDate(unix).toCalendar('gregorian').toLocale('en');
|
||||
let dateStr = gregorian.format('YYYY-MM-DD');
|
||||
@@ -1376,7 +1582,6 @@ $state = [
|
||||
currentTaskDetails.start_date = sdGreg?.value || null;
|
||||
currentTaskDetails.target_date = tdGreg?.value || null;
|
||||
|
||||
// refresh view bits (بدون بازنشانی مودال)
|
||||
const sdView = document.getElementById('modalTaskStartDateView');
|
||||
const tdView = document.getElementById('modalTaskTargetDateView');
|
||||
if (sdView) sdView.textContent = currentTaskDetails.start_date ? formatJalaliDate(currentTaskDetails.start_date) : '-';
|
||||
@@ -1452,6 +1657,35 @@ $state = [
|
||||
|
||||
// NOTE: مدیریت زمان از مودال جزئیات حذف شده و فقط در تب "زمانها" انجام میشود.
|
||||
|
||||
$(document).ready(function () {
|
||||
const datePickerOptions = {
|
||||
format: 'YYYY/MM/DD',
|
||||
autoClose: true,
|
||||
toolbox: {
|
||||
calendarSwitch: {enabled: false},
|
||||
todayButton: {enabled: true, text: {fa: 'امروز'}}
|
||||
}
|
||||
};
|
||||
|
||||
$('#dateFilterFrom').persianDatepicker({
|
||||
...datePickerOptions,
|
||||
onSelect: function (unix) {
|
||||
const gregorian = new persianDate(unix).toCalendar('gregorian').toLocale('en');
|
||||
$('#dateFilterFromGregorian').val(gregorian.format('YYYY-MM-DD'));
|
||||
applyTaskFilters();
|
||||
}
|
||||
});
|
||||
|
||||
$('#dateFilterTo').persianDatepicker({
|
||||
...datePickerOptions,
|
||||
onSelect: function (unix) {
|
||||
const gregorian = new persianDate(unix).toCalendar('gregorian').toLocale('en');
|
||||
$('#dateFilterToGregorian').val(gregorian.format('YYYY-MM-DD'));
|
||||
applyTaskFilters();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@endsection
|
||||
|
||||
Reference in New Issue
Block a user