ورود به پنل مدرسه

سامانه هوشمند مدیریت مدارس مدینو
با کد ۸ رقمی مدرسه و رمز اختصاصی وارد شوید.

اگر رمز را فراموش کرده‌اید، با مدیر کل مدینو برای بازنشانی رمز تماس بگیرید.

تغییر رمز عبور

برای حفاظت از حساب، پیش از ورود به سامانه رمز اولیه را تغییر دهید.

حداقل ۱۲ کاراکتر؛ رمز جدید باید با رمز قبلی متفاوت باشد. رمز را برای دیگران ارسال نکنید.
داشبورد مدرسهفضای کاری / مدرسه
سال تحصیلی فعال
حساب مدیرپنل مدیریت مدرسه
م
MODINO / ACADEMIC YEAR TRANSFER

انتقال دانش‌آموزان به سال جدید

در حال بررسی سال‌های تحصیلی...
دانش‌آموزان قابل انتقال—
کلاس‌های سال گذشته—
کلاس‌های سال جاری—
در حال دریافت اطلاعات...
دانش‌آموزان سال قبل
ردیفنام و نام خانوادگیکد ملینام پدرپایه قبلکلاس قبل
۰ دانش‌آموز انتخاب شده
MODINO / PHONEBOOK

دفترچه تلفن مدرسه

شماره‌های والدین و گیرندگان پیامک در سال تحصیلی فعال

تعداد دانش‌آموزان—
شماره‌های ناقص در صفحه—
صفحه فعلی۱
برای نمایش دفترچه تلفن وارد این بخش شوید.
ردیفدانش‌آموزنام پدرپایه / کلاسشماره پدرشماره مادرگیرنده پیامکعملیات
برای ویرایش شماره‌ها روی دکمه هر دانش‌آموز بزنید.
—
MODINO SCHOOL WORKSPACE

داشبورد مدرسه

فضای اختصاصی مدیریت مدرسه

نام مدرسه—
مدیر مدرسه—
وضعیت حسابفعال و تأییدشده

امکانات مدرسه

بخش موردنظر را انتخاب کنید

اطلاعات پایه / سال تحصیلی

مدیریت سال تحصیلی

ثبت و فعال‌سازی سال‌های تحصیلی اختصاصی مدرسه

سال‌های تحصیلی مدرسهاولین سال قابل ثبت ۱۴۰۵–۱۴۰۶ است؛ هر مدرسه تنها یک سال فعال دارد.
سال تحصیلی فعالدر حال دریافت...
در حال دریافت اطلاعات...

سال جدید ابتدا غیرفعال ثبت می‌شود. فعال‌سازی سال دیگری، سال قبلی را غیرفعال می‌کند اما اطلاعات آن را حذف نمی‌کند. برای تغییر سال حتماً تأیید شما گرفته می‌شود.

MODINO / CLASS STUDIO

مدیریت کلاس‌های مدرسه

ساخت و مدیریت کلاس‌های عادی و چندپایه، متناسب با دوره تحصیلی

در حال بررسی سال تحصیلی...
تعداد کلاس‌ها۰
کلاس‌های چندپایه۰
پایه‌های پوشش‌داده‌شده۰
مدیریت کلاس‌ها در سال تحصیلی فعال · ثبت، ویرایش و حذف ایمن
کلاس‌های ثبت‌شده
نمایش کلاس‌های سال فعال

تعداد دانش‌آموزان پس از راه‌اندازی کلاس‌بندی، از اطلاعات واقعی دانش‌آموزان محاسبه و نمایش داده می‌شود.

برای ویرایش، دکمه «ویرایش» کنار هر کلاس را بزنید. حذف فقط پس از تأیید مدیر انجام می‌شود؛ کلاس‌های دارای ارتباط دانش‌آموزی یا جدول وابسته، در سرور قابل حذف نیستند.

MODINO / TEACHING ASSIGNMENTS

تخصیص معلم به دروس

مدیریت معلمان هر کلاس و درس در سال تحصیلی فعال

دروس و معلمان کلاس
—
برای نمایش دروس، یک کلاس انتخاب کنید.

تخصیص‌های این بخش در دیتابیس مدرسه ذخیره می‌شوند. نمایش در پنل آنلاین معلمان پس از راه‌اندازی همگام‌سازی انجام می‌شود.

MODINO / SUBJECT STUDIO

مدیریت دروس مدرسه

دروس پیش‌فرض، دروس اختصاصی و ارتباط هوشمند با کلاس‌های سال تحصیلی فعال

فهرست دروس

هنوز بارگذاری نشده
نام درسپایهنوععملیات
یک کلاس انتخاب کنید.
MODINO / TEACHER MANAGEMENT

مدیریت معلمان و کارکنان

پرونده پرسنلی، وضعیت همکاری و سمت معلم در سال تحصیلی مدرسه

سال تحصیلی: در حال بررسی...

معلمان این مدرسه—
معلمان فعال—
سال تحصیلی فعال—
اطلاعات معلمان از سرور دریافت می‌شود. ثبت معلم جدید فعال است؛ ویرایش پرونده در مرحله بعد اضافه می‌شود.

فهرست معلمان

فهرست واقعی معلمان سال تحصیلی فعال؛ برای تازه‌سازی دوباره این بخش را باز کنید.
نام و نام خانوادگیکد ملیشماره همراهسمت در سال فعالوضعیتعملیات
در حال دریافت اطلاعات معلمان...
اطلاعات پایه / آموزشگاه

مشخصات مدرسه

اطلاعات تأییدشده از سرور مدینو

اتصال امن
پرونده مدرسهاین اطلاعات از حساب مدرسه شما دریافت می‌شوند
اطلاعات این صفحه فقط مربوط به مدرسه حساب شماست. ویرایش مشخصات مدرسه فعلاً از پنل مدیر کل انجام می‌شود.
تنظیمات / امنیت

حساب کاربری و امنیت

نام کاربری و تنظیمات امنیتی مدیر مدرسه

اطلاعات ورود

در صورت فراموشی رمز، مدیر کل می‌تواند رمز جدیدی تعیین کند. پس از بازنشانی، ورودهای قبلی باطل می‌شوند.

MODINO / CLASSIFICATION

دانش‌آموزان بدون کلاس

فهرست پیش‌ثبت‌نام‌های سال تحصیلی فعال، آماده برای کلاس‌بندی

سال تحصیلی: —
تعداد بدون کلاس—
انتخاب‌شده در این صفحه۰
کلاس‌های تعریف‌شده—

فهرست دانش‌آموزان

فقط دانش‌آموزان پیش‌ثبت‌نام‌شده که هنوز کلاس ندارند

دانش‌آموزنام پدرکد ملیوضعیت
برای مشاهده فهرست، وارد این بخش شوید.

انتخاب کلاس مقصد

کلاس‌های همین مدرسه و سال تحصیلی فعال نمایش داده می‌شوند.

هنوز کلاسی انتخاب نشده است.

این نسخه فقط برای مشاهده، جست‌وجو و انتخاب دانش‌آموزان و کلاس است. ثبت کلاس‌بندی تا راه‌اندازی API امن سرور غیرفعال می‌ماند و هیچ اطلاعاتی تغییر نمی‌کند.

MODINO / STUDENT REGISTRATION

پیش‌ثبت‌نام دانش‌آموزان

پرونده‌سازی در دو مرحله؛ سریع، دقیق و هماهنگ با سال تحصیلی فعال

در حال بررسی سال فعال...
پرونده جدید دانش‌آموزاطلاعات ضروری با ستاره مشخص شده‌اند
بدون عکس
عکس دانش‌آموز (اختیاری)فرمت JPG، PNG یا WebP؛ تبدیل خودکار به WebP با حداکثر ۶۰ کیلوبایت
عکسی انتخاب نشده است.

برای موارد زیر وضعیت را مشخص کنید. حالت نامشخص نیز قابل ثبت است.

وضعیت تحصیلی، اطلاعات سلامت، پوشش حمایتی و ویژگی‌های فردی دانش‌آموز را در این مرحله تکمیل کنید.

شماره پیامک بر اساس انتخاب پدر یا مادر تعیین می‌شود. ارسال به هر دو شماره در زمان راه‌اندازی سرویس پیامک انجام خواهد شد.

فهرست پیش‌ثبت‌نام‌های سال فعال
—پرونده‌های فهرست
—نتیجه جست‌وجو

برای نمایش پرونده‌ها، وارد این بخش شوید.
—
MODINO / ATTENDANCE

حضور و غیاب دانش‌آموزان

حضور پیش‌فرض است؛ فقط غیبت‌ها و موارد خاص را ثبت کنید

سال تحصیلی فعال
♙کل دانش‌آموزان مدرسه—سال تحصیلی فعال
✓حاضر در مدرسه—بر اساس ثبت روزانه
×غایب موجه / غیرموجه——
◷تأخیر / تعجیل——

آمار کل مدرسه برای تاریخ انتخاب‌شده؛ بدون ثبت غیبت، دانش‌آموز حاضر فرض می‌شود.

کلاس و تاریخ را انتخاب کنید؛ وضعیت خالی به معنی حضور است.

فهرست دانش‌آموزان✓ بدون ثبت غیبت = حاضر
MODINO / MONITOR

مرکز پایش غیبت دانش‌آموزان

مشاهده یکپارچه غیبت‌های ثبت‌شده در تمام کلاس‌های مدرسه

با تغییر تاریخ یا نوع گزارش، اطلاعات به‌صورت خودکار به‌روزرسانی می‌شوند.
دانش‌آموزان دارای غیبت—
موارد غیبت موجه—
موارد غیبت غیرموجه—
فهرست غایبانگزارش زنگی تعداد موارد ثبت‌شده در زنگ‌های روز را نشان می‌دهد؛ برای تعیین زنگ دقیق از گزارش انفرادی استفاده کنید.
هنوز گزارشی دریافت نشده است.
MODINO / REPORT CENTER

گزارش غیبت‌ها

سه گزارش رسمی، یک مرکز گزارش‌گیری

MODINO / STUDENT REPORT

ریز غیبت‌های هر دانش‌آموز

گزارش شخصی با سربرگ رسمی و چاپ A4

کلاس، دانش‌آموز و بازه را انتخاب کنید.
MODINO / SCHOOL REPORTS

گزارش غیبت دانش‌آموزان

گزارش رسمی قابل چاپ بر اساس بازه زمانی

گزارش روزانه و زنگی
بازه زمانی را انتخاب کنید.

گزارش غیبت به تفکیک کلاس

همه دانش‌آموزان کلاس، حتی افراد بدون غیبت، در گزارش چاپی نمایش داده می‌شوند.

کلاس و بازه زمانی را انتخاب کنید.
MODINO / STUDENT ATTENDANCE

تنظیم زنگ‌های حضور و غیاب

تعداد زنگ‌های آموزشی هر روز را برای تمام کلاس‌های مدرسه مشخص کنید.

در حال دریافت سال تحصیلی...

برنامه هفتگی مدرسه

صفر زنگ به معنی تعطیلی است. هر تغییر با تاریخ شروع اعتبار جدید ثبت می‌شود تا سوابق قبلی محفوظ بمانند.

تاریخ را از تقویم شمسی انتخاب کنید؛ تبدیل به میلادی فقط هنگام ذخیره‌سازی در سرور انجام می‌شود. این تنظیم برای کل مدرسه است.

تاریخچه تنظیمات

برنامه‌های ثبت‌شده برای سال تحصیلی فعال، به تفکیک تاریخ شروع اعتبار.

در حال دریافت...
مدیریت پرونده‌های دانش‌آموزی

دانش‌آموزان مدرسه

فهرست کلاس‌ها، پرونده‌ها و مدیریت کلاس‌بندی در یک نگاه

سال تحصیلی: —
کل دانش‌آموزان مدرسه—ثبت‌نام فعال در سال جاری
دانش‌آموزان کلاس انتخابی—کلاس را انتخاب کنید
فیلتر فهرستکلاس موردنظر را انتخاب کنید
فهرست دانش‌آموزان

۰ دانش‌آموز

برای انجام عملیات، دانش‌آموزان را انتخاب کنید۰ انتخاب‌شده
نام و تصویر دانش‌آموزنام پدرکد ملیپایه تحصیلیعملیات
کلاس را انتخاب کنید.
© مدینو · پنل اختصاصی مدرسهmodinoplus.ir · پشتیبانی: ۰۹۱۷۰۵۹۱۷۲۸
MODINO · GROUP STUDIO

گروه‌بندی هوشمند دانش‌آموزان

گروه‌های مستقل برای هر مدرسه و سال تحصیلی؛ با مدیریت سریع اعضا

سال تحصیلی: —
گروه‌های اصلی۰
زیرگروه‌ها۰
اعضای زیرگروه۰

ابتدا یک گروه انتخاب کنید

زیرگروه‌های گروه انتخاب‌شده

مدیریت اعضا

انتخاب چندنفره، فیلتر پایه و کلاس، حذف عضویت

دانش‌آموزان مدرسه

انتخاب چندنفره و افزودن به زیرگروه
۰ انتخاب‌شده
`); doc.close(); let printed=false; const doPrint=()=>{if(printed)return;printed=true;try{frame.contentWindow.focus();frame.contentWindow.print()}catch(e){msg('چاپ در مرورگر انجام نشد.',true)}setTimeout(()=>frame.remove(),60000)}; if(image&&image.src){ const img=doc.createElement('img');img.src=image.src;img.alt='عکس دانش‌آموز'; const target=doc.getElementById('photo');target.replaceWith(img);target.id='photo'; if(img.complete)setTimeout(doPrint,300);else{img.onload=()=>setTimeout(doPrint,200);img.onerror=doPrint;} }else setTimeout(doPrint,250); } async function openStudent(id){try{const d=await request('/api/school/students/'+id);const p=d.student?.profile_data||{};reset();editing=id;for(const k of textFields)$('stu_'+k).value=p[k]??'';for(const k of boolFields){const el=$('stu_'+k);el.indeterminate=false;el.checked=(k==='father_alive'||k==='mother_alive')?(p[k]===0||p[k]===false):(p[k]===1||p[k]===true);el.dataset.originalNull=(p[k]===null||p[k]===undefined)?'1':'0';}syncMedicalNotes();loadStudentPhoto(id);$('stu_national_code').readOnly=true;const sf=digits($('stu_father_mobile').value),sm=digits($('stu_mother_mobile').value),ss=digits($('stu_sms_mobile').value);smsChoice.value=ss&&ss===sf&&ss===sm?'both':ss&&ss===sf?'father':ss&&ss===sm?'mother':'';$('stuFormTitle').textContent='ویرایش پرونده سال فعال';$('stuSave').textContent='ذخیره تغییرات';msg('پرونده برای ویرایش باز شد.');if(!$('studentRegistrationPage').classList.contains('sa-modal-mode'))window.scrollTo({top:0,behavior:'smooth'});}catch(e){msg(e.message,true);}} window.saOpenExistingStudent=openStudent; window.showStudentRegistration=async function(){const saFromAffairs=!$('studentAffairsPage').classList.contains('hide');if(saFromAffairs){$('studentRegistrationPage').classList.add('sa-modal-mode');document.body.classList.add('sa-modal-active');}else{$('studentRegistrationPage').classList.remove('sa-modal-mode');document.body.classList.remove('sa-modal-active');}for(const id of ['dashboard','profilePage','accountPage','yearsPage','classesPage','teachersPage','subjectsPage','assignmentsPage','unclassifiedPage'])$(id)?.classList.add('hide');$('studentRegistrationPage').classList.remove('hide');if(saFromAffairs){$('studentAffairsPage').classList.remove('hide');}else{$('topTitle').textContent='پیش‌ثبت‌نام دانش‌آموز';}if(!saFromAffairs)$('topCrumb').textContent='امور دانش‌آموزی / پیش‌ثبت‌نام';$('sidebar')?.classList.remove('open');$('mask')?.classList.remove('open');reset();try{const d=await request('/api/school/students/context');$('stuYear').textContent='سال تحصیلی: '+(d.active_year?.caption||'فعال نشده');if(!d.active_year)msg('برای ثبت دانش‌آموز ابتدا سال تحصیلی را فعال کنید.',true);await refresh();}catch(e){msg('خطا در ارتباط با API: '+e.message,true);}}; document.querySelectorAll('[data-stu-tab]').forEach(b=>b.addEventListener('click',()=>tab(b.dataset.stuTab))); $('stuNext').addEventListener('click',()=>tab(document.querySelector('[data-stu-tab].active')?.dataset.stuTab==='main'?'extra':'main')); $('stuNew').addEventListener('click',reset); $('stuRefresh').addEventListener('click',async()=>{try{await refresh()}catch(e){msg(e.message,true)}}); $('stuSearch').addEventListener('input',()=>{clearTimeout(searchTimer);searchTimer=setTimeout(()=>{stuDirPage=1;refresh().catch(e=>msg(e.message,true))},300)}); for(const k of boolFields){const el=$('stu_'+k);el.addEventListener('change',()=>{el.indeterminate=false;el.dataset.touched='1';if(k==='has_medical_condition')syncMedicalNotes();});} $('stu_national_code').addEventListener('blur',()=>{const input=$('stu_national_code');const v=digits(input.value.trim());if(v&&!validNC(v)){tab('main');msg('کد ملی واردشده معتبر نیست. لطفاً آن را اصلاح کنید.',true);input.setAttribute('aria-invalid','true');}else{input.removeAttribute('aria-invalid');if(v)msg('');}}); // MODINO identity intelligence: lookup is advisory; server enforces matching identity. let stuIdentitySeq=0,stuIdentityLast=null; const stuIdentityBox=document.createElement('div'); stuIdentityBox.id='stuIdentityBox';stuIdentityBox.className='hide'; stuIdentityBox.style.cssText='margin:12px 0;padding:15px 17px;border-radius:15px;border:1px solid var(--stroke);background:var(--accent2);line-height:2;font-size:12px'; const stuIdentityInput=$('stu_national_code'); stuIdentityInput.closest('.field, .stu-field, label')?.insertAdjacentElement('afterend',stuIdentityBox); if(!stuIdentityBox.isConnected)stuIdentityInput.insertAdjacentElement('afterend',stuIdentityBox); function stuIdentityShow(text,warning=false){stuIdentityBox.textContent=text;stuIdentityBox.classList.remove('hide');stuIdentityBox.style.borderColor=warning?'#ed9b9b':'var(--stroke)';stuIdentityBox.style.background=warning?'#fff0f0':'var(--accent2)';stuIdentityBox.style.color=warning?'#a53142':'var(--ink)';} function stuIdentityClear(){stuIdentitySeq++;stuIdentityLast=null;stuIdentityBox.classList.add('hide');stuIdentityBox.textContent='';} async function stuIdentityCheck(){ if(editing)return null; const code=digits(stuIdentityInput.value).replace(/\D/g,''); const seq=++stuIdentitySeq; if(!validNC(code)){stuIdentityLast=null;stuIdentityBox.classList.add('hide');return null;} try{ const result=await request('/api/school/students/identity-lookup?national_code='+encodeURIComponent(code)); if(seq!==stuIdentitySeq||digits(stuIdentityInput.value).replace(/\D/g,'')!==code)return null; stuIdentityLast={code,result}; if(!result.found){stuIdentityShow('کد ملی جدید است؛ پرونده‌ای با این کد ملی در این مدرسه پیدا نشد.');return result;} const fullname=[result.first_name,result.last_name].filter(Boolean).join(' '); if(result.registered_current_year){stuIdentityShow('این کد ملی متعلق به «'+fullname+'» است و در سال جاری ثبت شده'+(result.class_name?'؛ کلاس: '+result.class_name:'؛ هنوز کلاس‌بندی نشده')+'. برای مشاهده یا ویرایش، پرونده موجود را باز کنید.',true);} else{stuIdentityShow('پرونده قبلی شناسایی شد: «'+fullname+'»؛ نام پدر: '+(result.father_name||'—')+'. این دانش‌آموز در سال جاری ثبت نشده است. برای ثبت‌نام مجدد، مشخصات هویتی باید دقیقاً با پرونده قبلی مطابقت داشته باشد.'); for(const [key,value] of [['first_name',result.first_name],['last_name',result.last_name],['father_name',result.father_name]]){const field=$('stu_'+key);if(field&&!field.value.trim())field.value=value||'';} } return result; }catch(e){if(seq===stuIdentitySeq)stuIdentityShow('بررسی کد ملی انجام نشد: '+e.message,true);return null;} } stuIdentityInput.addEventListener('input',()=>{stuIdentityClear();}); stuIdentityInput.addEventListener('blur',()=>{stuIdentityCheck();}); function stuNormName(v){return String(v||'').replace(/ي/g,'ی').replace(/ك/g,'ک').replace(/[\u200c\s]+/g,' ').trim();} async function stuIdentityBeforeSave(data){ if(editing)return true; const code=data.national_code; let found=stuIdentityLast?.code===code?stuIdentityLast.result:null; if(!found)found=await stuIdentityCheck(); if(!found){msg('بررسی پرونده قبلی انجام نشد؛ دوباره تلاش کنید.',true);return false;} if(!found.found)return true; if(found.registered_current_year){stuIdentityShow('این دانش‌آموز قبلاً در سال جاری ثبت‌نام شده است'+(found.class_name?' و در کلاس '+found.class_name+' قرار دارد':'')+'.',true);msg('ثبت‌نام تکراری مجاز نیست. پرونده موجود را باز کنید.',true);return false;} if(['first_name','last_name','father_name'].some(k=>stuNormName(data[k])!==stuNormName(found[k]))){stuIdentityShow('مغایرت هویتی! نام ثبت‌شده قبلی: '+found.first_name+' '+found.last_name+'، نام پدر: '+found.father_name+'. ثبت‌نام با نام متفاوت مسدود شد.',true);msg('کد ملی متعلق به دانش‌آموز دیگری است. مشخصات را مطابق پرونده قبلی اصلاح کنید.',true);return false;} return window.confirm('پرونده قبلی «'+found.first_name+' '+found.last_name+'» پیدا شد. ثبت‌نام مجدد همین دانش‌آموز در سال جاری انجام شود؟'); } $('stuForm').addEventListener('submit',async ev=>{ev.preventDefault();if(busy)return;const data=payload();if(!validNC(data.national_code)){tab('main');msg('کد ملی واردشده معتبر نیست. لطفاً کد ملی ۱۰ رقمی و رقم کنترل آن را بررسی کنید.',true);$('stu_national_code').focus();return;}if(!data.first_name||!data.last_name||!data.father_name||!data.birth_date||!data.gender){tab('main');msg('نام، نام خانوادگی، نام پدر، تاریخ تولد و جنسیت ضروری هستند.',true);return;}for(const k of ['father_mobile','mother_mobile','sms_mobile'])if(data[k]&&!/^09\d{9}$/.test(data[k])){tab('main');msg('شماره موبایل باید ۱۱ رقم و با ۰۹ شروع شود.',true);return;}if(smsChoice.value==='both'&&(!data.father_mobile||!data.mother_mobile)){msg('برای انتخاب پدر و مادر، شماره همراه هر دو را وارد کنید.',true);return;}if(smsChoice.value==='father'&&!data.father_mobile||smsChoice.value==='mother'&&!data.mother_mobile){msg('ابتدا شماره همراه دریافت‌کننده پیامک را وارد کنید.',true);return;}if(!(await stuIdentityBeforeSave(data)))return;busyState(true);msg('در حال ذخیره اطلاعات...');try{const d=await request(editing?'/api/school/students/'+editing:'/api/school/students',editing?'PUT':'POST',data);const savedId=Number(d.student_id||editing);if(!Number.isSafeInteger(savedId)||savedId<1)throw Error('اطلاعات ذخیره شد، اما شناسه دانش‌آموز برای ذخیره عکس دریافت نشد.');let photoMessage='';try{photoMessage=await saveStudentPhoto(savedId);}catch(photoError){editing=savedId;msg('اطلاعات دانش‌آموز ذخیره شد، اما عکس ذخیره نشد: '+photoError.message+' لطفاً دوباره ذخیره کنید؛ پرونده تکراری نسازید.',true);return;}reset();msg((d.message||'اطلاعات ذخیره شد.')+(photoMessage?' '+photoMessage:''));await refresh();}catch(e){msg(e.message,true);}finally{busyState(false);}}); // National-code prefix lookup: city of issuance is a suggestion, not proof of birthplace. const issueField=$('stu_issue_place'); const prefixChoices=$('stuPrefixChoices'); const prefixHint=$('stuPrefixHint'); async function lookupIssuePlace(){ const code=digits($('stu_national_code').value).replace(/\D/g,''); const isValid=code.length===10&&validNC(code); const prefix=isValid?code.slice(0,3):''; if(!isValid){ ++prefixRequestId; lastPrefix=''; issueField.value=''; delete issueField.dataset.prefixSuggested; prefixChoices.classList.add('hide'); prefixChoices.replaceChildren(new Option('انتخاب شهر محل صدور شناسنامه','')); prefixHint.textContent=''; return; } if(prefix===lastPrefix)return; lastPrefix=prefix; const seq=++prefixRequestId; prefixChoices.classList.add('hide'); prefixChoices.replaceChildren(new Option('انتخاب شهر محل صدور شناسنامه','')); prefixHint.textContent=''; if($('stu_national_code').readOnly)return; issueField.value=''; delete issueField.dataset.prefixSuggested; prefixHint.textContent='در حال بررسی پیشوند کد ملی...'; try{ const result=await request('/api/school/students/national-prefix?prefix='+encodeURIComponent(prefix)); if(seq!==prefixRequestId || !validNC(digits($('stu_national_code').value)))return; const matches=Array.isArray(result.matches)?result.matches:[]; if(!matches.length){prefixHint.textContent='شهری برای این پیشوند یافت نشد؛ محل صدور را دستی وارد کنید.';return;} if(matches.length===1){ const city=matches[0].city_name||''; issueField.value=city; issueField.dataset.prefixSuggested='1'; prefixHint.textContent='پیشنهاد بر اساس کد ملی: '+city+' ('+(matches[0].province_name||'')+'). با شناسنامه تطبیق دهید.'; return; } for(const row of matches){ const city=row.city_name||''; if(!city)continue; prefixChoices.add(new Option(city+' — '+(row.province_name||''),city)); } prefixChoices.classList.remove('hide'); prefixHint.textContent='این پیشوند متعلق به چند شهر است؛ محل صدور را از فهرست انتخاب یا دستی وارد کنید.'; }catch(e){ if(seq===prefixRequestId)prefixHint.textContent='استعلام شهر در دسترس نیست؛ محل صدور را دستی وارد کنید.'; } } prefixChoices.addEventListener('change',()=>{ if(prefixChoices.value){issueField.value=prefixChoices.value;issueField.dataset.prefixSuggested='1';} }); issueField.addEventListener('input',()=>{issueField.dataset.prefixSuggested='0';}); $('stu_national_code').addEventListener('input',lookupIssuePlace); // MODINO v2: Persian birth calendar and family SMS recipient choice. const smsChoice=$('stuSmsChoice'); const calBack=$('stuCalendarBackdrop'); const calFmt=new Intl.DateTimeFormat('en-US-u-ca-persian-nu-latn',{year:'numeric',month:'numeric',day:'numeric',timeZone:'UTC'}); const calMonthNames=['فروردین','اردیبهشت','خرداد','تیر','مرداد','شهریور','مهر','آبان','آذر','دی','بهمن','اسفند']; let calYear=1394,calMonth=7; function persianParts(d){const p=calFmt.formatToParts(d);return {y:Number(p.find(x=>x.type==='year')?.value),m:Number(p.find(x=>x.type==='month')?.value),d:Number(p.find(x=>x.type==='day')?.value)};} function pad2(n){return String(n).padStart(2,'0');} function calendarDays(y,m){const out=[];const approx=y+621;for(let t=Date.UTC(approx-1,11,1),end=Date.UTC(approx+1,4,1);ta.day-b.day);} function renderCalendar(){const ys=$('stuCalYear'),ms=$('stuCalMonth');if(!ys.options.length){for(let y=1370;y<=1410;y++){const o=document.createElement('option');o.value=y;o.textContent=String(y);ys.append(o);}for(let i=0;i<12;i++){const o=document.createElement('option');o.value=i+1;o.textContent=calMonthNames[i];ms.append(o);}}ys.value=String(calYear);ms.value=String(calMonth);const grid=$('stuCalGrid');grid.replaceChildren();for(const w of ['ش','ی','د','س','چ','پ','ج']){const d=document.createElement('div');d.className='weekday';d.textContent=w;grid.append(d);}const days=calendarDays(calYear,calMonth);if(!days.length)return;for(let i=0;i{$('stu_birth_date').value=val;calBack.classList.remove('open');});grid.append(b);}} function openCalendar(){const m=digits($('stu_birth_date').value).match(/^(\d{4})\/(\d{1,2})\/(\d{1,2})$/);if(m){calYear=Number(m[1]);calMonth=Number(m[2]);}renderCalendar();calBack.classList.add('open');} $('stu_birth_date').addEventListener('click',openCalendar);$('stuOpenCalendar').addEventListener('click',openCalendar); $('stuCalClose').addEventListener('click',()=>calBack.classList.remove('open')); calBack.addEventListener('click',e=>{if(e.target===calBack)calBack.classList.remove('open')}); $('stuCalYear').addEventListener('change',e=>{calYear=Number(e.target.value);renderCalendar()}); $('stuCalMonth').addEventListener('change',e=>{calMonth=Number(e.target.value);renderCalendar()}); function shiftCal(n){calMonth+=n;if(calMonth<1){calMonth=12;calYear--;}if(calMonth>12){calMonth=1;calYear++;}calYear=Math.min(1410,Math.max(1370,calYear));renderCalendar();} $('stuCalPrev').addEventListener('click',()=>shiftCal(-1));$('stuCalNext').addEventListener('click',()=>shiftCal(1)); $('stuCalClear').addEventListener('click',()=>{$('stu_birth_date').value='';calBack.classList.remove('open')}); function smsValue(){const f=digits($('stu_father_mobile').value.trim()),m=digits($('stu_mother_mobile').value.trim());if(smsChoice.value==='father')return f==='09'?'':f;if(smsChoice.value==='mother')return m==='09'?'':m;if(smsChoice.value==='both')return (f!=='09'?f:'')||(m!=='09'?m:'');return '';} function updateSms(){ $('stu_sms_mobile').value=smsValue();} smsChoice.addEventListener('change',updateSms);$('stu_father_mobile').addEventListener('input',updateSms);$('stu_mother_mobile').addEventListener('input',updateSms); // MODINO V4: persistent mobile prefix and national code controls. for(const key of ['father_mobile','mother_mobile']){ const el=$('stu_'+key); const normalize=()=>{let d=digits(el.value).replace(/\D/g,'');if(!d){el.value='09';return;}if(d.startsWith('0098'))d='0'+d.slice(4);else if(d.startsWith('98'))d='0'+d.slice(2);else if(d.startsWith('9')&&d.length>=10)d='0'+d;else if(!d.startsWith('09'))d='09'+d.replace(/^0+/,'');el.value=('09'+d.slice(2).replace(/\D/g,'').slice(0,9));}; el.addEventListener('focus',()=>{normalize();requestAnimationFrame(()=>{if(el.selectionStart<2)el.setSelectionRange(2,2);});}); el.addEventListener('input',()=>{const before=el.value;normalize();if(before!==el.value)el.setCustomValidity('');updateSms();}); el.addEventListener('keydown',e=>{if((e.key==='Backspace'&&el.selectionStart<=2&&el.selectionEnd<=2)||(e.key==='Delete'&&el.selectionStart<2&&el.selectionEnd<=2)){e.preventDefault();}}); el.addEventListener('click',()=>{if(el.selectionStart<2)el.setSelectionRange(2,2);}); el.addEventListener('blur',()=>{if(el.value==='09')el.value='';updateSms();}); } const national=$('stu_national_code'); national.setAttribute('maxlength','10');national.setAttribute('pattern','[0-9]{10}');national.setAttribute('dir','ltr'); let ncRefocusing=false; function nationalCheck(refocus=false){ if(national.readOnly)return true; const d=digits(national.value).replace(/\D/g,''); if(d.length>10){national.value=d.slice(0,10);national.setCustomValidity('کد ملی بیشتر از ۱۰ رقم مجاز نیست.');msg('کد ملی بیشتر از ۱۰ رقم مجاز نیست.',true);if(refocus)national.focus();return false;} national.value=d; if(d.length&&d.length!==10){national.setCustomValidity('کد ملی باید دقیقاً ۱۰ رقم باشد.');msg('کد ملی باید دقیقاً ۱۰ رقم باشد.',true);if(refocus)national.focus();return false;} if(d.length===10&&!validNC(d)){national.setCustomValidity('کد ملی واردشده معتبر نیست.');msg('کد ملی واردشده معتبر نیست.',true);if(refocus)national.focus();return false;} national.setCustomValidity('');return true; } national.addEventListener('beforeinput',e=>{if(e.inputType==='insertText'&&e.data){const d=digits(e.data);const selected=national.selectionEnd-national.selectionStart;if(/\D/.test(d)||national.value.length-selected+d.length>10){e.preventDefault();msg('کد ملی فقط ۱۰ رقم است؛ ورود رقم اضافی مجاز نیست.',true);}}}); national.addEventListener('paste',e=>{const pasted=digits(e.clipboardData?.getData('text')||'').replace(/\D/g,'');const selected=national.selectionEnd-national.selectionStart;if(national.value.length-selected+pasted.length>10){e.preventDefault();msg('کد ملی بیشتر از ۱۰ رقم است؛ مقدار را اصلاح کنید.',true);national.setCustomValidity('کد ملی بیشتر از ۱۰ رقم مجاز نیست.');national.focus();}}); national.addEventListener('input',()=>{national.setCustomValidity('');nationalCheck(false);}); national.addEventListener('blur',()=>{if(ncRefocusing||!national.value)return;if(!nationalCheck(false)){ncRefocusing=true;setTimeout(()=>{ncRefocusing=false;if(!national.readOnly&&national.isConnected&&document.visibilityState==='visible')national.focus();},0);}}); // ensure empty prefix after new/reset and after loading existing student, without altering other API data. const originalReset=reset; reset=function(){originalReset();for(const k of ['father_mobile','mother_mobile'])$('stu_'+k).value='09';}; reset(); })(); `); doc.close(); let printed=false; const doPrint=()=>{if(printed)return;printed=true;try{frame.contentWindow.focus();frame.contentWindow.print()}catch(e){msg('چاپ در مرورگر انجام نشد.',true)}setTimeout(()=>frame.remove(),60000)}; if(image&&image.src){ const img=doc.createElement('img');img.src=image.src;img.alt='عکس دانش‌آموز'; const target=doc.getElementById('photo');target.replaceWith(img);target.id='photo'; if(img.complete)setTimeout(doPrint,300);else{img.onload=()=>setTimeout(doPrint,200);img.onerror=doPrint;} }else setTimeout(doPrint,250); } async function openStudent(id){try{const d=await request('/api/school/students/'+id);const p=d.student?.profile_data||{};reset();editing=id;for(const k of textFields)$('stu_'+k).value=p[k]??'';for(const k of boolFields){const el=$('stu_'+k);el.indeterminate=false;el.checked=(k==='father_alive'||k==='mother_alive')?(p[k]===0||p[k]===false):(p[k]===1||p[k]===true);el.dataset.originalNull=(p[k]===null||p[k]===undefined)?'1':'0';}syncMedicalNotes();loadStudentPhoto(id);$('stu_national_code').readOnly=true;const sf=digits($('stu_father_mobile').value),sm=digits($('stu_mother_mobile').value),ss=digits($('stu_sms_mobile').value);smsChoice.value=ss&&ss===sf&&ss===sm?'both':ss&&ss===sf?'father':ss&&ss===sm?'mother':'';$('stuFormTitle').textContent='ویرایش پرونده سال فعال';$('stuSave').textContent='ذخیره تغییرات';msg('پرونده برای ویرایش باز شد.');if(!$('studentRegistrationPage').classList.contains('sa-modal-mode'))window.scrollTo({top:0,behavior:'smooth'});}catch(e){msg(e.message,true);}} window.saOpenExistingStudent=openStudent; window.showStudentRegistration=async function(){const saFromAffairs=!$('studentAffairsPage').classList.contains('hide');if(saFromAffairs){$('studentRegistrationPage').classList.add('sa-modal-mode');document.body.classList.add('sa-modal-active');}else{$('studentRegistrationPage').classList.remove('sa-modal-mode');document.body.classList.remove('sa-modal-active');}for(const id of ['dashboard','profilePage','accountPage','yearsPage','classesPage','teachersPage','subjectsPage','assignmentsPage','unclassifiedPage'])$(id)?.classList.add('hide');$('studentRegistrationPage').classList.remove('hide');if(saFromAffairs){$('studentAffairsPage').classList.remove('hide');}else{$('topTitle').textContent='پیش‌ثبت‌نام دانش‌آموز';}if(!saFromAffairs)$('topCrumb').textContent='امور دانش‌آموزی / پیش‌ثبت‌نام';$('sidebar')?.classList.remove('open');$('mask')?.classList.remove('open');reset();try{const d=await request('/api/school/students/context');$('stuYear').textContent='سال تحصیلی: '+(d.active_year?.caption||'فعال نشده');if(!d.active_year)msg('برای ثبت دانش‌آموز ابتدا سال تحصیلی را فعال کنید.',true);await refresh();}catch(e){msg('خطا در ارتباط با API: '+e.message,true);}}; document.querySelectorAll('[data-stu-tab]').forEach(b=>b.addEventListener('click',()=>tab(b.dataset.stuTab))); $('stuNext').addEventListener('click',()=>tab(document.querySelector('[data-stu-tab].active')?.dataset.stuTab==='main'?'extra':'main')); $('stuNew').addEventListener('click',reset); $('stuRefresh').addEventListener('click',async()=>{try{await refresh()}catch(e){msg(e.message,true)}}); $('stuSearch').addEventListener('input',()=>{clearTimeout(searchTimer);searchTimer=setTimeout(()=>{stuDirPage=1;refresh().catch(e=>msg(e.message,true))},300)}); for(const k of boolFields){const el=$('stu_'+k);el.addEventListener('change',()=>{el.indeterminate=false;el.dataset.touched='1';if(k==='has_medical_condition')syncMedicalNotes();});} $('stu_national_code').addEventListener('blur',()=>{const input=$('stu_national_code');const v=digits(input.value.trim());if(v&&!validNC(v)){tab('main');msg('کد ملی واردشده معتبر نیست. لطفاً آن را اصلاح کنید.',true);input.setAttribute('aria-invalid','true');}else{input.removeAttribute('aria-invalid');if(v)msg('');}}); // MODINO identity intelligence: lookup is advisory; server enforces matching identity. let stuIdentitySeq=0,stuIdentityLast=null; const stuIdentityBox=document.createElement('div'); stuIdentityBox.id='stuIdentityBox';stuIdentityBox.className='hide'; stuIdentityBox.style.cssText='margin:12px 0;padding:15px 17px;border-radius:15px;border:1px solid var(--stroke);background:var(--accent2);line-height:2;font-size:12px'; const stuIdentityInput=$('stu_national_code'); stuIdentityInput.closest('.field, .stu-field, label')?.insertAdjacentElement('afterend',stuIdentityBox); if(!stuIdentityBox.isConnected)stuIdentityInput.insertAdjacentElement('afterend',stuIdentityBox); function stuIdentityShow(text,warning=false){stuIdentityBox.textContent=text;stuIdentityBox.classList.remove('hide');stuIdentityBox.style.borderColor=warning?'#ed9b9b':'var(--stroke)';stuIdentityBox.style.background=warning?'#fff0f0':'var(--accent2)';stuIdentityBox.style.color=warning?'#a53142':'var(--ink)';} function stuIdentityClear(){stuIdentitySeq++;stuIdentityLast=null;stuIdentityBox.classList.add('hide');stuIdentityBox.textContent='';} async function stuIdentityCheck(){ if(editing)return null; const code=digits(stuIdentityInput.value).replace(/\D/g,''); const seq=++stuIdentitySeq; if(!validNC(code)){stuIdentityLast=null;stuIdentityBox.classList.add('hide');return null;} try{ const result=await request('/api/school/students/identity-lookup?national_code='+encodeURIComponent(code)); if(seq!==stuIdentitySeq||digits(stuIdentityInput.value).replace(/\D/g,'')!==code)return null; stuIdentityLast={code,result}; if(!result.found){stuIdentityShow('کد ملی جدید است؛ پرونده‌ای با این کد ملی در این مدرسه پیدا نشد.');return result;} const fullname=[result.first_name,result.last_name].filter(Boolean).join(' '); if(result.registered_current_year){stuIdentityShow('این کد ملی متعلق به «'+fullname+'» است و در سال جاری ثبت شده'+(result.class_name?'؛ کلاس: '+result.class_name:'؛ هنوز کلاس‌بندی نشده')+'. برای مشاهده یا ویرایش، پرونده موجود را باز کنید.',true);} else{stuIdentityShow('پرونده قبلی شناسایی شد: «'+fullname+'»؛ نام پدر: '+(result.father_name||'—')+'. این دانش‌آموز در سال جاری ثبت نشده است. برای ثبت‌نام مجدد، مشخصات هویتی باید دقیقاً با پرونده قبلی مطابقت داشته باشد.'); for(const [key,value] of [['first_name',result.first_name],['last_name',result.last_name],['father_name',result.father_name]]){const field=$('stu_'+key);if(field&&!field.value.trim())field.value=value||'';} } return result; }catch(e){if(seq===stuIdentitySeq)stuIdentityShow('بررسی کد ملی انجام نشد: '+e.message,true);return null;} } stuIdentityInput.addEventListener('input',()=>{stuIdentityClear();}); stuIdentityInput.addEventListener('blur',()=>{stuIdentityCheck();}); function stuNormName(v){return String(v||'').replace(/ي/g,'ی').replace(/ك/g,'ک').replace(/[\u200c\s]+/g,' ').trim();} async function stuIdentityBeforeSave(data){ if(editing)return true; const code=data.national_code; let found=stuIdentityLast?.code===code?stuIdentityLast.result:null; if(!found)found=await stuIdentityCheck(); if(!found){msg('بررسی پرونده قبلی انجام نشد؛ دوباره تلاش کنید.',true);return false;} if(!found.found)return true; if(found.registered_current_year){stuIdentityShow('این دانش‌آموز قبلاً در سال جاری ثبت‌نام شده است'+(found.class_name?' و در کلاس '+found.class_name+' قرار دارد':'')+'.',true);msg('ثبت‌نام تکراری مجاز نیست. پرونده موجود را باز کنید.',true);return false;} if(['first_name','last_name','father_name'].some(k=>stuNormName(data[k])!==stuNormName(found[k]))){stuIdentityShow('مغایرت هویتی! نام ثبت‌شده قبلی: '+found.first_name+' '+found.last_name+'، نام پدر: '+found.father_name+'. ثبت‌نام با نام متفاوت مسدود شد.',true);msg('کد ملی متعلق به دانش‌آموز دیگری است. مشخصات را مطابق پرونده قبلی اصلاح کنید.',true);return false;} return window.confirm('پرونده قبلی «'+found.first_name+' '+found.last_name+'» پیدا شد. ثبت‌نام مجدد همین دانش‌آموز در سال جاری انجام شود؟'); } $('stuForm').addEventListener('submit',async ev=>{ev.preventDefault();if(busy)return;const data=payload();if(!validNC(data.national_code)){tab('main');msg('کد ملی واردشده معتبر نیست. لطفاً کد ملی ۱۰ رقمی و رقم کنترل آن را بررسی کنید.',true);$('stu_national_code').focus();return;}if(!data.first_name||!data.last_name||!data.father_name||!data.birth_date||!data.gender){tab('main');msg('نام، نام خانوادگی، نام پدر، تاریخ تولد و جنسیت ضروری هستند.',true);return;}for(const k of ['father_mobile','mother_mobile','sms_mobile'])if(data[k]&&!/^09\d{9}$/.test(data[k])){tab('main');msg('شماره موبایل باید ۱۱ رقم و با ۰۹ شروع شود.',true);return;}if(smsChoice.value==='both'&&(!data.father_mobile||!data.mother_mobile)){msg('برای انتخاب پدر و مادر، شماره همراه هر دو را وارد کنید.',true);return;}if(smsChoice.value==='father'&&!data.father_mobile||smsChoice.value==='mother'&&!data.mother_mobile){msg('ابتدا شماره همراه دریافت‌کننده پیامک را وارد کنید.',true);return;}if(!(await stuIdentityBeforeSave(data)))return;busyState(true);msg('در حال ذخیره اطلاعات...');try{const d=await request(editing?'/api/school/students/'+editing:'/api/school/students',editing?'PUT':'POST',data);const savedId=Number(d.student_id||editing);if(!Number.isSafeInteger(savedId)||savedId<1)throw Error('اطلاعات ذخیره شد، اما شناسه دانش‌آموز برای ذخیره عکس دریافت نشد.');let photoMessage='';try{photoMessage=await saveStudentPhoto(savedId);}catch(photoError){editing=savedId;msg('اطلاعات دانش‌آموز ذخیره شد، اما عکس ذخیره نشد: '+photoError.message+' لطفاً دوباره ذخیره کنید؛ پرونده تکراری نسازید.',true);return;}reset();msg((d.message||'اطلاعات ذخیره شد.')+(photoMessage?' '+photoMessage:''));await refresh();}catch(e){msg(e.message,true);}finally{busyState(false);}}); // National-code prefix lookup: city of issuance is a suggestion, not proof of birthplace. const issueField=$('stu_issue_place'); const prefixChoices=$('stuPrefixChoices'); const prefixHint=$('stuPrefixHint'); async function lookupIssuePlace(){ const code=digits($('stu_national_code').value).replace(/\D/g,''); const isValid=code.length===10&&validNC(code); const prefix=isValid?code.slice(0,3):''; if(!isValid){ ++prefixRequestId; lastPrefix=''; issueField.value=''; delete issueField.dataset.prefixSuggested; prefixChoices.classList.add('hide'); prefixChoices.replaceChildren(new Option('انتخاب شهر محل صدور شناسنامه','')); prefixHint.textContent=''; return; } if(prefix===lastPrefix)return; lastPrefix=prefix; const seq=++prefixRequestId; prefixChoices.classList.add('hide'); prefixChoices.replaceChildren(new Option('انتخاب شهر محل صدور شناسنامه','')); prefixHint.textContent=''; if($('stu_national_code').readOnly)return; issueField.value=''; delete issueField.dataset.prefixSuggested; prefixHint.textContent='در حال بررسی پیشوند کد ملی...'; try{ const result=await request('/api/school/students/national-prefix?prefix='+encodeURIComponent(prefix)); if(seq!==prefixRequestId || !validNC(digits($('stu_national_code').value)))return; const matches=Array.isArray(result.matches)?result.matches:[]; if(!matches.length){prefixHint.textContent='شهری برای این پیشوند یافت نشد؛ محل صدور را دستی وارد کنید.';return;} if(matches.length===1){ const city=matches[0].city_name||''; issueField.value=city; issueField.dataset.prefixSuggested='1'; prefixHint.textContent='پیشنهاد بر اساس کد ملی: '+city+' ('+(matches[0].province_name||'')+'). با شناسنامه تطبیق دهید.'; return; } for(const row of matches){ const city=row.city_name||''; if(!city)continue; prefixChoices.add(new Option(city+' — '+(row.province_name||''),city)); } prefixChoices.classList.remove('hide'); prefixHint.textContent='این پیشوند متعلق به چند شهر است؛ محل صدور را از فهرست انتخاب یا دستی وارد کنید.'; }catch(e){ if(seq===prefixRequestId)prefixHint.textContent='استعلام شهر در دسترس نیست؛ محل صدور را دستی وارد کنید.'; } } prefixChoices.addEventListener('change',()=>{ if(prefixChoices.value){issueField.value=prefixChoices.value;issueField.dataset.prefixSuggested='1';} }); issueField.addEventListener('input',()=>{issueField.dataset.prefixSuggested='0';}); $('stu_national_code').addEventListener('input',lookupIssuePlace); // MODINO v2: Persian birth calendar and family SMS recipient choice. const smsChoice=$('stuSmsChoice'); const calBack=$('stuCalendarBackdrop'); const calFmt=new Intl.DateTimeFormat('en-US-u-ca-persian-nu-latn',{year:'numeric',month:'numeric',day:'numeric',timeZone:'UTC'}); const calMonthNames=['فروردین','اردیبهشت','خرداد','تیر','مرداد','شهریور','مهر','آبان','آذر','دی','بهمن','اسفند']; let calYear=1394,calMonth=7; function persianParts(d){const p=calFmt.formatToParts(d);return {y:Number(p.find(x=>x.type==='year')?.value),m:Number(p.find(x=>x.type==='month')?.value),d:Number(p.find(x=>x.type==='day')?.value)};} function pad2(n){return String(n).padStart(2,'0');} function calendarDays(y,m){const out=[];const approx=y+621;for(let t=Date.UTC(approx-1,11,1),end=Date.UTC(approx+1,4,1);ta.day-b.day);} function renderCalendar(){const ys=$('stuCalYear'),ms=$('stuCalMonth');if(!ys.options.length){for(let y=1370;y<=1410;y++){const o=document.createElement('option');o.value=y;o.textContent=String(y);ys.append(o);}for(let i=0;i<12;i++){const o=document.createElement('option');o.value=i+1;o.textContent=calMonthNames[i];ms.append(o);}}ys.value=String(calYear);ms.value=String(calMonth);const grid=$('stuCalGrid');grid.replaceChildren();for(const w of ['ش','ی','د','س','چ','پ','ج']){const d=document.createElement('div');d.className='weekday';d.textContent=w;grid.append(d);}const days=calendarDays(calYear,calMonth);if(!days.length)return;for(let i=0;i{$('stu_birth_date').value=val;calBack.classList.remove('open');});grid.append(b);}} function openCalendar(){const m=digits($('stu_birth_date').value).match(/^(\d{4})\/(\d{1,2})\/(\d{1,2})$/);if(m){calYear=Number(m[1]);calMonth=Number(m[2]);}renderCalendar();calBack.classList.add('open');} $('stu_birth_date').addEventListener('click',openCalendar);$('stuOpenCalendar').addEventListener('click',openCalendar); $('stuCalClose').addEventListener('click',()=>calBack.classList.remove('open')); calBack.addEventListener('click',e=>{if(e.target===calBack)calBack.classList.remove('open')}); $('stuCalYear').addEventListener('change',e=>{calYear=Number(e.target.value);renderCalendar()}); $('stuCalMonth').addEventListener('change',e=>{calMonth=Number(e.target.value);renderCalendar()}); function shiftCal(n){calMonth+=n;if(calMonth<1){calMonth=12;calYear--;}if(calMonth>12){calMonth=1;calYear++;}calYear=Math.min(1410,Math.max(1370,calYear));renderCalendar();} $('stuCalPrev').addEventListener('click',()=>shiftCal(-1));$('stuCalNext').addEventListener('click',()=>shiftCal(1)); $('stuCalClear').addEventListener('click',()=>{$('stu_birth_date').value='';calBack.classList.remove('open')}); function smsValue(){const f=digits($('stu_father_mobile').value.trim()),m=digits($('stu_mother_mobile').value.trim());if(smsChoice.value==='father')return f==='09'?'':f;if(smsChoice.value==='mother')return m==='09'?'':m;if(smsChoice.value==='both')return (f!=='09'?f:'')||(m!=='09'?m:'');return '';} function updateSms(){ $('stu_sms_mobile').value=smsValue();} smsChoice.addEventListener('change',updateSms);$('stu_father_mobile').addEventListener('input',updateSms);$('stu_mother_mobile').addEventListener('input',updateSms); // MODINO V4: persistent mobile prefix and national code controls. for(const key of ['father_mobile','mother_mobile']){ const el=$('stu_'+key); const normalize=()=>{let d=digits(el.value).replace(/\D/g,'');if(!d){el.value='09';return;}if(d.startsWith('0098'))d='0'+d.slice(4);else if(d.startsWith('98'))d='0'+d.slice(2);else if(d.startsWith('9')&&d.length>=10)d='0'+d;else if(!d.startsWith('09'))d='09'+d.replace(/^0+/,'');el.value=('09'+d.slice(2).replace(/\D/g,'').slice(0,9));}; el.addEventListener('focus',()=>{normalize();requestAnimationFrame(()=>{if(el.selectionStart<2)el.setSelectionRange(2,2);});}); el.addEventListener('input',()=>{const before=el.value;normalize();if(before!==el.value)el.setCustomValidity('');updateSms();}); el.addEventListener('keydown',e=>{if((e.key==='Backspace'&&el.selectionStart<=2&&el.selectionEnd<=2)||(e.key==='Delete'&&el.selectionStart<2&&el.selectionEnd<=2)){e.preventDefault();}}); el.addEventListener('click',()=>{if(el.selectionStart<2)el.setSelectionRange(2,2);}); el.addEventListener('blur',()=>{if(el.value==='09')el.value='';updateSms();}); } const national=$('stu_national_code'); national.setAttribute('maxlength','10');national.setAttribute('pattern','[0-9]{10}');national.setAttribute('dir','ltr'); let ncRefocusing=false; function nationalCheck(refocus=false){ if(national.readOnly)return true; const d=digits(national.value).replace(/\D/g,''); if(d.length>10){national.value=d.slice(0,10);national.setCustomValidity('کد ملی بیشتر از ۱۰ رقم مجاز نیست.');msg('کد ملی بیشتر از ۱۰ رقم مجاز نیست.',true);if(refocus)national.focus();return false;} national.value=d; if(d.length&&d.length!==10){national.setCustomValidity('کد ملی باید دقیقاً ۱۰ رقم باشد.');msg('کد ملی باید دقیقاً ۱۰ رقم باشد.',true);if(refocus)national.focus();return false;} if(d.length===10&&!validNC(d)){national.setCustomValidity('کد ملی واردشده معتبر نیست.');msg('کد ملی واردشده معتبر نیست.',true);if(refocus)national.focus();return false;} national.setCustomValidity('');return true; } national.addEventListener('beforeinput',e=>{if(e.inputType==='insertText'&&e.data){const d=digits(e.data);const selected=national.selectionEnd-national.selectionStart;if(/\D/.test(d)||national.value.length-selected+d.length>10){e.preventDefault();msg('کد ملی فقط ۱۰ رقم است؛ ورود رقم اضافی مجاز نیست.',true);}}}); national.addEventListener('paste',e=>{const pasted=digits(e.clipboardData?.getData('text')||'').replace(/\D/g,'');const selected=national.selectionEnd-national.selectionStart;if(national.value.length-selected+pasted.length>10){e.preventDefault();msg('کد ملی بیشتر از ۱۰ رقم است؛ مقدار را اصلاح کنید.',true);national.setCustomValidity('کد ملی بیشتر از ۱۰ رقم مجاز نیست.');national.focus();}}); national.addEventListener('input',()=>{national.setCustomValidity('');nationalCheck(false);}); national.addEventListener('blur',()=>{if(ncRefocusing||!national.value)return;if(!nationalCheck(false)){ncRefocusing=true;setTimeout(()=>{ncRefocusing=false;if(!national.readOnly&&national.isConnected&&document.visibilityState==='visible')national.focus();},0);}}); // ensure empty prefix after new/reset and after loading existing student, without altering other API data. const originalReset=reset; reset=function(){originalReset();for(const k of ['father_mobile','mother_mobile'])$('stu_'+k).value='09';}; reset(); })();
MODINO / STAFF ATTENDANCE

مدیریت حضور و غیاب کارکنان

سال تحصیلی فعال: در حال دریافت...

حاضر—
غایب موجه—
غایب غیرموجه—
تأخیر—
تعجیل—

ثبت وضعیت روزانه

در صورت ثبت نشدن غیبت یا وضعیت استثنایی، عنوان «حاضر» نمایش داده می‌شود. این نمایش به معنی تأیید قطعی حضور نیست.

تاریخمعلموضعیتتأخیر / تعجیلتوضیحاتعملیات

ثبت تأخیر ورود یا تعجیل خروج

نام معلم و تاریخ را انتخاب کنید؛ اگر حضور او هنوز ثبت نشده باشد، سیستم هنگام ثبت رویداد یک رکورد حضور ایجاد می‌کند. برای روزهای غیبت یا تعطیل، ثبت تأخیر و تعجیل مجاز نیست.

سوابق تأخیر و تعجیل

سوابق ثبت‌شده برای تاریخ انتخابی؛ اطلاعات مستقیماً از سرور خوانده می‌شود.

تاریخمعلمنوعساعت وقوعمدتعلتتوضیحات
برای مشاهده سوابق، تاریخ را انتخاب کنید.

ثبت مرخصی و مأموریت

درخواست‌ها

معلمنوعبازهوضعیتتصمیم مدیر

گزارش حضور و غیاب

خلاصه و سوابق بازه انتخابی

تاریخمعلموضعیت ثبت‌شدهتوضیحات