diff --git a/assets/css/custom.css b/assets/css/custom.css index e4322e1..06fb23e 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -522,3 +522,4 @@ body { color: #fff; } .group-meetings { color: #20c997 !important; } /* Teal */ +.group-committees { color: #8e24aa !important; } /* Purple */ diff --git a/charity_members.php b/charity_members.php new file mode 100644 index 0000000..44f93f1 --- /dev/null +++ b/charity_members.php @@ -0,0 +1,260 @@ +غير مصرح لك بالوصول لهذه الصفحة."; + require_once 'includes/footer.php'; + exit; +} + +$action = $_GET['action'] ?? 'list'; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + if (isset($_POST['add_member']) && (isAdmin() || canAdd('committees'))) { + $name = $_POST['name'] ?? ''; + $role = $_POST['role'] ?? ''; + $phone = $_POST['phone'] ?? ''; + $email = $_POST['email'] ?? ''; + $join_date = $_POST['join_date'] ?? date('Y-m-d'); + $status = $_POST['status'] ?? 'active'; + + $stmt = db()->prepare("INSERT INTO charity_members (name, role, phone, email, join_date, status) VALUES (?, ?, ?, ?, ?, ?)"); + $stmt->execute([$name, $role, $phone, $email, $join_date, $status]); + + $_SESSION['success'] = "تمت إضافة العضو بنجاح."; + redirect('charity_members.php'); + } elseif (isset($_POST['edit_member']) && (isAdmin() || canEdit('committees'))) { + $id = $_POST['id']; + $name = $_POST['name'] ?? ''; + $role = $_POST['role'] ?? ''; + $phone = $_POST['phone'] ?? ''; + $email = $_POST['email'] ?? ''; + $join_date = $_POST['join_date'] ?? date('Y-m-d'); + $status = $_POST['status'] ?? 'active'; + + $stmt = db()->prepare("UPDATE charity_members SET name = ?, role = ?, phone = ?, email = ?, join_date = ?, status = ? WHERE id = ?"); + $stmt->execute([$name, $role, $phone, $email, $join_date, $status, $id]); + + $_SESSION['success'] = "تم تحديث العضو بنجاح."; + redirect('charity_members.php'); + } elseif (isset($_POST['delete_member']) && (isAdmin() || canDelete('committees'))) { + $id = $_POST['id']; + $stmt = db()->prepare("DELETE FROM charity_members WHERE id = ?"); + $stmt->execute([$id]); + + $_SESSION['success'] = "تم حذف العضو بنجاح."; + redirect('charity_members.php'); + } +} + +// Fetch members +$stmt = db()->query("SELECT * FROM charity_members ORDER BY name ASC"); +$members = $stmt->fetchAll(); +?> + +
+

أعضاء الجمعية

+ + + +
+ + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
الاسمالدور/الصفةرقم الجوالالبريد الإلكترونيتاريخ الانضمامالحالةالإجراءات
+
+
+ +
+
+
+
+
+
+ + نشط + + غير نشط + + + + + + + + +
لا يوجد أعضاء مضافين حتى الآن
+
+
+
+ + + + + \ No newline at end of file diff --git a/charity_plans.php b/charity_plans.php new file mode 100644 index 0000000..1bf18f9 --- /dev/null +++ b/charity_plans.php @@ -0,0 +1,357 @@ +غير مصرح لك بالوصول لهذه الصفحة."; + require_once 'includes/footer.php'; + exit; +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + if (isset($_POST['add_plan']) && (isAdmin() || canAdd('committees'))) { + $title = $_POST['title']; + $description = $_POST['description'] ?? ''; + $start_date = $_POST['start_date'] ?? date('Y-m-d'); + $end_date = $_POST['end_date'] ?? date('Y-m-d', strtotime('+1 month')); + $target_value = (int)$_POST['target_value']; + $achieved_value = (int)($_POST['achieved_value'] ?? 0); + $status = $_POST['status'] ?? 'pending'; + + $stmt = db()->prepare("INSERT INTO charity_plans (title, description, start_date, end_date, target_value, achieved_value, status) VALUES (?, ?, ?, ?, ?, ?, ?)"); + $stmt->execute([$title, $description, $start_date, $end_date, $target_value, $achieved_value, $status]); + $_SESSION['success'] = "تمت إضافة الخطة بنجاح."; + redirect('charity_plans.php'); + } elseif (isset($_POST['edit_plan']) && (isAdmin() || canEdit('committees'))) { + $id = $_POST['id']; + $title = $_POST['title']; + $description = $_POST['description'] ?? ''; + $start_date = $_POST['start_date'] ?? date('Y-m-d'); + $end_date = $_POST['end_date'] ?? date('Y-m-d'); + $target_value = (int)$_POST['target_value']; + $achieved_value = (int)($_POST['achieved_value'] ?? 0); + $status = $_POST['status'] ?? 'pending'; + + $stmt = db()->prepare("UPDATE charity_plans SET title = ?, description = ?, start_date = ?, end_date = ?, target_value = ?, achieved_value = ?, status = ? WHERE id = ?"); + $stmt->execute([$title, $description, $start_date, $end_date, $target_value, $achieved_value, $status, $id]); + $_SESSION['success'] = "تم تحديث الخطة بنجاح."; + redirect('charity_plans.php'); + } elseif (isset($_POST['delete_plan']) && (isAdmin() || canDelete('committees'))) { + $id = $_POST['id']; + $stmt = db()->prepare("DELETE FROM charity_plans WHERE id = ?"); + $stmt->execute([$id]); + $_SESSION['success'] = "تم حذف الخطة بنجاح."; + redirect('charity_plans.php'); + } +} + +$stmt = db()->query("SELECT * FROM charity_plans ORDER BY created_at DESC"); +$plans = $stmt->fetchAll(); + +$total_plans = count($plans); +$completed_plans = 0; +$total_target = 0; +$total_achieved = 0; + +foreach ($plans as $plan) { + if ($plan['status'] === 'completed') { + $completed_plans++; + } + $total_target += $plan['target_value']; + $total_achieved += $plan['achieved_value']; +} + +$completion_rate = $total_plans > 0 ? round(($completed_plans / $total_plans) * 100) : 0; +$kpi_score = $total_target > 0 ? min(100, round(($total_achieved / $total_target) * 100)) : 0; +$overall_score = round(($completion_rate * 0.4) + ($kpi_score * 0.6)); + +$status_colors = [ + 'pending' => 'secondary', + 'in_progress' => 'warning', + 'completed' => 'success', + 'cancelled' => 'danger' +]; +$status_labels = [ + 'pending' => 'قيد الانتظار', + 'in_progress' => 'قيد التنفيذ', + 'completed' => 'مكتمل', + 'cancelled' => 'ملغي' +]; +?> + +
+

خطط وتقييم الجمعية (KPI)

+ + + +
+ + + + + + + +
+
+
+
التقييم العام للجمعية
+

%

+
+ = 85): ?> + أداء متميز + = 60): ?> + أداء جيد + + بحاجة لتحسين + +
+
+
+
+
+
معدل الإنجاز (المستهدف)
+
+ + + + % + +
+

إجمالي المحقق: من

+
+
+
+
+
إنجاز الخطط (الحالة)
+
+ + + + % + +
+

خطط مكتملة: من

+
+
+
+ + +
+
+
سجل الخطط والأهداف
+
+
+
+ + + + + + + + + + + + + + 0 ? min(100, round(($plan['achieved_value'] / $plan['target_value']) * 100)) : 0; + ?> + + + + + + + + + + + + + + + + + + + + + + +
عنوان الخطة / الهدفالفترةالمستهدفالمحققالنسبةالحالةالإجراءات
+
+
+
+
من:
+
إلى:
+
+
+
+
+
%
+
+ + + + + + + + + + +
لا توجد خطط مضافة حتى الآن
+
+
+
+ + + + + \ No newline at end of file diff --git a/committee_reports.php b/committee_reports.php new file mode 100644 index 0000000..595c745 --- /dev/null +++ b/committee_reports.php @@ -0,0 +1,211 @@ +لا توجد صلاحية للوصول لهذه الصفحة."; + require_once 'includes/footer.php'; + exit; +} + +// Fetch all committees and calculate stats +$stmt = db()->query(" + SELECT + c.id, c.name, + (SELECT COUNT(*) FROM committee_members WHERE committee_id = c.id) as members_count, + (SELECT COUNT(*) FROM committee_plans WHERE committee_id = c.id) as total_plans, + (SELECT COUNT(*) FROM committee_plans WHERE committee_id = c.id AND status = 'completed') as completed_plans, + (SELECT COUNT(*) FROM committee_activities WHERE committee_id = c.id) as activities_count + FROM committees c + ORDER BY c.name ASC +"); +$committees = $stmt->fetchAll(PDO::FETCH_ASSOC); + +// Overall stats +$total_committees = count($committees); +$total_members = 0; +$total_plans = 0; +$total_completed = 0; +$total_activities = 0; + +foreach ($committees as &$c) { + $total_members += $c['members_count']; + $total_plans += $c['total_plans']; + $total_completed += $c['completed_plans']; + $total_activities += $c['activities_count']; + + // Calculate performance score (smart assessment) + // Score based on: Completed plans (weight 60%) + Activities done (weight 40%) + // Let's make a simple normalized score out of 100 for visual assessment + $plan_completion_rate = $c['total_plans'] > 0 ? ($c['completed_plans'] / $c['total_plans']) * 100 : 0; + + // Assume an "active" committee should have at least 1 activity per month (say 5 is a good baseline for 100% activity score) + $activity_score = min(100, $c['activities_count'] * 20); + + // Final composite score + $score = ($plan_completion_rate * 0.6) + ($activity_score * 0.4); + $c['score'] = $score; + + // Determine badge + if ($score >= 80) { + $c['badge'] = 'ممتاز'; + $c['color'] = 'success'; + } elseif ($score >= 50) { + $c['badge'] = 'جيد'; + $c['color'] = 'primary'; + } elseif ($score > 0) { + $c['badge'] = 'يحتاج تحسين'; + $c['color'] = 'warning'; + } else { + $c['badge'] = 'غير نشط'; + $c['color'] = 'danger'; + } +} +unset($c); + +// Sort by score descending to rank them +usort($committees, function($a, $b) { + return $b['score'] <=> $a['score']; +}); + +?> + +
+
+

تقييم وتقارير اللجان

+
+ + طباعة تقرير اللجان والأعضاء + + + عودة لإدارة اللجان + +
+
+
+ + + + +
+
+
+
+ +
إجمالي اللجان
+

+
+
+
+
+
+
+ +
إنجاز الخطط
+

+ 0 ? round(($total_completed / $total_plans) * 100) : 0 ?>% +

+ من خطة مكتملة +
+
+
+
+
+
+ +
إجمالي الأنشطة
+

+
+
+
+
+
+
+ +
إجمالي الأعضاء
+

+
+
+
+
+ + +
+
+
ترتيب وتقييم أداء اللجان (الأعلى أداءً)
+
+
+
+ + + + + + + + + + + + + + + + + + $c): ?> + + + + + + + + + + + + + +
الترتيباللجنةالأعضاءإنجاز الخططالأنشطة المنجزةمؤشر الأداء (KPI)التقييم
لا توجد لجان مضافة حتى الآن.
+ 0): ?> + + 0): ?> + + 0): ?> + + + # + + + + +
+ / +
+ 0 ? ($c['completed_plans'] / $c['total_plans']) * 100 : 0; ?> +
+
+
+
+ + +
+ نسبة الإنجاز: + % +
+
+
+
+
+ + + + تقرير مفصل + +
+
+
+
+ + + \ No newline at end of file diff --git a/committees.php b/committees.php new file mode 100644 index 0000000..a8a8db1 --- /dev/null +++ b/committees.php @@ -0,0 +1,203 @@ +prepare("INSERT INTO committees (name, description) VALUES (?, ?)"); + $stmt->execute([$name, $description]); + $_SESSION['success'] = 'تم إضافة اللجنة بنجاح'; + } + } elseif ($action === 'edit' && $id && canEdit('committees')) { + if (empty($name)) { + $_SESSION['error'] = 'اسم اللجنة مطلوب'; + } else { + $stmt = $db->prepare("UPDATE committees SET name = ?, description = ? WHERE id = ?"); + $stmt->execute([$name, $description, $id]); + $_SESSION['success'] = 'تم تحديث اللجنة بنجاح'; + } + } + } catch (PDOException $e) { + $_SESSION['error'] = 'حدث خطأ: ' . $e->getMessage(); + } + redirect('committees.php'); + } +} + +if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['id'])) { + if (!canDelete('committees')) redirect('committees.php'); + $id = $_GET['id']; + try { + $db = db(); + $stmt = $db->prepare("DELETE FROM committees WHERE id = ?"); + $stmt->execute([$id]); + $_SESSION['success'] = 'تم حذف اللجنة بنجاح'; + } catch (PDOException $e) { + $_SESSION['error'] = 'حدث خطأ: ' . $e->getMessage(); + } + redirect('committees.php'); +} + +$committees = db()->query("SELECT * FROM committees ORDER BY id DESC")->fetchAll(PDO::FETCH_ASSOC); + +if (isset($_SESSION['success'])) { + $success = $_SESSION['success']; + unset($_SESSION['success']); +} +if (isset($_SESSION['error'])) { + $error = $_SESSION['error']; + unset($_SESSION['error']); +} +?> + +
+

اللجان

+ + + +
+ + + + + + + + + +
+
+
+ + + + + + + + + + + 0): ?> + + + + + + + + + + + + + + +
الرقماسم اللجنةالوصفالإجراءات
+ إدارة + + + + + + + + +
لا توجد لجان مضافة حتى الآن.
+
+
+
+ + + + + + + diff --git a/db/migrations/030_add_committees_module.sql b/db/migrations/030_add_committees_module.sql new file mode 100644 index 0000000..f094c4d --- /dev/null +++ b/db/migrations/030_add_committees_module.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS `committees` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `name` VARCHAR(255) NOT NULL, + `description` TEXT, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; \ No newline at end of file diff --git a/db/migrations/031_add_committee_details.sql b/db/migrations/031_add_committee_details.sql new file mode 100644 index 0000000..3261f15 --- /dev/null +++ b/db/migrations/031_add_committee_details.sql @@ -0,0 +1,35 @@ +CREATE TABLE IF NOT EXISTS committee_members ( + id INT AUTO_INCREMENT PRIMARY KEY, + committee_id INT NOT NULL, + user_id INT NOT NULL, + role VARCHAR(100) DEFAULT 'عضو', + joined_at DATE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (committee_id) REFERENCES committees(id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + UNIQUE KEY unique_member (committee_id, user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS committee_plans ( + id INT AUTO_INCREMENT PRIMARY KEY, + committee_id INT NOT NULL, + title VARCHAR(255) NOT NULL, + description TEXT, + start_date DATE, + end_date DATE, + status ENUM('pending', 'in_progress', 'completed', 'cancelled') DEFAULT 'pending', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (committee_id) REFERENCES committees(id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS committee_activities ( + id INT AUTO_INCREMENT PRIMARY KEY, + committee_id INT NOT NULL, + title VARCHAR(255) NOT NULL, + description TEXT, + activity_date DATE, + location VARCHAR(255), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (committee_id) REFERENCES committees(id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; \ No newline at end of file diff --git a/db/migrations/032_add_charity_members_plans.sql b/db/migrations/032_add_charity_members_plans.sql new file mode 100644 index 0000000..94c23cc --- /dev/null +++ b/db/migrations/032_add_charity_members_plans.sql @@ -0,0 +1,38 @@ +CREATE TABLE IF NOT EXISTS charity_members ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + role VARCHAR(100), + phone VARCHAR(50), + email VARCHAR(100), + join_date DATE, + status ENUM('active', 'inactive') DEFAULT 'active', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS charity_plans ( + id INT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255) NOT NULL, + description TEXT, + start_date DATE, + end_date DATE, + target_value INT DEFAULT 100, + achieved_value INT DEFAULT 0, + status ENUM('pending', 'in_progress', 'completed', 'cancelled') DEFAULT 'pending', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +DROP TABLE IF EXISTS committee_members; + +CREATE TABLE IF NOT EXISTS committee_members ( + id INT AUTO_INCREMENT PRIMARY KEY, + committee_id INT NOT NULL, + charity_member_id INT NOT NULL, + role VARCHAR(100) DEFAULT 'عضو', + joined_at DATE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (committee_id) REFERENCES committees(id) ON DELETE CASCADE, + FOREIGN KEY (charity_member_id) REFERENCES charity_members(id) ON DELETE CASCADE, + UNIQUE KEY unique_member (committee_id, charity_member_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; \ No newline at end of file diff --git a/hr_zkteco.php b/hr_zkteco.php index 135e5a4..4b9480b 100644 --- a/hr_zkteco.php +++ b/hr_zkteco.php @@ -1,6 +1,12 @@
خطأ: مجلد vendor غير موجود على الاستضافة. يرجى التأكد من رفع مجلد المكتبات (vendor) مع باقي ملفات النظام، أو تنفيذ أمر composer install إذا كنت تستخدم سطر الأوامر.
"; + require_once 'includes/footer.php'; + exit; +} +require $autoloadPath; use Rats\Zkteco\Lib\ZKTeco; diff --git a/includes/header.php b/includes/header.php index 5cb6107..331e3e8 100644 --- a/includes/header.php +++ b/includes/header.php @@ -73,6 +73,13 @@ $is_stock_open = in_array($cp, $stock_pages); $expenses_pages = ['expenses.php', 'expense_categories.php', 'expense_reports.php']; $is_expenses_open = in_array($cp, $expenses_pages); +$charity_pages = ['charity_members.php', 'charity_plans.php']; +$is_charity_open = in_array($cp, $charity_pages); + +$committees_pages = ["committees.php", "view_committee.php", "committee_reports.php", "print_committees_report.php"]; +$is_committees_open = in_array($cp, $committees_pages); + + $meetings_pages = ['meetings.php']; $is_meetings_open = in_array($cp, $meetings_pages); @@ -470,6 +477,59 @@ $is_admin_open = in_array($cp, $admin_pages); + + + + + + + + + + +