import { useState, useEffect, useMemo, useCallback, useRef } from "react"; const DEFAULT_TEACHERS = [ {name:"خالد علي العطوي",id:"",specialty:"",jobNum:"",phone:""}, {name:"احمد ظافر الشهري",id:"",specialty:"",jobNum:"",phone:""}, {name:"اسامة عبدالرحمن العتيق",id:"",specialty:"",jobNum:"",phone:""}, {name:"سهل عبدالله البلوي",id:"",specialty:"",jobNum:"",phone:""}, {name:"عمر فالح البلوي",id:"",specialty:"",jobNum:"",phone:""}, {name:"فهد محمد الشامان",id:"",specialty:"",jobNum:"",phone:""}, {name:"طارق حسن حكمي",id:"",specialty:"",jobNum:"",phone:""}, {name:"سلمان عوده البلوي",id:"",specialty:"",jobNum:"",phone:""}, {name:"احمد موسى نوح",id:"",specialty:"",jobNum:"",phone:""}, {name:"عبدالهادي عمر العنزي",id:"",specialty:"",jobNum:"",phone:""}, {name:"سلطان صالح الشمري",id:"",specialty:"",jobNum:"",phone:""}, {name:"صالح شرفاتي العبدلي",id:"",specialty:"",jobNum:"",phone:""}, {name:"سليمان حمد العطوي",id:"",specialty:"",jobNum:"",phone:""}, {name:"محمد عياد البلوي",id:"",specialty:"",jobNum:"",phone:""}, {name:"عبدالله محمد سلامي",id:"",specialty:"",jobNum:"",phone:""}, {name:"محمد مسلم البلوي",id:"",specialty:"",jobNum:"",phone:""} ]; const TASK_KEYS=["morning","waiting","supervision","duty","madrasati"]; const TASK_LABELS=["الطابور الصباحي","الانتظار","الإشراف","المناوبة","منصة مدرستي"]; const DAYS_AR=["الأحد","الإثنين","الثلاثاء","الأربعاء","الخميس"]; const MONTHS_G=["يناير","فبراير","مارس","أبريل","مايو","يونيو","يوليو","أغسطس","سبتمبر","أكتوبر","نوفمبر","ديسمبر"]; const SK="task_reg_v5"; const DEFAULT_SCHOOL={schoolName:"مسلمة بن عبدالملك الابتدائية",region:"الإدارة العامة للتعليم بمنطقة تبوك",principal:"صالح فريج الحويطي",vice:"سعد عتيق العطوي",gender:"male"}; const GL={male:{principal:"مدير المدرسة",vice:"وكيل المدرسة",teacher:"المعلم",teachers:"المعلمين"},female:{principal:"مديرة المدرسة",vice:"وكيلة المدرسة",teacher:"المعلمة",teachers:"المعلمات"}}; function dk(d){return`${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`} function fmtD(d){return`${d.getDate()} ${MONTHS_G[d.getMonth()]} ${d.getFullYear()}م`} function dayN(d){return["الأحد","الإثنين","الثلاثاء","الأربعاء","الخميس","الجمعة","السبت"][d.getDay()]} function dayI(d){return d.getDay()} function wkD(b){const d=new Date(b),diff=d.getDate()-d.getDay();return Array.from({length:5},(_,i)=>{const x=new Date(d);x.setDate(diff+i);return x})} function mKey(d){return`${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}`} function getWD(y,m){const days=[],dim=new Date(y,m+1,0).getDate();for(let i=1;i<=dim;i++){const d=new Date(y,m,i);if(d.getDay()<=4)days.push(d)}return days} const Chk=()=>; const Xm=()=>; const Lk=()=>; const ARi=()=>; const ALi=()=>; export default function App(){ const[teachers,setTeachers]=useState(DEFAULT_TEACHERS); const[records,setRecords]=useState({}); const[att,setAtt]=useState({}); const[schedules,setSchedules]=useState({supervisionDay:{},dutyMonth:{},dailyDuty:{}}); const[school,setSchool]=useState(DEFAULT_SCHOOL); const[curDate,setCurDate]=useState(new Date()); const[page,setPage]=useState("daily"); const[toast,setToast]=useState(null); const[loaded,setLoaded]=useState(false); const[accForm,setAccForm]=useState(null); const dKey=dk(curDate),tDI=dayI(curDate),gl=GL[school.gender]||GL.male; useEffect(()=>{(async()=>{try{if(window.storage){const r=await window.storage.get(SK);if(r?.value){const p=JSON.parse(r.value);if(p.teachers)setTeachers(p.teachers);if(p.records)setRecords(p.records);if(p.att)setAtt(p.att);if(p.schedules)setSchedules(s=>({...s,...p.schedules}));if(p.school)setSchool(s=>({...s,...p.school}));}}}catch(e){}setLoaded(true)})();},[]); const save=useCallback((t,r,s,sc,a)=>{try{window.storage?.set(SK,JSON.stringify({teachers:t,records:r,schedules:s,school:sc,att:a}))}catch(e){}},[]); const upT=t=>{setTeachers(t);save(t,records,schedules,school,att)}; const upR=r=>{setRecords(r);save(teachers,r,schedules,school,att)}; const upS=s=>{setSchedules(s);save(teachers,records,s,school,att)}; const upSc=sc=>{setSchool(sc);save(teachers,records,schedules,sc,att)}; const upA=a=>{setAtt(a);save(teachers,records,schedules,school,a)}; const emptyDay=useCallback(()=>teachers.map(()=>TASK_KEYS.map(()=>null)),[teachers]); const dayData=useMemo(()=>records[dKey]||emptyDay(),[records,dKey,emptyDay]); const dayAtt=useMemo(()=>att[dKey]||{},[att,dKey]); const setDD=useCallback(nd=>{upR({...records,[dKey]:nd})},[dKey,records]); const togAtt=useCallback(ti=>{const na={...att};if(!na[dKey])na[dKey]={};const cur=na[dKey][ti];na[dKey][ti]=cur===undefined?true:cur===true?false:undefined;if(na[dKey][ti]===undefined)delete na[dKey][ti];upA(na)},[att,dKey]); const appTasks=useMemo(()=>teachers.map((_,ti)=>TASK_KEYS.map(key=>{ if(dayAtt[ti]===false)return false; if(key==="supervision")return schedules.supervisionDay[ti]!==undefined&&schedules.supervisionDay[ti]===tDI; if(key==="duty"){if(schedules.dailyDuty?.[ti])return true;const mk2=mKey(curDate),dm=schedules.dutyMonth?.[mk2];if(dm)return Object.entries(dm).some(([d2,idx])=>idx===ti&&d2===dKey);return false} return true })),[schedules,tDI,teachers,curDate,dKey,dayAtt]); const togCell=useCallback((ti,ki)=>{if(!appTasks[ti]?.[ki])return;const nd=dayData.map(r=>[...r]);const c=nd[ti]?.[ki];nd[ti][ki]=c===null?1:c===1?0:null;setDD(nd)},[dayData,setDD,appTasks]); const stats=useMemo(()=>{let comp=0,nonC=0,absent=0,present=0;teachers.forEach((_,ti)=>{if(dayAtt[ti]===false){absent++;return}if(dayAtt[ti]===true)present++;let has=false,allG=true;(dayData[ti]||[]).forEach((v,ki)=>{if(!appTasks[ti]?.[ki])return;if(v!==null){has=true;if(v!==1)allG=false}});if(has){if(allG)comp++;else nonC++}});const rated=comp+nonC;return{total:teachers.length,comp,nonC,rate:rated>0?Math.round(comp/rated*100):0,absent,present}},[dayData,appTasks,teachers,dayAtt]); const nav=n=>{const d=new Date(curDate);d.setDate(d.getDate()+n);setCurDate(d)}; const sToast=m=>{setToast(m);setTimeout(()=>setToast(null),2500)}; if(!loaded)return
سجل متابعة المهام
جاري التحميل...
; return(
وزارة التعليم
{school.region}
{school.schoolName}

سجل متابعة المهام

{[{k:'daily',l:'📋 اليومية'},{k:'teachers',l:`👥 ${gl.teachers}`},{k:'schedules',l:'📅 الجداول'},{k:'reports',l:'📊 التقارير'},{k:'settings',l:'⚙️'}].map(t=>)}
{page==='daily'&&setCurDate(new Date())} resetDay={()=>{if(confirm('مسح بيانات هذا اليوم؟')){setDD(emptyDay());const na={...att};delete na[dKey];upA(na);sToast('تم ✓')}}}/>} {page==='teachers'&&} {page==='schedules'&&} {page==='reports'&&} {page==='settings'&&} {accForm&&setAccForm(null)}/>}
{gl.vice}
{school.vice}
{gl.principal}
{school.principal}
{toast&&
{toast}
}
); } /* ═══════ DAILY ═══════ */ function DailyPage({teachers,curDate,nav,dayData,togCell,stats,appTasks,goToday,resetDay,dKey,gl,dayAtt,togAtt,school,setAccForm,sToast,emptyDay}){ const isToday=dk(curDate)===dk(new Date()),isWE=dayI(curDate)>=5; const exportCSV=()=>{let csv='\uFEFF#,الحالة,اسم '+gl.teacher+','+TASK_LABELS.join(',')+'\n';dayData.forEach((row,i)=>{if(i>=teachers.length)return;const a=dayAtt[i]===false?'غائب':'حاضر';const vals=row.map((v,ki)=>!appTasks[i]?.[ki]?'غير مطلوب':v===1?'ملتزم':v===0?'غير ملتزم':'-');csv+=[i+1,a,teachers[i].name,...vals].join(',')+'\n'});const b=new Blob([csv],{type:'text/csv;charset=utf-8;'});const a=document.createElement('a');a.href=URL.createObjectURL(b);a.download=`سجل_${dKey}.csv`;a.click();sToast('تم التصدير ✓')}; return
{dayN(curDate)} - {fmtD(curDate)}
{!isToday&&}{isToday&&
● اليوم
}
{isWE?
عطلة نهاية الأسبوع
:<>
{[{l:'إجمالي',v:stats.total,c:'#1a5c3a'},{l:'حاضر',v:stats.present,c:'#3b82f6'},{l:'غائب',v:stats.absent,c:'#f59e0b'},{l:'ملتزم',v:stats.comp,c:'#22c55e'},{l:'الالتزام',v:stats.rate+'%',c:'#c8a951'}].map((s,i)=>
{s.v}
{s.l}
)}
بيانات {gl.teachers}
{TASK_LABELS.map((t,i)=>)} {teachers.map((t,ti)=>{ const isAbs=dayAtt[ti]===false,isPres=dayAtt[ti]===true; const hasViol=(dayData[ti]||[]).some((v,ki)=>appTasks[ti]?.[ki]&&v===0); return {TASK_KEYS.map((_,ki)=>{const ap=appTasks[ti]?.[ki],val=dayData[ti]?.[ki]??null;return })} })}
# الحضور اسم {gl.teacher}{t}إجراء
{ti+1} {t.name}{ap?:
}
{isAbs&&} {!isAbs&&hasViol&&}
}
; } /* ═══════ ACCOUNTABILITY MODAL ═══════ */ function AccModal({accForm,teachers,school,gl,onClose}){ const t=teachers[accForm.ti],isAbs=accForm.type==='absence'; const printForm=()=>{const w=window.open('','_blank','width=800,height=1000');w.document.write(`

المملكة العربية السعودية - وزارة التعليم
${school.region}
${school.schoolName}

${isAbs?'نموذج رقم (٢٠)':'نموذج مساءلة'}

${isAbs?'مساءلة غياب':'مساءلة عدم التزام'}

${!isAbs?``:''}
الاسم${t.name}التخصص${t.specialty||'—'}
رقم الهوية${t.id||'—'}الرقم الوظيفي${t.jobNum||'—'}
التاريخ${accForm.date}
المخالفة${accForm.task}
${isAbs?`

(١) طلب الإفادة

المكرم / ${t.name}   وفقه الله

السلام عليكم ورحمة الله وبركاته وبعد ،،،

من خلال متابعة سجل العمل تبيّن غيابكم خلال الفترة الموضحة بعاليه، آمل الإفادة عن أسباب ذلك وعليكم تقديم ما يؤيد عذركم خلال أسبوع من تاريخه، علماً بأنه في حالة عدم الالتزام سيتم اتخاذ اللازم حسب التعليمات.

اسم الرئيس المباشر: ${school.principal}
التوقيع: .........
التاريخ:  / / ١٤هـ

(٢) الإفادة

المكرم / ${gl.principal}   وفقه الله

السلام عليكم ورحمة الله وبركاته وبعد:

أفيدكم أن غيابي كان للأسباب التالية:

وسأقوم بتقديم ما يثبت ذلك خلال أسبوع من تاريخه

اسم ${gl.teacher}: .........
التوقيع: .........
التاريخ:  / / ١٤هـ

(٣) ${gl.principal}:

أ. تحتسب له إجازة مرضية بعد التأكد من نظامية التقرير

ب. يحتسب غيابه من رصيده للإجازات الاضطرارية لقبول عذره إذا كان رصيده يسمح وإلا يحسم عليه.

ج. يعتمد الحسم لعدم قبول عذره

اسم الرئيس المباشر: .........
التوقيع: .........
التاريخ:  / / ١٤هـ
ملاحظات هامة:
` :`

تفاصيل المخالفة

تبيّن من خلال المتابعة اليومية عدم التزام ${gl.teacher} / ${t.name} بالمهام التالية:

${accForm.task}

وذلك بتاريخ: ${accForm.date}

نأمل الإفادة عن أسباب ذلك:

توقيع ${gl.teacher}
${t.name}
${gl.vice}
${school.vice}
${gl.principal}
${school.principal}
`}
`);w.document.close()}; return
e.stopPropagation()} style={{background:'white',borderRadius:14,padding:'18px',maxWidth:400,width:'100%',maxHeight:'80vh',overflow:'auto',boxShadow:'0 16px 50px rgba(0,0,0,.3)'}}>
{isAbs?'📄 مساءلة غياب':'⚠️ مساءلة عدم التزام'}
{isAbs?'نموذج رقم (٢٠) - الدليل الإجرائي':'نموذج مساءلة'}
الاسم:{t.name}
التاريخ:{accForm.date}
{!isAbs&&
المخالفة:{accForm.task}
}
; } /* ═══════ TEACHERS ═══════ */ function TeachersPage({teachers,upT,sToast,gl}){ const[nn,setNN]=useState('');const[eIdx,setEIdx]=useState(null);const[eVal,setEVal]=useState({});const fRef=useRef(); const add=()=>{const n=nn.trim();if(!n)return;upT([...teachers,{name:n,id:'',specialty:'',jobNum:'',phone:''}]);setNN('');sToast('تمت الإضافة ✓')}; const del=i=>{if(!confirm(`حذف ${teachers[i].name}؟`))return;const t=[...teachers];t.splice(i,1);upT(t);sToast('تم الحذف ✓')}; const importFile=e=>{const file=e.target.files[0];if(!file)return;const reader=new FileReader();reader.onload=evt=>{const s=document.createElement('script');s.src='https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js';s.onload=()=>{try{const wb=window.XLSX.read(evt.target.result,{type:'array'});const ws=wb.Sheets[wb.SheetNames[0]];const json=window.XLSX.utils.sheet_to_json(ws);const imp=json.map(r=>{const name=(r['الاسم']||r['اسم المعلم']||Object.values(r)[1]||'').toString().trim();const id=String(r['رقم الهوية']||r['الهوية']||Object.values(r)[0]||'');const phone=String(r['الجوال']||'');const specialty=String(r['التخصص']||'');const jobNum=String(r['رقم الوظيفة']||'');return{name,id,specialty,jobNum,phone}}).filter(t=>t.name);if(imp.length>0){upT(imp);sToast(`تم استيراد ${imp.length} ${gl.teacher} ✓`)}else sToast('لم يتم العثور على بيانات')}catch(err){sToast('خطأ في الملف')}};document.head.appendChild(s)};reader.readAsArrayBuffer(file)}; return
إضافة {gl.teacher}
setNN(e.target.value)} placeholder={`اسم ${gl.teacher}`} onKeyDown={e=>e.key==='Enter'&&add()} style={{flex:1,...inpS}}/>
📥 استيراد من نظام نور
قائمة {gl.teachers} ({teachers.length})
{teachers.map((t,i)=>
{eIdx===i?
setEVal(p=>({...p,name:e.target.value}))} placeholder="الاسم" style={inpS}/>
setEVal(p=>({...p,id:e.target.value}))} placeholder="رقم الهوية" style={{...inpS,flex:1}}/>setEVal(p=>({...p,specialty:e.target.value}))} placeholder="التخصص" style={{...inpS,flex:1}}/>
:
{i+1}.{t.name}
{(t.id||t.specialty)&&
{t.id&&`هوية: ${t.id}`}{t.specialty&&` • ${t.specialty}`}
}
}
)}
; } /* ═══════ SCHEDULES ═══════ */ function SchedPage({teachers,schedules,upS,curDate,sToast,gl}){ const[tab,setTab]=useState('supervision');const[dm,setDm]=useState(new Date(curDate)); const mk=mKey(dm),wds=useMemo(()=>getWD(dm.getFullYear(),dm.getMonth()),[dm]),dutyMap=schedules.dutyMonth?.[mk]||{}; return
{[{k:'supervision',l:'🔍 الإشراف'},{k:'duty',l:'🛡️ المناوبة'}].map(t=>)}
{tab==='supervision'&&
جدول الإشراف الأسبوعي
{DAYS_AR.map((d,i)=>)}{teachers.map((t,ti)=>{[0,1,2,3,4].map(di=>)})}
#{gl.teacher}{d}
{ti+1}{t.name}
} {tab==='duty'&&<>
مناوبة يومية
{teachers.map((t,ti)=>)}
{MONTHS_G[dm.getMonth()]} {dm.getFullYear()}
{wds.map((wd,wi)=>{const wdk2=dk(wd),assigned=dutyMap[wdk2];return })}
#اليومالتاريخ{gl.teacher}
{wi+1}{dayN(wd)}{wd.getDate()}/{wd.getMonth()+1}
}
; } /* ═══════ REPORTS ═══════ */ function ReportsPage({teachers,curDate,nav,records,schedules,att,gl}){ const wd=wkD(curDate); const wr=useMemo(()=>wd.map(d=>{const k=dk(d),data=records[k],at=att[k]||{};if(!data)return{date:d,filled:false,rate:0,comp:0,rated:0,abs:0};const di=dayI(d),mk2=mKey(d);let comp=0,rated=0,abs=0;data.forEach((row,ti)=>{if(ti>=teachers.length)return;if(at[ti]===false){abs++;return}let has=false,good=true;row.forEach((v,ki)=>{const tk=TASK_KEYS[ki];let ap=true;if(tk==="supervision")ap=schedules.supervisionDay[ti]===di;if(tk==="duty"){if(schedules.dailyDuty?.[ti])ap=true;else{const dm=schedules.dutyMonth?.[mk2];ap=dm?Object.entries(dm).some(([d2,idx])=>idx===ti&&d2===dk(d)):false}}if(!ap)return;if(v!==null){has=true;if(v!==1)good=false}});if(has){rated++;if(good)comp++}});return{date:d,filled:rated>0||abs>0,rate:rated>0?Math.round(comp/rated*100):0,comp,rated,abs}}),[records,curDate,schedules,teachers,att]); return
التقرير الأسبوعي
{fmtD(wd[0])} — {fmtD(wd[4])}
{wr.map((d,i)=>
{DAYS_AR[i]}
=70?'#22c55e':d.rate>=40?'#f59e0b':'#ef4444'):'#ddd'}}>{d.filled?d.rate+'%':'–'}
{d.filled?`${d.comp}/${d.rated}`:''}{d.abs>0?` غياب:${d.abs}`:''}
)}
ملخص الأسبوع
{(()=>{const f=wr.filter(d=>d.filled),avg=f.length>0?Math.round(f.reduce((s,d)=>s+d.rate,0)/f.length):0,tAbs=f.reduce((s,d)=>s+d.abs,0);return[{l:'أيام مسجّلة',v:f.length+'/5'},{l:'متوسط الالتزام',v:avg+'%'},{l:'إجمالي الغياب',v:tAbs}].map((s,i)=>
{s.v}
{s.l}
)})()}
; } /* ═══════ SETTINGS ═══════ */ function SettPage({school,upSc,sToast,gl}){ const[f,sF]=useState({...school});const fgl=GL[f.gender]||GL.male;const ch=JSON.stringify(f)!==JSON.stringify(school); return
بيانات المدرسة
⚧ جنس الكادر
{[{k:'male',l:'بنين 👦'},{k:'female',l:'بنات 👧'}].map(g=>)}
{[{k:'schoolName',l:'اسم المدرسة'},{k:'region',l:'الإدارة'},{k:'principal',l:fgl.principal},{k:'vice',l:fgl.vice}].map(x=>
{x.l}
sF(p=>({...p,[x.k]:e.target.value}))} style={{...inpS,width:'100%'}}/>
)}
; } const TH={background:'#1a5c3a',color:'white',fontWeight:600,fontSize:8.5,padding:'7px 4px',textAlign:'center',whiteSpace:'nowrap'}; const TD={padding:'3px 3px',textAlign:'center',fontSize:9,verticalAlign:'middle'}; const BTN={padding:'7px 12px',borderRadius:6,fontFamily:"'Tajawal',sans-serif",fontSize:10,fontWeight:700,display:'flex',alignItems:'center',gap:3,transition:'all .2s',border:'none'}; const inpS={padding:'7px 9px',borderRadius:6,border:'2px solid #e2e5ea',fontSize:11,outline:'none',background:'#fafafa',fontFamily:'Tajawal'};