نظام حضور وانصراف NFC

<!DOCTYPE html>

<html lang="ar" dir="rtl">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width,initial-scale=1.0">


<title>نظام حضور وانصراف NFC</title>


<style>


*{

    box-sizing:border-box;

    font-family:Tahoma,Arial,sans-serif;

}


جسم{

    margin:0;

    الخلفية: #f3f6f9؛

    اللون: #222؛

}


رأس الصفحة {

    الخلفية: #075985؛

    اللون: أبيض؛

    text-align:center;

    الحشو: 25 بكسل 15 بكسل؛

}


رأس الصفحة h1{

    margin:0 0 8px;

}


.حاوية{

    الحد الأقصى للعرض: 900 بكسل؛

    margin:auto;

    الحشو: 15 بكسل؛

}


.بطاقة{

    الخلفية: بيضاء؛

    margin:15px 0;

    الحشو: 20 بكسل؛

    border-radius:15px;

    box-shadow:0 3px 15px rgba(0,0,0,.08);

}


.موظف{

    text-align:center;

}


.اسم-الموظف{

    حجم الخط: 27 بكسل؛

    وزن الخط: عريض؛

    اللون: #075985؛

    margin:10px;

}


.ساعة{

    حجم الخط: 35 بكسل؛

    وزن الخط: عريض؛

    margin:15px;

}


زر{

    العرض: 100%؛

    الحشو: 17 بكسل؛

    margin:6px 0;

    border:0;

    border-radius:10px;

    اللون: أبيض؛

    الخلفية: #075985؛

    حجم الخط: 18 بكسل؛

}


زر: تحويم

    الشفافية: 0.9؛

}


.nfc{

    الخلفية: #0284c7؛

    حجم الخط: 21 بكسل؛

}


.أخضر{

    الخلفية: #16a34a؛

}


.أحمر{

    الخلفية: #dc2626؛

}


.رمادي{

    الخلفية: #475569؛

}


.رسالة{

    الحشو: 15 بكسل؛

    border-radius:10px;

    margin-top:12px;

    text-align:center;

}


.نجاح{

    الخلفية: #dcfce7؛

    اللون: #166534؛

}


.خطأ{

    الخلفية: #fee2e2;

    اللون: #991b1b؛

}


.معلومات{

    الخلفية: #e0f2fe;

    اللون: #075985؛

}


.حالة{

    display:grid;

    grid-template-columns:1fr 1fr;

    الفجوة: 12 بكسل؛

}


.status-box{

    text-align:center;

    الخلفية: #f8fafc;

    الحشو: 20 بكسل؛

    border-radius:12px;

}


.status-box span{

    display:block;

    اللون: #64748b؛

}


.status-box strong{

    display:block;

    حجم الخط: 27 بكسل؛

    اللون: #075985؛

    margin-top:8px;

}


.stats{

    display:grid;

    grid-template-columns:repeat(3,1fr);

    الفجوة: 10 بكسل؛

}


.stat{

    الخلفية: #f0f9ff;

    text-align:center;

    الحشو: 20 بكسل؛

    border-radius:12px;

}


.stat strong{

    display:block;

    حجم الخط: 30 بكسل؛

    اللون: #075985؛

}


مدخل{

    العرض: 100%؛

    الحشو: 13 بكسل؛

    border:1px solid #ccc;

    border-radius:8px;

    حجم الخط: 16 بكسل؛

    margin:5px 0 10px;

}


.طاولة{

    overflow-x:auto;

}


طاولة{

    العرض: 100%؛

    الحد الأدنى للعرض: 600 بكسل؛

    border-collapse:collapse;

    margin-top:15px;

}


ذ{

    الخلفية: #075985؛

    اللون: أبيض؛

    الحشو: 12 بكسل؛

}


td{

    text-align:center;

    الحشو: 12 بكسل؛

    border-bottom:1px solid #ddd;

}


.uid{

    الاتجاه: من اليسار إلى اليمين؛

    font-family:monospace;

    word-break:break-all;

}


.صغير{

    حجم الخط: 13 بكسل؛

    ارتفاع السطر: 1.8؛

    اللون: #64748b؛

}


@media(max-width:600px){


    .حالة{

        grid-template-columns:1fr;

    }


    .stats{

        grid-template-columns:1fr;

    }


    .ساعة{

        حجم الخط: 28 بكسل؛

    }

}


</style>

</head>


<body>


<header>


<h1> ونظام الحضور النصراف</h1>


<div>تسجيل الموظف بواسطة بطاقة NFC</div>


</header>



<div class="container">



<!-- بيانات الموظف -->


<div class="card employee">


<h2>بيانات الموظف</h2>


<div class="employee-name" id="employeeName">

فوت شو

</div>


<div id="date"></div>


<div class="clock" id="clock">

00:00:00

</div>


</div>



<!-- NFC -->


<div class="card">


📱 اتصال NFC </h2>


<button class="nfc" onclick="startNFC()">


📡 أجهزة NFC


</button>


<div id="nfcMessage"></div>


</div>



<!-- مناسب اليوم -->


<div class="card">


<h2>الاستراتيجية اليوم</h2>


<div class="status">


<div class="status-box">


<span>وقت الحضور</span>


<strong id="inTime">

--

</strong>


</div>



<div class="status-box">


<span>وقت الانصراف</span>


<strong id="outTime">

--

</strong>


</div>


</div>


</div>



<!--الرياضات -->


<div class="card">


<h2> إحصائيات الشهر</h2>


<div class="stats">


<div class="stat">


<strong id="daysPresent">

0

</strong>


ميزت


</div>



<div class="stat">


<strong id="daysComplete">

0

</strong>


خطط مرفوعة


</div>



<div class="stat">


<strong id="hours">

0

</strong>


ركنوك


</div>


</div>


</div>



<!-- خطأ -->


<div class="card">


<h2>📊 تغليف</h2>


<input

اكتب "شهر"

id="month"

onchange="showReport()"

>


<button onclick="exportCSV()">


📥 تصدير التقرير


</button>


<div class="table">


<جدول>


<thead>


<tr>


<th>الجهاز</th>


<th>المينور</th>


<th>الانصراف</th>


<th>ساعات العمل</th>


</tr>


</thead>


<tbody id="report"></tbody>


</table>


</div>


</div>



<!-- اختيار -->


<div class="card">


<h2>⚙️ إعداد الموظف</h2>


<input

id="name"

placeholder="اسم الموظف"

>


<input

id="uid"

class="uid"

العنصر النائب = "بطاقة UID NFC"

>


<button onclick="saveSettings()">


حفظ بيانات الموظف


</button>


<p class="small">


لربط البطاقة بالموظف، شغّل قارئ NFC ثم مرر البطاقة.

سيظهر بطاقة UID هنا مباشرة.

بعد ذلك اضغط على حفظ بيانات الموظف.


</p>


</div>



<!--حذف البيانات -->


<div class="card">


<button class="red" onclick="deleteAll()">


حذف جميع تسجيلات


</button>


</div>



</div>



<script>


/* ==============================

   جحيم

============================== */


let settings =

JSON.parse(

localStorage.getItem("nfcEmployee")

) || {


    الاسم:"موظف المدرسة"،

    uid:""


};



/* ==============================

   أليس

============================== */


let records =

JSON.parse(

localStorage.getItem("nfcAttendance")

) || {};



/* ==============================

   سبي

============================== */


دالة حفظ(){


localStorage.setItem(

"موظف إن إف سي"

JSON.stringify(settings)

);


localStorage.setItem(

"حضور تقنية الاتصال قريب المدى"،

JSON.stringify(records)

);


}



/* ==============================

   الشعب

============================== */


دالة dateKey(){


let d=new Date();


return d.getFullYear()+"-"+


String(d.getMonth()+1).padStart(2,"0")+"-"+


String(d.getDate()).padStart(2,0");


}



/* ==============================

   خطر

============================== */


دالة timeString(date){


إرجاع التاريخ.toLocaleTimeString(

"ar-SA"،

{

الساعة: "رقمين"،

دقيقة: "رقمين"،

ثانياً: "رقمين"

});


}



/* ==============================

   زعيم

============================== */


دالة الساعة(){


let now=new Date();


document.getElementById("clock")

.textContent=

timeString(now);


document.getElementById("date")

.textContent=

now.toLocaleDateString(

"ar-SA"،

{

أيام الأسبوع: "طويلة"

السنة: "رقمية"،

الشهر: "طويل"

اليوم: "رقمي"

});


}


setInterval(clock,1000);


ساعة()؛



/* ==============================

   تحميل

============================== */


دالة تحميل الإعدادات(){


document.getElementById("employeeName")

.textContent=settings.name;


document.getElementById("name")

.value=settings.name;


document.getElementById("uid")

.value=settings.uid;


}



/* ==============================

   حفظ الكفال

============================== */


دالة حفظ الإعدادات(){


let name=

document.getElementById("name")

.value.trim();


let uid=

document.getElementById("uid")

.value.trim();


إذا لم يكن هناك اسم {


تنبيه("أدخل اسم الموظف");


يعود؛


}


settings.name=name;


settings.uid=

normalizeUID(uid);


يحفظ()؛


document.getElementById("employeeName")

.textContent=name;


عرض الرسالة (

"تم حفظ بيانات الموظف والبطاقة"،

"نجاح"

);


}



/* ==============================

   حساب UID

============================== */


دالة normalizeUID(uid){


أعد String(uid)

.replace(/:/g,"")

.replace(/-/g,"")

.replace(/\s/g,"")

.toUpperCase();


}



/* ==============================

   رات

============================== */


دالة عرض الرسالة (النص، النوع) {


document.getElementById("nfcMessage")

.innerHTML=

`<div class="message ${type}">

${text}

</div>`;


}



/* ==============================

   ولاية كارولاينا الشمالية

============================== */


دالة تسجيل البطاقة (uid){


uid=normalizeUID(uid);



/* إذا لم تكن هناك بطاقة محفوظة */


إذا لم يكن هناك معرف مستخدم (!settings.uid)


document.getElementById("uid")

.value=uid;


عرض الرسالة (

"تمت إضافة بطاقة فعالة. تم وضع UID في خانة البطاقة. اضغط على حفظ بيانات الموظف.",

"نجاح"

);


يعود؛


}



/* التحقق من البطاقة */


إذا كان (uid !== normalizeUID(settings.uid)){


عرض الرسالة (

"❌ هذه ليست بطاقة الموظف البديل.",

"خطأ"

);


يعود؛


}



/* تصميم اليوم */


let today=dateKey();



/* سجل التاريخ */


إذا لم تكن السجلات موجودة في [اليوم] {


السجلات[اليوم]={

in:null,

الناتج: لا شيء

};


}



let record=records[today];


let now=new Date();



/* ==============================

   حضر

============================== */


إذا لم يكن السجل موجودًا،


record.in=now.toISOString();


يحفظ()؛


عرض الرسالة (

""تم تسجيل الحضور للساعة"+

timeString(now),

"نجاح"

);


}



/* ==============================

   الصارف

============================== */


وإلا إذا لم يكن السجل موجودًا (!record.out){


record.out=now.toISOString();


يحفظ()؛


عرض الرسالة (

""تم تسجيل الانصراف الساعة"+

timeString(now),

"نجاح"

);


}



/* ==============================

   ميو

============================== */


آخر{


عرض الرسالة (

"ℹ️ تم تسجيل الحضور والانصراف لهذا اليوم ليالي.",

"معلومات"

);


}



تحديث اليوم();


showReport();


}



/* ==============================

   تقنية NFC

============================== */


async function startNFC(){


/* دراسة القص */


إذا لم يكن "NDEFReader" موجودًا في النافذة،


عرض الرسالة (

"❌ Web NFC غير مدعوم. استخدم Samsung Android + Google Chrome، وتأكد من تشغيل NFC.",

"خطأ"

);


يعود؛


}



يحاول{


const ndef=

new NDEFReader();



عرض الرسالة (

"جاري تشغيل قارئ NFC...",

"معلومات"

);



انتظر ndef.scan();



عرض الرسالة (

"🟢 قارئ NFC جاهز. ضع بطاقة خلف الهاتف.",

"نجاح"

);



/* عالم */


ndef.onreading=

الحدث => {


let uid=

event.serialNumber;



/* إذا لم يتوفر إصدار UID */


إذا لم يكن هناك معرف مستخدم (uid) {


عرض الرسالة (

"❌ لم يتم الحصول على UID من البطاقة.",

"خطأ"

);


يعود؛


}



registerCard(uid);


};



/* معرفة الألف */


ndef.onreadingerror=()=>{


عرض الرسالة (

"❌ عذرت قراءة البطاقة. ضعها ملاصقة للهاتف مرة أخرى.",

"خطأ"

);


};



}catch(error){


console.error(error);


عرض الرسالة (

"❌ خطأ في التشغيل في NFC: "+

رسالة الخطأ،

"خطأ"

);


}


}



/* ==============================

   تحديث اليوم

============================== */


دالة تحديث اليوم(){


let today=dateKey();


let record=records[today];



document.getElementById("inTime")

.textContent="--";


document.getElementById("outTime")

.textContent="--";



إذا لم يكن هناك سجل، فقم بالخروج.



إذا كان (record.in) {


document.getElementById("inTime")

.textContent=

timeString(

تاريخ جديد (record.in)

);


}



إذا كان (record.out) {


document.getElementById("outTime")

.textContent=

timeString(

تاريخ جديد (record.out)

);


}


}



/* ==============================

   النتيجة

============================== */


دالة عرض التقرير(){


let month=

document.getElementById("month").value;


let table=

document.getElementById("report");


table.innerHTML="";



let present=0;


let complete=0;


لنفترض أن إجمالي الساعات يساوي صفرًا؛



Object.keys(records)

.نوع()

.يعكس()

.forEach(date=>{


إذا لم يبدأ التاريخ بالشهر

يعود؛



let r=records[date];


موجود++;



let hours=0;



إذا كان (r.in && r.out){


let start=

تاريخ جديد(r.in)؛


let end=

new Date(r.out);


ساعات=

(نهاية-بداية)/1000/60/60؛


totalHours+=hours;


مكتمل++;


}



let tr=

document.createElement("tr");



tr.innerHTML=`


<td>${date}</td>


<td>

${r.in ? timeString(new Date(r.in)):"--"}

</td>


<td>

${r.out ? timeString(new Date(r.out)):"--"}

</td>


<td>

${ساعات؟ hours.toFixed(2)+"ساعة":"--"}

</td>


`;



table.appendChild(tr);


});



document.getElementById("daysPresent")

.textContent=present;



document.getElementById("daysComplete")

.textContent=complete;



document.getElementById("hours")

.textContent=

totalHours.toFixed(1);


}



/* ==============================

   ستيت لا

============================== */


دالة تصدير ملف CSV(){


let month=

document.getElementById("month").value;



let csv=

"\uFEFFالتاريخ,الحضور,الانصراف,ساعات العمل\n";



Object.keys(records)

.نوع()

.forEach(date=>{


إذا لم يبدأ التاريخ بالشهر

يعود؛



let r=records[date];


let hours="";



إذا كان (r.in && r.out){


ساعات=

(

(تاريخ جديد(r.out)-تاريخ جديد(r.in))

/1000/60/60

).toFixed(2);


}



csv+=

تاريخ+","+

(r.in ? timeString(new Date(r.in)):"")+","+

(r.out ? timeString(new Date(r.out)):"")+","+

ساعات+"\n";


});



let blob=

كائن ثنائي كبير جديد (

[csv],

{

type:"text/csv;charset=utf-8;"

}

);



let url=

URL.createObjectURL(blob);



ليكن a=

document.createElement("a");


a.href=url;


a.download=

"تقرير_الحضور_"+month+".csv";


a.click();


URL.revokeObjectURL(url);


}



/* ==============================

   غليتي

============================== */


دالة حذف الكل(){


إذا لم يتم التأكيد

"هل أنت متأكد من حذف جميع السجلات وحضورها وانصراف؟"

)){


يعود؛


}



السجلات = {};


يحفظ()؛


تحديث اليوم();


showReport();


تنبيه("تم حذف جميع أرقام تسجيل الدخول");


}



/* ==============================

   تاربت

============================== */


دالة التهيئة(){


loadSettings();


let now=new Date();


let month=

now.getFullYear()+"-"+

String(now.getMonth()+1).padStart(2,"0");


document.getElementById("month")

.value=month;


تحديث اليوم();


showReport();


}



تهيئة();


</script>


</body>

</html>