عنوان المهمة *
✓ حفظ المهمة
🗑 حذف هذه المهمة
let _lang = localStorage.getItem('alum_lang') || 'ar';
const TRANSLATIONS = {
ar: {
lang_btn:'EN',
login_title:'ذُروة', login_sub:'نظام متابعة المشروع — سجّل دخولك',
lbl_username:'اسم المستخدم', ph_username:'الاسم الكامل',
lbl_password:'كلمة المرور', btn_login:'دخول',
live:'محدَّث', export_btn:'📋 نسخ التقرير', print_btn:'🖨️ طباعة', share_btn:'🔗 مشاركة',
logout_btn:'خروج', logout_confirm:'تسجيل الخروج؟',
lbl_steps:'الخطوات', lbl_done:'مكتملة', lbl_prog:'جارية', lbl_pend:'لم تبدأ', lbl_pct:'إنجاز الخطوات',
tab_overview:'نظرة عامة', tab_technical:'التقني', tab_marketing:'التسويقي',
tab_admin:'الإداري', tab_contacts:'قائمة المستخدمين', tab_taskmanager:'إدارة المهام',
f_all:'الكل', f_pending:'لم يبدأ', f_inprog:'قيد التنفيذ', f_done:'مكتمل',
btn_start:'▶ ابدأ', btn_done:'✓ تم الإنجاز', btn_reopen:'↩ إعادة فتح', btn_reset:'⟲ لم يبدأ',
lbl_start:'بداية', lbl_end:'نهاية',
lbl_attach:'📎 المرفقات', btn_upload:'+ رفع ملف', no_attach:'لا توجد مرفقات',
lbl_status:'الحالة:', lbl_note:'أضف ملاحظة…', btn_save_note:'حفظ', lbl_view_only:'👁 للمشاهدة فقط',
lbl_ppl:'👥 المسؤولون عن هذه المهمة', btn_assign:'+ تعيين ▾', no_assignee:'لا يوجد معينون',
st_pending:'لم يبدأ', st_inprog:'قيد التنفيذ', st_done:'مكتمل ✓',
badge_done:'✓ مكتمل', badge_prog:'⟳ جارٍ', badge_pend:'◌ لم يبدأ',
badge_new:'✦ جديد', badge_urgent:'⚡ عاجل',
end_title:'📅 تاريخ الانتهاء', end_sub:'حدد الموعد المتوقع لإنهاء هذه الخطوة',
btn_end_confirm:'تأكيد', btn_end_skip:'تخطي',
notify_title:'📢 إرسال إشعار', btn_skip_notify:'تخطي الإشعار',
last_upd_none:'لم يتم حفظ أي تغييرات بعد'
},
en: {
lang_btn:'عر',
live:'Live', export_btn:'📋 Copy Report', print_btn:'🖨️ Print', share_btn:'🔗 Share',
logout_btn:'Sign Out', logout_confirm:'Sign out?',
lbl_steps:'Steps', lbl_done:'Done', lbl_prog:'In Progress', lbl_pend:'Not Started', lbl_pct:'Step Progress',
tab_overview:'Overview', tab_technical:'Technical', tab_marketing:'Marketing',
tab_admin:'Admin', tab_contacts:'Users', tab_taskmanager:'Manage Tasks',
f_all:'All', f_pending:'Not Started', f_inprog:'In Progress', f_done:'Done',
btn_start:'▶ Start', btn_done:'✓ Complete', btn_reopen:'↩ Reopen', btn_reset:'⟲ Reset',
lbl_start:'Start', lbl_end:'End',
lbl_attach:'📎 Attachments', btn_upload:'+ Upload', no_attach:'No attachments',
lbl_status:'Status:', lbl_note:'Add a note…', btn_save_note:'Save', lbl_view_only:'👁 View only',
lbl_ppl:'👥 Assigned to this task', btn_assign:'+ Assign ▾', no_assignee:'No one assigned',
st_pending:'Not Started', st_inprog:'In Progress', st_done:'Done ✓',
badge_done:'✓ Done', badge_prog:'⟳ In Progress', badge_pend:'◌ Not Started',
badge_new:'✦ New', badge_urgent:'⚡ Urgent',
end_title:'📅 End Date', end_sub:'Set the expected completion date for this step',
btn_end_confirm:'Confirm', btn_end_skip:'Skip',
notify_title:'📢 Send Notification', btn_skip_notify:'Skip Notification',
last_upd_none:'No changes saved yet'
}
};
function t(k){ return (TRANSLATIONS[_lang]||{})[k] || (TRANSLATIONS.ar||{})[k] || k; }
function switchLang(){
_lang = (_lang==='ar') ? 'en' : 'ar';
localStorage.setItem('alum_lang', _lang);
document.documentElement.dir = (_lang==='en') ? 'ltr' : 'rtl';
document.documentElement.lang = _lang;
refreshAfterLang();
}
function applyStaticTranslations(){
// Apply translations to data-i18n elements
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
const txt = t(key);
// Only update if translation exists and is not the raw key
if(txt && txt !== key) el.textContent = txt;
});
// Handle placeholders
document.querySelectorAll('[data-i18n-ph]').forEach(el => {
const key = el.getAttribute('data-i18n-ph');
const txt = t(key);
if(txt && txt !== key) el.placeholder = txt;
});
// Update known labeled elements
const elMap = {
's-total-lbl': t('lbl_steps'),
's-done-lbl': t('lbl_done'),
's-prog-lbl': t('lbl_prog'),
's-pend-lbl': t('lbl_pend'),
};
Object.entries(elMap).forEach(([id, txt]) => {
const el = document.getElementById(id);
if(el && txt) el.textContent = txt;
});
}
function refreshAfterLang(){
applyStaticTranslations();
if(_currentUser){
buildFilters();
['technical','marketing','admin'].forEach(sec=>renderTasks(sec));
renderAllCustomSecs();
updateOverview();
updateCounters();
renderContacts();
renderUsersTable();
renderTaskManager();
}
}
// ═══════════════════════════════════════════════════════════════
// FIREBASE STORAGE LAYER — replace YOUR_* with actual values
// ═══════════════════════════════════════════════════════════════
const FIREBASE_CONFIG = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_PROJECT.firebaseapp.com",
databaseURL: "https://YOUR_PROJECT-default-rtdb.firebaseio.com",
projectId: "YOUR_PROJECT",
storageBucket: "YOUR_PROJECT.appspot.com",
messagingSenderId: "YOUR_SENDER_ID",
appId: "YOUR_APP_ID"
};
let _db = null;
let _fbReady = false;
const LOCAL_ONLY = ['alum_pwds','alum_lang','alum_session'];
function initFirebase(){
if(FIREBASE_CONFIG.apiKey==='YOUR_API_KEY'){
console.warn('Firebase not configured — using localStorage');
return Promise.resolve(false);
}
try{
if(!firebase.apps.length) firebase.initializeApp(FIREBASE_CONFIG);
_db = firebase.database();
_fbReady = true;
console.log('Firebase ✓');
return Promise.resolve(true);
}catch(e){ console.error('Firebase error:',e); return Promise.resolve(false); }
}
function setupFirebaseListeners(){
if(!_fbReady) return;
['alum_v3','alum_contacts','alum_dyn_v2','alum_secs'].forEach(key=>{
_db.ref('dhurwa/'+key).on('value', snap=>{
const val=snap.val();
if(val===null) return;
try{ localStorage.setItem(key,JSON.stringify(val)); }catch(e){}
if(_currentUser) onFirebaseUpdate(key);
});
});
}
function onFirebaseUpdate(key){
if(key==='alum_v3'||key==='alum_dyn_v2'){
['technical','marketing','admin'].forEach(s=>renderTasks(s));
getCustomSecs().forEach(s=>renderTasks(s.key));
updateCounters(); updateOverview(); updateUpd();
} else if(key==='alum_contacts'){
renderContacts(); renderUsersTable();
} else if(key==='alum_secs'){
renderAllCustomSecs();
}
// Flash sync indicator
const dot=document.querySelector('.live-dot');
if(dot){dot.style.color='#C1392B';setTimeout(()=>dot.style.color='',600);}
}
function fbWrite(key,value){
try{ localStorage.setItem(key,JSON.stringify(value)); }catch(e){}
if(_fbReady && !LOCAL_ONLY.includes(key)){
_db.ref('dhurwa/'+key).set(value).catch(e=>console.error('fbWrite:',e));
}
}
// ── Storage ──────────────────────────────────────────────────
function gs(){try{const s=localStorage.getItem('alum_v3');return s?JSON.parse(s):{}}catch(e){return {}}}
function ss(state){fbWrite('alum_v3',state);updateUpd();}
function getContacts(){
try{const s=localStorage.getItem('alum_contacts');return s?JSON.parse(s):JSON.parse(JSON.stringify(DEF_CONTACTS));}
catch(e){return JSON.parse(JSON.stringify(DEF_CONTACTS));}
}
function saveContacts(c){fbWrite('alum_contacts',c);}
function getDynTasks(){
try{const s=localStorage.getItem('alum_dyn_v2');return s?JSON.parse(s):[];}catch(e){return [];}
}
function saveDynTasks(t){fbWrite('alum_dyn_v2',t);}
function getCustomSecs(){
try{const s=localStorage.getItem('alum_secs');return s?JSON.parse(s):[];}catch(e){return [];}
}
function saveCustomSecs(s){fbWrite('alum_secs',s);}
function getPasswords(){
try{const s=localStorage.getItem('alum_pwds');return s?JSON.parse(s):{};}catch(e){return {};}
}
function savePasswords(p){localStorage.setItem('alum_pwds',JSON.stringify(p));}
function getTaskSt(id){return gs()[id]||{status:'pending',note:'',people:[],stepDates:{},stepPeople:{}};}
function setTaskStatus(id,status){const s=gs();if(!s[id])s[id]={status:'pending',note:'',people:[],stepDates:{},stepPeople:{}};s[id].status=status;ss(s);}
function setTaskNote(id,note){const s=gs();if(!s[id])s[id]={status:'pending',note:'',people:[],stepDates:{},stepPeople:{}};s[id].note=note;ss(s);}
function getTaskPeople(id){const s=gs();if(s[id]&&s[id].people!==undefined)return s[id].people;const all=[...tasks.technical,...tasks.marketing,...tasks.admin];const t=all.find(t=>t.id===id);return t?t.resp.split(/[&,،]/).map(n=>n.trim()).filter(Boolean):[];}
function saveTaskPeople(id,p){const s=gs();if(!s[id])s[id]={status:'pending',note:'',people:[],stepDates:{},stepPeople:{}};s[id].people=p;ss(s);}
function getStepDate(id,i,f){const s=gs();return(s[id]&&s[id].stepDates&&s[id].stepDates[i]&&s[id].stepDates[i][f])||'';}
function getStepAttachments(taskId,si){
const s=gs();
return (s[taskId]&&s[taskId].stepAttach&&s[taskId].stepAttach[si])||[];
}
function saveStepAttachment(taskId,si,attach){
const s=gs();
if(!s[taskId])s[taskId]={status:'pending',note:'',people:[],stepDates:{},stepPeople:{},stepStatus:{},stepAttach:{}};
if(!s[taskId].stepAttach)s[taskId].stepAttach={};
if(!s[taskId].stepAttach[si])s[taskId].stepAttach[si]=[];
s[taskId].stepAttach[si].push(attach);
ss(s);
}
function deleteStepAttachment(taskId,si,aidx){
const s=gs();
if(!s[taskId]||!s[taskId].stepAttach||!s[taskId].stepAttach[si])return;
s[taskId].stepAttach[si].splice(aidx,1);
ss(s);
rerenderStepPpl(taskId,si); // reuse rerender to refresh
rerenderStepAttach(taskId,si);
}
function rerenderStepAttach(taskId,si){
const el=document.getElementById('satt-'+taskId+'-'+si);
if(el)el.innerHTML=renderAttachList(taskId,si);
}
function renderAttachList(taskId,si){
const atts=getStepAttachments(taskId,si);
if(!atts.length)return t('no_attach');
return atts.map((a,aidx)=>{
const isImg=a.type&&a.type.startsWith('image/');
const thumb=isImg?`
`:
`
${a.name.endsWith('.pdf')?'📄':a.name.endsWith('.xlsx')||a.name.endsWith('.xls')?'📊':a.name.endsWith('.docx')||a.name.endsWith('.doc')?'📝':'📎'}
`;
return `
`;
}).join('');
}
function handleStepFile(taskId,si,input){
const files=Array.from(input.files);
files.forEach(file=>{
if(file.size>5*1024*1024){alert(`${file.name}: الملف أكبر من 5MB`);return;}
const reader=new FileReader();
reader.onload=e=>{
const sizeStr=file.size>1024*1024?(file.size/1024/1024).toFixed(1)+'MB':(file.size/1024).toFixed(0)+'KB';
saveStepAttachment(taskId,si,{name:file.name,type:file.type,data:e.target.result,size:sizeStr,date:new Date().toLocaleDateString('ar-SA')});
rerenderStepAttach(taskId,si);
};
reader.readAsDataURL(file);
});
input.value='';
}
function getStepStatus(taskId,si){
const s=gs();
return (s[taskId]&&s[taskId].stepStatus&&s[taskId].stepStatus[si])||'pending';
}
function setStepStatus(taskId,si,status){
const s=gs();
if(!s[taskId])s[taskId]={status:'pending',note:'',people:[],stepDates:{},stepPeople:{},stepStatus:{}};
if(!s[taskId].stepStatus)s[taskId].stepStatus={};
s[taskId].stepStatus[si]=status;
ss(s);
}
function isMyStep(taskId,si){
if(!_currentUser)return false;
const perm=getUserPerm();
if(perm==='admin'||perm==='viewer')return true;
const people=getStepPeople(taskId,si);
return people.includes(_currentUser.name);
}
function setStepDate(id,i,f,v){const s=gs();if(!s[id])s[id]={status:'pending',note:'',people:[],stepDates:{},stepPeople:{}};if(!s[id].stepDates)s[id].stepDates={};if(!s[id].stepDates[i])s[id].stepDates[i]={};s[id].stepDates[i][f]=v;ss(s);}
function getStepPeople(id,i){const s=gs();return(s[id]&&s[id].stepPeople&&s[id].stepPeople[i])||[];}
function saveStepPeople(id,i,p){const s=gs();if(!s[id])s[id]={status:'pending',note:'',people:[],stepDates:{},stepPeople:{}};if(!s[id].stepPeople)s[id].stepPeople={};s[id].stepPeople[i]=p;ss(s);}
// ── Contacts ─────────────────────────────────────────────────
const DEF_CONTACTS=[
{id:'c1',name:'أحمد كمال',email:'',wa:'',role:'مسؤول تقني',photo:'',perm:'member'},
{id:'c2',name:'عمار',email:'',wa:'',role:'مسؤول تقني',photo:'',perm:'member'},
{id:'c3',name:'البرعي',email:'',wa:'',role:'مسؤول تقني',photo:'',perm:'member'},
{id:'c4',name:'راكان العوبلي',email:'',wa:'',role:'مسؤول تسويقي',photo:'',perm:'member'},
{id:'c5',name:'محمد',email:'',wa:'',role:'مسؤول إداري',photo:'',perm:'member'},
{id:'c6',name:'عبدالعزيز',email:'',wa:'',role:'مسؤول إداري',photo:'',perm:'member'},
];
function addContact(){
const name=document.getElementById('nc-name').value.trim();
const email=document.getElementById('nc-email').value.trim();
const wa=document.getElementById('nc-wa').value.trim().replace(/\D/g,'');
if(!name){alert('الاسم مطلوب');return;}
const c=getContacts();
if(c.find(x=>x.name===name)){alert('هذا الاسم موجود مسبقاً');return;}
const perm=document.getElementById('nc-perm')?document.getElementById('nc-perm').value:'member';
c.push({id:'c'+Date.now(),name,email,wa,role:'',photo:'',perm});
saveContacts(c);
document.getElementById('nc-name').value='';
document.getElementById('nc-email').value='';
document.getElementById('nc-wa').value='';
renderContacts();
}
function deleteContact(id){
if(!confirm('حذف هذا الشخص؟'))return;
saveContacts(getContacts().filter(c=>c.id!==id));
renderContacts();
}
let _editId=null;
function startEdit(id){_editId=id;renderContacts();setTimeout(()=>{const el=document.getElementById('ei-n-'+id);if(el)el.focus();},80);}
function cancelEdit(){_editId=null;renderContacts();}
function saveEdit(id){
const fld=k=>((document.getElementById('ei-'+k+'-'+id)||{}).value||'').trim();
const c=getContacts();const idx=c.findIndex(x=>x.id===id);
if(idx>=0){
c[idx].name=fld('n')||c[idx].name;
c[idx].role=fld('r');
c[idx].email=fld('e');
c[idx].wa=fld('w').replace(/\D/g,'');
}
saveContacts(c);_editId=null;renderContacts();
}
function triggerPhotoUpload(id){
const inp=document.getElementById('photo-inp-'+id);
if(inp)inp.click();
}
function handlePhotoUpload(id,input){
const file=input.files[0];
if(!file)return;
if(file.size>2*1024*1024){alert('الصورة كبيرة جداً (الحد الأقصى 2MB)');return;}
const reader=new FileReader();
reader.onload=function(e){
const c=getContacts();const idx=c.findIndex(x=>x.id===id);
if(idx>=0)c[idx].photo=e.target.result;
saveContacts(c);renderContacts();
setTimeout(()=>{startEdit(id);},50);
};
reader.readAsDataURL(file);
}
function permLabel(p){
if(p==='admin') return '
👑 مدير ';
if(p==='viewer') return '
👁 مشاهد ';
return '
👤 عضو ';
}
function renderUsersTable(){
const tbody = document.getElementById('users-tbody');
if(!tbody) return;
const contacts = getContacts();
const pwds = getPasswords();
tbody.innerHTML = contacts.map(c => {
const col=avColor(c.name), ini=avIni(c.name);
const av = c.photo
? `
`
: `
${ini}
`;
const hasPwd = !!pwds[c.id];
const perm = c.perm || 'member';
const waBtn = c.wa ? `
💬 ` : '
— ';
const emBtn = c.email ? `
✉ ` : '';
return `
${c.name}
${c.role ? `
${c.role}
` : ''}
${c.role||'—'}
👤 عضو
👁 مشاهد
👑 مدير
حفظ
✓
${waBtn}${emBtn}
✏️
🗑
`;
}).join('');
}
function updatePerm(id, perm){
const c = getContacts();
const idx = c.findIndex(x=>x.id===id);
if(idx>=0){ c[idx].perm = perm; saveContacts(c); }
}
function renderContacts(){
const grid=document.getElementById('contacts-grid');
if(!grid)return;
const c=getContacts();
if(!c.length){grid.innerHTML='
لا يوجد أشخاص.
';return;}
grid.innerHTML=c.map(x=>{
const col=avColor(x.name),ini=avIni(x.name);
const photoEl=x.photo
?`
`
:`
${ini}
`;
if(_editId===x.id){
const photoEdit=x.photo
?`
`
:`
${ini}
`;
return `
`;
}
const waBtn=x.wa?`
💬 `:'';
const emBtn=x.email?`
✉ `:'';
return `
${x.name}
${x.role?`
${x.role}
`:''}
${x.email?`
✉ ${x.email}
`:'
⚠ لا يوجد إيميل
'}
${x.wa?`
📱 +${x.wa}
`:'
⚠ لا يوجد واتساب
'}
${waBtn}${emBtn}✏️ 🗑
`;
}).join('');
}
// ── Avatar helpers ────────────────────────────────────────────
const AV_COLORS=['#3b82f6','#22c55e','#f59e0b','#a78bfa','#2dd4bf','#ef4444','#ec4899','#f97316'];
function avColor(n){let h=0;for(let c of n)h=(h*31+c.charCodeAt(0))&0xffffffff;return AV_COLORS[Math.abs(h)%AV_COLORS.length];}
function avIni(n){const p=n.trim().split(/\s+/);return p.length>=2?p[0][0]+p[1][0]:n.slice(0,2);}
// ── Dropdown ──────────────────────────────────────────────────
let _openDD=null;
document.addEventListener('click',()=>{if(_openDD){_openDD.classList.remove('open');_openDD=null;}});
function toggleDD(id,e){
e.stopPropagation();
const m=document.getElementById(id);
if(!m)return;
if(_openDD&&_openDD!==m){_openDD.classList.remove('open');}
m.classList.toggle('open');
_openDD=m.classList.contains('open')?m:null;
}
function getSecForTask(taskId){
if(taskId.startsWith('t')) return 'technical';
if(taskId.startsWith('m')) return 'marketing';
if(taskId.startsWith('dyn')){
const dt=getDynTasks().find(t=>t.id===taskId);
return dt?dt.sec:'admin';
}
return 'admin';
}
function reRenderSection(taskId, forceAll){
const sec=getSecForTask(taskId);
const wasOpen=document.getElementById('card-'+taskId)?.classList.contains('open');
let filter='all';
if(!forceAll){
const activeBtn=document.querySelector('#f-'+sec+' .filter-btn.active');
filter=activeBtn?activeBtn.dataset.filter:'all';
} else {
// Reset filter buttons to 'all'
document.querySelectorAll('#f-'+sec+' .filter-btn').forEach(b=>{
b.classList.toggle('active', b.dataset.filter==='all');
});
}
renderTasks(sec,filter);
if(wasOpen){const card=document.getElementById('card-'+taskId);if(card)card.classList.add('open');}
updateCounters();updateOverview();
}
function buildDDMenuForTask(taskId){
const c=getContacts();
const mid='td-'+taskId;
if(!c.length)return '
أضف أشخاصاً من قائمة المستخدمين
';
return c.map(x=>{
const col=avColor(x.name),ini=avIni(x.name);
const av=x.photo
? `
`
: `
${ini} `;
return `
${av}${x.name} ${x.role?`${x.role} `:''}
`;
}).join('');
}
function buildDDMenuForStep(taskId,si){
const c=getContacts();
if(!c.length)return '
أضف أشخاصاً من قائمة المستخدمين
';
return c.map(x=>{
const col=avColor(x.name),ini=avIni(x.name);
const av=x.photo
? `
`
: `
${ini} `;
return `
${av}${x.name} ${x.role?`${x.role} `:''}
`;
}).join('');
}
// Keep old buildDDMenu for any legacy calls
function buildDDMenu(menuId,onSelectFn){ return buildDDMenuForTask(menuId.replace('td-','')); }
function addTaskPersonClick(el,e){
e.stopPropagation();
if(_openDD){_openDD.classList.remove('open');_openDD=null;}
const taskId=el.dataset.taskid;
const name=el.dataset.name;
if(!taskId||!name)return;
const p=getTaskPeople(taskId);
if(!p.includes(name)){p.push(name);saveTaskPeople(taskId,p);}
refreshTaskPpl(taskId);
}
function addStepPersonClick(el,e){
e.stopPropagation();
if(_openDD){_openDD.classList.remove('open');_openDD=null;}
const taskId=el.dataset.taskid;
const si=parseInt(el.dataset.si);
const name=el.dataset.name;
if(!taskId||!name)return;
const p=getStepPeople(taskId,si);
if(!p.includes(name)){p.push(name);saveStepPeople(taskId,si,p);}
rerenderStepPpl(taskId,si);
}
// ── Task people ───────────────────────────────────────────────
function renderTaskPplSection(taskId){
const people=getTaskPeople(taskId);
const perm=getUserPerm();
const isAdm=perm==='admin';
// Admin chips — with remove button
const adminChips=people.map(p=>{
const col=avColor(p),ini=avIni(p);
const pj=p.replace(/"/g,'"');
return `
`;
}).join('');
// Read-only chips for non-admin
const readChips=people.map(p=>{
const col=avColor(p),ini=avIni(p);
return `
`;
}).join('');
const mid='td-'+taskId;
const notify=buildNotifyBar(taskId);
if(!isAdm){
return `
${readChips||('— ')}
${notify}`;
}
const menu=buildDDMenuForTask(taskId);
return `
${adminChips||(''+t('no_assignee')+' ')}
${notify}`;
}
function addTaskPerson(taskId,name,e){
if(e)e.stopPropagation();
if(_openDD){_openDD.classList.remove('open');_openDD=null;}
const p=getTaskPeople(taskId);
if(!p.includes(name)){p.push(name);saveTaskPeople(taskId,p);}
refreshTaskPpl(taskId);
}
function removeTaskPerson(taskId,name,e){
if(e)e.stopPropagation();
const p=getTaskPeople(taskId).filter(x=>x!==name);
saveTaskPeople(taskId,p);
refreshTaskPpl(taskId);
}
function refreshTaskPpl(taskId){
const c=document.getElementById('tpc-'+taskId);
if(c&&c.parentElement){
const sec=c.closest('.task-ppl-sec');
if(sec)sec.querySelector('.task-chips').parentElement.innerHTML=renderTaskPplSection(taskId);
}
const b=document.querySelector('#card-'+taskId+' .b-resp');
if(b)b.textContent=getTaskPeople(taskId).join(' & ')||'—';
}
// ── Notify bar ────────────────────────────────────────────────
function buildNotifyBar(taskId){
const people=getTaskPeople(taskId);
const contacts=getContacts();
const assigned=people.map(n=>contacts.find(c=>c.name===n)).filter(Boolean);
if(!assigned.length)return '';
const waLinks=assigned.filter(c=>c.wa).map(c=>`
💬 ${c.name} `).join('');
const emails=assigned.filter(c=>c.email).map(c=>c.email).join(',');
const emBtn=emails?`
✉ إشعار بالإيميل `:'';
if(!waLinks&&!emBtn)return '';
return `
إشعار: ${waLinks}${emBtn}
`;
}
// ── Step people ───────────────────────────────────────────────
function renderStepPpl(taskId,si){
const people=getStepPeople(taskId,si);
const perm=getUserPerm();
const isAdm=perm==='admin';
if(!isAdm){
// Read-only: just show chips, no add/remove
return people.map(p=>{
const col=avColor(p),ini=avIni(p);
return `
${ini} ${p}`;
}).join('')||'
— ';
}
const chips=people.map(p=>{
const col=avColor(p),ini=avIni(p);
const pj=p.replace(/"/g,'"');
return `
${ini} ${p}× `;
}).join('');
const mid='sd-'+taskId+'-'+si;
const menu=buildDDMenuForStep(taskId,si);
return `${chips}
+ شخص ▾ `;
}
function addStepPpl(taskId,si,name,e){
if(e)e.stopPropagation();
if(_openDD){_openDD.classList.remove('open');_openDD=null;}
const p=getStepPeople(taskId,si);
if(!p.includes(name)){p.push(name);saveStepPeople(taskId,si,p);}
rerenderStepPpl(taskId,si);
}
function removeStepPpl(taskId,si,name,e){
if(e)e.stopPropagation();
saveStepPeople(taskId,si,getStepPeople(taskId,si).filter(x=>x!==name));
rerenderStepPpl(taskId,si);
}
function rerenderStepPpl(taskId,si){
const el=document.getElementById('sppl-'+taskId+'-'+si);
if(el)el.innerHTML=renderStepPpl(taskId,si);
}
// ── Render tasks ──────────────────────────────────────────────
function stBadge(s){
if(s==='done')return `
${t('badge_done')} `;
if(s==='inprog')return `
${t('badge_prog')} `;
return `
${t('badge_pend')} `;
}
function computeTaskStatus(taskId, steps){
// Derive task status from step statuses
if(!steps||!steps.length) return 'pending';
const statuses = steps.map((_,si) => getStepStatus(taskId, si));
const done = statuses.every(s => s==='done');
const pend = statuses.every(s => s==='pending');
const prog = statuses.some(s => s==='inprog') || (!done && !pend);
if(done) return 'done';
if(pend) return 'pending';
return 'inprog';
}
function isMyTask(task){
if(!_currentUser) return false;
const perm = getUserPerm();
if(perm === 'admin' || perm === 'viewer') return true;
const name = _currentUser.name;
// 1. Task-level assignment (dفتر العناوين / people array)
const taskPeople = getTaskPeople(task.id);
if(taskPeople.includes(name)) return true;
// 2. Original resp string
if(task.resp && task.resp.split(/[&,،]/).map(n=>n.trim()).includes(name)) return true;
// 3. Step-level assignment
const state=gs();
const sp=(state[task.id]&&state[task.id].stepPeople)||{};
return Object.values(sp).some(arr=>arr.includes(name));
}
function canEdit(task){
if(!_currentUser) return false;
const perm = getUserPerm();
if(perm === 'admin') return true;
if(perm === 'viewer') return false; // viewers cannot edit
// member: can only edit their own tasks
const people = getTaskPeople(task.id);
return people.includes(_currentUser.name) ||
task.resp.split(/[&,،]/).map(n=>n.trim()).includes(_currentUser.name);
}
function renderTasks(sec,filter){
filter=filter||'all';
const container=document.getElementById(sec+'-tasks');
if(!container){ console.warn('No container for sec:',sec); return; }
const state=gs();
const perm=getUserPerm();
const allSecTasks=getAllTasks(sec);
const html=allSecTasks.map(task=>{
// Member: hide tasks where no steps are assigned to them
if(perm==='member'){
const hasMyStep=(task.steps||[]).some((_,si)=>isMyStep(task.id,si));
if(!hasMyStep) return '';
} else if(perm==='none'){
return '';
}
const ts=state[task.id]||{status:'pending',note:''};
// Auto-derive status from steps
const autoStatus=computeTaskStatus(task.id, task.steps);
ts.status=autoStatus;
if(filter!=='all'){
if(perm==='member'){
// Member: show task if any of THEIR steps match the filter
const myStepIndices=(task.steps||[]).map((_,si)=>si).filter(si=>isMyStep(task.id,si));
if(!myStepIndices.some(si=>getStepStatus(task.id,si)===filter)) return '';
} else {
// Admin/viewer: show task if ANY step matches the filter status
const hasMatchStep=(task.steps||[]).some((_,si)=>getStepStatus(task.id,si)===filter);
if(!hasMatchStep) return '';
}
}
const isDone=ts.status==='done',isProg=ts.status==='inprog';
const stepsHtml=(task.steps||[]).map((step,i)=>{
const myStep=isMyStep(task.id,i);
const stepPerm=getUserPerm();
const isAdminV=stepPerm==='admin';
// Non-admin: skip steps not assigned to them
if(!myStep && stepPerm==='member') return '';
// Filter: hide steps that don't match current filter (for all users)
if(filter!=='all'){
const sSt_check=getStepStatus(task.id,i);
if(stepPerm==='member'){
// Member sees only their steps at the filtered status
if(!myStep || sSt_check!==filter) return '';
} else {
// Admin sees steps at the filtered status
if(sSt_check!==filter) return '';
}
}
const sv=getStepDate(task.id,i,'start'),ev=getStepDate(task.id,i,'end');
const ov=ev&&new Date(ev)
${ssDone?'✓':ssProg?'⟳':(i+1)}
${step}
${canEditStep?(
ssDone
? `
↩ إعادة فتح
⟲ لم يبدأ
`
: ssProg
? `
✓ تم الإنجاز
⟲ لم يبدأ
`
: `
▶ ابدأ `
):''}
${canEditStep?``:''}
${isAdminV?renderStepPpl(task.id,i):getStepPeople(task.id,i).map(p=>{const col=avColor(p),ini=avIni(p);return `${ini} ${p} `;}).join('')||'— '}
${canEditStep?`
${renderAttachList(task.id,i)}
`:''}
`;
}).filter(Boolean).join('');
// If filter active and no steps visible, hide the whole task
if(filter!=='all' && !stepsHtml.trim()) return '';
return `
${isDone?'✓':isProg?'⟳':''}
${task.title}
${task.isNew?'✦ جديد ':''}
${task.isUrgent?'⚡ عاجل ':''}
${stBadge(autoStatus)}
▾
${canEdit(task) ? `
${t('lbl_status')}
${t('st_pending')}
${t('st_inprog')}
${t('st_done')}
` : `
${t('lbl_view_only')}
`}
${t('lbl_ppl')}
${renderTaskPplSection(task.id)}
`;
}).join('');
const isAdmin=_currentUser&&_currentUser.isAdmin;
const myCount=getAllTasks(sec).filter(t=>isMyTask(t)).length;
const trimmedHtml=html.replace(/\s/g,'');
if(!isAdmin && myCount===0){
container.innerHTML=`
✅
لا توجد مهام مسندة إليك في هذا القسم
`;
} else if(!trimmedHtml){
const filterLabels={all:'الكل',pending:'لم يبدأ',inprog:'قيد التنفيذ',done:'مكتمل'};
container.innerHTML=`
🔍
لا توجد مهام بحالة «${filterLabels[filter]||filter}»
`;
} else {
container.innerHTML=html;
}
updateCounters();
}
function toggleCard(id){document.getElementById('card-'+id).classList.toggle('open');}
function chgStatus(id,status,e){
e.stopPropagation();
setTaskStatus(id,status);
// Determine section for dynamic tasks too
let sec='admin';
if(id.startsWith('t')) sec='technical';
else if(id.startsWith('m')) sec='marketing';
else if(id.startsWith('dyn')){
const dt=getDynTasks().find(t=>t.id===id);
sec=dt?dt.sec:'admin';
}
const activeFilter=document.querySelector('#f-'+sec+' .filter-btn.active');
renderTasks(sec,activeFilter?activeFilter.dataset.filter:'all');
updateOverview();
updateCounters();
}
function saveNote(id,e){
e.stopPropagation();
setTaskNote(id,(document.getElementById('nt-'+id)||{}).value||'');
const el=document.getElementById('ns-'+id);if(el){el.style.display='block';setTimeout(()=>el.style.display='none',1800);}
}
function changeStepStatus(taskId,si,status,btn){
const today=new Date().toISOString().split('T')[0];
if(status==='inprog'){
// Set start date to today if not set
const curStart=getStepDate(taskId,si,'start');
if(!curStart){ setStepDate(taskId,si,'start',today); }
setStepStatus(taskId,si,'inprog');
// Show end date picker modal if not set
const curEnd=getStepDate(taskId,si,'end');
if(!curEnd){
reRenderSection(taskId,true);
showEndDateModal(taskId,si);
return;
}
reRenderSection(taskId, true);
sendStepNotification(taskId,si,'start');
} else if(status==='done'){
setStepStatus(taskId,si,'done');
sendStepNotification(taskId,si,'done');
} else if(status==='pending'){
setStepStatus(taskId,si,'pending');
// Clear start and end dates when resetting to pending
setStepDate(taskId,si,'start','');
setStepDate(taskId,si,'end','');
}
reRenderSection(taskId, true);
}
function sendStepNotification(taskId,si,type){
const all=[...tasks.technical,...tasks.marketing,...tasks.admin,...getDynTasks()];
const task=all.find(t=>t.id===taskId);
if(!task)return;
const stepText=(task.steps||[])[si]||'خطوة';
const contacts=getContacts();
const stepPeople=getStepPeople(taskId,si);
const startDate=getStepDate(taskId,si,'start');
const endDate=getStepDate(taskId,si,'end');
let msg='';
if(type==='start'){
msg=`مرحباً،
تم البدء في الخطوة: "${stepText}"
المهمة: ${task.title}
تاريخ البداية: ${startDate||'—'}
تاريخ الانتهاء المتوقع: ${endDate||'—'}
يُرجى المتابعة.`;
} else if(type==='done'){
msg=`✓ تم إنجاز الخطوة: "${stepText}"
المهمة: ${task.title}
تاريخ الإنجاز: ${new Date().toLocaleDateString('ar-SA')}`;
} else if(type==='deadline'){
msg=`⚠️ تنبيه: اقتراب موعد انتهاء الخطوة!
الخطوة: "${stepText}"
المهمة: ${task.title}
الموعد: ${endDate}`;
}
// Collect people to notify: assigned to step + admin contacts
const toNotify=new Set([...stepPeople]);
// Also notify admin
contacts.filter(c=>c.perm==='admin').forEach(c=>toNotify.add(c.name));
const notifyContacts=contacts.filter(c=>toNotify.has(c.name));
const waLinks=notifyContacts.filter(c=>c.wa).map(c=>
`https://wa.me/${c.wa}?text=${encodeURIComponent(msg)}`
);
const emails=notifyContacts.filter(c=>c.email).map(c=>c.email).join(',');
if(waLinks.length===0 && !emails){return;}
// Show notification panel
showNotifyPanel(msg, waLinks, emails, notifyContacts);
}
function showEndDateModal(taskId,si){
// Remove any existing modal
const existing=document.getElementById('end-date-modal');
if(existing)existing.remove();
const modal=document.createElement('div');
modal.id='end-date-modal';
modal.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:600;display:flex;align-items:center;justify-content:center;padding:1rem';
const today=new Date().toISOString().split('T')[0];
modal.innerHTML=`
${t('end_title')}
${t('end_sub')}
${t('btn_end_confirm')}
تخطي
`;
document.body.appendChild(modal);
// Focus the date input
setTimeout(()=>{
const inp=document.getElementById('end-date-inp');
if(inp)inp.focus();
},100);
// Confirm button
document.getElementById('end-date-confirm').onclick=function(){
const inp=document.getElementById('end-date-inp');
const val=inp?inp.value:'';
if(!val){
inp.style.borderColor='var(--red)';
inp.placeholder='يرجى اختيار تاريخ';
return;
}
setStepDate(taskId,si,'end',val);
modal.remove();
reRenderSection(taskId,true);
sendStepNotification(taskId,si,'start');
};
// Close on backdrop click
modal.addEventListener('click',function(e){
if(e.target===modal){ modal.remove(); }
});
}
function showNotifyPanel(msg, waLinks, emailTo, people){
let panel=document.getElementById('notify-panel');
if(!panel){
panel=document.createElement('div');
panel.id='notify-panel';
panel.style.cssText='position:fixed;bottom:80px;left:1.5rem;background:var(--bg2);border:1px solid var(--border2);border-radius:14px;padding:16px;max-width:320px;z-index:400;box-shadow:0 8px 32px rgba(0,0,0,.5)';
document.body.appendChild(panel);
}
const waButtons=waLinks.map((url,i)=>
`💬 ${people.filter(c=>c.wa)[i]?.name||''} `
).join('');
const emailBtn=emailTo?`✉ إيميل `:'';
panel.innerHTML=`
${t('notify_title')}
✕
${msg}
${waButtons}${emailBtn}
${t('btn_skip_notify')} `;
setTimeout(()=>{if(panel.parentElement)panel.remove();},30000);
}
function onDateChange(id,i,field,inp){
setStepDate(id,i,field,inp.value);
const isDone=(gs()[id]||{}).status==='done';
if(field==='start'){
inp.className='step-date-inp'+(inp.value?' s-start':'');
} else {
const ov=inp.value&&new Date(inp.value)ind.classList.remove('show'),1400);}
}
// ── Filter bars ───────────────────────────────────────────────
function buildFilters(){
['technical','marketing','admin'].forEach(sec=>{
const bar=document.getElementById('f-'+sec);
if(!bar)return;
bar.innerHTML=['all','pending','inprog','done'].map((f,i)=>{
const labels=[t('f_all'),t('f_pending'),t('f_inprog'),t('f_done')];
return `${labels[i]} `;
}).join('');
});
}
function applyFilter(sec,filter,btn){
document.querySelectorAll('#f-'+sec+' .filter-btn').forEach(b=>b.classList.remove('active'));
btn.classList.add('active');
renderTasks(sec,filter);
}
// ── Counters & Overview ───────────────────────────────────────
function getStepCounts(){
// Count all steps across all tasks by their step-level status
const state=gs();
const perm=getUserPerm();
const allSecs=getAllSecs();
const allTasks=allSecs.flatMap(s=>getAllTasks(s.key));
let totalSteps=0, doneSteps=0, progSteps=0;
allTasks.forEach(task=>{
(task.steps||[]).forEach((step,si)=>{
// Only count steps relevant to current user
if(perm==='member' && !isMyStep(task.id,si)) return;
totalSteps++;
const st=getStepStatus(task.id,si);
if(st==='done') doneSteps++;
else if(st==='inprog') progSteps++;
});
});
return {total:totalSteps, done:doneSteps, prog:progSteps, pend:totalSteps-doneSteps-progSteps};
}
function updateCounters(){
const counts=getStepCounts();
const pct=counts.total?Math.round(counts.done/counts.total*100):0;
document.getElementById('s-total').textContent=counts.total;
document.getElementById('s-done').textContent=counts.done;
document.getElementById('s-prog').textContent=counts.prog;
document.getElementById('s-pend').textContent=counts.pend;
document.getElementById('s-pct').textContent=pct+'%';
document.getElementById('s-fill').style.width=pct+'%';
}
function updateOverview(){
const state=gs();
const isAdmin=_currentUser&&_currentUser.isAdmin;
const fills=['#C1392B','#C9954A','#9E8A78','#7DB87A','#a78bfa','#2dd4bf'];
const baseSecs=[
{key:'technical',label:'التقني',icon:'⚙️',fill:'#C1392B',resp:'أحمد كمال · عمار · البرعي'},
{key:'marketing',label:'التسويقي',icon:'📊',fill:'#C9954A',resp:'راكان العوبلي',dl:'25 مايو'},
{key:'admin',label:'الإداري',icon:'🏢',fill:'#9E8A78',resp:'محمد · عبدالعزيز · عمار'},
];
const secs=[...baseSecs,...getCustomSecs().map((s,i)=>({key:s.key,label:s.label,icon:'📌',fill:fills[(3+i)%fills.length],resp:''}))];
const grid=document.getElementById('ov-grid');
if(!grid)return;
grid.innerHTML=secs.map(s=>{
const list=getAllTasks(s.key);
// Count steps, not tasks
let totalSteps=0,doneSteps=0,progSteps=0;
list.forEach(task=>{
(task.steps||[]).forEach((_,si)=>{
if(!isAdmin && !isMyStep(task.id,si)) return;
totalSteps++;
const st=getStepStatus(task.id,si);
if(st==='done') doneSteps++;
else if(st==='inprog') progSteps++;
});
});
if(!isAdmin&&totalSteps===0)return '';
const pct=totalSteps?Math.round(doneSteps/totalSteps*100):0;
return `
${s.icon}
${s.label}
${s.resp}${s.dl?` · ${s.dl} `:''}
${pct}%
${totalSteps} خطوة
${doneSteps} مكتملة
${progSteps} جارية
`;
}).join('');
}
// ── Tabs ──────────────────────────────────────────────────────
document.getElementById('tabs-bar').addEventListener('click',function(e){
const btn=e.target.closest('.tab');
if(!btn)return;
const id=btn.dataset.tab;
document.querySelectorAll('.pane').forEach(p=>p.classList.remove('active'));
document.querySelectorAll('.tab').forEach(t=>t.classList.remove('active'));
document.getElementById(id).classList.add('active');
btn.classList.add('active');
if(id==='contacts'){ renderContacts(); renderUsersTable(); }
if(id==='taskmanager') renderTaskManager();
});
// ── Share / Export ────────────────────────────────────────────
function doShare(){
const url=window.location.href;
if(navigator.clipboard)navigator.clipboard.writeText(url).then(()=>alert('✓ تم نسخ الرابط!'));
else prompt('الرابط:',url);
}
function doExport(){
const state=gs();
const lines=['تقرير ذُروة — الألمنيوم بروفيل','='.repeat(38),''];
['technical','marketing','admin'].forEach(sec=>{
const labels={technical:'التقني',marketing:'التسويقي',admin:'الإداري'};
lines.push('── '+labels[sec]+' ──');
tasks[sec].forEach(t=>{
const ts=state[t.id]||{};
const sl=ts.status==='done'?'✓':ts.status==='inprog'?'⟳':'◌';
lines.push(` ${sl} ${t.title} (${t.resp})`);
if(ts.note)lines.push(` ملاحظة: ${ts.note}`);
});
lines.push('');
});
if(navigator.clipboard)navigator.clipboard.writeText(lines.join('\n')).then(()=>alert('✓ تم نسخ التقرير!'));
}
function updateUpd(){
const el=document.getElementById('last-upd');
if(el)el.textContent='آخر تحديث: '+new Date().toLocaleString('ar-SA',{dateStyle:'medium',timeStyle:'short'});
}
// ═══════════════════════════════════════════════════════════
// AUTH SYSTEM
// ═══════════════════════════════════════════════════════════
const ADMIN_USER = 'admin';
let _currentUser = null; // { name, isAdmin }
function hashPwd(p){
// Simple consistent hash using btoa
try { return btoa(p); } catch(e) { return btoa(unescape(encodeURIComponent(p))); }
}
const ADMIN_PASS_HASH = hashPwd('admin123');
function doLogin(){
const user = document.getElementById('li-user').value.trim();
const pass = document.getElementById('li-pass').value;
const errEl = document.getElementById('li-err');
errEl.classList.remove('show');
if(!user || !pass){ errEl.textContent='يرجى إدخال الاسم وكلمة المرور'; errEl.classList.add('show'); return; }
// Check admin
if(user === ADMIN_USER){
if(hashPwd(pass) === ADMIN_PASS_HASH){
_currentUser = { name:'admin', displayName:'المدير', isAdmin:true };
onLoginSuccess();
} else {
errEl.textContent='كلمة مرور المدير غير صحيحة';
errEl.classList.add('show');
}
return;
}
// Check member
const contacts = getContacts();
const member = contacts.find(c => c.name === user);
if(!member){ errEl.textContent='اسم المستخدم غير موجود'; errEl.classList.add('show'); return; }
const pwds = getPasswords();
if(!pwds[member.id]){ errEl.textContent='لم يتم تعيين كلمة مرور لهذا المستخدم بعد — تواصل مع المدير'; errEl.classList.add('show'); return; }
if(hashPwd(pass) !== pwds[member.id]){ errEl.textContent='كلمة المرور غير صحيحة'; errEl.classList.add('show'); return; }
_currentUser = { name: member.name, displayName: member.name, isAdmin:false, memberId: member.id, photo: member.photo||'' };
onLoginSuccess();
}
function onLoginSuccess(){
// Save session
sessionStorage.setItem('alum_session', JSON.stringify(_currentUser));
// Hide login screen
document.getElementById('login-screen').style.display='none';
// Show app
document.querySelector('.site-header').style.visibility='visible';
document.querySelector('.summary-strip').style.visibility='visible';
document.querySelector('.main').style.visibility='visible';
// Update header badge
updateUserBadge();
// If not admin, hide contacts add-form and restrict views
applyPermissions();
// Init app
initApp();
}
function updateUserBadge(){
if(!_currentUser) return;
const av = document.getElementById('user-badge-av');
const nm = document.getElementById('user-badge-name');
const col = _currentUser.isAdmin ? '#C1392B' : avColor(_currentUser.name);
const ini = _currentUser.isAdmin ? '👑' : avIni(_currentUser.name);
if(_currentUser.photo){
av.innerHTML = ` `;
} else {
av.style.background = col+'22'; av.style.color = col;
av.textContent = ini;
}
nm.textContent = _currentUser.isAdmin ? 'المدير' : _currentUser.displayName;
// Set welcome banner
const wn = document.getElementById('welcome-name');
if(wn) wn.textContent = _currentUser.isAdmin ? '' : `مرحباً، ${_currentUser.displayName} 👋`;
}
function getUserPerm(){
if(!_currentUser) return 'none';
if(_currentUser.isAdmin) return 'admin';
// Check contacts for perm field
const c = getContacts().find(x=>x.name===_currentUser.name);
return (c && c.perm) || 'member';
}
function applyPermissions(){
const perm = getUserPerm();
const isAdmin = perm === 'admin';
const isMember = perm === 'member';
// ── Admin-only UI elements ────────────────────────────────────
const addForm = document.querySelector('.add-cc-form');
if(addForm) addForm.style.display = isAdmin ? '' : 'none';
const pm = document.getElementById('pwd-manager');
if(pm) pm.style.display = isAdmin ? '' : 'none';
const tmTab = document.getElementById('tab-taskmanager');
if(tmTab) tmTab.style.display = isAdmin ? '' : 'none';
const fab = document.getElementById('fab-add');
if(fab) fab.classList.toggle('visible', isAdmin);
if(isAdmin){
// ── ADMIN: show EVERYTHING ────────────────────────────────
// Restore all tabs visibility
document.querySelectorAll('.tab').forEach(t => t.style.display = '');
// Keep task-manager tab visible (already set above)
// Keep contacts visible for admin
// Restore all section panes visibility
document.querySelectorAll('.pane').forEach(p => p.style.display = '');
// Overview tab active by default if nothing active
const activePaneExists = document.querySelector('.pane.active');
if(!activePaneExists){
const ovPane = document.getElementById('overview');
if(ovPane) ovPane.classList.add('active');
const ovTab = document.querySelector('[data-tab="overview"]');
if(ovTab) ovTab.classList.add('active');
}
// Hide welcome banner
const banner = document.getElementById('my-tasks-banner');
if(banner) banner.style.display = 'none';
} else if(isMember){
// ── MEMBER: show only their sections & steps ──────────────
// Hide overview tab + pane
const ovTab = document.querySelector('[data-tab="overview"]');
if(ovTab) ovTab.style.display = 'none';
const ovPane = document.getElementById('overview');
if(ovPane){ ovPane.style.display = 'none'; ovPane.classList.remove('active'); }
// Hide contacts + taskmanager tabs
const cTab = document.querySelector('[data-tab="contacts"]');
if(cTab) cTab.style.display = 'none';
// Show only sections where user has assigned steps
const allSecs = getAllSecs();
let firstVisibleTab = null;
allSecs.forEach(s => {
const tab = document.querySelector(`[data-tab="${s.key}"]`);
const hasMySteps = getAllTasks(s.key).some(task =>
(task.steps||[]).some((_, si) => isMyStep(task.id, si))
);
if(tab){
tab.style.display = hasMySteps ? '' : 'none';
if(hasMySteps && !firstVisibleTab) firstVisibleTab = s.key;
}
});
// Navigate to first section with steps
document.querySelectorAll('.pane').forEach(p=>{ p.classList.remove('active'); });
document.querySelectorAll('.tab').forEach(t=>t.classList.remove('active'));
if(firstVisibleTab){
const pane = document.getElementById(firstVisibleTab);
if(pane){ pane.classList.add('active'); pane.style.display=''; }
const btn = document.querySelector(`[data-tab="${firstVisibleTab}"]`);
if(btn) btn.classList.add('active');
}
// Welcome banner
const banner = document.getElementById('my-tasks-banner');
if(banner) banner.style.display = 'flex';
} else {
// ── VIEWER: see all, edit nothing ────────────────────────
document.querySelectorAll('.tab').forEach(t => t.style.display = '');
const cTab2 = document.querySelector('[data-tab="contacts"]');
if(cTab2) cTab2.style.display = 'none';
const tmTab2 = document.getElementById('tab-taskmanager');
if(tmTab2) tmTab2.style.display = 'none';
const banner = document.getElementById('my-tasks-banner');
if(banner) banner.style.display = 'none';
}
}
function doLogout(){
if(!confirm(t('logout_confirm'))) return;
sessionStorage.removeItem('alum_session');
_currentUser = null;
document.getElementById('li-user').value='';
document.getElementById('li-pass').value='';
document.getElementById('li-err').classList.remove('show');
document.getElementById('login-screen').style.display='flex';
document.querySelector('.site-header').style.visibility='hidden';
document.querySelector('.summary-strip').style.visibility='hidden';
document.querySelector('.main').style.visibility='hidden';
}
function checkSession(){
try{
const s = sessionStorage.getItem('alum_session');
if(s){ _currentUser = JSON.parse(s); onLoginSuccess(); return true; }
} catch(e){}
return false;
}
// Restrict task rendering based on user
function canEditTask(task){
if(!_currentUser) return false;
if(_currentUser.isAdmin) return true;
const people = getTaskPeople(task.id);
return people.includes(_currentUser.name);
}
// ── Password manager ─────────────────────────────────────────
function renderPwdManager(){
const rows = document.getElementById('pwd-rows');
if(!rows) return;
const contacts = getContacts();
const pwds = getPasswords();
rows.innerHTML = contacts.map(c => {
const col=avColor(c.name), ini=avIni(c.name);
const av = c.photo
? ``
: `${ini}
`;
const hasPass = !!pwds[c.id];
return `
${av}
${c.name}
حفظ
✓
`;
}).join('');
}
function setPwd(contactId){
const inp = document.getElementById('pwd-'+contactId);
if(!inp || !inp.value.trim()){ alert('أدخل كلمة مرور'); return; }
const pwds = getPasswords();
pwds[contactId] = hashPwd(inp.value.trim());
savePasswords(pwds);
inp.value='';
inp.placeholder='تم التعيين ✓';
const ok = document.getElementById('pwd-ok-'+contactId);
if(ok){ ok.classList.add('show'); setTimeout(()=>ok.classList.remove('show'),2000); }
}
// ═══════════════════════════════════════════════════════════════
// DYNAMIC TASKS (admin-created)
// ═══════════════════════════════════════════════════════════════
function getAllTasks(sec){
const base = tasks[sec] || [];
const dyn = getDynTasks().filter(t=>t.sec===sec);
return [...base, ...dyn];
}
function renderAllCustomSecs(){
// Render any custom section panes dynamically
const customSecs=getCustomSecs();
customSecs.forEach(s=>{
let pane=document.getElementById(s.key);
if(!pane){
pane=document.createElement('div');
pane.id=s.key;
pane.className='pane';
pane.innerHTML=`
📌 ${s.label}
قسم مخصص
`;
document.querySelector('.main').insertBefore(pane, document.getElementById('taskmanager'));
}
// Add tab if not exists
let tabBtn=document.querySelector(`[data-tab="${s.key}"]`);
if(!tabBtn){
tabBtn=document.createElement('button');
tabBtn.className='tab';
tabBtn.dataset.tab=s.key;
tabBtn.innerHTML=` ${s.label}`;
document.getElementById('tabs-bar').insertBefore(tabBtn, document.getElementById('tab-taskmanager'));
}
});
// Build filters for custom sections
customSecs.forEach(s=>buildFilterForSec(s.key));
// Render tasks for custom sections
customSecs.forEach(s=>renderTasks(s.key));
}
function buildFilterForSec(secKey){
const bar=document.getElementById('f-'+secKey);
if(!bar||bar.innerHTML)return;
bar.innerHTML=['all','pending','inprog','done'].map((f,i)=>{
const labels=[t('f_all'),t('f_pending'),t('f_inprog'),t('f_done')];
return `${labels[i]} `;
}).join('');
}
// ── Task Manager UI ─────────────────────────────────────────
let _editStepCount = 0;
// ── Section management ──────────────────────────────────────
function getAllSecs(){
const base=[
{key:'technical',label:'التقني',icon:'⚙️'},
{key:'marketing',label:'التسويقي',icon:'📊'},
{key:'admin',label:'الإداري',icon:'🏢'},
];
const custom=getCustomSecs().map(s=>({key:s.key,label:s.label,icon:'📌',isCustom:true}));
return [...base,...custom];
}
function getTasksBySec(secKey){
const base=(tasks[secKey]||[]);
const dyn=getDynTasks().filter(t=>t.sec===secKey);
return [...base,...dyn];
}
function populateSecDropdown(selectedKey){
const sel=document.getElementById('modal-task-sec');
if(!sel)return;
const secs=getAllSecs();
sel.innerHTML=`— اختر أو اكتب جديد — `
+secs.map(s=>`${s.icon} ${s.label} `).join('')
+`+ قسم جديد… `;
}
function onSecChange(val){
const inp=document.getElementById('modal-task-sec-new');
if(inp) inp.style.display=(val==='__new__')?'block':'none';
}
function renderTaskManager(){
const secs=getAllSecs();
const container=document.getElementById('tm-sections');
if(!container)return;
container.innerHTML=secs.map(s=>{
const dyn=getDynTasks().filter(t=>t.sec===s.key);
const base=tasks[s.key]||[];
const dynRows=dyn.map(t=>`
${t.title}
${t.steps?t.steps.length:0} خطوات
✏️ تعديل
`).join('') || 'لا توجد مهام مضافة بعد
';
const baseRows=base.map(t=>`
${t.title}
${t.steps?t.steps.length:0} خطوات · أصلية
✏️ تعديل خطوات
`).join('');
return `
${s.icon} ${s.label}
+ مهمة جديدة
${dynRows}
${baseRows}
`;
}).join('');
}
function openTaskModal(sec){
_editStepCount=0;
document.getElementById('modal-task-id').value='';
document.getElementById('modal-task-title').value='';
document.getElementById('modal-mode-title').textContent='➕ إضافة مهمة جديدة';
document.getElementById('modal-del-btn').style.display='none';
populateSecDropdown(sec||'');
const inp=document.getElementById('modal-task-sec-new');
if(inp)inp.style.display='none';
document.getElementById('steps-builder').innerHTML='';
addStepRow(); addStepRow(); addStepRow(); // start with 3 empty steps
document.getElementById('task-modal').classList.remove('hidden');
setTimeout(()=>document.getElementById('modal-task-title').focus(),100);
}
function openEditModal(taskId, type, sec){
_editStepCount=0;
const isBase=(type==='base');
let task;
if(isBase){
task = tasks[sec].find(t=>t.id===taskId);
} else {
task = getDynTasks().find(t=>t.id===taskId);
}
if(!task)return;
document.getElementById('modal-task-id').value = isBase ? 'base:'+sec+':'+taskId : taskId;
document.getElementById('modal-task-title').value = task.title;
document.getElementById('modal-mode-title').textContent='✏️ تعديل المهمة';
document.getElementById('modal-del-btn').style.display = isBase?'none':'inline-flex';
populateSecDropdown(isBase?sec:(task.sec||''));
const inp2=document.getElementById('modal-task-sec-new');
if(inp2)inp2.style.display='none';
// Build step rows
const builder=document.getElementById('steps-builder');
builder.innerHTML='';
const steps = task.steps||[];
// Get step assignments from state
const state=gs();
const sp=(state[taskId]&&state[taskId].stepPeople)||{};
steps.forEach((step,i)=>{
addStepRow(step, sp[i]||[]);
});
if(!steps.length) addStepRow();
document.getElementById('task-modal').classList.remove('hidden');
}
function closeTaskModal(){
document.getElementById('task-modal').classList.add('hidden');
}
function addStepRow(text='', assignees=[]){
const idx=_editStepCount++;
const builder=document.getElementById('steps-builder');
const contacts=getContacts();
const options=contacts.map(c=>`${c.name} `).join('');
const row=document.createElement('div');
row.className='step-builder-row';
row.dataset.idx=idx;
row.innerHTML=`
${idx+1}
المسؤول
${options}
🗑 `;
builder.appendChild(row);
// Set multiple select values
if(assignees.length){
const sel=row.querySelector('select');
Array.from(sel.options).forEach(o=>{ if(assignees.includes(o.value))o.selected=true; });
}
}
function renumberSteps(){
document.querySelectorAll('.step-builder-row').forEach((row,i)=>{
const num=row.querySelector('.step-builder-num');
if(num)num.textContent=i+1;
});
}
function saveTaskModal(){
const taskIdVal=document.getElementById('modal-task-id').value;
const title=document.getElementById('modal-task-title').value.trim();
let sec=document.getElementById('modal-task-sec').value;
const flag=document.getElementById('modal-task-flag').value;
if(!title){alert('يرجى إدخال عنوان المهمة');return;}
// Handle new custom section
if(sec==='__new__'){
const newSecName=(document.getElementById('modal-task-sec-new')||{}).value||'';
if(!newSecName.trim()){alert('أدخل اسم القسم الجديد');return;}
const secKey='custom_'+Date.now();
const customSecs=getCustomSecs();
customSecs.push({key:secKey,label:newSecName.trim()});
saveCustomSecs(customSecs);
sec=secKey;
}
if(!sec){alert('اختر القسم');return;}
// Collect steps + assignees
const rows=document.querySelectorAll('.step-builder-row');
const stepTexts=[];
const stepAssignees={};
rows.forEach((row,i)=>{
const txt=row.querySelector('input').value.trim();
if(!txt)return;
const sel=row.querySelector('select');
const assigned=Array.from(sel.selectedOptions).map(o=>o.value).filter(v=>v);
stepTexts.push(txt);
if(assigned.length) stepAssignees[stepTexts.length-1]=assigned;
});
if(!stepTexts.length){alert('أضف خطوة واحدة على الأقل');return;}
const isBase=taskIdVal.startsWith('base:');
if(isBase){
// Update base task step assignments only (not text)
const parts=taskIdVal.split(':');
const bSec=parts[1], bId=parts[2];
const state=gs();
if(!state[bId])state[bId]={status:'pending',note:'',people:[],stepDates:{},stepPeople:{}};
state[bId].stepPeople=stepAssignees;
localStorage.setItem('alum_v3',JSON.stringify(state));
} else if(taskIdVal){
// Edit existing dynamic task
const dyn=getDynTasks();
const idx=dyn.findIndex(t=>t.id===taskIdVal);
if(idx>=0){
dyn[idx].title=title;
dyn[idx].sec=sec;
dyn[idx].steps=stepTexts;
dyn[idx].isNew=flag==='new';
dyn[idx].isUrgent=flag==='urgent';
saveDynTasks(dyn);
// Save step assignees
const state=gs();
if(!state[taskIdVal])state[taskIdVal]={status:'pending',note:'',people:[],stepDates:{},stepPeople:{}};
state[taskIdVal].stepPeople=stepAssignees;
localStorage.setItem('alum_v3',JSON.stringify(state));
}
} else {
// New dynamic task
const newId='dyn_'+Date.now();
const newTask={id:newId,title,sec,steps:stepTexts,resp:'',isNew:flag==='new',isUrgent:flag==='urgent'};
const dyn=getDynTasks();
dyn.push(newTask);
saveDynTasks(dyn);
// Save step assignees
const state=gs();
state[newId]={status:'pending',note:'',people:[],stepDates:{},stepPeople:stepAssignees};
localStorage.setItem('alum_v3',JSON.stringify(state));
}
closeTaskModal();
renderAllCustomSecs();
renderTaskManager();
renderTasks(sec);
updateOverview();
updateCounters();
ss(gs());
}
function deleteTask(){
const taskId=document.getElementById('modal-task-id').value;
if(!taskId||taskId.startsWith('base:'))return;
if(!confirm('حذف هذه المهمة نهائياً؟'))return;
const dyn=getDynTasks().filter(t=>t.id!==taskId);
saveDynTasks(dyn);
closeTaskModal();
renderTaskManager();
['technical','marketing','admin'].forEach(s=>renderTasks(s));
updateOverview(); updateCounters();
}
// ═══════════════════════════════════════════════════════════
// ADD TASK (Admin only)
// ═══════════════════════════════════════════════════════════
let _atfSec = null;
let _atfSteps = []; // [{text:'', people:[]}]
function showAddTask(sec){
_atfSec = sec;
_atfSteps = [{text:'',people:[]}];
renderAddTaskForm(sec);
document.getElementById('atf-'+sec).style.display='block';
document.getElementById('atf-'+sec).scrollIntoView({behavior:'smooth',block:'nearest'});
}
function hideAddTask(sec){
document.getElementById('atf-'+sec).style.display='none';
_atfSec=null; _atfSteps=[];
}
function renderAddTaskForm(sec){
const form = document.getElementById('atf-'+sec);
if(!form) return;
const stepsHtml = _atfSteps.map((s,i)=>{
const chips = s.people.map(p=>{
const col=avColor(p),ini=avIni(p);
return `${ini} ${p}× `;
}).join('');
const contacts = getContacts();
const ddId = 'atf-dd-'+i;
const menuItems = contacts.map(c=>{
const col=avColor(c.name),ini=avIni(c.name);
const av=c.photo?` `:`${ini} `;
return `${av}${c.name}
`;
}).join('') || 'لا يوجد مستخدمون
';
return `
${i+1}
👤 إسناد ▾
${_atfSteps.length>1?`🗑 `:''}
${chips?`${chips}
`:''}`;
}).join('');
form.innerHTML = `+ إضافة مهمة جديدة
عنوان المهمة *
الخطوات والإسناد
+ إضافة خطوة
حفظ المهمة
إلغاء
`;
}
function atfUpdateStep(i, val){ if(_atfSteps[i]) _atfSteps[i].text=val; }
function atfAddStep(){
_atfSteps.push({text:'',people:[]});
renderAddTaskForm(_atfSec);
// focus new step
setTimeout(()=>{
const inputs = document.querySelectorAll('#atf-steps-'+_atfSec+' .atf-step-inp');
if(inputs.length) inputs[inputs.length-1].focus();
},50);
}
function atfRemoveStep(i){
if(_atfSteps.length<=1) return;
_atfSteps.splice(i,1);
renderAddTaskForm(_atfSec);
}
function atfAddStepPerson(el,e){
e.stopPropagation();
if(_openDD){_openDD.classList.remove('open');_openDD=null;}
const i=parseInt(el.dataset.si);
const name=el.dataset.name;
if(!_atfSteps[i].people.includes(name)) _atfSteps[i].people.push(name);
renderAddTaskForm(_atfSec);
}
function atfRemoveStepPerson(i, name){
if(_atfSteps[i]) _atfSteps[i].people=_atfSteps[i].people.filter(p=>p!==name);
renderAddTaskForm(_atfSec);
}
function atfSave(sec){
const titleEl = document.getElementById('atf-name-'+sec);
const title = titleEl ? titleEl.value.trim() : '';
if(!title){ alert('يرجى كتابة عنوان المهمة'); return; }
const steps = _atfSteps.map(s=>s.text.trim()).filter(Boolean);
if(!steps.length){ alert('أضف خطوة واحدة على الأقل'); return; }
// Generate unique ID
const id = sec[0] + 'x' + Date.now();
const newTask = { id, title, resp:'', steps, isNew:true };
// Add to tasks array
tasks[sec].push(newTask);
// Save step people assignments
const state = gs();
if(!state[id]) state[id]={status:'pending',note:'',people:[],stepDates:{},stepPeople:{}};
_atfSteps.forEach((s,i)=>{
if(s.people.length) state[id].stepPeople[i]=s.people;
});
ss(state);
// Save tasks to localStorage so they persist
saveCustomTasks();
hideAddTask(sec);
renderTasks(sec);
updateOverview();
updateCounters();
alert('✓ تم إضافة المهمة بنجاح!');
}
function saveCustomTasks(){
// Save any added tasks to localStorage
const custom={};
['technical','marketing','admin'].forEach(sec=>{
custom[sec]=tasks[sec].filter(t=>t.id.includes('x')).map(t=>({...t}));
});
localStorage.setItem('alum_dyn_v2',JSON.stringify(custom));
}
function initApp(){
if(_lang==='en'){document.documentElement.dir='ltr';document.documentElement.lang='en';}
applyStaticTranslations();
// Setup real-time listeners after Firebase is ready
setupFirebaseListeners();
document.getElementById('cur-date').textContent=new Date().toLocaleDateString('ar-SA',{dateStyle:'long'});
buildFilters();
renderTasks('technical');
renderTasks('marketing');
renderTasks('admin');
updateOverview();
updateCounters();
renderContacts();
renderUsersTable();
renderAllCustomSecs();
renderTaskManager();
if(localStorage.getItem('alum_v3'))updateUpd();
}
// Hide app until login
document.querySelector('.site-header').style.visibility='hidden';
document.querySelector('.summary-strip').style.visibility='hidden';
document.querySelector('.main').style.visibility='hidden';
// Focus username field
setTimeout(()=>{ const el=document.getElementById('li-user'); if(el)el.focus(); },200);
// Apply initial language direction
if(_lang==='en'){document.documentElement.dir='ltr';document.documentElement.lang='en';}
applyStaticTranslations();
// Init Firebase then restore session
initFirebase().then(() => {
setupFirebaseListeners();
checkSession();
});
l.focus(); ,200);
// Apply initial language direction
if(_lang==='en'){document.documentElement.dir='ltr';document.documentElement.lang='en';}
applyStaticTranslations();
// Try restore session
checkSession();