complete story 6.6 with qa test
This commit is contained in:
@@ -0,0 +1,332 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\TimelineStatus;
|
||||
use App\Models\Timeline;
|
||||
use App\Models\User;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use Illuminate\Support\Carbon;
|
||||
use League\Csv\Writer;
|
||||
use Livewire\Volt\Component;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
new class extends Component {
|
||||
public string $clientSearch = '';
|
||||
public ?int $clientId = null;
|
||||
public string $status = 'all';
|
||||
public string $dateFrom = '';
|
||||
public string $dateTo = '';
|
||||
public bool $includeUpdates = false;
|
||||
|
||||
public function getClientsProperty()
|
||||
{
|
||||
if (strlen($this->clientSearch) < 2) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return User::query()
|
||||
->whereIn('user_type', ['individual', 'company'])
|
||||
->where(fn ($q) => $q
|
||||
->where('full_name', 'like', "%{$this->clientSearch}%")
|
||||
->orWhere('email', 'like', "%{$this->clientSearch}%"))
|
||||
->limit(10)
|
||||
->get();
|
||||
}
|
||||
|
||||
public function selectClient(int $id): void
|
||||
{
|
||||
$this->clientId = $id;
|
||||
$this->clientSearch = User::find($id)?->full_name ?? '';
|
||||
}
|
||||
|
||||
public function clearClient(): void
|
||||
{
|
||||
$this->clientId = null;
|
||||
$this->clientSearch = '';
|
||||
}
|
||||
|
||||
public function clearFilters(): void
|
||||
{
|
||||
$this->clientId = null;
|
||||
$this->clientSearch = '';
|
||||
$this->status = 'all';
|
||||
$this->dateFrom = '';
|
||||
$this->dateTo = '';
|
||||
$this->includeUpdates = false;
|
||||
}
|
||||
|
||||
public function exportCsv(): ?StreamedResponse
|
||||
{
|
||||
$count = $this->getFilteredTimelines()->count();
|
||||
|
||||
if ($count === 0) {
|
||||
$this->dispatch('notify', type: 'info', message: __('export.no_timelines_match'));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$locale = auth()->user()->preferred_language ?? 'ar';
|
||||
|
||||
return response()->streamDownload(function () use ($locale) {
|
||||
// UTF-8 BOM for Excel Arabic support
|
||||
echo "\xEF\xBB\xBF";
|
||||
|
||||
$csv = Writer::createFromString();
|
||||
|
||||
// Headers based on admin language
|
||||
$csv->insertOne([
|
||||
__('export.case_name', [], $locale),
|
||||
__('export.case_reference', [], $locale),
|
||||
__('export.client_name', [], $locale),
|
||||
__('export.status', [], $locale),
|
||||
__('export.created_date', [], $locale),
|
||||
__('export.updates_count', [], $locale),
|
||||
__('export.last_update', [], $locale),
|
||||
]);
|
||||
|
||||
$this->getFilteredTimelines()->cursor()->each(function ($timeline) use ($csv, $locale) {
|
||||
$csv->insertOne([
|
||||
$timeline->case_name,
|
||||
$timeline->case_reference ?? '-',
|
||||
$timeline->user->full_name,
|
||||
__('export.timeline_status_'.$timeline->status->value, [], $locale),
|
||||
$timeline->created_at->format('Y-m-d'),
|
||||
$timeline->updates_count,
|
||||
$timeline->updates_max_created_at
|
||||
? Carbon::parse($timeline->updates_max_created_at)->format('Y-m-d H:i')
|
||||
: '-',
|
||||
]);
|
||||
});
|
||||
|
||||
echo $csv->toString();
|
||||
}, 'timelines-export-'.now()->format('Y-m-d').'.csv', [
|
||||
'Content-Type' => 'text/csv; charset=UTF-8',
|
||||
]);
|
||||
}
|
||||
|
||||
public function exportPdf(): ?StreamedResponse
|
||||
{
|
||||
$query = $this->getFilteredTimelines();
|
||||
|
||||
if ($this->includeUpdates) {
|
||||
$query->with(['updates' => fn ($q) => $q->orderBy('created_at', 'desc')]);
|
||||
}
|
||||
|
||||
$timelines = $query->get();
|
||||
|
||||
if ($timelines->isEmpty()) {
|
||||
$this->dispatch('notify', type: 'info', message: __('export.no_timelines_match'));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($timelines->count() > 500) {
|
||||
$this->dispatch('notify', type: 'warning', message: __('export.large_export_warning'));
|
||||
}
|
||||
|
||||
$locale = auth()->user()->preferred_language ?? 'ar';
|
||||
|
||||
$pdf = Pdf::loadView('pdf.timelines-export', [
|
||||
'timelines' => $timelines,
|
||||
'includeUpdates' => $this->includeUpdates,
|
||||
'locale' => $locale,
|
||||
'generatedAt' => now(),
|
||||
'filters' => $this->getActiveFilters(),
|
||||
'totalCount' => $timelines->count(),
|
||||
]);
|
||||
|
||||
$pdf->setOption('isHtml5ParserEnabled', true);
|
||||
$pdf->setOption('defaultFont', 'DejaVu Sans');
|
||||
|
||||
return response()->streamDownload(
|
||||
fn () => print($pdf->output()),
|
||||
'timelines-export-'.now()->format('Y-m-d').'.pdf'
|
||||
);
|
||||
}
|
||||
|
||||
public function with(): array
|
||||
{
|
||||
return [
|
||||
'statuses' => [
|
||||
'all' => __('export.all_statuses'),
|
||||
'active' => __('export.timeline_status_active'),
|
||||
'archived' => __('export.timeline_status_archived'),
|
||||
],
|
||||
'previewCount' => $this->getFilteredTimelines()->count(),
|
||||
];
|
||||
}
|
||||
|
||||
private function getFilteredTimelines()
|
||||
{
|
||||
return Timeline::query()
|
||||
->with('user:id,full_name')
|
||||
->withCount('updates')
|
||||
->withMax('updates', 'created_at')
|
||||
->when($this->clientId, fn ($q) => $q->where('user_id', $this->clientId))
|
||||
->when($this->status !== 'all', fn ($q) => $q->where('status', $this->status))
|
||||
->when($this->dateFrom, fn ($q) => $q->whereDate('created_at', '>=', $this->dateFrom))
|
||||
->when($this->dateTo, fn ($q) => $q->whereDate('created_at', '<=', $this->dateTo))
|
||||
->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
private function getActiveFilters(): array
|
||||
{
|
||||
$filters = [];
|
||||
|
||||
if ($this->clientId) {
|
||||
$filters['client'] = User::find($this->clientId)?->full_name;
|
||||
}
|
||||
|
||||
if ($this->status !== 'all') {
|
||||
$filters['status'] = __('export.timeline_status_'.$this->status);
|
||||
}
|
||||
|
||||
if ($this->dateFrom) {
|
||||
$filters['date_from'] = $this->dateFrom;
|
||||
}
|
||||
|
||||
if ($this->dateTo) {
|
||||
$filters['date_to'] = $this->dateTo;
|
||||
}
|
||||
|
||||
return $filters;
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div>
|
||||
<div class="mb-6 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<flux:heading size="xl">{{ __('export.export_timelines') }}</flux:heading>
|
||||
<flux:text class="mt-1 text-zinc-500 dark:text-zinc-400">{{ __('export.export_timelines_description') }}</flux:text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-6 rounded-lg border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<flux:heading size="lg" class="mb-4">{{ __('export.filters_applied') }}</flux:heading>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{{-- Client Search --}}
|
||||
<div class="relative">
|
||||
<flux:input
|
||||
wire:model.live.debounce.300ms="clientSearch"
|
||||
:label="__('export.client_name')"
|
||||
:placeholder="__('export.search_client_placeholder')"
|
||||
icon="magnifying-glass"
|
||||
/>
|
||||
|
||||
@if ($clientId)
|
||||
<button
|
||||
wire:click="clearClient"
|
||||
type="button"
|
||||
class="absolute end-2 top-8 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300"
|
||||
>
|
||||
<flux:icon name="x-mark" class="h-4 w-4" />
|
||||
</button>
|
||||
@endif
|
||||
|
||||
@if (strlen($clientSearch) >= 2 && ! $clientId && $this->clients->count() > 0)
|
||||
<div class="absolute z-10 mt-1 w-full rounded-lg border border-zinc-200 bg-white shadow-lg dark:border-zinc-700 dark:bg-zinc-800">
|
||||
@foreach ($this->clients as $client)
|
||||
<button
|
||||
wire:click="selectClient({{ $client->id }})"
|
||||
type="button"
|
||||
class="block w-full px-4 py-2 text-start hover:bg-zinc-100 dark:hover:bg-zinc-700"
|
||||
>
|
||||
<span class="font-medium">{{ $client->full_name }}</span>
|
||||
<span class="text-sm text-zinc-500">{{ $client->email }}</span>
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Status Filter --}}
|
||||
<div>
|
||||
<flux:select wire:model.live="status" :label="__('export.status')">
|
||||
@foreach ($statuses as $value => $label)
|
||||
<flux:select.option value="{{ $value }}">{{ $label }}</flux:select.option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
</div>
|
||||
|
||||
{{-- Date From --}}
|
||||
<div>
|
||||
<flux:input
|
||||
wire:model.live="dateFrom"
|
||||
type="date"
|
||||
:label="__('export.date_from')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{{-- Date To --}}
|
||||
<div>
|
||||
<flux:input
|
||||
wire:model.live="dateTo"
|
||||
type="date"
|
||||
:label="__('export.date_to')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Include Updates Toggle --}}
|
||||
<div class="mt-4">
|
||||
<flux:checkbox
|
||||
wire:model.live="includeUpdates"
|
||||
:label="__('export.include_updates')"
|
||||
/>
|
||||
<flux:text class="ms-6 text-sm text-zinc-500 dark:text-zinc-400">
|
||||
{{ __('export.include_updates_description') }}
|
||||
</flux:text>
|
||||
</div>
|
||||
|
||||
@if ($clientId || $status !== 'all' || $dateFrom || $dateTo || $includeUpdates)
|
||||
<div class="mt-4">
|
||||
<flux:button wire:click="clearFilters" variant="ghost" icon="x-mark" size="sm">
|
||||
{{ __('export.clear_filters') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<div class="flex flex-col items-center justify-between gap-4 sm:flex-row">
|
||||
<div>
|
||||
<flux:text class="text-zinc-600 dark:text-zinc-400">
|
||||
{{ __('export.total_records') }}: <span class="font-semibold text-zinc-900 dark:text-zinc-100">{{ $previewCount }}</span>
|
||||
</flux:text>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<flux:button
|
||||
wire:click="exportCsv"
|
||||
wire:loading.attr="disabled"
|
||||
wire:target="exportCsv,exportPdf"
|
||||
variant="primary"
|
||||
icon="document-arrow-down"
|
||||
:disabled="$previewCount === 0"
|
||||
>
|
||||
<span wire:loading.remove wire:target="exportCsv">{{ __('export.export_csv') }}</span>
|
||||
<span wire:loading wire:target="exportCsv">{{ __('export.exporting') }}</span>
|
||||
</flux:button>
|
||||
|
||||
<flux:button
|
||||
wire:click="exportPdf"
|
||||
wire:loading.attr="disabled"
|
||||
wire:target="exportCsv,exportPdf"
|
||||
variant="filled"
|
||||
icon="document-text"
|
||||
:disabled="$previewCount === 0"
|
||||
>
|
||||
<span wire:loading.remove wire:target="exportPdf">{{ __('export.export_pdf') }}</span>
|
||||
<span wire:loading wire:target="exportPdf">{{ __('export.exporting') }}</span>
|
||||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($previewCount === 0)
|
||||
<div class="mt-6 rounded-lg bg-zinc-50 p-8 text-center dark:bg-zinc-900">
|
||||
<flux:icon name="clock" class="mx-auto mb-4 h-12 w-12 text-zinc-400" />
|
||||
<flux:text class="text-zinc-500 dark:text-zinc-400">{{ __('export.no_timelines_match') }}</flux:text>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@@ -127,9 +127,14 @@ new class extends Component
|
||||
<flux:heading size="xl">{{ __('timelines.timelines') }}</flux:heading>
|
||||
<p class="text-sm text-zinc-500 dark:text-zinc-400 mt-1">{{ __('timelines.timelines_description') }}</p>
|
||||
</div>
|
||||
<flux:button href="{{ route('admin.timelines.create') }}" variant="primary" icon="plus" wire:navigate>
|
||||
{{ __('timelines.create_timeline') }}
|
||||
</flux:button>
|
||||
<div class="flex gap-2">
|
||||
<flux:button href="{{ route('admin.timelines.export') }}" icon="arrow-down-tray" wire:navigate>
|
||||
{{ __('export.export_timelines') }}
|
||||
</flux:button>
|
||||
<flux:button href="{{ route('admin.timelines.create') }}" variant="primary" icon="plus" wire:navigate>
|
||||
{{ __('timelines.create_timeline') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if(session('success'))
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ $locale }}" dir="{{ $locale === 'ar' ? 'rtl' : 'ltr' }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<title>{{ __('export.timelines_export_title', [], $locale) }}</title>
|
||||
<style>
|
||||
@page {
|
||||
margin: 100px 50px 80px 50px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'DejaVu Sans', sans-serif;
|
||||
font-size: 10px;
|
||||
color: #333;
|
||||
direction: {{ $locale === 'ar' ? 'rtl' : 'ltr' }};
|
||||
}
|
||||
|
||||
header {
|
||||
position: fixed;
|
||||
top: -80px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 70px;
|
||||
border-bottom: 3px solid #D4AF37;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: table;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header-left, .header-right {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
text-align: {{ $locale === 'ar' ? 'right' : 'left' }};
|
||||
}
|
||||
|
||||
.header-right {
|
||||
text-align: {{ $locale === 'ar' ? 'left' : 'right' }};
|
||||
}
|
||||
|
||||
.brand-name {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
color: #0A1F44;
|
||||
}
|
||||
|
||||
.brand-subtitle {
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.report-title {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #0A1F44;
|
||||
}
|
||||
|
||||
footer {
|
||||
position: fixed;
|
||||
bottom: -60px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 50px;
|
||||
border-top: 2px solid #D4AF37;
|
||||
padding-top: 10px;
|
||||
font-size: 9px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
display: table;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer-left, .footer-right {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.footer-left {
|
||||
text-align: {{ $locale === 'ar' ? 'right' : 'left' }};
|
||||
}
|
||||
|
||||
.footer-right {
|
||||
text-align: {{ $locale === 'ar' ? 'left' : 'right' }};
|
||||
}
|
||||
|
||||
.page-number:after {
|
||||
content: counter(page);
|
||||
}
|
||||
|
||||
.filters-section {
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 4px;
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.filters-title {
|
||||
font-weight: bold;
|
||||
color: #0A1F44;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: inline-block;
|
||||
margin-{{ $locale === 'ar' ? 'left' : 'right' }}: 15px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.summary {
|
||||
margin-bottom: 15px;
|
||||
color: #0A1F44;
|
||||
}
|
||||
|
||||
.summary strong {
|
||||
color: #D4AF37;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #0A1F44;
|
||||
color: #fff;
|
||||
padding: 10px 8px;
|
||||
text-align: {{ $locale === 'ar' ? 'right' : 'left' }};
|
||||
font-weight: bold;
|
||||
font-size: 9px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
text-align: {{ $locale === 'ar' ? 'right' : 'left' }};
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.status-active {
|
||||
color: #28a745;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-archived {
|
||||
color: #6c757d;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.updates-section {
|
||||
background-color: #f8f9fa;
|
||||
padding: 10px;
|
||||
margin: 5px 0;
|
||||
border-{{ $locale === 'ar' ? 'right' : 'left' }}: 3px solid #D4AF37;
|
||||
}
|
||||
|
||||
.update-entry {
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px dashed #e9ecef;
|
||||
}
|
||||
|
||||
.update-entry:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.update-date {
|
||||
font-size: 8px;
|
||||
color: #666;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.update-text {
|
||||
font-size: 9px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.updates-row td {
|
||||
background-color: #fafafa;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="brand-name">Libra</div>
|
||||
<div class="brand-subtitle">{{ __('export.libra_law_firm', [], $locale) }}</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="report-title">{{ __('export.timelines_export_title', [], $locale) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<footer>
|
||||
<div class="footer-content">
|
||||
<div class="footer-left">
|
||||
{{ __('export.generated_at', [], $locale) }}: {{ $generatedAt->format($locale === 'ar' ? 'd/m/Y H:i' : 'm/d/Y H:i') }}
|
||||
</div>
|
||||
<div class="footer-right">
|
||||
{{ __('export.page', [], $locale) }} <span class="page-number"></span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<main>
|
||||
@if(count($filters) > 0)
|
||||
<div class="filters-section">
|
||||
<div class="filters-title">{{ __('export.filters_applied', [], $locale) }}:</div>
|
||||
@foreach($filters as $key => $value)
|
||||
<span class="filter-item">
|
||||
@if($key === 'client')
|
||||
{{ __('export.client_name', [], $locale) }}: {{ $value }}
|
||||
@elseif($key === 'status')
|
||||
{{ __('export.status', [], $locale) }}: {{ $value }}
|
||||
@elseif($key === 'date_from')
|
||||
{{ __('export.date_from', [], $locale) }}: {{ $value }}
|
||||
@elseif($key === 'date_to')
|
||||
{{ __('export.date_to', [], $locale) }}: {{ $value }}
|
||||
@endif
|
||||
</span>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="summary">
|
||||
{{ __('export.total_records', [], $locale) }}: <strong>{{ $totalCount }}</strong>
|
||||
</div>
|
||||
|
||||
@if($timelines->count() > 0)
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ __('export.case_name', [], $locale) }}</th>
|
||||
<th>{{ __('export.case_reference', [], $locale) }}</th>
|
||||
<th>{{ __('export.client_name', [], $locale) }}</th>
|
||||
<th>{{ __('export.status', [], $locale) }}</th>
|
||||
<th>{{ __('export.created_date', [], $locale) }}</th>
|
||||
<th>{{ __('export.updates_count', [], $locale) }}</th>
|
||||
<th>{{ __('export.last_update', [], $locale) }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($timelines as $timeline)
|
||||
<tr>
|
||||
<td>{{ $timeline->case_name }}</td>
|
||||
<td>{{ $timeline->case_reference ?? '-' }}</td>
|
||||
<td>{{ $timeline->user->full_name }}</td>
|
||||
<td class="status-{{ $timeline->status->value }}">
|
||||
{{ __('export.timeline_status_' . $timeline->status->value, [], $locale) }}
|
||||
</td>
|
||||
<td>{{ $timeline->created_at->format($locale === 'ar' ? 'd/m/Y' : 'm/d/Y') }}</td>
|
||||
<td>{{ $timeline->updates_count }}</td>
|
||||
<td>
|
||||
@if($timeline->updates_max_created_at)
|
||||
{{ \Carbon\Carbon::parse($timeline->updates_max_created_at)->format($locale === 'ar' ? 'd/m/Y H:i' : 'm/d/Y H:i') }}
|
||||
@else
|
||||
-
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@if($includeUpdates && $timeline->updates->count() > 0)
|
||||
<tr class="updates-row">
|
||||
<td colspan="7">
|
||||
<div class="updates-section">
|
||||
<strong>{{ __('export.updates', [], $locale) }}:</strong>
|
||||
@foreach($timeline->updates->take(10) as $update)
|
||||
<div class="update-entry">
|
||||
<div class="update-date">
|
||||
{{ $update->created_at->format($locale === 'ar' ? 'd/m/Y H:i' : 'm/d/Y H:i') }}
|
||||
</div>
|
||||
<div class="update-text">
|
||||
{{ Str::limit($update->update_text, 500) }}
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
@if($timeline->updates->count() > 10)
|
||||
<div class="update-entry">
|
||||
<em>{{ __('export.more_updates', ['count' => $timeline->updates->count() - 10], $locale) }}</em>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<div class="no-data">
|
||||
{{ __('export.no_timelines_match', [], $locale) }}
|
||||
</div>
|
||||
@endif
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user