diff --git a/app/Http/Controllers/Api/V1/TimeEntryController.php b/app/Http/Controllers/Api/V1/TimeEntryController.php index d9770dda..5e2e6d0b 100644 --- a/app/Http/Controllers/Api/V1/TimeEntryController.php +++ b/app/Http/Controllers/Api/V1/TimeEntryController.php @@ -427,11 +427,12 @@ class TimeEntryController extends Controller $request = Gotenberg::chromium(config('services.gotenberg.url')) ->pdf() ->waitForExpression("window.status === 'ready'") - ->pdfa('PDF/A-3b') ->margins(0.39, 0.78, 0.39, 0.39) ->paperSize('8.27', '11.7') // A4 ->footer(Stream::string('footer', $footerHtml)) - ->assets(Stream::path(resource_path('pdf-js/echarts.min.js'), 'echarts.min.js')) + ->assets(Stream::path(resource_path('pdf/echarts.min.js'), 'echarts.min.js'), + Stream::path(resource_path('pdf/Outfit-VariableFont_wght.ttf'), 'outfit.ttf'), + ) ->html(Stream::string('body', $html)); $tempFolder = TemporaryDirectory::make(); $filenameTemp = Gotenberg::save($request, $tempFolder->path(), $client); diff --git a/app/Service/ColorService.php b/app/Service/ColorService.php index e9ff97c0..2708a35a 100644 --- a/app/Service/ColorService.php +++ b/app/Service/ColorService.php @@ -33,8 +33,12 @@ class ColorService private const string VALID_REGEX = '/^#[0-9a-f]{6}$/'; - public function getRandomColor(): string + public function getRandomColor(string $seed = null): string { + if ($seed !== null) { + srand(crc32($seed)); + } + return self::COLORS[array_rand(self::COLORS)]; } diff --git a/resources/js/packages/api/src/openapi.json.client.ts b/resources/js/packages/api/src/openapi.json.client.ts index 709f2119..974d310d 100644 --- a/resources/js/packages/api/src/openapi.json.client.ts +++ b/resources/js/packages/api/src/openapi.json.client.ts @@ -3203,6 +3203,11 @@ If the group parameters are all set to `null` or are all missing, the type: 'Query', schema: z.enum(['true', 'false']).optional(), }, + { + name: 'debug', + type: 'Query', + schema: z.enum(['true', 'false']).optional(), + }, { name: 'member_ids', type: 'Query', @@ -3229,7 +3234,12 @@ If the group parameters are all set to `null` or are all missing, the schema: z.array(z.string()).min(1).optional(), }, ], - response: z.object({ download_url: z.string() }).passthrough(), + response: z.union([ + z.object({ download_url: z.string() }).passthrough(), + z + .object({ html: z.string(), footer_html: z.string() }) + .passthrough(), + ]), errors: [ { status: 400, @@ -3320,6 +3330,11 @@ If the group parameters are all set to `null` or are all missing, the type: 'Query', schema: z.enum(['true', 'false']).optional(), }, + { + name: 'debug', + type: 'Query', + schema: z.enum(['true', 'false']).optional(), + }, { name: 'member_ids', type: 'Query', @@ -3341,7 +3356,12 @@ If the group parameters are all set to `null` or are all missing, the schema: z.array(z.string().uuid()).min(1).optional(), }, ], - response: z.object({ download_url: z.string() }).passthrough(), + response: z.union([ + z.object({ download_url: z.string() }).passthrough(), + z + .object({ html: z.string(), footer_html: z.string() }) + .passthrough(), + ]), errors: [ { status: 400, diff --git a/public/fonts/Outfit-VariableFont_wght.ttf b/resources/pdf/Outfit-VariableFont_wght.ttf similarity index 100% rename from public/fonts/Outfit-VariableFont_wght.ttf rename to resources/pdf/Outfit-VariableFont_wght.ttf diff --git a/resources/pdf-js/echarts.min.js b/resources/pdf/echarts.min.js similarity index 100% rename from resources/pdf-js/echarts.min.js rename to resources/pdf/echarts.min.js diff --git a/resources/views/reports/time-entry-aggregate/pdf.blade.php b/resources/views/reports/time-entry-aggregate/pdf.blade.php index 48e70433..39b01708 100644 --- a/resources/views/reports/time-entry-aggregate/pdf.blade.php +++ b/resources/views/reports/time-entry-aggregate/pdf.blade.php @@ -7,30 +7,101 @@ - + Report - + + + @if($debug) + + + @endif + +
+

Report

+
+ {{ $start->format('d.m.Y') }} - {{ $end->format('d.m.Y') }}

+
-

Report

- -
- -
- {{ $start->format('d.m.Y') }} - {{ $end->format('d.m.Y') }}

-
- -
- Duration: {{ $interval->format(CarbonInterval::seconds($aggregatedData['seconds'])) }}
- Total cost: {{ Money::of(BigDecimal::ofUnscaledValue($aggregatedData['cost'], 2)->__toString(), $currency)->formatTo('en_US') }}
-
+
-
+
+
+
Duration
+
{{ $interval->format(CarbonInterval::seconds($aggregatedData['seconds'])) }}
+
+
+
Total cost
+
{{ Money::of(BigDecimal::ofUnscaledValue($aggregatedData['cost'], 2)->__toString(), $currency)->formatTo('en_US') }}
+
+ +
+
+ +
+ + +
+
+
+
+
+
+ + + + + + + + + @foreach($aggregatedData['grouped_data'] as $group1Entry) + + + + + + + @endforeach + + + + + + + +
+ {{ $group->description() }} + DurationCost
+
+
+ + {{ $group1Entry['description'] ?? $group1Entry['key'] ?? 'No '.Str::lower($group->description()) }} + +
+ {{ $interval->format(CarbonInterval::seconds($group1Entry['seconds'])) }} + + {{ Money::of(BigDecimal::ofUnscaledValue($group1Entry['cost'], 2)->__toString(), $currency)->formatTo('en_US') }} +
+ Total + + {{ $interval->format(CarbonInterval::seconds($aggregatedData['seconds'])) }} + + {{ Money::of(BigDecimal::ofUnscaledValue($aggregatedData['cost'], 2)->__toString(), $currency)->formatTo('en_US') }} +
+
+ +
+
@foreach($aggregatedData['grouped_data'] as $group1Entry)
-

{{ $group1Entry['description'] ?? $group1Entry['key'] ?? 'No '.Str::lower($group->description()) }}

+

+ @if($group1Entry['key']) + + {{ $group->description() }}: + + @endif + {{ $group1Entry['description'] ?? $group1Entry['key'] ?? 'No '.Str::lower($group->description()) }} +

- - - - - - - - - - - @php - $counter = 1; - $totalDuration = 0; - $totalCost = 0; - @endphp - @foreach($group1Entry['grouped_data'] as $group2Entry) - @php - $duration = CarbonInterval::seconds($group2Entry['seconds']); - @endphp +
+ + +
- {{ $subGroup->description() }} - - Duration - - Duration (decimal) - - Cost -
+ - - - - + + + + + + @php - $totalDuration += $group2Entry['seconds']; - $totalCost += $group2Entry['cost']; + $counter = 1; + $totalDuration = 0; + $totalCost = 0; @endphp - @endforeach - -
- {{ $group2Entry['description'] ?? $group2Entry['key'] ?? '-' }} - - {{ $interval->format($duration) }} - - {{ round($duration->totalHours, 2) }} - - {{ Money::of(BigDecimal::ofUnscaledValue($group2Entry['cost'], 2)->__toString(), $currency)->formatTo('en_US') }} - + {{ $subGroup->description() }} + + Duration + + Duration (h) + + Cost +
+ @foreach($group1Entry['grouped_data'] as $group2Entry) + @php + $duration = CarbonInterval::seconds($group2Entry['seconds']); + @endphp + + + {{ $group2Entry['description'] ?? $group2Entry['key'] ?? '-' }} + + + {{ $interval->format($duration) }} + + + {{ round($duration->totalHours, 2) }} + + + {{ Money::of(BigDecimal::ofUnscaledValue($group2Entry['cost'], 2)->__toString(), $currency)->formatTo('en_US') }} + + + @php + $totalDuration += $group2Entry['seconds']; + $totalCost += $group2Entry['cost']; + @endphp + @endforeach + + +
+
@endforeach