feat: AfterSales CRM - SOP compliant real-estate customer care system

- Departments + cross-department transfer workflow
- Role-based closing (staff→resolved, manager→closed) with proof_of_resolution
- Private file storage with collection system + temporary signed URLs
- Dashboard: resolved tickets table, stats, handler performance bar chart
- Spatie RBAC (admin/manager/staff)
- Notifications: TicketTransferred, TicketClosed (database + mail)
- Pest tests: 8 tests, 21 assertions
- Docker production-ready (Dockerfile + compose + entrypoint)
This commit is contained in:
2026-04-27 05:29:48 +00:00
parent 8ad7826f56
commit 887765bbd7
134 changed files with 17416 additions and 45 deletions

View File

@@ -0,0 +1,42 @@
<div style="padding: 1rem;">
@if(empty($links) || count($links) === 0)
<p>No attachments.</p>
@else
<table style="width: 100%; border-collapse: collapse;">
<thead>
<tr style="border-bottom: 2px solid #e5e7eb;">
<th style="text-align: left; padding: 8px;">File</th>
<th style="text-align: left; padding: 8px;">Collection</th>
<th style="text-align: left; padding: 8px;">Size</th>
<th style="text-align: right; padding: 8px;">Action</th>
</tr>
</thead>
<tbody>
@foreach($links as $link)
<tr style="border-bottom: 1px solid #f3f4f6;">
<td style="padding: 8px;">{{ $link['name'] }}</td>
<td style="padding: 8px;">
<span style="
display: inline-block; padding: 2px 8px; border-radius: 9999px; font-size: 12px;
@if($link['collection'] === 'proof_of_resolution') background: #dcfce7; color: #166534;
@elseif($link['collection'] === 'customer_evidence') background: #dbeafe; color: #1e40af;
@else background: #f3f4f6; color: #374151;
@endif
">
{{ $link['collection'] }}
</span>
</td>
<td style="padding: 8px;">{{ $link['size'] }}</td>
<td style="padding: 8px; text-align: right;">
<a href="{{ $link['url'] }}" target="_blank" style="
display: inline-block; padding: 4px 12px; border-radius: 6px;
background: #3b82f6; color: white; text-decoration: none; font-size: 13px;
">Download</a>
</td>
</tr>
@endforeach
</tbody>
</table>
<p style="margin-top: 12px; font-size: 12px; color: #6b7280;">Links expire after 60 minutes.</p>
@endif
</div>

View File

@@ -0,0 +1,68 @@
@php
use App\Models\Feedback;
$record = $this->getRecord();
$tagIds = $record->tags->pluck('id')->toArray();
$similarCases = collect();
if (! empty($tagIds)) {
$similarCases = Feedback::where('id', '!=', $record->id)
->whereHas('tags', fn ($q) => $q->whereIn('feedback_tag_id', $tagIds))
->with(['customer', 'tags', 'assignedTo'])
->latest()
->limit(5)
->get();
}
@endphp
@if($similarCases->isNotEmpty())
<div class="p-6 bg-white rounded-xl shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10">
<h3 class="text-lg font-semibold text-gray-950 dark:text-white mb-4">
Similar Cases (shared tags)
</h3>
<div class="overflow-hidden rounded-lg border border-gray-200 dark:border-gray-700">
<table class="w-full text-sm text-left">
<thead class="bg-gray-50 dark:bg-gray-800">
<tr>
<th class="px-4 py-3 font-medium">Title</th>
<th class="px-4 py-3 font-medium">Customer</th>
<th class="px-4 py-3 font-medium">Status</th>
<th class="px-4 py-3 font-medium">Assigned To</th>
<th class="px-4 py-3 font-medium">Date</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
@foreach($similarCases as $case)
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/50">
<td class="px-4 py-3">
<a href="{{ \App\Filament\Resources\Feedback\FeedbackResource::getUrl('edit', ['record' => $case]) }}"
class="text-blue-600 dark:text-blue-400 hover:underline font-medium">
{{ $case->title }}
</a>
</td>
<td class="px-4 py-3 text-gray-600 dark:text-gray-400">
{{ $case->customer?->name }}
</td>
<td class="px-4 py-3">
<span class="px-2 py-1 text-xs rounded-full font-medium
@switch($case->status)
@case('pending') bg-yellow-100 text-yellow-800 @break
@case('processing') bg-blue-100 text-blue-800 @break
@case('resolved') bg-green-100 text-green-800 @break
@case('closed') bg-gray-100 text-gray-800 @break
@endswitch">
{{ ucfirst($case->status) }}
</span>
</td>
<td class="px-4 py-3 text-gray-600 dark:text-gray-400">
{{ $case->assignedTo?->name ?? 'Unassigned' }}
</td>
<td class="px-4 py-3 text-gray-500 dark:text-gray-400">
{{ $case->created_at->format('d/m/Y') }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@endif