393 lines
22 KiB
PHP
393 lines
22 KiB
PHP
@extends('layouts.admin')
|
|
@section('title', 'إدارة المشغّلين — دمشق باركينغ')
|
|
@section('page-title', 'إدارة المشغّلين')
|
|
|
|
@section('styles')
|
|
<style>
|
|
.op-table th { font-size:.8rem; font-weight:700; color:#64748b; text-transform:uppercase; letter-spacing:.04em; white-space:nowrap; }
|
|
.op-table td { vertical-align:middle; font-size:.875rem; }
|
|
.op-avatar {
|
|
width:38px; height:38px; border-radius:50%;
|
|
background:rgba(99,102,241,.12); color:#6366f1;
|
|
display:flex; align-items:center; justify-content:center;
|
|
font-weight:800; font-size:.9rem; flex-shrink:0;
|
|
}
|
|
.lot-pill {
|
|
display:inline-flex; align-items:center; gap:.35rem;
|
|
padding:.25em .75em; border-radius:20px; font-size:.75rem; font-weight:700;
|
|
background:rgba(16,185,129,.1); color:#059669;
|
|
}
|
|
.no-lot-pill {
|
|
display:inline-flex; align-items:center; gap:.35rem;
|
|
padding:.25em .75em; border-radius:20px; font-size:.75rem; font-weight:600;
|
|
background:#f1f5f9; color:#94a3b8;
|
|
}
|
|
</style>
|
|
@endsection
|
|
|
|
@section('content')
|
|
|
|
<div class="d-flex align-items-center justify-content-between flex-wrap gap-3 mb-4">
|
|
<div>
|
|
<p class="mb-0" style="color:#64748b;font-size:.875rem;">إنشاء حسابات المشغّلين وتخصيص مواقف لهم</p>
|
|
</div>
|
|
<button id="btnOpenCreate" class="btn btn-primary fw-700" style="font-family:'Cairo',sans-serif;border-radius:.625rem;">
|
|
<i class="bi bi-person-plus me-1"></i>إضافة مشغّل
|
|
</button>
|
|
</div>
|
|
|
|
{{-- Operators table --}}
|
|
<div class="card border-0 shadow-sm" style="border-radius:1rem;overflow:hidden;">
|
|
<div class="table-responsive">
|
|
<table class="table op-table mb-0">
|
|
<thead style="background:#f8fafc;">
|
|
<tr>
|
|
<th class="px-4 py-3">المشغّل</th>
|
|
<th class="py-3">البريد الإلكتروني</th>
|
|
<th class="py-3">الهاتف</th>
|
|
<th class="py-3">الموقف المخصص</th>
|
|
<th class="py-3 text-center">إجراءات</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse($operators as $op)
|
|
<tr>
|
|
<td class="px-4 py-3">
|
|
<div class="d-flex align-items-center gap-2">
|
|
<div class="op-avatar">{{ mb_substr($op->name, 0, 1) }}</div>
|
|
<span class="fw-600" style="color:#0f172a;">{{ $op->name }}</span>
|
|
</div>
|
|
</td>
|
|
<td class="py-3" style="color:#475569;">{{ $op->email }}</td>
|
|
<td class="py-3" style="color:#475569;">{{ $op->phone ?? '—' }}</td>
|
|
<td class="py-3">
|
|
@if($op->assignedLot)
|
|
<span class="lot-pill">
|
|
<i class="bi bi-buildings"></i>
|
|
{{ $op->assignedLot->name }}
|
|
</span>
|
|
@else
|
|
<span class="no-lot-pill">
|
|
<i class="bi bi-dash-circle"></i>
|
|
غير مخصص
|
|
</span>
|
|
@endif
|
|
</td>
|
|
<td class="py-3 text-center">
|
|
<div class="d-flex align-items-center justify-content-center gap-2">
|
|
<button class="btn btn-sm fw-600 btn-edit-op"
|
|
style="background:#f1f5f9;color:#475569;border:none;border-radius:.5rem;font-family:'Cairo',sans-serif;"
|
|
data-id="{{ $op->id }}"
|
|
data-name="{{ $op->name }}"
|
|
data-email="{{ $op->email }}"
|
|
data-phone="{{ $op->phone ?? '' }}"
|
|
data-lot="{{ $op->parking_lot_id ?? '' }}">
|
|
<i class="bi bi-pencil me-1"></i>تعديل
|
|
</button>
|
|
<button class="btn btn-sm fw-600 btn-delete-op"
|
|
style="background:rgba(239,68,68,.1);color:#ef4444;border:none;border-radius:.5rem;font-family:'Cairo',sans-serif;"
|
|
data-id="{{ $op->id }}"
|
|
data-name="{{ $op->name }}">
|
|
<i class="bi bi-trash me-1"></i>حذف
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="5" class="text-center py-5" style="color:#94a3b8;">
|
|
<i class="bi bi-people d-block mb-2" style="font-size:2.5rem;opacity:.3;"></i>
|
|
<span>لا يوجد مشغّلون بعد. أضف أول مشغّل الآن.</span>
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ══ CREATE MODAL ══════════════════════════════════════════════════════════ --}}
|
|
<div class="modal fade" id="createModal" tabindex="-1">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content border-0" style="border-radius:1rem;">
|
|
<div class="modal-header border-0 pb-0">
|
|
<h5 class="modal-title fw-800" style="color:#0f172a;">إضافة مشغّل جديد</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body pt-3">
|
|
<div id="createError" class="alert alert-danger d-none border-0 rounded-3 py-2 mb-3" role="alert"></div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label fw-600" style="font-size:.875rem;color:#374151;">الاسم الكامل</label>
|
|
<input type="text" id="createName" class="form-control" placeholder="مثال: أحمد محمد">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-600" style="font-size:.875rem;color:#374151;">البريد الإلكتروني</label>
|
|
<input type="email" id="createEmail" class="form-control" placeholder="operator@example.com" dir="ltr">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-600" style="font-size:.875rem;color:#374151;">رقم الهاتف <span style="color:#94a3b8;font-weight:400;">(اختياري)</span></label>
|
|
<input type="text" id="createPhone" class="form-control" placeholder="09xxxxxxxx">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-600" style="font-size:.875rem;color:#374151;">كلمة المرور</label>
|
|
<input type="password" id="createPassword" class="form-control" placeholder="8 أحرف على الأقل" dir="ltr">
|
|
</div>
|
|
<div class="mb-1">
|
|
<label class="form-label fw-600" style="font-size:.875rem;color:#374151;">الموقف المخصص <span style="color:#94a3b8;font-weight:400;">(اختياري)</span></label>
|
|
<select id="createLot" class="form-select">
|
|
<option value="">— بدون تخصيص —</option>
|
|
@foreach($lots as $lot)
|
|
<option value="{{ $lot->id }}">{{ $lot->name }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer border-0 pt-1">
|
|
<button type="button" class="btn fw-600"
|
|
style="background:#f1f5f9;color:#475569;border:none;border-radius:.625rem;font-family:'Cairo',sans-serif;"
|
|
data-bs-dismiss="modal">إلغاء</button>
|
|
<button type="button" class="btn btn-primary fw-700" id="createBtn"
|
|
style="border-radius:.625rem;font-family:'Cairo',sans-serif;">
|
|
<span id="createSpinner" class="spinner-border spinner-border-sm me-1 d-none"></span>
|
|
إنشاء الحساب
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ══ EDIT MODAL ════════════════════════════════════════════════════════════ --}}
|
|
<div class="modal fade" id="editModal" tabindex="-1">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content border-0" style="border-radius:1rem;">
|
|
<div class="modal-header border-0 pb-0">
|
|
<h5 class="modal-title fw-800" style="color:#0f172a;">تعديل بيانات المشغّل</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body pt-3">
|
|
<div id="editError" class="alert alert-danger d-none border-0 rounded-3 py-2 mb-3" role="alert"></div>
|
|
<input type="hidden" id="editId">
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label fw-600" style="font-size:.875rem;color:#374151;">الاسم الكامل</label>
|
|
<input type="text" id="editName" class="form-control">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-600" style="font-size:.875rem;color:#374151;">البريد الإلكتروني</label>
|
|
<input type="email" id="editEmail" class="form-control" dir="ltr">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-600" style="font-size:.875rem;color:#374151;">رقم الهاتف <span style="color:#94a3b8;font-weight:400;">(اختياري)</span></label>
|
|
<input type="text" id="editPhone" class="form-control">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-600" style="font-size:.875rem;color:#374151;">كلمة مرور جديدة <span style="color:#94a3b8;font-weight:400;">(اتركها فارغة للإبقاء على الحالية)</span></label>
|
|
<input type="password" id="editPassword" class="form-control" placeholder="8 أحرف على الأقل" dir="ltr">
|
|
</div>
|
|
<div class="mb-1">
|
|
<label class="form-label fw-600" style="font-size:.875rem;color:#374151;">الموقف المخصص</label>
|
|
<select id="editLot" class="form-select">
|
|
<option value="">— بدون تخصيص —</option>
|
|
@foreach($lots as $lot)
|
|
<option value="{{ $lot->id }}">{{ $lot->name }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer border-0 pt-1">
|
|
<button type="button" class="btn fw-600"
|
|
style="background:#f1f5f9;color:#475569;border:none;border-radius:.625rem;font-family:'Cairo',sans-serif;"
|
|
data-bs-dismiss="modal">إلغاء</button>
|
|
<button type="button" class="btn btn-primary fw-700" id="editBtn"
|
|
style="border-radius:.625rem;font-family:'Cairo',sans-serif;">
|
|
<span id="editSpinner" class="spinner-border spinner-border-sm me-1 d-none"></span>
|
|
حفظ التغييرات
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ══ DELETE MODAL ══════════════════════════════════════════════════════════ --}}
|
|
<div class="modal fade" id="deleteModal" tabindex="-1">
|
|
<div class="modal-dialog modal-dialog-centered modal-sm">
|
|
<div class="modal-content border-0" style="border-radius:1rem;">
|
|
<div class="modal-header border-0 pb-0">
|
|
<h5 class="modal-title fw-800" style="color:#0f172a;">حذف المشغّل</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body text-center py-3">
|
|
<div style="width:56px;height:56px;background:rgba(239,68,68,.1);border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 1rem;">
|
|
<i class="bi bi-person-x-fill" style="font-size:1.6rem;color:#ef4444;"></i>
|
|
</div>
|
|
<p class="mb-1" style="color:#374151;font-size:.9rem;">هل تريد حذف حساب</p>
|
|
<p class="fw-800 mb-0" style="color:#0f172a;" id="deleteOpName">—</p>
|
|
<p class="mt-2 mb-0" style="color:#94a3b8;font-size:.8rem;">لا يمكن التراجع عن هذا الإجراء.</p>
|
|
</div>
|
|
<div class="modal-footer border-0 pt-0 justify-content-center gap-2">
|
|
<button type="button" class="btn fw-600"
|
|
style="background:#f1f5f9;color:#475569;border:none;border-radius:.625rem;font-family:'Cairo',sans-serif;"
|
|
data-bs-dismiss="modal">إلغاء</button>
|
|
<button type="button" class="btn fw-700" id="deleteBtn"
|
|
style="background:#ef4444;color:#fff;border:none;border-radius:.625rem;font-family:'Cairo',sans-serif;">
|
|
<span id="deleteSpinner" class="spinner-border spinner-border-sm me-1 d-none"></span>
|
|
حذف
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Toast --}}
|
|
<div id="opToast" class="toast align-items-center border-0 position-fixed bottom-0 end-0 m-3"
|
|
style="z-index:9999;min-width:260px;" role="alert">
|
|
<div class="d-flex">
|
|
<div class="toast-body fw-600" id="opToastMsg"></div>
|
|
<button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast"></button>
|
|
</div>
|
|
</div>
|
|
|
|
@endsection
|
|
|
|
@push('scripts')
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
|
|
const csrf = document.querySelector('meta[name="csrf-token"]').content;
|
|
|
|
// ── Toast ─────────────────────────────────────────────────────────────────
|
|
function showToast(msg, success) {
|
|
const el = document.getElementById('opToast');
|
|
document.getElementById('opToastMsg').textContent = msg;
|
|
el.classList.remove('text-bg-success', 'text-bg-danger');
|
|
el.classList.add(success !== false ? 'text-bg-success' : 'text-bg-danger');
|
|
bootstrap.Toast.getOrCreateInstance(el, { delay: 3500 }).show();
|
|
}
|
|
|
|
// ── Modals (lazy) ─────────────────────────────────────────────────────────
|
|
function modal(id) { return bootstrap.Modal.getOrCreateInstance(document.getElementById(id)); }
|
|
|
|
// ── Open create ───────────────────────────────────────────────────────────
|
|
document.getElementById('btnOpenCreate').addEventListener('click', function () {
|
|
['createName','createEmail','createPhone','createPassword'].forEach(id => document.getElementById(id).value = '');
|
|
document.getElementById('createLot').value = '';
|
|
document.getElementById('createError').classList.add('d-none');
|
|
modal('createModal').show();
|
|
});
|
|
|
|
// ── Open edit ─────────────────────────────────────────────────────────────
|
|
document.querySelectorAll('.btn-edit-op').forEach(btn => {
|
|
btn.addEventListener('click', function () {
|
|
document.getElementById('editId').value = this.dataset.id;
|
|
document.getElementById('editName').value = this.dataset.name;
|
|
document.getElementById('editEmail').value = this.dataset.email;
|
|
document.getElementById('editPhone').value = this.dataset.phone || '';
|
|
document.getElementById('editPassword').value = '';
|
|
document.getElementById('editLot').value = this.dataset.lot || '';
|
|
document.getElementById('editError').classList.add('d-none');
|
|
modal('editModal').show();
|
|
});
|
|
});
|
|
|
|
// ── Open delete ───────────────────────────────────────────────────────────
|
|
let pendingDeleteId = null;
|
|
document.querySelectorAll('.btn-delete-op').forEach(btn => {
|
|
btn.addEventListener('click', function () {
|
|
pendingDeleteId = this.dataset.id;
|
|
document.getElementById('deleteOpName').textContent = this.dataset.name;
|
|
modal('deleteModal').show();
|
|
});
|
|
});
|
|
|
|
// ── Submit create ─────────────────────────────────────────────────────────
|
|
document.getElementById('createBtn').addEventListener('click', async function () {
|
|
const btn = this;
|
|
const spinner = document.getElementById('createSpinner');
|
|
const errEl = document.getElementById('createError');
|
|
btn.disabled = true;
|
|
spinner.classList.remove('d-none');
|
|
errEl.classList.add('d-none');
|
|
try {
|
|
const res = await fetch('{{ route("admin.operators.store") }}', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrf },
|
|
body: JSON.stringify({
|
|
name: document.getElementById('createName').value.trim(),
|
|
email: document.getElementById('createEmail').value.trim(),
|
|
phone: document.getElementById('createPhone').value.trim() || null,
|
|
password: document.getElementById('createPassword').value,
|
|
parking_lot_id: document.getElementById('createLot').value || null,
|
|
}),
|
|
});
|
|
const json = await res.json();
|
|
if (json.success) {
|
|
modal('createModal').hide();
|
|
showToast(json.message);
|
|
setTimeout(() => location.reload(), 800);
|
|
} else {
|
|
errEl.textContent = json.message || 'حدث خطأ.';
|
|
errEl.classList.remove('d-none');
|
|
}
|
|
} catch { errEl.textContent = 'تعذّر الاتصال بالخادم.'; errEl.classList.remove('d-none'); }
|
|
finally { btn.disabled = false; spinner.classList.add('d-none'); }
|
|
});
|
|
|
|
// ── Submit edit ───────────────────────────────────────────────────────────
|
|
document.getElementById('editBtn').addEventListener('click', async function () {
|
|
const id = document.getElementById('editId').value;
|
|
const btn = this;
|
|
const spinner = document.getElementById('editSpinner');
|
|
const errEl = document.getElementById('editError');
|
|
btn.disabled = true;
|
|
spinner.classList.remove('d-none');
|
|
errEl.classList.add('d-none');
|
|
try {
|
|
const res = await fetch(`/admin/operators/${id}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrf },
|
|
body: JSON.stringify({
|
|
name: document.getElementById('editName').value.trim(),
|
|
email: document.getElementById('editEmail').value.trim(),
|
|
phone: document.getElementById('editPhone').value.trim() || null,
|
|
password: document.getElementById('editPassword').value || null,
|
|
parking_lot_id: document.getElementById('editLot').value || null,
|
|
}),
|
|
});
|
|
const json = await res.json();
|
|
if (json.success) {
|
|
modal('editModal').hide();
|
|
showToast(json.message);
|
|
setTimeout(() => location.reload(), 800);
|
|
} else {
|
|
errEl.textContent = json.message || 'حدث خطأ.';
|
|
errEl.classList.remove('d-none');
|
|
}
|
|
} catch { errEl.textContent = 'تعذّر الاتصال بالخادم.'; errEl.classList.remove('d-none'); }
|
|
finally { btn.disabled = false; spinner.classList.add('d-none'); }
|
|
});
|
|
|
|
// ── Submit delete ─────────────────────────────────────────────────────────
|
|
document.getElementById('deleteBtn').addEventListener('click', async function () {
|
|
if (!pendingDeleteId) return;
|
|
const btn = this;
|
|
const spinner = document.getElementById('deleteSpinner');
|
|
btn.disabled = true;
|
|
spinner.classList.remove('d-none');
|
|
try {
|
|
const res = await fetch(`/admin/operators/${pendingDeleteId}`, {
|
|
method: 'DELETE',
|
|
headers: { 'X-CSRF-TOKEN': csrf },
|
|
});
|
|
const json = await res.json();
|
|
modal('deleteModal').hide();
|
|
showToast(json.message, json.success);
|
|
if (json.success) setTimeout(() => location.reload(), 800);
|
|
} catch { showToast('تعذّر الاتصال بالخادم.', false); }
|
|
finally { btn.disabled = false; spinner.classList.add('d-none'); }
|
|
});
|
|
|
|
}); // DOMContentLoaded
|
|
</script>
|
|
@endpush
|