/* ضع رابط Web App الناتج من Google Apps Script هنا فقط. مثال: const API_URL = "https://script.google.com/macros/s/XXXXX/exec"; */ const API_URL = "https://script.google.com/macros/s/AKfycbzP3FbtCu3u5PitxRyxm1f1fiIU7UWuoEvcco3oK_MlTLFhlA0D13RgTK_V8e9TXOI/exec"; const $ = (id) => document.getElementById(id); let allTasks = []; let selectedDates = []; function sortDates(list) { return [...new Set(list)].sort((a,b) => a.localeCompare(b)); } function renderSelectedDates() { const box = $("selectedDates"); const addBtn = $("addDateBtn"); if (addBtn) addBtn.textContent = selectedDates.length ? `+ إضافة تاريخ آخر (${selectedDates.length})` : "+ إضافة التاريخ"; if (!selectedDates.length) { box.innerHTML = `لم تتم إضافة أي تواريخ بعد`; return; } box.innerHTML = selectedDates.map(date => ` 📅 ${escapeHTML(formatArabicDate(date, true))} `).join(""); } function addCurrentDate() { const date = $("taskDate").value; if (!date) return toast("اختر التاريخ أولًا", true); if (selectedDates.includes(date)) return toast("هذا التاريخ مضاف بالفعل", true); selectedDates = sortDates([...selectedDates, date]); renderSelectedDates(); } function localISODate(d = new Date()) { const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, "0"); const day = String(d.getDate()).padStart(2, "0"); return `${y}-${m}-${day}`; } function formatArabicDate(value, withWeekday = false) { if (!value) return "—"; const d = new Date(`${value}T12:00:00`); if (Number.isNaN(d.getTime())) return value; return new Intl.DateTimeFormat("ar-SA-u-ca-gregory", { weekday: withWeekday ? "long" : undefined, day: "numeric", month: "long", year: "numeric" }).format(d); } function dayDateLabel(value) { if (!value) return "—"; const d = new Date(`${value}T12:00:00`); if (Number.isNaN(d.getTime())) return value; return "📅 " + new Intl.DateTimeFormat("ar-SA-u-ca-gregory", { weekday:"long", day:"numeric", month:"long", year:"numeric" }).format(d); } function updateSelectedDateLabels() { const singleLabel = $("selectedDateLabel"); if (singleLabel && $("taskDate")) { singleLabel.textContent = dayDateLabel($("taskDate").value); } if ($("editDate") && $("editDateLabel")) { $("editDateLabel").textContent = dayDateLabel($("editDate").value); } } function escapeHTML(str = "") { return String(str).replace(/[&<>"']/g, c => ({ "&":"&","<":"<",">":">",'"':""","'":"'" }[c])); } function toast(message, isError = false) { const el = $("toast"); el.textContent = message; el.className = `toast show${isError ? " error" : ""}`; clearTimeout(toast.timer); toast.timer = setTimeout(() => el.className = "toast", 3200); } function apiReady() { return API_URL && !API_URL.includes("PUT_YOUR_"); } async function apiRequest(payload) { if (!apiReady()) throw new Error("لم يتم وضع رابط Google Apps Script داخل script.js"); const res = await fetch(API_URL, { method: "POST", headers: {"Content-Type":"text/plain;charset=utf-8"}, body: JSON.stringify({...payload, _ts: Date.now()}), redirect: "follow" }); if (!res.ok) throw new Error("تعذر الاتصال بالخادم"); const data = await res.json(); if (!data.ok) throw new Error(data.message || "حدث خطأ"); return data; } async function loadTasks() { $("statusBox").hidden = false; $("statusBox").textContent = "جاري تحميل المهام..."; $("taskList").hidden = true; $("emptyState").hidden = true; try { const data = await apiRequest({action:"list"}); allTasks = Array.isArray(data.tasks) ? data.tasks : []; updateDepartments(); updateStats(); renderTasks(); $("lastUpdated").textContent = `آخر تحديث: ${new Intl.DateTimeFormat("ar-SA-u-ca-gregory",{hour:"numeric",minute:"2-digit"}).format(new Date())}`; } catch (err) { $("statusBox").hidden = false; $("statusBox").textContent = err.message; toast(err.message, true); } } function updateStats() { const today = localISODate(); $("totalCount").textContent = allTasks.length; $("todayCount").textContent = allTasks.filter(t => t.date === today).length; $("deptCount").textContent = new Set(allTasks.map(t => (t.department || "").trim()).filter(Boolean)).size; } function updateDepartments() { const current = $("filterDept").value; const depts = [...new Set(allTasks.map(t => (t.department || "").trim()).filter(Boolean))] .sort((a,b)=>a.localeCompare(b,"ar")); $("filterDept").innerHTML = `` + depts.map(d => ``).join(""); if (depts.includes(current)) $("filterDept").value = current; } function filteredTasks() { const q = $("searchInput").value.trim().toLowerCase(); const date = $("filterDate").value; const dept = $("filterDept").value; return allTasks.filter(t => { const hay = `${t.task || ""} ${t.department || ""}`.toLowerCase(); return (!q || hay.includes(q)) && (!date || t.date === date) && (!dept || t.department === dept); }); } function renderTasks() { const tasks = filteredTasks(); $("statusBox").hidden = true; if (!tasks.length) { $("taskList").hidden = true; $("emptyState").hidden = false; return; } $("emptyState").hidden = true; $("taskList").hidden = false; $("taskList").innerHTML = tasks.map(t => { const d = new Date(`${t.date}T12:00:00`); const day = Number.isNaN(d.getTime()) ? "" : new Intl.DateTimeFormat("ar-SA-u-ca-gregory",{day:"numeric"}).format(d); const month = Number.isNaN(d.getTime()) ? "" : new Intl.DateTimeFormat("ar-SA-u-ca-gregory",{month:"long"}).format(d); const weekday = Number.isNaN(d.getTime()) ? "" : new Intl.DateTimeFormat("ar-SA-u-ca-gregory",{weekday:"long"}).format(d); return `
📅 ${escapeHTML(weekday)} ${escapeHTML(day)} ${escapeHTML(month)} • ${escapeHTML(t.date || "")}

${escapeHTML(t.task || "")}

تم تسجيل المهمة ضمن سجل الإنجازات

${escapeHTML(t.department || "بدون قسم")} ${t.status ? `${escapeHTML(t.status)}` : ""}
`; }).join(""); } async function saveTask(e) { e.preventDefault(); const task = $("taskText").value.trim(); const department = $("taskDept").value.trim(); // إذا اختار المستخدم تاريخًا ولم يضغط "إضافة التاريخ"، نضيفه تلقائيًا. let dates = [...selectedDates]; const currentDate = $("taskDate").value; if (currentDate && !dates.includes(currentDate)) dates.push(currentDate); dates = sortDates(dates); if (!dates.length || !task || !department) return toast("أكمل التاريخ والمهمة والقسم", true); const btn = $("saveBtn"); btn.disabled = true; $("saveBtnText").textContent = dates.length > 1 ? `جاري حفظ ${dates.length} تواريخ...` : "جاري الحفظ..."; try { for (const date of dates) { await apiRequest({action:"add", date, task, department}); } toast(dates.length > 1 ? `تم تسجيل المهمة في ${dates.length} تواريخ ✓` : "تم تسجيل المهمة بنجاح ✓"); $("taskText").value = ""; $("charCount").textContent = "0"; selectedDates = []; $("taskDate").value = localISODate(); renderSelectedDates(); updateSelectedDateLabels(); await loadTasks(); } catch (err) { toast(err.message, true); } finally { btn.disabled = false; $("saveBtnText").textContent = "حفظ المهمة"; } } function taskByRow(row) { return allTasks.find(t => Number(t.row) === Number(row)); } function openEdit(row) { const t = taskByRow(row); if (!t) return; $("editRow").value = t.row; $("editDate").value = t.date || ""; $("editDept").value = t.department || ""; $("editText").value = t.task || ""; updateSelectedDateLabels(); $("editModal").hidden = false; document.body.style.overflow = "hidden"; } function closeEdit() { $("editModal").hidden = true; document.body.style.overflow = ""; } async function updateTask(e) { e.preventDefault(); const btn = $("editSaveBtn"); btn.disabled = true; btn.textContent = "جاري الحفظ..."; try { await apiRequest({ action:"update", row:Number($("editRow").value), date:$("editDate").value, task:$("editText").value.trim(), department:$("editDept").value, status:(taskByRow(Number($("editRow").value)) || {}).status || "" }); closeEdit(); toast("تم تعديل المهمة بنجاح ✓"); await loadTasks(); } catch (err) { toast(err.message, true); } finally { btn.disabled = false; btn.textContent = "حفظ التعديل"; } } async function updateStatus(row, status) { const t = taskByRow(row); if (!t) return; try { await apiRequest({ action:"update", row:Number(row), date:t.date, task:t.task, department:t.department || "", status:status || "" }); toast(status ? `تم تحديث الحالة إلى: ${status}` : "تم إزالة الحالة"); await loadTasks(); } catch (err) { toast(err.message, true); } } async function deleteTask(row) { const t = taskByRow(row); if (!t) return; if (!confirm(`هل تريد حذف المهمة: "${t.task}"؟`)) return; try { await apiRequest({action:"delete", row:Number(row)}); toast("تم حذف المهمة"); await loadTasks(); } catch (err) { toast(err.message, true); } } document.addEventListener("DOMContentLoaded", () => { $("taskDate").value = localISODate(); $("todayText").textContent = "📅 " + formatArabicDate(localISODate(), true); selectedDates = [localISODate()]; try { renderSelectedDates(); } catch (e) { console.warn(e); } try { updateSelectedDateLabels(); } catch (e) { console.warn(e); } // تحميل السجل مبكرًا وبشكل مستقل حتى تظهر المهام السابقة دائمًا loadTasks(); if ($("entryToggle") && $("entryPanel")) { $("entryToggle").addEventListener("click", () => { const panel = $("entryPanel"); const willOpen = panel.hidden; panel.hidden = !willOpen; $("entryToggle").setAttribute("aria-expanded", String(willOpen)); $("entryToggle").classList.toggle("is-open", willOpen); const hint = $("entryToggle").querySelector("p"); if (hint) hint.textContent = willOpen ? "اضغط هنا لإخفاء منطقة إدخال المهام." : "اضغط هنا لإظهار منطقة إدخال المهام."; if (willOpen) setTimeout(() => panel.scrollIntoView({behavior:"smooth", block:"start"}), 50); }); } $("taskForm").addEventListener("submit", saveTask); $("taskDate").addEventListener("change", updateSelectedDateLabels); if ($("addDateBtn")) $("addDateBtn").addEventListener("click", addCurrentDate); if ($("selectedDates")) $("selectedDates").addEventListener("click", e => { const btn = e.target.closest("[data-remove-date]"); if (!btn) return; selectedDates = selectedDates.filter(d => d !== btn.dataset.removeDate); renderSelectedDates(); }); $("editDate").addEventListener("change", updateSelectedDateLabels); $("editForm").addEventListener("submit", updateTask); document.querySelectorAll("[data-close-modal]").forEach(el => el.addEventListener("click", closeEdit)); $("taskList").addEventListener("click", e => { const edit = e.target.closest("[data-edit-row]"); const del = e.target.closest("[data-delete-row]"); const status = e.target.closest("[data-status-row]"); if (status) return updateStatus(status.dataset.statusRow, status.dataset.statusValue); if (edit) openEdit(edit.dataset.editRow); if (del) deleteTask(del.dataset.deleteRow); }); $("taskText").addEventListener("input", e => $("charCount").textContent = e.target.value.length); $("refreshBtn").addEventListener("click", loadTasks); $("searchInput").addEventListener("input", renderTasks); $("filterDate").addEventListener("change", renderTasks); $("filterDept").addEventListener("change", renderTasks); $("clearFilters").addEventListener("click", () => { $("searchInput").value = ""; $("filterDate").value = ""; $("filterDept").value = ""; renderTasks(); }); $("taskForm").addEventListener("reset", () => { setTimeout(() => { $("taskDate").value = localISODate(); $("charCount").textContent = "0"; selectedDates = [localISODate()]; renderSelectedDates(); updateSelectedDateLabels(); }, 0); }); });