Initial commit
This commit is contained in:
459
resources/views/reports/pdf.blade.php
Normal file
459
resources/views/reports/pdf.blade.php
Normal file
@@ -0,0 +1,459 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<meta charset="utf-8">
|
||||
<title>{{ __('Financial Report') }}</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 18.75px;
|
||||
margin: 20px;
|
||||
color: #333;
|
||||
}
|
||||
.header {
|
||||
text-align: left;
|
||||
margin-top: 80px;
|
||||
margin-bottom: 60px;
|
||||
border-bottom: 2px solid #333;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-size: 37.5px;
|
||||
color: #333;
|
||||
}
|
||||
.header h2 {
|
||||
margin: 5px 0 0 0;
|
||||
font-size: 25px;
|
||||
color: #666;
|
||||
font-weight: normal;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
background-color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
th:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
th:not(:first-child) {
|
||||
text-align: right;
|
||||
}
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.total-row {
|
||||
background-color: #f0f0f0;
|
||||
font-weight: bold;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 25px;
|
||||
font-weight: bold;
|
||||
margin: 20px 0 10px 0;
|
||||
color: #333;
|
||||
}
|
||||
.footer {
|
||||
margin: 48px 0;
|
||||
text-align: left;
|
||||
font-size: 15.625px;
|
||||
color: #666;
|
||||
padding-top: 10px;
|
||||
}
|
||||
.logo-container {
|
||||
text-align: center;
|
||||
margin: 6px 12px 80px 12px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
.logo-container svg {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
fill: #333;
|
||||
}
|
||||
.section-container
|
||||
{
|
||||
margin-top: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.page-footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
background: white;
|
||||
padding: 5px 0;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@php
|
||||
$hasSecondPage = $isOrganization
|
||||
&& ($returnRatioChartImage || $chartImage || ($chartData && $returnRatioTimelineData))
|
||||
&& $returnRatioTimelineData
|
||||
&& count($returnRatioTimelineData) > 1;
|
||||
@endphp
|
||||
|
||||
<!-- Page 1 footer — only shown when there is actually a second page -->
|
||||
@if($hasSecondPage)
|
||||
<div class="page-footer">
|
||||
{{ __('messages.report.page_numbering', ['current' => '1', 'total' => '2']) }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Logo positioned at top right -->
|
||||
<div class="logo-container">
|
||||
@php
|
||||
$logoRelativePath = theme_logo('email_logo', 'app-images/app_mail_logo.png');
|
||||
$logoPath = storage_path('app/public/' . $logoRelativePath);
|
||||
$logoBase64 = '';
|
||||
if (file_exists($logoPath)) {
|
||||
$logoBase64 = 'data:image/png;base64,' . base64_encode(file_get_contents($logoPath));
|
||||
}
|
||||
@endphp
|
||||
@if($logoBase64)
|
||||
<img src="{{ $logoBase64 }}" alt="{{ config('app.name') }} Logo" style="max-width: 120px; height: auto;">
|
||||
<div style="font-size: 21.875px; font-weight: bold; color: #333;">
|
||||
{{ config('messages.platform_slogan') }}
|
||||
</div>
|
||||
@else
|
||||
<div style="font-size: 21.875px; font-weight: bold; color: #333;">
|
||||
{{ config('app.name') }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="header">
|
||||
<h1>{{ $title['header'] ?? __('Financial overview') }}</h1>
|
||||
<h2>{{ $title['sub'] ?? '' }}</h2>
|
||||
</div>
|
||||
|
||||
<!-- Account Balances -->
|
||||
@if($accountsData && $accountsData->count() > 0)
|
||||
<div class="section-container">
|
||||
<div class="section-title">{{ __('Account Balances') }}</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 40%;">{{ __('Account Name') }}</th>
|
||||
<th class="text-right" style="width: 20%;">{{ __('Start Balance') }}</th>
|
||||
<th class="text-right" style="width: 20%;">{{ __('End Balance') }}</th>
|
||||
<th class="text-right" style="width: 20%;">{{ __('Difference') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($accountsData as $account)
|
||||
<tr>
|
||||
<td>{{ $account['name'] }}</td>
|
||||
<td class="text-right">{{ $account['start_balance_formatted'] }}</td>
|
||||
<td class="text-right">{{ $account['end_balance_formatted'] }}</td>
|
||||
<td class="text-right">
|
||||
{{ $account['difference_formatted'] }}{{ $account['difference'] > 0 ? ' +' : '' }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
<!-- Totals Row -->
|
||||
@php
|
||||
$totalDifference = $accountsData->sum('difference');
|
||||
$decimalFormat = $decimalFormat ?? false;
|
||||
$fmtAmount = function($minutes) use ($decimalFormat) {
|
||||
if ($decimalFormat) {
|
||||
$isNeg = $minutes < 0;
|
||||
return ($isNeg ? '-' : '') . number_format(abs($minutes) / 60, 2, ',', '.') . ' ' . __('h.');
|
||||
}
|
||||
return tbFormat($minutes);
|
||||
};
|
||||
@endphp
|
||||
<tr class="total-row">
|
||||
<td>{{ __('TOTALS') }}</td>
|
||||
<td class="text-right">{{ $fmtAmount($accountsData->sum('start_balance')) }}</td>
|
||||
<td class="text-right">{{ $fmtAmount($accountsData->sum('end_balance')) }}</td>
|
||||
<td class="text-right">
|
||||
{{ $fmtAmount($totalDifference) }}{{ $totalDifference > 0 ? ' +' : '' }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
|
||||
|
||||
<!-- Account Balances Timeline Chart -->
|
||||
@if($accountBalancesChartImage && isset($returnRatioTimelineData) && count($returnRatioTimelineData) > 4)
|
||||
<div class="section-container">
|
||||
<div class="section-title">{{ __('Account Balances Over Time') }}</div>
|
||||
<div style="margin-bottom: 20px; font-size: 17.1875px; color: #666;">
|
||||
{{ __('Track your account balances across different time periods') }}
|
||||
</div>
|
||||
|
||||
<div style="border: 1px solid #ddd; padding: 15px; margin-bottom: 20px; background-color: #fafafa;">
|
||||
<!-- Account Balances Chart Image from JavaScript -->
|
||||
<div style="text-align: center; margin-bottom: 15px;">
|
||||
<img src="{{ $accountBalancesChartImage }}" alt="{{ __('Account Balances Over Time Chart') }}" style="width: 100%; height: auto; border: 1px solid #ccc;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Page break before Reciprocity Rate section -->
|
||||
@if($hasSecondPage)
|
||||
<div style="page-break-before: always;"></div>
|
||||
<!-- Page 2 footer -->
|
||||
<div class="page-footer">
|
||||
{{ __('messages.report.page_numbering', ['current' => '2', 'total' => '2']) }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Reciprocity Rate Timeline Chart -->
|
||||
@if($hasSecondPage)
|
||||
<div class="section-container">
|
||||
<div class="section-title">{{ __('Reciprocity Rate Timeline') }}</div>
|
||||
<div style="margin-bottom: 8px; font-size: 17.1875px; color: #666;">
|
||||
{{ __('Reciprocity rate: the share of your Hours that were also returned by the same people you helped during this period.') }}
|
||||
</div>
|
||||
<div style="margin-bottom: 20px; font-size: 17.1875px; color: #666;">
|
||||
{{ __('A high rate means you are part of a more closed exchange network with frequent returning exchange partners, while a low rate suggests you are part of a more open network with many different exchange partners.') }}
|
||||
</div>
|
||||
|
||||
<div style="border: 1px solid #ddd; padding: 15px; margin-bottom: 20px; background-color: #fafafa;">
|
||||
@if($returnRatioChartImage || $chartImage)
|
||||
<!-- Chart Image from JavaScript (high-res) -->
|
||||
<div style="text-align: center; margin-bottom: 15px;">
|
||||
<img src="{{ $returnRatioChartImage ?? $chartImage }}" alt="{{ __('Reciprocity Rate Timeline Chart') }}" style="width: 100%; height: auto; border: 1px solid #ccc;">
|
||||
</div>
|
||||
@else
|
||||
<!-- Chart Title -->
|
||||
<div style="text-align: center; font-weight: bold; margin-bottom: 15px; font-size: 18.75px;">
|
||||
{{ __('Reciprocity Rate Timeline') }} ({{ min($chartData['values']) }}% - {{ max($chartData['values']) }}%)
|
||||
</div>
|
||||
|
||||
<!-- Fallback Chart Area - Enhanced chart with lines for DomPDF -->
|
||||
<div style="position: relative; height: 180px; border: 1px solid #ccc; background-color: white; margin-bottom: 10px; padding: 10px;">
|
||||
<!-- Y-axis labels -->
|
||||
<div style="position: absolute; left: -30px; top: 0; height: 100%; width: 25px;">
|
||||
<div style="position: absolute; top: 10px; right: 5px; font-size: 14.0625px;">{{ number_format($chartData['maxValue'], 0) }}%</div>
|
||||
<div style="position: absolute; top: 50%; right: 5px; font-size: 14.0625px; margin-top: -6px;">{{ number_format(($chartData['maxValue'] + $chartData['minValue']) / 2, 0) }}%</div>
|
||||
<div style="position: absolute; bottom: 10px; right: 5px; font-size: 14.0625px;">{{ number_format($chartData['minValue'], 0) }}%</div>
|
||||
</div>
|
||||
|
||||
<!-- Horizontal grid lines -->
|
||||
@for ($i = 1; $i <= 4; $i++)
|
||||
<div style="position: absolute; top: {{ $i * 20 }}%; left: 10px; right: 10px; height: 1px; background-color: #e0e0e0;"></div>
|
||||
@endfor
|
||||
|
||||
<!-- Simple HTML/CSS lines that work in DomPDF -->
|
||||
@if(count($chartData['points']) > 1)
|
||||
@for ($i = 0; $i < count($chartData['points']) - 1; $i++)
|
||||
@php
|
||||
list($x1, $y1) = explode(',', $chartData['points'][$i]);
|
||||
list($x2, $y2) = explode(',', $chartData['points'][$i + 1]);
|
||||
|
||||
// Convert to chart coordinates (scale to 80% and offset by 10%)
|
||||
$x1_pos = $x1 * 0.8 + 10;
|
||||
$y1_pos = $y1 * 0.8 + 10;
|
||||
$x2_pos = $x2 * 0.8 + 10;
|
||||
$y2_pos = $y2 * 0.8 + 10;
|
||||
|
||||
// Calculate line properties for straight line
|
||||
$width = abs($x2_pos - $x1_pos);
|
||||
$height = abs($y2_pos - $y1_pos);
|
||||
$left = min($x1_pos, $x2_pos);
|
||||
$top = min($y1_pos, $y2_pos);
|
||||
|
||||
// Determine if line is more horizontal or vertical
|
||||
$isHorizontal = $width > $height;
|
||||
@endphp
|
||||
|
||||
@if($isHorizontal)
|
||||
<!-- Horizontal-ish line -->
|
||||
<div style="position: absolute; left: {{ $left }}%; top: {{ ($y1_pos + $y2_pos) / 2 }}%; width: {{ $width }}%; height: 2px; background-color: #374151; z-index: 1;"></div>
|
||||
@else
|
||||
<!-- Vertical-ish line -->
|
||||
<div style="position: absolute; left: {{ ($x1_pos + $x2_pos) / 2 }}%; top: {{ $top }}%; width: 2px; height: {{ $height }}%; background-color: #374151; z-index: 1;"></div>
|
||||
@endif
|
||||
@endfor
|
||||
@endif
|
||||
|
||||
<!-- Trend line using same approach -->
|
||||
@if(count($chartData['trendPoints']) > 1)
|
||||
@for ($i = 0; $i < count($chartData['trendPoints']) - 1; $i++)
|
||||
@php
|
||||
list($x1, $y1) = explode(',', $chartData['trendPoints'][$i]);
|
||||
list($x2, $y2) = explode(',', $chartData['trendPoints'][$i + 1]);
|
||||
|
||||
$x1_pos = $x1 * 0.8 + 10;
|
||||
$y1_pos = $y1 * 0.8 + 10;
|
||||
$x2_pos = $x2 * 0.8 + 10;
|
||||
$y2_pos = $y2 * 0.8 + 10;
|
||||
|
||||
$width = abs($x2_pos - $x1_pos);
|
||||
$height = abs($y2_pos - $y1_pos);
|
||||
$left = min($x1_pos, $x2_pos);
|
||||
$top = min($y1_pos, $y2_pos);
|
||||
|
||||
$isHorizontal = $width > $height;
|
||||
@endphp
|
||||
|
||||
@if($isHorizontal)
|
||||
<!-- Horizontal trend line (dashed using border) -->
|
||||
<div style="position: absolute; left: {{ $left }}%; top: {{ ($y1_pos + $y2_pos) / 2 }}%; width: {{ $width }}%; height: 1px; border-top: 1px dashed #dc2626; z-index: 2;"></div>
|
||||
@else
|
||||
<!-- Vertical trend line (dashed using border) -->
|
||||
<div style="position: absolute; left: {{ ($x1_pos + $x2_pos) / 2 }}%; top: {{ $top }}%; width: 1px; height: {{ $height }}%; border-left: 1px dashed #dc2626; z-index: 2;"></div>
|
||||
@endif
|
||||
@endfor
|
||||
@endif
|
||||
|
||||
<!-- Data points as HTML elements -->
|
||||
@if(count($chartData['points']) > 0)
|
||||
@foreach ($chartData['points'] as $index => $point)
|
||||
@php
|
||||
list($x, $y) = explode(',', $point);
|
||||
@endphp
|
||||
<div style="position: absolute; left: {{ $x * 0.8 + 10 }}%; top: {{ $y * 0.8 + 10 }}%; width: 6px; height: 6px; background-color: #374151; border: 1px solid white; border-radius: 50%; margin-left: -3px; margin-top: -3px; z-index: 3;"></div>
|
||||
@endforeach
|
||||
@endif
|
||||
|
||||
|
||||
<!-- Chart title in the chart area -->
|
||||
<div style="position: absolute; top: 5px; left: 50%; margin-left: -60px; font-size: 15.625px; color: #333; font-weight: bold;">
|
||||
Reciprocity Rate Over Time
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Legend -->
|
||||
<div style="margin-top: 10px; margin-bottom: 10px; font-size: 15.625px;">
|
||||
<span style="display: inline-block; width: 12px; height: 12px; background-color: #374151; border-radius: 50%; margin-right: 5px; vertical-align: middle;"></span>
|
||||
<span style="vertical-align: middle;">{{ __('Reciprocity Rate %') }}</span>
|
||||
@if($returnRatioTrendData && count($returnRatioTrendData) > 1)
|
||||
|
||||
<span style="display: inline-block; width: 16px; height: 2px; background: linear-gradient(to right, #dc2626 50%, transparent 50%); background-size: 4px 2px; margin-right: 5px; vertical-align: middle;"></span>
|
||||
<span style="vertical-align: middle;">{{ __('Trend Line') }}</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- X-axis labels -->
|
||||
<div style="display: flex; justify-content: space-between; font-size: 14.0625px; margin-top: 5px;">
|
||||
@foreach ($returnRatioTimelineData as $index => $point)
|
||||
@if ($index == 0 || $index == count($returnRatioTimelineData) - 1 || $index % max(1, floor(count($returnRatioTimelineData) / 6)) == 0)
|
||||
<span>{{ $point['label'] }}</span>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<!-- Legend -->
|
||||
<div style="margin-top: 15px; font-size: 15.625px;">
|
||||
<span style="color: #374151;">●</span> {{ __('Return Ratio %') }}
|
||||
@if($returnRatioTrendData && count($returnRatioTrendData) > 1)
|
||||
|
||||
<span style="color: #B91C1C;">---</span> {{ __('Trend Line') }}
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Transaction Types Breakdown -->
|
||||
@if($transactionTypesData && $transactionTypesData->count() > 0)
|
||||
<div class="section-container">
|
||||
<div class="section-title">{{ __('Transaction Types') }}</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 40%;">{{ __('Transaction Type') }}</th>
|
||||
<th class="text-right" style="width: 20%;">{{ __('Credit') }}</th>
|
||||
<th class="text-right" style="width: 20%;">{{ __('Debit') }}</th>
|
||||
<th class="text-right" style="width: 20%;">{{ __('Total') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($transactionTypesData as $transactionType)
|
||||
<tr>
|
||||
<td>{{ $transactionType['type_name'] }}</td>
|
||||
<td class="text-right">{{ $transactionType['incoming_formatted'] }}</td>
|
||||
<td class="text-right">{{ $transactionType['outgoing_formatted'] }}</td>
|
||||
<td class="text-right">{{ $transactionType['net_formatted'] }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Period Statistics -->
|
||||
@if($statisticsData && ($statisticsData['transaction_count'] > 0 || $statisticsData['unique_profiles'] > 0))
|
||||
<div class="section-container">
|
||||
<div class="section-title">{{ __('Period Statistics') }}</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 70%;">{{ __('Statistic') }}</th>
|
||||
<th style="width: 30%;">{{ __('Value') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ __('Number of Transactions') }}</td>
|
||||
<td class="text-right">{{ number_format($statisticsData['transaction_count']) }}</td>
|
||||
</tr>
|
||||
@if($statisticsData['profiles_by_type'] && $statisticsData['profiles_by_type']->count() > 0)
|
||||
@foreach($statisticsData['profiles_by_type'] as $modelType => $count)
|
||||
<tr>
|
||||
<td>{{ __('Unique ' . class_basename($modelType) . 's') }}</td>
|
||||
<td class="text-right">{{ number_format($count) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@endif
|
||||
<tr>
|
||||
<td>{{ __('Total unique profiles') }}</td>
|
||||
<td class="text-right">{{ number_format($statisticsData['unique_profiles']) }}</td>
|
||||
</tr>
|
||||
@if($isOrganization)
|
||||
<tr>
|
||||
<td>{{ __('Average reciprocity rate') }}</td>
|
||||
<td class="text-right">{{ number_format($statisticsData['average_return_ratio'], 1) }}%</td>
|
||||
</tr>
|
||||
@endif
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="section-container mt-12">
|
||||
{{ __('Generated on') }} {{ now()->translatedFormat('l d F Y') }}
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<div class="my-6">
|
||||
<br/>
|
||||
{{ __('messages.report.interest_info')}}
|
||||
</div>
|
||||
<div class="my-6">
|
||||
<br/>
|
||||
{{ __('messages.report.disclaimer')}}
|
||||
</div>
|
||||
<div class="my-6">
|
||||
<br/>
|
||||
{{ __('messages.report.return_ratio_info')}}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user