Initial commit

This commit is contained in:
Ronald Huynen
2026-03-23 21:37:59 +01:00
commit 2547717edb
2193 changed files with 972171 additions and 0 deletions

View 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)
&nbsp;&nbsp;&nbsp;
<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)
&nbsp;&nbsp;&nbsp;
<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>