Commit 91ab2d74 authored by Mahmoud Aglan's avatar Mahmoud Aglan

hgdghkd

parent f458cd55
<?php
declare(strict_types=1);
namespace App\Modules\ActivitySubscriptions\Controllers;
use App\Core\Controller;
use App\Core\Request;
use App\Core\Response;
use App\Core\App;
use App\Modules\Disciplines\Models\SportDiscipline;
class EnrollWizardController extends Controller
{
public function index(Request $request): Response
{
$this->authorize('activity_sub.view');
$db = App::getInstance()->db();
$disciplines = SportDiscipline::allActive();
$levels = $db->select(
"SELECT al.*, a.name_ar AS academy_name, a.discipline_id
FROM academy_levels al
JOIN academies a ON a.id = al.academy_id
WHERE a.is_active = 1
ORDER BY a.discipline_id, al.sort_order ASC"
);
$groupTypes = [
'private' => ['label' => 'خاص (1 لاعب)', 'max' => 1],
'small_group' => ['label' => 'مجموعة صغيرة (2-4)', 'max' => 4],
'group' => ['label' => 'مجموعة (5-12)', 'max' => 12],
'team' => ['label' => 'فريق (13+)', 'max' => 25],
];
$pricing = $db->select(
"SELECT ap.*, sd.name_ar AS discipline_name
FROM activity_pricing ap
LEFT JOIN sport_disciplines sd ON sd.id = ap.reference_id AND ap.pricing_type = 'discipline'
WHERE ap.is_active = 1 AND ap.pricing_type = 'discipline'
AND (ap.effective_to IS NULL OR ap.effective_to >= CURDATE())
AND ap.effective_from <= CURDATE()
ORDER BY ap.reference_id, ap.group_type"
);
$pricingMatrix = [];
foreach ($pricing as $p) {
$key = $p['reference_id'] . '_' . ($p['group_type'] ?? 'group');
$pricingMatrix[$key] = $p;
}
return $this->view('ActivitySubscriptions.Views.enroll_wizard', [
'disciplines' => $disciplines,
'levels' => $levels,
'groupTypes' => $groupTypes,
'pricingMatrix' => $pricingMatrix,
]);
}
public function getAvailability(Request $request): Response
{
$db = App::getInstance()->db();
$disciplineId = (int) ($_GET['discipline_id'] ?? 0);
$groupType = $_GET['group_type'] ?? 'group';
if (!$disciplineId) {
return $this->json(['success' => false]);
}
$groups = $db->select(
"SELECT tg.*, c.full_name_ar AS coach_name, f.name_ar AS facility_name
FROM training_groups tg
LEFT JOIN coaches c ON c.id = tg.coach_id
LEFT JOIN facilities f ON f.id = tg.facility_id
JOIN academies a ON a.id = tg.academy_id
WHERE a.discipline_id = ? AND tg.group_type = ?
AND tg.is_active = 1 AND tg.is_full = 0
ORDER BY (tg.max_capacity - tg.current_count) DESC",
[$disciplineId, $groupType]
);
return $this->json(['success' => true, 'groups' => $groups]);
}
public function getLevels(Request $request): Response
{
$db = App::getInstance()->db();
$disciplineId = (int) ($_GET['discipline_id'] ?? 0);
if (!$disciplineId) {
return $this->json(['success' => false]);
}
$levels = $db->select(
"SELECT al.id, al.name_ar, al.sort_order
FROM academy_levels al
JOIN academies a ON a.id = al.academy_id
WHERE a.discipline_id = ? AND a.is_active = 1
GROUP BY al.name_ar
ORDER BY al.sort_order ASC",
[$disciplineId]
);
return $this->json(['success' => true, 'levels' => $levels]);
}
public function getPrice(Request $request): Response
{
$disciplineId = (int) ($_GET['discipline_id'] ?? 0);
$groupType = $_GET['group_type'] ?? 'group';
$isMember = ($_GET['is_member'] ?? '1') === '1';
$db = App::getInstance()->db();
$pricing = $db->selectOne(
"SELECT * FROM activity_pricing
WHERE pricing_type = 'discipline' AND reference_id = ?
AND (group_type = ? OR group_type IS NULL)
AND is_active = 1
AND effective_from <= CURDATE()
AND (effective_to IS NULL OR effective_to >= CURDATE())
ORDER BY group_type DESC
LIMIT 1",
[$disciplineId, $groupType]
);
if (!$pricing) {
return $this->json(['success' => true, 'price' => 0, 'note' => 'لم يتم تحديد سعر بعد']);
}
$price = $isMember ? (float) $pricing['member_rate'] : (float) $pricing['nonmember_rate'];
return $this->json(['success' => true, 'price' => $price]);
}
public function enroll(Request $request): Response
{
$this->authorize('academy.enroll');
$db = App::getInstance()->db();
$playerId = (int) ($_POST['player_id'] ?? 0);
$disciplineId = (int) ($_POST['discipline_id'] ?? 0);
$levelId = (int) ($_POST['level_id'] ?? 0);
$groupType = $_POST['group_type'] ?? 'group';
if (!$playerId || !$disciplineId) {
return $this->redirect('/activity-subscriptions/enroll')->withError('يجب تحديد اللاعب والنشاط');
}
$player = $db->selectOne("SELECT * FROM players WHERE id = ?", [$playerId]);
if (!$player) {
return $this->redirect('/activity-subscriptions/enroll')->withError('اللاعب غير موجود');
}
$group = $db->selectOne(
"SELECT tg.*
FROM training_groups tg
JOIN academies a ON a.id = tg.academy_id
WHERE a.discipline_id = ? AND tg.group_type = ?
AND tg.is_active = 1 AND tg.is_full = 0
" . ($levelId ? "AND tg.level_id = ?" : "") . "
ORDER BY (tg.max_capacity - tg.current_count) DESC
LIMIT 1",
$levelId ? [$disciplineId, $groupType, $levelId] : [$disciplineId, $groupType]
);
if (!$group) {
return $this->redirect('/activity-subscriptions/enroll')->withError(
'لا توجد مجموعات متاحة لهذا النشاط والمستوى. يتم إضافة اللاعب لقائمة الانتظار.'
);
}
$academyId = (int) $group['academy_id'];
$groupLevelId = (int) $group['level_id'];
$employee = App::getInstance()->currentEmployee();
$enrollment = \App\Modules\PlayerAffairs\Models\AcademyEnrollment::create([
'player_id' => $playerId,
'academy_id' => $academyId,
'level_id' => $groupLevelId,
'schedule_id' => null,
'season' => date('Y') . '-' . (date('Y') + 1),
'enrolled_at' => date('Y-m-d H:i:s'),
'enrollment_day' => (int) date('j'),
'status' => 'active',
'created_by' => $employee ? (int) $employee->id : null,
]);
$db->insert('group_memberships', [
'group_id' => (int) $group['id'],
'enrollment_id' => (int) $enrollment->id,
'player_id' => $playerId,
'status' => 'active',
'joined_at' => date('Y-m-d H:i:s'),
]);
$db->update('training_groups', [
'current_count' => (int) $group['current_count'] + 1,
'is_full' => ((int) $group['current_count'] + 1) >= (int) $group['max_capacity'] ? 1 : 0,
], 'id = ?', [(int) $group['id']]);
return $this->redirect('/players/' . $playerId)
->withSuccess('تم تسجيل اللاعب بنجاح في ' . ($group['name_ar'] ?? 'المجموعة'));
}
}
<?php
declare(strict_types=1);
namespace App\Modules\ActivitySubscriptions\Controllers;
use App\Core\Controller;
use App\Core\Request;
use App\Core\Response;
use App\Core\App;
class PlayerSearchController extends Controller
{
public function search(Request $request): Response
{
$q = trim((string) ($_GET['q'] ?? ''));
if (mb_strlen($q) < 2) {
return $this->json(['success' => true, 'players' => []]);
}
$db = App::getInstance()->db();
$players = $db->select(
"SELECT p.id, p.full_name_ar, p.national_id, p.player_type, p.phone
FROM players p
WHERE (p.full_name_ar LIKE ? OR p.national_id LIKE ? OR p.phone LIKE ?)
AND p.is_archived = 0
ORDER BY p.full_name_ar ASC
LIMIT 15",
["%{$q}%", "%{$q}%", "%{$q}%"]
);
$results = [];
foreach ($players as $p) {
$results[] = [
'id' => (int) $p['id'],
'name' => $p['full_name_ar'],
'national_id' => $p['national_id'] ?? '',
'phone' => $p['phone'] ?? '',
'player_type' => $p['player_type'] ?? 'non_member',
];
}
return $this->json(['success' => true, 'players' => $results]);
}
}
...@@ -9,4 +9,14 @@ return [ ...@@ -9,4 +9,14 @@ return [
['GET', '/activity-subscriptions/{id:\d+}', 'ActivitySubscriptions\Controllers\ActivitySubscriptionController@show', ['auth'], 'activity_sub.view'], ['GET', '/activity-subscriptions/{id:\d+}', 'ActivitySubscriptions\Controllers\ActivitySubscriptionController@show', ['auth'], 'activity_sub.view'],
['POST', '/activity-subscriptions/{id:\d+}/pay', 'ActivitySubscriptions\Controllers\ActivitySubscriptionController@pay', ['auth', 'csrf'], 'activity_sub.collect'], ['POST', '/activity-subscriptions/{id:\d+}/pay', 'ActivitySubscriptions\Controllers\ActivitySubscriptionController@pay', ['auth', 'csrf'], 'activity_sub.collect'],
['POST', '/activity-subscriptions/{id:\d+}/exempt', 'ActivitySubscriptions\Controllers\ActivitySubscriptionController@exempt', ['auth', 'csrf'], 'activity_sub.exempt'], ['POST', '/activity-subscriptions/{id:\d+}/exempt', 'ActivitySubscriptions\Controllers\ActivitySubscriptionController@exempt', ['auth', 'csrf'], 'activity_sub.exempt'],
// Enrollment Wizard
['GET', '/activity-subscriptions/enroll', 'ActivitySubscriptions\Controllers\EnrollWizardController@index', ['auth'], 'activity_sub.view'],
['GET', '/activity-subscriptions/enroll/levels', 'ActivitySubscriptions\Controllers\EnrollWizardController@getLevels', ['auth'], 'activity_sub.view'],
['GET', '/activity-subscriptions/enroll/price', 'ActivitySubscriptions\Controllers\EnrollWizardController@getPrice', ['auth'], 'activity_sub.view'],
['GET', '/activity-subscriptions/enroll/availability', 'ActivitySubscriptions\Controllers\EnrollWizardController@getAvailability', ['auth'], 'activity_sub.view'],
['POST', '/activity-subscriptions/enroll', 'ActivitySubscriptions\Controllers\EnrollWizardController@enroll', ['auth', 'csrf'], 'academy.enroll'],
// Player Search API (used by wizard)
['GET', '/api/players/search', 'ActivitySubscriptions\Controllers\PlayerSearchController@search', ['auth'], 'player.view'],
]; ];
<?php
$__template->layout('Layout.main');
?>
<?php $__template->section('title'); ?>تسجيل لاعب في نشاط<?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<div style="max-width:800px;margin:0 auto;">
<!-- Page Header with Guide -->
<div style="margin-bottom:24px;">
<h2 style="margin:0 0 8px;font-size:22px;font-weight:800;">تسجيل لاعب في نشاط رياضي</h2>
<p style="margin:0;font-size:13px;color:#6B7280;line-height:1.7;">
<i data-lucide="info" style="width:14px;height:14px;vertical-align:middle;margin-left:4px;color:#0D7377;"></i>
اختر النشاط الرياضي والمستوى ونوع الخدمة. النظام يحسب السعر تلقائيا ويعين اللاعب في مجموعة متاحة مع مدرب.
</p>
</div>
<!-- Step indicator -->
<div style="display:flex;align-items:center;gap:0;margin-bottom:24px;">
<div class="step-item active" id="step1indicator">
<span class="step-num">1</span>
<span class="step-label">اللاعب</span>
</div>
<div class="step-line"></div>
<div class="step-item" id="step2indicator">
<span class="step-num">2</span>
<span class="step-label">النشاط</span>
</div>
<div class="step-line"></div>
<div class="step-item" id="step3indicator">
<span class="step-num">3</span>
<span class="step-label">الخدمة</span>
</div>
<div class="step-line"></div>
<div class="step-item" id="step4indicator">
<span class="step-num">4</span>
<span class="step-label">التأكيد</span>
</div>
</div>
<form method="POST" action="/activity-subscriptions/enroll" id="enrollForm">
<?= csrf_field() ?>
<!-- Step 1: Player -->
<div class="card" style="padding:24px;margin-bottom:16px;" id="step1">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:16px;">
<i data-lucide="user" style="width:20px;height:20px;color:#0D7377;"></i>
<h3 style="margin:0;font-size:16px;font-weight:700;">اختيار اللاعب</h3>
</div>
<p style="margin:0 0 12px;font-size:12px;color:#6B7280;">
<i data-lucide="arrow-left" style="width:12px;height:12px;vertical-align:middle;margin-left:4px;"></i>
ابحث باسم اللاعب او رقم العضوية. نوع العضوية يؤثر على السعر.
</p>
<div style="display:grid;grid-template-columns:1fr auto;gap:12px;">
<div style="position:relative;">
<input type="text" id="playerSearch" placeholder="اكتب اسم اللاعب او رقم العضوية..." style="width:100%;padding:12px 16px 12px 40px;border:1px solid #D1D5DB;border-radius:8px;font-size:14px;">
<i data-lucide="search" style="width:16px;height:16px;position:absolute;top:14px;left:14px;color:#9CA3AF;"></i>
<div id="playerResults" style="display:none;position:absolute;top:100%;right:0;left:0;background:white;border:1px solid #E5E7EB;border-radius:8px;box-shadow:0 4px 16px rgba(0,0,0,.1);z-index:10;max-height:200px;overflow-y:auto;margin-top:4px;"></div>
</div>
<input type="hidden" name="player_id" id="selectedPlayerId" required>
</div>
<div id="selectedPlayerCard" style="display:none;margin-top:12px;padding:12px 16px;background:#F0FDFA;border:1px solid #99F6E4;border-radius:8px;">
<div style="display:flex;align-items:center;justify-content:space-between;">
<div>
<span style="font-weight:700;color:#111;" id="selectedPlayerName"></span>
<span style="font-size:12px;color:#6B7280;margin-right:8px;" id="selectedPlayerType"></span>
</div>
<button type="button" onclick="clearPlayer()" style="background:none;border:none;cursor:pointer;color:#DC2626;">
<i data-lucide="x" style="width:16px;height:16px;"></i>
</button>
</div>
</div>
</div>
<!-- Step 2: Sport/Discipline -->
<div class="card" style="padding:24px;margin-bottom:16px;opacity:0.5;" id="step2">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:16px;">
<i data-lucide="trophy" style="width:20px;height:20px;color:#0D7377;"></i>
<h3 style="margin:0;font-size:16px;font-weight:700;">اختيار النشاط والمستوى</h3>
</div>
<p style="margin:0 0 12px;font-size:12px;color:#6B7280;">
<i data-lucide="arrow-left" style="width:12px;height:12px;vertical-align:middle;margin-left:4px;"></i>
اختر النشاط الرياضي ثم المستوى المناسب لقدرات اللاعب.
</p>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div>
<label style="display:block;font-size:12px;font-weight:600;margin-bottom:6px;color:#374151;">النشاط الرياضي</label>
<select name="discipline_id" id="disciplineSelect" style="width:100%;padding:10px 12px;border:1px solid #D1D5DB;border-radius:8px;font-size:13px;" onchange="onDisciplineChange()">
<option value="">-- اختر النشاط --</option>
<?php foreach ($disciplines as $d): ?>
<option value="<?= (int) $d['id'] ?>"><?= e($d['name_ar']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div>
<label style="display:block;font-size:12px;font-weight:600;margin-bottom:6px;color:#374151;">المستوى</label>
<select name="level_id" id="levelSelect" style="width:100%;padding:10px 12px;border:1px solid #D1D5DB;border-radius:8px;font-size:13px;" disabled>
<option value="">-- اختر النشاط اولا --</option>
</select>
<span style="font-size:11px;color:#9CA3AF;margin-top:4px;display:block;">
<i data-lucide="help-circle" style="width:11px;height:11px;vertical-align:middle;margin-left:2px;"></i>
المستوى يحدد بناء على تقييم المدرب
</span>
</div>
</div>
</div>
<!-- Step 3: Service Type -->
<div class="card" style="padding:24px;margin-bottom:16px;opacity:0.5;" id="step3">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:16px;">
<i data-lucide="users" style="width:20px;height:20px;color:#0D7377;"></i>
<h3 style="margin:0;font-size:16px;font-weight:700;">نوع الخدمة (حجم المجموعة)</h3>
</div>
<p style="margin:0 0 12px;font-size:12px;color:#6B7280;">
<i data-lucide="arrow-left" style="width:12px;height:12px;vertical-align:middle;margin-left:4px;"></i>
كل ما قل عدد المجموعة، زاد الاهتمام الفردي وارتفع السعر.
</p>
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:12px;" id="groupTypeCards">
<?php foreach ($groupTypes as $key => $gt): ?>
<label style="display:block;cursor:pointer;">
<input type="radio" name="group_type" value="<?= e($key) ?>" style="display:none;" onchange="onGroupTypeChange()">
<div class="service-card" data-type="<?= e($key) ?>">
<div style="font-size:13px;font-weight:700;color:#111;margin-bottom:4px;"><?= e($gt['label']) ?></div>
<div style="font-size:11px;color:#6B7280;">اقصى <?= $gt['max'] ?> لاعب</div>
<div class="service-price" style="margin-top:8px;font-size:16px;font-weight:800;color:#0D7377;" data-type="<?= e($key) ?>">--</div>
<div style="font-size:10px;color:#9CA3AF;">ج.م / شهر</div>
</div>
</label>
<?php endforeach; ?>
</div>
</div>
<!-- Step 4: Confirmation -->
<div class="card" style="padding:24px;margin-bottom:16px;opacity:0.5;" id="step4">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:16px;">
<i data-lucide="check-circle" style="width:20px;height:20px;color:#0D7377;"></i>
<h3 style="margin:0;font-size:16px;font-weight:700;">ملخص وتأكيد</h3>
</div>
<p style="margin:0 0 12px;font-size:12px;color:#6B7280;">
<i data-lucide="arrow-left" style="width:12px;height:12px;vertical-align:middle;margin-left:4px;"></i>
النظام يعين المجموعة والمدرب والموعد تلقائيا بناء على المتاح. الاشتراك الشهري يتولد في اول كل شهر.
</p>
<div id="summaryBox" style="display:none;padding:16px;background:#F9FAFB;border-radius:8px;border:1px solid #E5E7EB;">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;font-size:13px;">
<div><span style="color:#6B7280;">اللاعب:</span> <strong id="sumPlayer">--</strong></div>
<div><span style="color:#6B7280;">النشاط:</span> <strong id="sumDiscipline">--</strong></div>
<div><span style="color:#6B7280;">المستوى:</span> <strong id="sumLevel">--</strong></div>
<div><span style="color:#6B7280;">نوع الخدمة:</span> <strong id="sumGroupType">--</strong></div>
<div style="grid-column:1/-1;padding-top:12px;border-top:1px solid #E5E7EB;">
<span style="color:#6B7280;">السعر الشهري:</span>
<strong style="font-size:18px;color:#0D7377;" id="sumPrice">--</strong>
<span style="font-size:12px;color:#9CA3AF;">ج.م</span>
</div>
</div>
</div>
<div style="margin-top:16px;padding:12px 16px;background:#FEF3C7;border-radius:8px;font-size:12px;color:#92400E;line-height:1.7;">
<i data-lucide="lightbulb" style="width:14px;height:14px;vertical-align:middle;margin-left:4px;"></i>
<strong>ماذا يحدث بعد التسجيل؟</strong><br>
1. يتم تعيين اللاعب في مجموعة متاحة تلقائيا<br>
2. في اول كل شهر يتولد اشتراك شهري بالسعر المتفق عليه<br>
3. الاشتراك يستحق الدفع في اليوم السابع من كل شهر<br>
4. لو التسجيل بعد يوم 15 في الشهر، يحسب نصف شهر فقط
</div>
<button type="submit" class="btn btn-primary" style="width:100%;padding:14px;margin-top:16px;font-size:15px;" id="submitBtn" disabled>
<i data-lucide="check" style="width:16px;height:16px;vertical-align:middle;margin-left:6px;"></i>
تسجيل اللاعب
</button>
</div>
</form>
</div>
<?php $__template->endSection(); ?>
<?php $__template->section('styles'); ?>
<style>
.step-item { display:flex;align-items:center;gap:6px; }
.step-item .step-num {
width:28px;height:28px;border-radius:50%;background:#E5E7EB;color:#6B7280;
display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;
}
.step-item.active .step-num { background:#0D7377;color:white; }
.step-item.done .step-num { background:#059669;color:white; }
.step-item .step-label { font-size:12px;font-weight:600;color:#6B7280; }
.step-item.active .step-label { color:#0D7377; }
.step-line { flex:1;height:2px;background:#E5E7EB;margin:0 8px; }
.service-card {
padding:16px;border:2px solid #E5E7EB;border-radius:10px;text-align:center;
transition:all .15s;
}
.service-card:hover { border-color:#0D7377;background:#F0FDFA; }
input[type="radio"]:checked + .service-card { border-color:#0D7377;background:#F0FDFA;box-shadow:0 0 0 3px rgba(13,115,119,.15); }
</style>
<?php $__template->endSection(); ?>
<?php $__template->section('scripts'); ?>
<script>
const CSRF = document.querySelector('meta[name="csrf-token"]')?.content || '';
let selectedPlayer = null;
let searchTimeout = null;
// Player search
document.getElementById('playerSearch').addEventListener('input', function() {
clearTimeout(searchTimeout);
const q = this.value.trim();
if (q.length < 2) { document.getElementById('playerResults').style.display = 'none'; return; }
searchTimeout = setTimeout(() => {
fetch(`/api/players/search?q=${encodeURIComponent(q)}`)
.then(r => r.json())
.then(data => {
const results = document.getElementById('playerResults');
if (!data.players || data.players.length === 0) {
results.innerHTML = '<div style="padding:12px;color:#9CA3AF;font-size:13px;text-align:center;">لا توجد نتائج</div>';
} else {
results.innerHTML = data.players.map(p => `
<div style="padding:10px 16px;cursor:pointer;border-bottom:1px solid #F3F4F6;font-size:13px;"
onmouseenter="this.style.background='#F9FAFB'" onmouseleave="this.style.background=''"
onclick="selectPlayer(${p.id}, '${p.name}', '${p.player_type}')">
<strong>${p.name}</strong>
<span style="color:#6B7280;margin-right:8px;">${p.player_type === 'member' ? 'عضو' : 'غير عضو'}</span>
</div>
`).join('');
}
results.style.display = 'block';
});
}, 300);
});
function selectPlayer(id, name, playerType) {
selectedPlayer = {id, name, playerType};
document.getElementById('selectedPlayerId').value = id;
document.getElementById('selectedPlayerName').textContent = name;
document.getElementById('selectedPlayerType').textContent = playerType === 'member' ? '(عضو)' : '(غير عضو)';
document.getElementById('selectedPlayerCard').style.display = 'block';
document.getElementById('playerResults').style.display = 'none';
document.getElementById('playerSearch').value = '';
activateStep(2);
updatePrices();
}
function clearPlayer() {
selectedPlayer = null;
document.getElementById('selectedPlayerId').value = '';
document.getElementById('selectedPlayerCard').style.display = 'none';
deactivateFrom(2);
}
function onDisciplineChange() {
const disciplineId = document.getElementById('disciplineSelect').value;
if (!disciplineId) return;
fetch(`/activity-subscriptions/enroll/levels?discipline_id=${disciplineId}`)
.then(r => r.json())
.then(data => {
const sel = document.getElementById('levelSelect');
sel.innerHTML = '<option value="">-- اختر المستوى --</option>';
if (data.levels) {
data.levels.forEach(l => {
sel.innerHTML += `<option value="${l.id}">${l.name_ar}</option>`;
});
}
sel.disabled = false;
});
activateStep(3);
updatePrices();
}
function onGroupTypeChange() {
activateStep(4);
updateSummary();
}
function updatePrices() {
const disciplineId = document.getElementById('disciplineSelect').value;
if (!disciplineId || !selectedPlayer) return;
const isMember = selectedPlayer.playerType === 'member' ? '1' : '0';
['private','small_group','group','team'].forEach(gt => {
fetch(`/activity-subscriptions/enroll/price?discipline_id=${disciplineId}&group_type=${gt}&is_member=${isMember}`)
.then(r => r.json())
.then(data => {
const el = document.querySelector(`.service-price[data-type="${gt}"]`);
if (el) el.textContent = data.price > 0 ? data.price.toLocaleString() : '--';
});
});
}
function updateSummary() {
const dSel = document.getElementById('disciplineSelect');
const lSel = document.getElementById('levelSelect');
const gtSel = document.querySelector('input[name="group_type"]:checked');
document.getElementById('sumPlayer').textContent = selectedPlayer ? selectedPlayer.name : '--';
document.getElementById('sumDiscipline').textContent = dSel.selectedOptions[0]?.text || '--';
document.getElementById('sumLevel').textContent = lSel.selectedOptions[0]?.text || '--';
document.getElementById('sumGroupType').textContent = gtSel ? gtSel.closest('label').querySelector('.service-card div').textContent : '--';
const priceEl = document.querySelector(`.service-price[data-type="${gtSel?.value}"]`);
document.getElementById('sumPrice').textContent = priceEl ? priceEl.textContent : '--';
document.getElementById('summaryBox').style.display = 'block';
document.getElementById('submitBtn').disabled = false;
}
function activateStep(n) {
for (let i = 1; i <= 4; i++) {
const step = document.getElementById('step' + i);
const ind = document.getElementById('step' + i + 'indicator');
if (i <= n) {
step.style.opacity = '1';
if (i < n) ind.classList.add('done');
if (i === n) { ind.classList.add('active'); ind.classList.remove('done'); }
}
}
}
function deactivateFrom(n) {
for (let i = n; i <= 4; i++) {
const step = document.getElementById('step' + i);
const ind = document.getElementById('step' + i + 'indicator');
step.style.opacity = '0.5';
ind.classList.remove('active', 'done');
}
document.getElementById('submitBtn').disabled = true;
document.getElementById('summaryBox').style.display = 'none';
}
// Click outside to close search results
document.addEventListener('click', function(e) {
if (!e.target.closest('#playerSearch') && !e.target.closest('#playerResults')) {
document.getElementById('playerResults').style.display = 'none';
}
});
</script>
<?php $__template->endSection(); ?>
...@@ -5,6 +5,9 @@ $__template->layout('Layout.main'); ...@@ -5,6 +5,9 @@ $__template->layout('Layout.main');
<?php $__template->section('title'); ?>اشتراكات الأنشطة<?php $__template->endSection(); ?> <?php $__template->section('title'); ?>اشتراكات الأنشطة<?php $__template->endSection(); ?>
<?php $__template->section('page_actions'); ?> <?php $__template->section('page_actions'); ?>
<a href="/activity-subscriptions/enroll" class="btn btn-primary" style="gap:6px;">
<i data-lucide="user-plus" style="width:16px;height:16px;vertical-align:middle;margin-left:4px;"></i> تسجيل لاعب جديد
</a>
<form method="POST" action="/activity-subscriptions/generate" style="display:inline-flex;gap:6px;align-items:center;"> <form method="POST" action="/activity-subscriptions/generate" style="display:inline-flex;gap:6px;align-items:center;">
<?= csrf_field() ?> <?= csrf_field() ?>
<input type="month" name="month" value="<?= e(date('Y-m')) ?>" class="form-input" style="width:160px;font-size:13px;"> <input type="month" name="month" value="<?= e(date('Y-m')) ?>" class="form-input" style="width:160px;font-size:13px;">
...@@ -17,6 +20,31 @@ $__template->layout('Layout.main'); ...@@ -17,6 +20,31 @@ $__template->layout('Layout.main');
<?php $__template->section('content'); ?> <?php $__template->section('content'); ?>
<!-- Workflow Guide -->
<div class="card" style="margin-bottom:20px;padding:16px 20px;border-right:4px solid #0D7377;background:#F0FDFA;">
<div style="display:flex;align-items:start;gap:12px;">
<i data-lucide="info" style="width:20px;height:20px;color:#0D7377;flex-shrink:0;margin-top:2px;"></i>
<div style="font-size:13px;color:#374151;line-height:1.8;">
<strong style="color:#0D7377;">كيف تعمل الاشتراكات:</strong>
اللاعب يختار الرياضة والمستوى ونوع الخدمة (فردي، مجموعة صغيرة، مجموعة، فريق) - والنظام يقوم بتعيينه تلقائيا في المجموعة المناسبة.
الاشتراك الشهري يتولد تلقائيا بناء على التسجيل الفعال. استخدم زر "تسجيل لاعب جديد" لبدء العملية.
<br>
<span style="color:#6B7280;">
<i data-lucide="arrow-left" style="width:12px;height:12px;vertical-align:middle;"></i>
تسجيل لاعب
<i data-lucide="arrow-left" style="width:12px;height:12px;vertical-align:middle;"></i>
اختيار رياضة ومستوى
<i data-lucide="arrow-left" style="width:12px;height:12px;vertical-align:middle;"></i>
تحديد نوع الخدمة
<i data-lucide="arrow-left" style="width:12px;height:12px;vertical-align:middle;"></i>
التعيين التلقائي في مجموعة
<i data-lucide="arrow-left" style="width:12px;height:12px;vertical-align:middle;"></i>
توليد اشتراك شهري
</span>
</div>
</div>
</div>
<?php <?php
$currentStatus = $filters['status'] ?? ''; $currentStatus = $filters['status'] ?? '';
$allStatuses = ActivitySubscription::getStatuses(); $allStatuses = ActivitySubscription::getStatuses();
......
...@@ -34,9 +34,19 @@ foreach (($pricings ?? []) as $p) { ...@@ -34,9 +34,19 @@ foreach (($pricings ?? []) as $p) {
<div style="display:flex;align-items:start;gap:12px;"> <div style="display:flex;align-items:start;gap:12px;">
<i data-lucide="info" style="width:20px;height:20px;color:#0D7377;flex-shrink:0;margin-top:2px;"></i> <i data-lucide="info" style="width:20px;height:20px;color:#0D7377;flex-shrink:0;margin-top:2px;"></i>
<div style="font-size:13px;color:#374151;line-height:1.8;"> <div style="font-size:13px;color:#374151;line-height:1.8;">
<strong>ما هذه الصفحة؟</strong> هنا تحدد سعر الاشتراك الشهري لكل أكاديمية أو نشاط رياضي.<br> <strong>كيف يعمل التسعير:</strong>
عند توليد الاشتراكات الشهرية، يستخدم النظام هذه الأسعار لحساب المبلغ المطلوب من كل لاعب.<br> السعر الشهري يتحدد بناء على ثلاث عوامل: النشاط الرياضي، نوع الخدمة (حجم المجموعة)، ونوع العضوية (عضو / غير عضو).<br>
<strong>عضو</strong> = لاعب عضو بالنادي &nbsp;&middot;&nbsp; <strong>غير عضو</strong> = لاعب خارجي &nbsp;&middot;&nbsp; <strong>مسائي</strong> = فترة مسائية (بعد <?= e(date('g', mktime(17, 0))) ?>:00 م) كل ما قل حجم المجموعة زاد السعر (فردي اغلى من مجموعة). السعر ثابت لكل combination ولا يتغير حسب الاكاديمية او المدرب.<br>
<span style="color:#6B7280;">
<i data-lucide="arrow-left" style="width:12px;height:12px;vertical-align:middle;"></i>
فردي (1 لاعب)
<i data-lucide="arrow-left" style="width:12px;height:12px;vertical-align:middle;"></i>
مجموعة صغيرة (2-4)
<i data-lucide="arrow-left" style="width:12px;height:12px;vertical-align:middle;"></i>
مجموعة (5-12)
<i data-lucide="arrow-left" style="width:12px;height:12px;vertical-align:middle;"></i>
فريق (13+)
</span>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -116,16 +116,38 @@ final class GridStateService ...@@ -116,16 +116,38 @@ final class GridStateService
switch ($type) { switch ($type) {
case 'row': case 'row':
$row = (int) ($json['row'] ?? 0); // New format: array of {row, col} cells — extract unique rows
for ($c = 0; $c < $totalCols; $c++) { if (isset($json[0]['row'])) {
$positions[] = ['row' => $row, 'col' => $c]; $rows = array_unique(array_column($json, 'row'));
foreach ($rows as $row) {
for ($c = 0; $c < $totalCols; $c++) {
$positions[] = ['row' => (int) $row, 'col' => $c];
}
}
} else {
// Legacy format: {"row": N}
$row = (int) ($json['row'] ?? 0);
for ($c = 0; $c < $totalCols; $c++) {
$positions[] = ['row' => $row, 'col' => $c];
}
} }
break; break;
case 'column': case 'column':
$col = (int) ($json['col'] ?? 0); // New format: array of {row, col} cells — extract unique cols
for ($r = 0; $r < $totalRows; $r++) { if (isset($json[0]['row'])) {
$positions[] = ['row' => $r, 'col' => $col]; $cols = array_unique(array_column($json, 'col'));
foreach ($cols as $col) {
for ($r = 0; $r < $totalRows; $r++) {
$positions[] = ['row' => $r, 'col' => (int) $col];
}
}
} else {
// Legacy format: {"col": N}
$col = (int) ($json['col'] ?? 0);
for ($r = 0; $r < $totalRows; $r++) {
$positions[] = ['row' => $r, 'col' => $col];
}
} }
break; break;
......
...@@ -91,7 +91,34 @@ $planMonth = $_GET['month'] ?? date('Y-m'); ...@@ -91,7 +91,34 @@ $planMonth = $_GET['month'] ?? date('Y-m');
display: flex; align-items: center; justify-content: center; display: flex; align-items: center; justify-content: center;
font-size: 12px; font-weight: 700; color: #374151; font-size: 12px; font-weight: 700; color: #374151;
background: #F9FAFB; border-radius: 6px; padding: 8px 4px; background: #F9FAFB; border-radius: 6px; padding: 8px 4px;
cursor: pointer; transition: background .15s;
} }
.row-label:hover, .col-label:hover { background: #E0F2FE; }
.row-label.selected, .col-label.selected { background: #0D7377; color: white; }
.pool-box.selected {
border-color: #0D7377 !important; box-shadow: 0 0 0 3px rgba(13,115,119,.25) !important;
background: #F0FDFA !important;
}
.selection-toolbar {
display: flex; align-items: center; gap: 8px; margin-bottom: 12px;
padding: 10px 16px; background: #F9FAFB; border-radius: 10px; border: 1px solid #E5E7EB;
}
.sel-mode-btn {
padding: 6px 14px; border-radius: 6px; border: 1px solid #D1D5DB;
font-size: 12px; font-weight: 600; cursor: pointer; background: white;
color: #374151; transition: all .15s; display: inline-flex; align-items: center; gap: 4px;
}
.sel-mode-btn:hover { border-color: #0D7377; color: #0D7377; }
.sel-mode-btn.active { background: #0D7377; color: white; border-color: #0D7377; }
.selection-actions {
display: none; align-items: center; gap: 8px; margin-right: auto;
}
.selection-actions.visible { display: flex; }
.sel-count { font-size: 12px; font-weight: 700; color: #0D7377; }
.plan-header { .plan-header {
display: flex; align-items: center; justify-content: space-between; display: flex; align-items: center; justify-content: space-between;
...@@ -188,21 +215,52 @@ $planMonth = $_GET['month'] ?? date('Y-m'); ...@@ -188,21 +215,52 @@ $planMonth = $_GET['month'] ?? date('Y-m');
<div style="display:flex;gap:8px;font-size:11px;color:#6B7280;"> <div style="display:flex;gap:8px;font-size:11px;color:#6B7280;">
<span><span style="display:inline-block;width:10px;height:10px;border-radius:3px;background:#F0FDF4;border:1px dashed #86EFAC;vertical-align:middle;margin-left:4px;"></span>متاح</span> <span><span style="display:inline-block;width:10px;height:10px;border-radius:3px;background:#F0FDF4;border:1px dashed #86EFAC;vertical-align:middle;margin-left:4px;"></span>متاح</span>
<span><span style="display:inline-block;width:10px;height:10px;border-radius:3px;background:#DBEAFE;border:1px solid #3B82F6;vertical-align:middle;margin-left:4px;"></span>مشغول</span> <span><span style="display:inline-block;width:10px;height:10px;border-radius:3px;background:#DBEAFE;border:1px solid #3B82F6;vertical-align:middle;margin-left:4px;"></span>مشغول</span>
<span style="color:#1F2937;font-weight:600;">Box: أقصى ٥ | حارة طولية: ٨ | حارة عرضية: ٦</span> <span><span style="display:inline-block;width:10px;height:10px;border-radius:3px;background:#F0FDFA;border:2px solid #0D7377;vertical-align:middle;margin-left:4px;"></span>محدد</span>
<span style="color:#1F2937;font-weight:600;">خانة: ٥ | حارة طولية: ٨ | حارة عرضية: ٦</span>
</div>
</div>
<!-- Selection Mode Toolbar -->
<div class="selection-toolbar">
<span style="font-size:12px;color:#6B7280;margin-left:8px;">
<i data-lucide="mouse-pointer-2" style="width:14px;height:14px;vertical-align:middle;margin-left:4px;"></i>
وضع التحديد:
</span>
<button class="sel-mode-btn active" data-mode="cells" onclick="setSelectionMode('cells')">
<i data-lucide="grid-3x3" style="width:13px;height:13px;"></i> خانات
</button>
<button class="sel-mode-btn" data-mode="row" onclick="setSelectionMode('row')">
<i data-lucide="arrow-left-right" style="width:13px;height:13px;"></i> حارة كاملة (طولي)
</button>
<button class="sel-mode-btn" data-mode="column" onclick="setSelectionMode('column')">
<i data-lucide="arrow-up-down" style="width:13px;height:13px;"></i> حارة كاملة (عرضي)
</button>
<button class="sel-mode-btn" data-mode="all" onclick="setSelectionMode('all')">
<i data-lucide="maximize-2" style="width:13px;height:13px;"></i> الكل
</button>
<div class="selection-actions" id="selActions">
<span class="sel-count" id="selCount">0 محدد</span>
<button class="btn btn-primary" style="font-size:12px;padding:6px 14px;" onclick="openMultiBoxModal()">
<i data-lucide="edit-3" style="width:13px;height:13px;vertical-align:middle;margin-left:4px;"></i> تعيين المحدد
</button>
<button class="btn btn-outline" style="font-size:12px;padding:6px 14px;" onclick="clearSelection()">
<i data-lucide="x" style="width:13px;height:13px;vertical-align:middle;margin-left:4px;"></i> إلغاء
</button>
</div> </div>
</div> </div>
<div class="pool-grid" id="poolGrid" style="grid-template-columns: 50px repeat(<?= $cols ?>, 1fr);"> <div class="pool-grid" id="poolGrid" style="grid-template-columns: 50px repeat(<?= $cols ?>, 1fr);">
<!-- Corner --> <!-- Corner (select all) -->
<div></div> <div class="row-label" onclick="selectAll()" title="تحديد الكل" style="cursor:pointer;font-size:10px;">الكل</div>
<!-- Column Headers --> <!-- Column Headers -->
<?php for ($c = 0; $c < $cols; $c++): ?> <?php for ($c = 0; $c < $cols; $c++): ?>
<div class="col-label"><?= e($grid->col_label_ar ?? 'قسم') ?> <?= $c + 1 ?></div> <div class="col-label" data-col="<?= $c ?>" onclick="selectColumn(<?= $c ?>)"><?= e($grid->col_label_ar ?? 'قسم') ?> <?= $c + 1 ?></div>
<?php endfor; ?> <?php endfor; ?>
<!-- Rows --> <!-- Rows -->
<?php for ($r = 0; $r < $rows; $r++): ?> <?php for ($r = 0; $r < $rows; $r++): ?>
<div class="row-label"><?= e($grid->row_label_ar ?? 'حارة') ?> <?= $r + 1 ?></div> <div class="row-label" data-row="<?= $r ?>" onclick="selectRow(<?= $r ?>)"><?= e($grid->row_label_ar ?? 'حارة') ?> <?= $r + 1 ?></div>
<?php for ($c = 0; $c < $cols; $c++): <?php for ($c = 0; $c < $cols; $c++):
$key = $r . ':' . $c; $key = $r . ':' . $c;
$zone = $zones[$key] ?? null; $zone = $zones[$key] ?? null;
...@@ -225,7 +283,7 @@ $planMonth = $_GET['month'] ?? date('Y-m'); ...@@ -225,7 +283,7 @@ $planMonth = $_GET['month'] ?? date('Y-m');
data-row="<?= $r ?>" data-col="<?= $c ?>" data-row="<?= $r ?>" data-col="<?= $c ?>"
data-zone-id="<?= $zone['zone_id'] ?? 0 ?>" data-zone-id="<?= $zone['zone_id'] ?? 0 ?>"
ondragover="dragOver(event)" ondragleave="dragLeave(event)" ondrop="dropTrainee(event, <?= $zone['zone_id'] ?? 0 ?>)" ondragover="dragOver(event)" ondragleave="dragLeave(event)" ondrop="dropTrainee(event, <?= $zone['zone_id'] ?? 0 ?>)"
onclick="openBoxModal(<?= $r ?>, <?= $c ?>, this)"> onclick="handleBoxClick(<?= $r ?>, <?= $c ?>, this, event)">
<?php if ($status === 'occupied' && $isCurrentHour && $schedule): ?> <?php if ($status === 'occupied' && $isCurrentHour && $schedule): ?>
<?php if ($traineeCount > 0): ?> <?php if ($traineeCount > 0): ?>
...@@ -361,6 +419,190 @@ let selectedHour = <?= $currentHour ?>; ...@@ -361,6 +419,190 @@ let selectedHour = <?= $currentHour ?>;
let gridState = <?= json_encode($state, JSON_UNESCAPED_UNICODE) ?>; let gridState = <?= json_encode($state, JSON_UNESCAPED_UNICODE) ?>;
let draggedTraineeId = null; let draggedTraineeId = null;
// ─── SELECTION STATE ───
let selectionMode = 'cells'; // cells, row, column, all
let selectedCells = []; // [{row, col}, ...]
function setSelectionMode(mode) {
selectionMode = mode;
clearSelection();
document.querySelectorAll('.sel-mode-btn').forEach(b => b.classList.remove('active'));
document.querySelector(`.sel-mode-btn[data-mode="${mode}"]`)?.classList.add('active');
if (mode === 'all') {
selectAll();
}
}
function handleBoxClick(row, col, el, event) {
if (event && event.target.closest('.remove-btn')) return;
if (event && event.target.closest('.trainee-item[draggable]')) return;
if (selectionMode === 'cells') {
toggleCell(row, col);
} else if (selectionMode === 'row') {
selectRow(row);
} else if (selectionMode === 'column') {
selectColumn(col);
} else if (selectionMode === 'all') {
selectAll();
}
}
function toggleCell(row, col) {
const idx = selectedCells.findIndex(c => c.row === row && c.col === col);
if (idx >= 0) {
selectedCells.splice(idx, 1);
} else {
selectedCells.push({row, col});
}
updateSelectionVisuals();
}
function selectRow(row) {
// Toggle: if entire row already selected, deselect it
const rowCells = [];
for (let c = 0; c < COLS; c++) rowCells.push({row, col: c});
const allSelected = rowCells.every(rc => selectedCells.some(s => s.row === rc.row && s.col === rc.col));
if (allSelected) {
selectedCells = selectedCells.filter(s => s.row !== row);
} else {
// Remove any cells from this row first, then add all
selectedCells = selectedCells.filter(s => s.row !== row);
selectedCells.push(...rowCells);
}
updateSelectionVisuals();
}
function selectColumn(col) {
const colCells = [];
for (let r = 0; r < ROWS; r++) colCells.push({row: r, col});
const allSelected = colCells.every(rc => selectedCells.some(s => s.row === rc.row && s.col === rc.col));
if (allSelected) {
selectedCells = selectedCells.filter(s => s.col !== col);
} else {
selectedCells = selectedCells.filter(s => s.col !== col);
selectedCells.push(...colCells);
}
updateSelectionVisuals();
}
function selectAll() {
if (selectedCells.length === ROWS * COLS) {
clearSelection();
return;
}
selectedCells = [];
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
selectedCells.push({row: r, col: c});
}
}
updateSelectionVisuals();
}
function clearSelection() {
selectedCells = [];
updateSelectionVisuals();
}
function updateSelectionVisuals() {
// Update boxes
document.querySelectorAll('.pool-box').forEach(box => {
const r = parseInt(box.dataset.row);
const c = parseInt(box.dataset.col);
const isSelected = selectedCells.some(s => s.row === r && s.col === c);
box.classList.toggle('selected', isSelected);
});
// Update row labels
document.querySelectorAll('.row-label[data-row]').forEach(label => {
const r = parseInt(label.dataset.row);
const rowFull = Array.from({length: COLS}, (_, c) => c).every(c =>
selectedCells.some(s => s.row === r && s.col === c)
);
label.classList.toggle('selected', rowFull);
});
// Update col labels
document.querySelectorAll('.col-label[data-col]').forEach(label => {
const c = parseInt(label.dataset.col);
const colFull = Array.from({length: ROWS}, (_, r) => r).every(r =>
selectedCells.some(s => s.row === r && s.col === c)
);
label.classList.toggle('selected', colFull);
});
// Update action bar
const actions = document.getElementById('selActions');
const count = selectedCells.length;
if (count > 0) {
actions.classList.add('visible');
document.getElementById('selCount').textContent = count + ' محدد';
} else {
actions.classList.remove('visible');
}
}
function getSelectionType() {
if (selectedCells.length === 0) return 'cells';
if (selectedCells.length === ROWS * COLS) return 'all';
// Check if it's a full row
const rows = [...new Set(selectedCells.map(c => c.row))];
if (rows.length === 1 && selectedCells.length === COLS) return 'row';
// Check if it's full column(s)
const cols = [...new Set(selectedCells.map(c => c.col))];
if (cols.length === 1 && selectedCells.length === ROWS) return 'column';
// Check if it's multiple full rows
const allFullRows = rows.every(r =>
Array.from({length: COLS}, (_, c) => c).every(c =>
selectedCells.some(s => s.row === r && s.col === c)
)
);
if (allFullRows && selectedCells.length === rows.length * COLS) return 'row';
// Check if it's multiple full columns
const allFullCols = cols.every(c =>
Array.from({length: ROWS}, (_, r) => r).every(r =>
selectedCells.some(s => s.row === r && s.col === c)
)
);
if (allFullCols && selectedCells.length === cols.length * ROWS) return 'column';
return 'cells';
}
function openMultiBoxModal() {
if (selectedCells.length === 0) return;
const selType = getSelectionType();
document.querySelector('[name="zone_selection_type"]').value = selType;
document.getElementById('modalSelJson').value = JSON.stringify(selectedCells);
document.getElementById('modalStart').value = String(selectedHour).padStart(2,'0') + ':00';
document.getElementById('modalEnd').value = String(selectedHour + 1).padStart(2,'0') + ':00';
const count = selectedCells.length;
let maxLabel = '';
if (selType === 'row') maxLabel = ' (اقصى ٨ متدرب)';
else if (selType === 'column') maxLabel = ' (اقصى ٦ متدرب)';
else if (selType === 'all') maxLabel = ' (الحوض كامل)';
else maxLabel = count > 1 ? ` (${count} خانات)` : '';
document.getElementById('modalTitle').textContent = 'تعيين' + maxLabel;
document.getElementById('boxForm').reset();
document.querySelector('[name="zone_selection_type"]').value = selType;
document.getElementById('modalSelJson').value = JSON.stringify(selectedCells);
document.getElementById('modalStart').value = String(selectedHour).padStart(2,'0') + ':00';
document.getElementById('modalEnd').value = String(selectedHour + 1).padStart(2,'0') + ':00';
document.getElementById('boxModal').classList.add('active');
}
// ─── HOUR SELECTION ─── // ─── HOUR SELECTION ───
function selectHour(h) { function selectHour(h) {
selectedHour = h; selectedHour = h;
...@@ -368,6 +610,7 @@ function selectHour(h) { ...@@ -368,6 +610,7 @@ function selectHour(h) {
document.querySelector(`.hour-tab[data-hour="${h}"]`)?.classList.add('active'); document.querySelector(`.hour-tab[data-hour="${h}"]`)?.classList.add('active');
document.getElementById('selectedHourLabel').textContent = document.getElementById('selectedHourLabel').textContent =
String(h).padStart(2,'0') + ':00–' + String(h+1).padStart(2,'0') + ':00'; String(h).padStart(2,'0') + ':00–' + String(h+1).padStart(2,'0') + ':00';
clearSelection();
refreshGrid(); refreshGrid();
} }
...@@ -394,8 +637,9 @@ function renderPoolGrid() { ...@@ -394,8 +637,9 @@ function renderPoolGrid() {
const sch = zone.schedule; const sch = zone.schedule;
const isOccupied = zone.status === 'occupied' && sch; const isOccupied = zone.status === 'occupied' && sch;
const isSelected = selectedCells.some(s => s.row === r && s.col === c);
box.className = 'pool-box ' + (isOccupied ? 'occupied' : 'available'); box.className = 'pool-box ' + (isOccupied ? 'occupied' : 'available') + (isSelected ? ' selected' : '');
if (isOccupied) box.style.setProperty('--box-color', sch.color || '#3B82F6'); if (isOccupied) box.style.setProperty('--box-color', sch.color || '#3B82F6');
let html = ''; let html = '';
...@@ -443,11 +687,12 @@ function reloadForDate() { ...@@ -443,11 +687,12 @@ function reloadForDate() {
window.location.href = `/facility-grids/${GRID_ID}?date=${date}&month=${PLAN_MONTH}`; window.location.href = `/facility-grids/${GRID_ID}?date=${date}&month=${PLAN_MONTH}`;
} }
// ─── BOX MODAL ─── // ─── BOX MODAL (single cell click in cells mode when nothing selected) ───
function openBoxModal(row, col, el) { function openBoxModal(row, col) {
const key = row + ':' + col; const key = row + ':' + col;
const zone = gridState.zones[key]; const zone = gridState.zones[key];
document.querySelector('[name="zone_selection_type"]').value = 'cells';
document.getElementById('modalSelJson').value = JSON.stringify([{row, col}]); document.getElementById('modalSelJson').value = JSON.stringify([{row, col}]);
document.getElementById('modalStart').value = String(selectedHour).padStart(2,'0') + ':00'; document.getElementById('modalStart').value = String(selectedHour).padStart(2,'0') + ':00';
document.getElementById('modalEnd').value = String(selectedHour + 1).padStart(2,'0') + ':00'; document.getElementById('modalEnd').value = String(selectedHour + 1).padStart(2,'0') + ':00';
...@@ -462,6 +707,7 @@ function openBoxModal(row, col, el) { ...@@ -462,6 +707,7 @@ function openBoxModal(row, col, el) {
} else { } else {
document.getElementById('modalTitle').textContent = 'تعيين خانة جديدة'; document.getElementById('modalTitle').textContent = 'تعيين خانة جديدة';
document.getElementById('boxForm').reset(); document.getElementById('boxForm').reset();
document.querySelector('[name="zone_selection_type"]').value = 'cells';
document.getElementById('modalSelJson').value = JSON.stringify([{row, col}]); document.getElementById('modalSelJson').value = JSON.stringify([{row, col}]);
document.getElementById('modalStart').value = String(selectedHour).padStart(2,'0') + ':00'; document.getElementById('modalStart').value = String(selectedHour).padStart(2,'0') + ':00';
document.getElementById('modalEnd').value = String(selectedHour + 1).padStart(2,'0') + ':00'; document.getElementById('modalEnd').value = String(selectedHour + 1).padStart(2,'0') + ':00';
...@@ -490,6 +736,7 @@ function saveBox(e) { ...@@ -490,6 +736,7 @@ function saveBox(e) {
.then(data => { .then(data => {
if (data.success) { if (data.success) {
closeModal(); closeModal();
clearSelection();
refreshGrid(); refreshGrid();
} else { } else {
alert(data.message || 'حدث خطأ'); alert(data.message || 'حدث خطأ');
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment