Commit 3902c88f authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat(members): add photo upload/update/delete for member and all dependents

- Member header photo: camera icon to upload/change, X to delete
- Spouses: hover avatar to upload/delete photo (routes + controller)
- Children: hover avatar to upload/delete photo (routes + controller)
- Temporary members: hover avatar to upload/delete photo (routes + controller)
- All use existing PhotoUploadService with compression + thumbnails
- CSS hover reveals action buttons on dependent avatars
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 0233895c
......@@ -400,4 +400,54 @@ class ChildController extends Controller
$result = ChildFeeCalculator::calculate($memberId, $request->all());
return $this->json($result);
}
public function uploadPhoto(Request $request, string $memberId, string $id): Response
{
$db = App::getInstance()->db();
$child = $db->selectOne("SELECT * FROM children WHERE id = ? AND member_id = ?", [(int) $id, (int) $memberId]);
if (!$child) {
return $this->redirect('/members/' . $memberId)->withError('الابن/ة غير موجود');
}
$photoFile = $_FILES['photo'] ?? [];
if (!PhotoUploadService::isUploaded($photoFile)) {
return $this->redirect('/members/' . $memberId)->withError('لم يتم رفع صورة');
}
$validation = PhotoUploadService::validate($photoFile);
if (!$validation['valid']) {
$session = App::getInstance()->session();
$session->flash('_alerts', array_map(fn($e) => ['type' => 'error', 'message' => $e], $validation['errors']));
return $this->redirect('/members/' . $memberId);
}
$result = PhotoUploadService::upload($photoFile, 'children', (int) $id);
if ($result) {
$db->update('children', ['photo_path' => $result['path']], 'id = ?', [(int) $id]);
return $this->redirect('/members/' . $memberId)->withSuccess('تم رفع صورة الابن/ة بنجاح');
}
return $this->redirect('/members/' . $memberId)->withError('حدث خطأ أثناء حفظ الصورة');
}
public function deletePhoto(Request $request, string $memberId, string $id): Response
{
$db = App::getInstance()->db();
$child = $db->selectOne("SELECT * FROM children WHERE id = ? AND member_id = ?", [(int) $id, (int) $memberId]);
if (!$child) {
return $this->redirect('/members/' . $memberId)->withError('الابن/ة غير موجود');
}
if (empty($child['photo_path'])) {
return $this->redirect('/members/' . $memberId)->withError('لا توجد صورة لحذفها');
}
$fullPath = App::getInstance()->basePath() . '/public/' . $child['photo_path'];
if (is_file($fullPath)) {
@unlink($fullPath);
}
$db->update('children', ['photo_path' => null], 'id = ?', [(int) $id]);
return $this->redirect('/members/' . $memberId)->withSuccess('تم حذف صورة الابن/ة');
}
}
\ No newline at end of file
......@@ -9,5 +9,7 @@ return [
['POST', '/members/{memberId}/children/{id}', 'Children\Controllers\ChildController@update', ['auth', 'csrf'], 'child.edit'],
['POST', '/members/{memberId}/children/{id}/archive', 'Children\Controllers\ChildController@archive', ['auth', 'csrf'], 'child.remove'],
['POST', '/members/{memberId}/children/{id}/freeze', 'Children\Controllers\ChildController@freeze', ['auth', 'csrf'], 'child.freeze'],
['POST', '/members/{memberId}/children/{id}/photo', 'Children\Controllers\ChildController@uploadPhoto', ['auth', 'csrf'], 'child.edit'],
['POST', '/members/{memberId}/children/{id}/photo/delete', 'Children\Controllers\ChildController@deletePhoto', ['auth', 'csrf'], 'child.edit'],
['POST', '/api/children/calculate-fee', 'Children\Controllers\ChildController@calculateFee', ['auth'], 'child.add'],
];
\ No newline at end of file
......@@ -873,6 +873,26 @@ class MemberController extends Controller
return $this->redirect('/members/' . $id)->withError('حدث خطأ أثناء حفظ الصورة');
}
public function deletePhoto(Request $request, string $id): Response
{
$member = Member::find((int) $id);
if (!$member || $member->is_archived) {
return $this->redirect('/members')->withError('العضو غير موجود');
}
if (empty($member->photo_path)) {
return $this->redirect('/members/' . $id)->withError('لا توجد صورة لحذفها');
}
$fullPath = App::getInstance()->basePath() . '/public/' . $member->photo_path;
if (is_file($fullPath)) {
@unlink($fullPath);
}
$member->update(['photo_path' => null]);
return $this->redirect('/members/' . $id)->withSuccess('تم حذف الصورة');
}
public function changeStatus(Request $request, string $id): Response
{
$member = Member::find((int) $id);
......
......@@ -16,6 +16,7 @@ return [
['GET', '/members/{id}/edit', 'Members\Controllers\MemberController@edit', ['auth'], 'member.edit'],
['POST', '/members/{id}', 'Members\Controllers\MemberController@update', ['auth', 'csrf'], 'member.edit'],
['POST', '/members/{id}/photo', 'Members\Controllers\MemberController@uploadPhoto', ['auth', 'csrf'], 'member.edit'],
['POST', '/members/{id}/photo/delete', 'Members\Controllers\MemberController@deletePhoto', ['auth', 'csrf'], 'member.edit'],
['POST', '/members/{id}/status', 'Members\Controllers\MemberController@changeStatus', ['auth', 'csrf'], 'member.change_status'],
['POST', '/members/{id}/pay-form-fee', 'Members\Controllers\MemberController@payFormFee', ['auth', 'csrf'], 'member.pay_form_fee'],
['POST', '/members/{id}/pay-membership', 'Members\Controllers\MemberController@payMembership',['auth', 'csrf'], 'member.pay_membership'],
......
......@@ -124,13 +124,34 @@ $_memberInitials = mb_substr($member->full_name_ar, 0, 2);
<div style="padding:0 25px 25px;margin-top:-45px;position:relative;">
<div style="display:flex;align-items:end;gap:20px;">
<!-- Member Photo -->
<div style="width:100px;height:100px;border-radius:50%;overflow:hidden;border:4px solid #fff;box-shadow:0 4px 20px rgba(0,0,0,0.15);flex-shrink:0;background:<?= $_memberHasPhoto ? '#F3F4F6' : 'linear-gradient(135deg, #667EEA, #764BA2)' ?>;display:flex;align-items:center;justify-content:center;">
<div style="position:relative;flex-shrink:0;">
<div style="width:100px;height:100px;border-radius:50%;overflow:hidden;border:4px solid #fff;box-shadow:0 4px 20px rgba(0,0,0,0.15);background:<?= $_memberHasPhoto ? '#F3F4F6' : 'linear-gradient(135deg, #667EEA, #764BA2)' ?>;display:flex;align-items:center;justify-content:center;">
<?php if ($_memberHasPhoto): ?>
<img src="/<?= e($member->photo_path) ?>" alt="صورة العضو" style="width:100%;height:100%;object-fit:cover;">
<?php else: ?>
<span style="font-size:32px;font-weight:700;color:#fff;letter-spacing:-1px;"><?= e($_memberInitials) ?></span>
<?php endif; ?>
</div>
<?php if ($canEdit): ?>
<div style="position:absolute;bottom:0;right:0;display:flex;gap:3px;">
<label style="width:28px;height:28px;border-radius:50%;background:#0D7377;border:2px solid #fff;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 2px 6px rgba(0,0,0,0.2);" title="<?= $_memberHasPhoto ? 'تغيير الصورة' : 'رفع صورة' ?>">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
<form method="POST" action="/members/<?= (int) $member->id ?>/photo" enctype="multipart/form-data" style="display:none;" id="memberPhotoForm">
<?= csrf_field() ?>
<input type="file" name="photo" accept="image/jpeg,image/png,image/webp" onchange="this.form.submit();">
</form>
</label>
<?php if ($_memberHasPhoto): ?>
<form method="POST" action="/members/<?= (int) $member->id ?>/photo/delete" style="display:inline;" onsubmit="return confirm('حذف صورة العضو؟')">
<?= csrf_field() ?>
<button type="submit" style="width:28px;height:28px;border-radius:50%;background:#DC2626;border:2px solid #fff;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 2px 6px rgba(0,0,0,0.2);padding:0;" title="حذف الصورة">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.5"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
</form>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<div style="flex:1;padding-bottom:5px;">
<h2 style="margin:0 0 2px;color:#1A1A2E;font-size:24px;font-weight:800;"><?= e($member->full_name_ar) ?></h2>
<?php if ($member->full_name_en): ?><div style="color:#6B7280;font-size:14px;margin-bottom:4px;"><?= e($member->full_name_en) ?></div><?php endif; ?>
......@@ -1036,7 +1057,29 @@ $childClassLabels = ['included' => 'تابع مشمول', 'dependent_with_fee' =
?>
<tr <?= $sBreakdown ? 'style="cursor:pointer;" onclick="toggleBillDetail(\'spouse-' . $sIdx . '\')"' : '' ?>>
<td><?= (int) $s['spouse_order'] ?></td>
<td style="font-weight:600;"><div style="display:flex;align-items:center;gap:8px;"><div style="width:36px;height:36px;border-radius:50%;overflow:hidden;flex-shrink:0;background:<?= $_sHasPhoto ? '#F3F4F6' : 'linear-gradient(135deg, #EC4899, #F472B6)' ?>;display:flex;align-items:center;justify-content:center;border:2px solid <?= $_sHasPhoto ? '#0D7377' : '#F9A8D4' ?>;box-shadow:0 1px 4px rgba(0,0,0,0.08);"><?php if ($_sHasPhoto): ?><img src="/<?= e($s['photo_path']) ?>" style="width:100%;height:100%;object-fit:cover;"><?php else: ?><span style="font-size:13px;font-weight:700;color:#fff;"><?= e($_sInitials) ?></span><?php endif; ?></div><span><?= e($s['full_name_ar']) ?></span></div></td>
<td style="font-weight:600;"><div style="display:flex;align-items:center;gap:8px;">
<div style="position:relative;flex-shrink:0;" class="dep-photo-wrap">
<div style="width:36px;height:36px;border-radius:50%;overflow:hidden;background:<?= $_sHasPhoto ? '#F3F4F6' : 'linear-gradient(135deg, #EC4899, #F472B6)' ?>;display:flex;align-items:center;justify-content:center;border:2px solid <?= $_sHasPhoto ? '#0D7377' : '#F9A8D4' ?>;box-shadow:0 1px 4px rgba(0,0,0,0.08);"><?php if ($_sHasPhoto): ?><img src="/<?= e($s['photo_path']) ?>" style="width:100%;height:100%;object-fit:cover;"><?php else: ?><span style="font-size:13px;font-weight:700;color:#fff;"><?= e($_sInitials) ?></span><?php endif; ?></div>
<?php if ($canEdit): ?>
<label class="dep-photo-btn" style="position:absolute;bottom:-4px;right:-4px;width:18px;height:18px;border-radius:50%;background:#0D7377;border:1.5px solid #fff;display:none;align-items:center;justify-content:center;cursor:pointer;" title="رفع صورة">
<svg xmlns="http://www.w3.org/2000/svg" width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
<form method="POST" action="/members/<?= (int) $member->id ?>/spouses/<?= (int) $s['id'] ?>/photo" enctype="multipart/form-data" style="display:none;">
<?= csrf_field() ?>
<input type="file" name="photo" accept="image/jpeg,image/png,image/webp" onchange="this.form.submit();">
</form>
</label>
<?php if ($_sHasPhoto): ?>
<form method="POST" action="/members/<?= (int) $member->id ?>/spouses/<?= (int) $s['id'] ?>/photo/delete" class="dep-photo-del" style="position:absolute;top:-4px;right:-4px;display:none;" onsubmit="return confirm('حذف الصورة؟')">
<?= csrf_field() ?>
<button type="submit" style="width:16px;height:16px;border-radius:50%;background:#DC2626;border:1.5px solid #fff;display:flex;align-items:center;justify-content:center;cursor:pointer;padding:0;" title="حذف">
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
</form>
<?php endif; ?>
<?php endif; ?>
</div>
<span><?= e($s['full_name_ar']) ?></span>
</div></td>
<td><?php
$sClassification = $s['classification'] ?? 'working';
$sTypeLabel = match($sClassification) { 'dependent' => 'عضو تابع', 'working' => 'عضو عامل', default => 'عضو عامل' };
......@@ -1145,7 +1188,28 @@ $childClassLabels = ['included' => 'تابع مشمول', 'dependent_with_fee' =
?>
<tr <?= $cBreakdown && !$cIsInactive ? 'style="cursor:pointer;" onclick="toggleBillDetail(\'child-' . $cIdx . '\')"' : '' ?> <?= $cIsInactive ? 'style="opacity:0.5;background:#F9FAFB;"' : '' ?>>
<td><?= (int) $c['child_order'] ?></td>
<td style="font-weight:600;"><div style="display:flex;align-items:center;gap:8px;"><div style="width:36px;height:36px;border-radius:50%;overflow:hidden;flex-shrink:0;background:<?= $_cHasPhoto ? '#F3F4F6' : $_cGradient ?>;display:flex;align-items:center;justify-content:center;border:2px solid <?= $_cHasPhoto ? '#0D7377' : $_cBorder ?>;box-shadow:0 1px 4px rgba(0,0,0,0.08);"><?php if ($_cHasPhoto): ?><img src="/<?= e($c['photo_path']) ?>" style="width:100%;height:100%;object-fit:cover;"><?php else: ?><span style="font-size:13px;font-weight:700;color:#fff;"><?= e($_cInitials) ?></span><?php endif; ?></div><div><?= e($c['full_name_ar']) ?>
<td style="font-weight:600;"><div style="display:flex;align-items:center;gap:8px;">
<div style="position:relative;flex-shrink:0;" class="dep-photo-wrap">
<div style="width:36px;height:36px;border-radius:50%;overflow:hidden;background:<?= $_cHasPhoto ? '#F3F4F6' : $_cGradient ?>;display:flex;align-items:center;justify-content:center;border:2px solid <?= $_cHasPhoto ? '#0D7377' : $_cBorder ?>;box-shadow:0 1px 4px rgba(0,0,0,0.08);"><?php if ($_cHasPhoto): ?><img src="/<?= e($c['photo_path']) ?>" style="width:100%;height:100%;object-fit:cover;"><?php else: ?><span style="font-size:13px;font-weight:700;color:#fff;"><?= e($_cInitials) ?></span><?php endif; ?></div>
<?php if ($canEdit && !$cIsInactive): ?>
<label class="dep-photo-btn" style="position:absolute;bottom:-4px;right:-4px;width:18px;height:18px;border-radius:50%;background:#0D7377;border:1.5px solid #fff;display:none;align-items:center;justify-content:center;cursor:pointer;" title="رفع صورة">
<svg xmlns="http://www.w3.org/2000/svg" width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
<form method="POST" action="/members/<?= (int) $member->id ?>/children/<?= (int) $c['id'] ?>/photo" enctype="multipart/form-data" style="display:none;">
<?= csrf_field() ?>
<input type="file" name="photo" accept="image/jpeg,image/png,image/webp" onchange="this.form.submit();">
</form>
</label>
<?php if ($_cHasPhoto): ?>
<form method="POST" action="/members/<?= (int) $member->id ?>/children/<?= (int) $c['id'] ?>/photo/delete" class="dep-photo-del" style="position:absolute;top:-4px;right:-4px;display:none;" onsubmit="return confirm('حذف الصورة؟')">
<?= csrf_field() ?>
<button type="submit" style="width:16px;height:16px;border-radius:50%;background:#DC2626;border:1.5px solid #fff;display:flex;align-items:center;justify-content:center;cursor:pointer;padding:0;" title="حذف">
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
</form>
<?php endif; ?>
<?php endif; ?>
</div>
<div><?= e($c['full_name_ar']) ?>
<?php if ($cIsSeparated && !empty($c['separated_membership_number'])): ?>
<a href="/members/<?= (int) $c['separated_member_id'] ?>" style="display:block;font-size:11px;color:#0D7377;font-weight:600;margin-top:2px;">عضوية #<?= e($c['separated_membership_number']) ?></a>
<?php endif; ?>
......@@ -1235,7 +1299,29 @@ $childClassLabels = ['included' => 'تابع مشمول', 'dependent_with_fee' =
$_tBorder = ($t['gender'] === 'male') ? '#C4B5FD' : '#FDE68A';
?>
<tr <?= $tBreakdown ? 'style="cursor:pointer;" onclick="toggleBillDetail(\'temp-' . $tIdx . '\')"' : '' ?>>
<td style="font-weight:600;"><div style="display:flex;align-items:center;gap:8px;"><div style="width:36px;height:36px;border-radius:50%;overflow:hidden;flex-shrink:0;background:<?= $_tHasPhoto ? '#F3F4F6' : $_tGradient ?>;display:flex;align-items:center;justify-content:center;border:2px solid <?= $_tHasPhoto ? '#0D7377' : $_tBorder ?>;box-shadow:0 1px 4px rgba(0,0,0,0.08);"><?php if ($_tHasPhoto): ?><img src="/<?= e($t['photo_path']) ?>" style="width:100%;height:100%;object-fit:cover;"><?php else: ?><span style="font-size:13px;font-weight:700;color:#fff;"><?= e($_tInitials) ?></span><?php endif; ?></div><span><?= e($t['full_name_ar']) ?></span></div></td>
<td style="font-weight:600;"><div style="display:flex;align-items:center;gap:8px;">
<div style="position:relative;flex-shrink:0;" class="dep-photo-wrap">
<div style="width:36px;height:36px;border-radius:50%;overflow:hidden;background:<?= $_tHasPhoto ? '#F3F4F6' : $_tGradient ?>;display:flex;align-items:center;justify-content:center;border:2px solid <?= $_tHasPhoto ? '#0D7377' : $_tBorder ?>;box-shadow:0 1px 4px rgba(0,0,0,0.08);"><?php if ($_tHasPhoto): ?><img src="/<?= e($t['photo_path']) ?>" style="width:100%;height:100%;object-fit:cover;"><?php else: ?><span style="font-size:13px;font-weight:700;color:#fff;"><?= e($_tInitials) ?></span><?php endif; ?></div>
<?php if ($canEdit): ?>
<label class="dep-photo-btn" style="position:absolute;bottom:-4px;right:-4px;width:18px;height:18px;border-radius:50%;background:#0D7377;border:1.5px solid #fff;display:none;align-items:center;justify-content:center;cursor:pointer;" title="رفع صورة">
<svg xmlns="http://www.w3.org/2000/svg" width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
<form method="POST" action="/members/<?= (int) $member->id ?>/temporary/<?= (int) $t['id'] ?>/photo" enctype="multipart/form-data" style="display:none;">
<?= csrf_field() ?>
<input type="file" name="photo" accept="image/jpeg,image/png,image/webp" onchange="this.form.submit();">
</form>
</label>
<?php if ($_tHasPhoto): ?>
<form method="POST" action="/members/<?= (int) $member->id ?>/temporary/<?= (int) $t['id'] ?>/photo/delete" class="dep-photo-del" style="position:absolute;top:-4px;right:-4px;display:none;" onsubmit="return confirm('حذف الصورة؟')">
<?= csrf_field() ?>
<button type="submit" style="width:16px;height:16px;border-radius:50%;background:#DC2626;border:1.5px solid #fff;display:flex;align-items:center;justify-content:center;cursor:pointer;padding:0;" title="حذف">
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
</form>
<?php endif; ?>
<?php endif; ?>
</div>
<span><?= e($t['full_name_ar']) ?></span>
</div></td>
<td><span style="background:#E0F2FE;color:#0369A1;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600;">عضو مؤقت</span></td>
<td style="font-size:12px;"><?= $categoryLabels[$t['category']] ?? e($t['category']) ?></td>
<td><?= $t['gender'] === 'male' ? 'ذكر' : 'أنثى' ?></td>
......@@ -1513,6 +1599,10 @@ $childClassLabels = ['included' => 'تابع مشمول', 'dependent_with_fee' =
</div>
<?php endif; ?>
<style>
.dep-photo-wrap:hover .dep-photo-btn,
.dep-photo-wrap:hover .dep-photo-del { display: flex !important; }
</style>
<script>
function toggleBillDetail(idx) {
var el = document.getElementById('bill-detail-' + idx);
......
......@@ -403,4 +403,54 @@ class SpouseController extends Controller
$result = SpouseFeeCalculator::calculate($memberId, $spouseData);
return $this->json($result);
}
public function uploadPhoto(Request $request, string $memberId, string $id): Response
{
$db = App::getInstance()->db();
$spouse = $db->selectOne("SELECT * FROM spouses WHERE id = ? AND member_id = ? AND is_archived = 0", [(int) $id, (int) $memberId]);
if (!$spouse) {
return $this->redirect('/members/' . $memberId)->withError('التابع غير موجود');
}
$photoFile = $_FILES['photo'] ?? [];
if (!PhotoUploadService::isUploaded($photoFile)) {
return $this->redirect('/members/' . $memberId)->withError('لم يتم رفع صورة');
}
$validation = PhotoUploadService::validate($photoFile);
if (!$validation['valid']) {
$session = App::getInstance()->session();
$session->flash('_alerts', array_map(fn($e) => ['type' => 'error', 'message' => $e], $validation['errors']));
return $this->redirect('/members/' . $memberId);
}
$result = PhotoUploadService::upload($photoFile, 'spouses', (int) $id);
if ($result) {
$db->update('spouses', ['photo_path' => $result['path']], 'id = ?', [(int) $id]);
return $this->redirect('/members/' . $memberId)->withSuccess('تم رفع صورة الزوج/ة بنجاح');
}
return $this->redirect('/members/' . $memberId)->withError('حدث خطأ أثناء حفظ الصورة');
}
public function deletePhoto(Request $request, string $memberId, string $id): Response
{
$db = App::getInstance()->db();
$spouse = $db->selectOne("SELECT * FROM spouses WHERE id = ? AND member_id = ? AND is_archived = 0", [(int) $id, (int) $memberId]);
if (!$spouse) {
return $this->redirect('/members/' . $memberId)->withError('التابع غير موجود');
}
if (empty($spouse['photo_path'])) {
return $this->redirect('/members/' . $memberId)->withError('لا توجد صورة لحذفها');
}
$fullPath = App::getInstance()->basePath() . '/public/' . $spouse['photo_path'];
if (is_file($fullPath)) {
@unlink($fullPath);
}
$db->update('spouses', ['photo_path' => null], 'id = ?', [(int) $id]);
return $this->redirect('/members/' . $memberId)->withSuccess('تم حذف صورة الزوج/ة');
}
}
\ No newline at end of file
......@@ -8,5 +8,7 @@ return [
['GET', '/members/{memberId}/spouses/{id}/edit', 'Spouses\Controllers\SpouseController@edit', ['auth'], 'spouse.edit'],
['POST', '/members/{memberId}/spouses/{id}', 'Spouses\Controllers\SpouseController@update', ['auth', 'csrf'], 'spouse.edit'],
['POST', '/members/{memberId}/spouses/{id}/archive', 'Spouses\Controllers\SpouseController@archive', ['auth', 'csrf'], 'spouse.remove'],
['POST', '/members/{memberId}/spouses/{id}/photo', 'Spouses\Controllers\SpouseController@uploadPhoto', ['auth', 'csrf'], 'spouse.edit'],
['POST', '/members/{memberId}/spouses/{id}/photo/delete', 'Spouses\Controllers\SpouseController@deletePhoto', ['auth', 'csrf'], 'spouse.edit'],
['POST', '/api/spouses/calculate-fee', 'Spouses\Controllers\SpouseController@calculateFee', ['auth'], 'spouse.add'],
];
\ No newline at end of file
......@@ -296,4 +296,54 @@ class TemporaryController extends Controller
return $this->redirect("/members/{$memberId}")->withSuccess('تم إزالة العضو المؤقت');
}
public function uploadPhoto(Request $request, string $memberId, string $id): Response
{
$db = App::getInstance()->db();
$temp = $db->selectOne("SELECT * FROM temporary_members WHERE id = ? AND member_id = ? AND is_archived = 0", [(int) $id, (int) $memberId]);
if (!$temp) {
return $this->redirect('/members/' . $memberId)->withError('العضو المؤقت غير موجود');
}
$photoFile = $_FILES['photo'] ?? [];
if (!PhotoUploadService::isUploaded($photoFile)) {
return $this->redirect('/members/' . $memberId)->withError('لم يتم رفع صورة');
}
$validation = PhotoUploadService::validate($photoFile);
if (!$validation['valid']) {
$session = App::getInstance()->session();
$session->flash('_alerts', array_map(fn($e) => ['type' => 'error', 'message' => $e], $validation['errors']));
return $this->redirect('/members/' . $memberId);
}
$result = PhotoUploadService::upload($photoFile, 'temporary', (int) $id);
if ($result) {
$db->update('temporary_members', ['photo_path' => $result['path']], 'id = ?', [(int) $id]);
return $this->redirect('/members/' . $memberId)->withSuccess('تم رفع صورة العضو المؤقت بنجاح');
}
return $this->redirect('/members/' . $memberId)->withError('حدث خطأ أثناء حفظ الصورة');
}
public function deletePhoto(Request $request, string $memberId, string $id): Response
{
$db = App::getInstance()->db();
$temp = $db->selectOne("SELECT * FROM temporary_members WHERE id = ? AND member_id = ? AND is_archived = 0", [(int) $id, (int) $memberId]);
if (!$temp) {
return $this->redirect('/members/' . $memberId)->withError('العضو المؤقت غير موجود');
}
if (empty($temp['photo_path'])) {
return $this->redirect('/members/' . $memberId)->withError('لا توجد صورة لحذفها');
}
$fullPath = App::getInstance()->basePath() . '/public/' . $temp['photo_path'];
if (is_file($fullPath)) {
@unlink($fullPath);
}
$db->update('temporary_members', ['photo_path' => null], 'id = ?', [(int) $id]);
return $this->redirect('/members/' . $memberId)->withSuccess('تم حذف صورة العضو المؤقت');
}
}
\ No newline at end of file
......@@ -7,4 +7,6 @@ return [
['POST', '/members/{memberId}/temporary', 'Temporary\Controllers\TemporaryController@store', ['auth', 'csrf'], 'temp.add'],
['GET', '/members/{memberId}/temporary/{id}', 'Temporary\Controllers\TemporaryController@show', ['auth'], 'temp.view'],
['POST', '/members/{memberId}/temporary/{id}/archive', 'Temporary\Controllers\TemporaryController@archive', ['auth', 'csrf'], 'temp.remove'],
['POST', '/members/{memberId}/temporary/{id}/photo', 'Temporary\Controllers\TemporaryController@uploadPhoto', ['auth', 'csrf'], 'temp.add'],
['POST', '/members/{memberId}/temporary/{id}/photo/delete', 'Temporary\Controllers\TemporaryController@deletePhoto', ['auth', 'csrf'], 'temp.add'],
];
\ No newline at end of file
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