Skip to content

Commit 81facb7

Browse files
committed
refactor: adjust project views and state logic, add support for conditional approval section and attachment display
- Removed unused menu items and adjusted gradient styles. - Conditionally rendered the approval section in `show-project` based on user roles and project state. - Added attachment display using the `file-card` component. - Updated `routes/web.php` to include a route for serving attachments. - Refactored validation and state transition logic in `ProjectState` and `Draft` classes.
1 parent 41116a1 commit 81facb7

File tree

7 files changed

+103
-76
lines changed

7 files changed

+103
-76
lines changed

app/Livewire/Project/EditProject.php

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use App\Models\Legacy\ExpenseReceiptPost;
66
use App\Models\Legacy\LegacyBudgetPlan;
77
use App\Models\Legacy\Project;
8+
use App\Models\Legacy\ProjectAttachment;
89
use App\Models\Legacy\ProjectPost;
910
use App\States\Project\ProjectState;
1011
use Cknow\Money\Money;
@@ -13,6 +14,7 @@
1314
use Illuminate\Support\Facades\DB;
1415
use Illuminate\Support\Facades\Gate;
1516
use Illuminate\Support\Facades\Validator;
17+
use Illuminate\Validation\Rules\File;
1618
use Livewire\Attributes\Computed;
1719
use Livewire\Attributes\Locked;
1820
use Livewire\Attributes\Url;
@@ -131,12 +133,26 @@ private function getValues(): array
131133
];
132134
}
133135

136+
/**
137+
* Add an empty post row
138+
*/
139+
public function addEmptyPost(): void
140+
{
141+
$this->posts[] = ([
142+
'name' => '',
143+
'bemerkung' => '',
144+
'einnahmen' => Money::EUR(0),
145+
'ausgaben' => Money::EUR(0),
146+
'titel_id' => null,
147+
]);
148+
}
149+
134150
public function isPostDeletable(int $index): bool
135151
{
136-
return
137-
count($this->posts) > 1 && (
152+
return count($this->posts) > 1 && (
138153
(isset($this->posts[$index]['id']) && ExpenseReceiptPost::where('projekt_posten_id', $this->posts[$index]['id'])->doesntExist())
139-
|| ! isset($this->posts[$index]['id']));
154+
|| ! isset($this->posts[$index]['id'])
155+
);
140156
}
141157

142158
/**
@@ -232,20 +248,6 @@ public function saveAs($stateName)
232248
}
233249
}
234250

235-
/**
236-
* Add an empty post row
237-
*/
238-
public function addEmptyPost(): void
239-
{
240-
$this->posts[] = ([
241-
'name' => '',
242-
'bemerkung' => '',
243-
'einnahmen' => Money::EUR(0),
244-
'ausgaben' => Money::EUR(0),
245-
'titel_id' => null,
246-
]);
247-
}
248-
249251
/**
250252
* Get the sum of all income posts
251253
*/

app/Livewire/Project/ShowProject.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use App\Models\Enums\ChatMessageType;
66
use App\Models\Legacy\ChatMessage;
77
use App\Models\Legacy\Project;
8+
use App\States\Project\Draft;
89
use App\States\Project\ProjectState;
910
use Flux\Flux;
1011
use Livewire\Attributes\Url;
@@ -19,11 +20,16 @@ class ShowProject extends Component
1920

2021
public $newState;
2122

23+
public $fileUrl;
24+
2225
public function render()
2326
{
2427
$project = Project::findOrFail($this->project_id);
28+
$state = $project->state;
29+
30+
$showApproval = \Auth::user()->getGroups()->has('ref-finanzen-hv') || !$state->equals(Draft::class);
2531

26-
return view('livewire.project.show-project', compact('project'));
32+
return view('livewire.project.show-project', compact('project', 'showApproval'));
2733
}
2834

2935
public function changeState(): void
@@ -33,7 +39,8 @@ public function changeState(): void
3339
$filtered = $this->validate(['newState' => ['required', new ValidStateRule(ProjectState::class)]]);
3440
$newState = ProjectState::make($filtered['newState'], $project);
3541
// Business Logic check: are some values missing for the new state
36-
$newState->validate();
42+
$v = $newState->getValidator();
43+
$v->validate();
3744
// Authorization check: can the user transition to this state
3845
$this->authorize('transition-to', [$project, $newState]);
3946

app/States/Project/Draft.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ public function rules(): array
3333
'protocol' => 'sometimes|nullable|string|url',
3434
// 'recht' => 'required|string|in:...',
3535
// 'recht-additional' => 'sometimes|nullable|string',
36-
'date_start' => 'sometimes|date',
37-
'date_end' => 'sometimes|date|after:date_start',
36+
'date_start' => 'sometimes|nullable|date',
37+
'date_end' => 'sometimes|nullable|date',
3838
'beschreibung' => ['sometimes', 'string', new FluxEditorRule],
3939
'posts' => 'sometimes|array|min:1',
4040
'posts.*.id' => 'sometimes|integer',

app/States/Project/ProjectState.php

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ abstract class ProjectState extends State implements Wireable
1818
public function iconName(): string
1919
{
2020
return 'file-pen';
21-
2221
}
2322

2423
public function color(): string
@@ -52,7 +51,7 @@ public static function config(): StateConfig
5251
->allowTransition(Draft::class, Applied::class)
5352
->allowTransition([ApprovedByOrg::class, ApprovedByFinance::class, ApprovedByOther::class], Terminated::class)
5453
->allowTransition([Applied::class, NeedOrgApproval::class, NeedFinanceApproval::class], Revoked::class)
55-
->allowTransition([Revoked::class], Draft::class);
54+
->allowTransition([Applied::class, Revoked::class], Draft::class);
5655

5756
// here would be some dynamic logic from config possible
5857

@@ -130,20 +129,33 @@ public function rules(): array
130129
'posts.*.ausgaben' => ['required', 'money:EUR', new ExactlyOneZeroMoneyRule('posts.*.einnahmen')],
131130
'posts.*.position' => 'sometimes|integer',
132131
'posts.*.bemerkung' => 'sometimes|string|max:256',
132+
133133
];
134134
}
135135

136-
public function getValidator(): \Illuminate\Contracts\Validation\Validator
136+
/**
137+
* Create and return a validator instance for the provided data or the model's attributes.
138+
*
139+
* If the provided data is empty, it retrieves the model's attributes and populates additional
140+
* data such as related posts and attachments.
141+
*
142+
* @param array $data An optional array of data to validate. If empty, the model's attributes will be used.
143+
*
144+
* @return \Illuminate\Contracts\Validation\Validator The validator instance for the given data.
145+
*/
146+
public function getValidator(array $data = []): \Illuminate\Contracts\Validation\Validator
137147
{
138-
$model = $this->getModel();
139-
$data = [...$model->getAttributes(), 'posts' => $model->posts->all()];
140-
148+
if(empty($data)){
149+
$model = $this->getModel();
150+
$data = $this->getModel()->getAttributes();
151+
$data['posts'] = $model->posts->all();
152+
}
141153
return Validator::make($data, static::rules());
142154
}
143155

144-
public function validate(): array
156+
public function validate(array $data = []): array
145157
{
146-
return $this->getValidator()->validate();
158+
return $this->getValidator($data)->validate();
147159
}
148160

149161
public function toLivewire(): array

resources/views/livewire/project/edit-project.blade.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
<div class="flex flex-col space-y-4 mt-6">
2222
<div class="flex flex-col items-end space-y-4">
2323
<div class="flex items-center space-x-4">
24-
<flux:button :href="url()->previous()" variant="outline" icon="arrow-left">Zurück</flux:button>
24+
<flux:button :href="$isNew ? url()->previous() : route('project.show', $project_id)" variant="outline" icon="arrow-left">Zurück</flux:button>
2525
<flux:button wire:click="saveAs('{{ $state_name }}')" variant="primary">
26-
Speichern
26+
Speichern als {{ $this->getState()->label() }}
2727
</flux:button>
2828
</div>
2929
@error('save')
@@ -360,9 +360,9 @@
360360
<div class="flex flex-col space-y-4 mt-6">
361361
<div class="flex flex-col items-end space-y-4">
362362
<div class="flex items-center space-x-4">
363-
<flux:button :href="url()->previous()" variant="outline" icon="arrow-left">Zurück</flux:button>
364-
<flux:button wire:click="saveAs({{ $state_name }})" variant="primary">
365-
Speichern
363+
<flux:button :href="$isNew ? url()->previous() : route('project.show', $project_id)" variant="outline" icon="arrow-left">Zurück</flux:button>
364+
<flux:button wire:click="saveAs('{{ $state_name }}')" variant="primary">
365+
Speichern als {{ $this->getState()->label() }}
366366
</flux:button>
367367
@if($this->getState()->equals(\App\States\Project\Draft::class))
368368
<flux:button wire:click="saveAs('wip')" variant="primary">
@@ -375,5 +375,4 @@
375375
@enderror
376376
</div>
377377
</div>
378-
@dump($this->getErrorBag()->keys())
379378
</div>

resources/views/livewire/project/show-project.blade.php

Lines changed: 47 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
@php $totalRemainingEinnahmen = $project->totalRemainingEinnahmen() @endphp
99
@php $totalRatioEinnahmen = $project->totalRatioEinnahmen(); @endphp
1010

11-
<div class="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 py-8">
11+
<div class="min-h-screen py-8">
1212
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 space-y-4">
1313

1414
<!-- Header with Status and Actions -->
@@ -57,12 +57,6 @@
5757
<flux:button icon="ellipsis-vertical"/>
5858

5959
<flux:menu>
60-
<flux:menu.item href="{{ route('project.history', $project->id) }}" icon="inbox-stack">
61-
Secret New Feature :)
62-
</flux:menu.item>
63-
<flux:menu.item icon="document-duplicate">
64-
Duplicate (WIP)
65-
</flux:menu.item>
6660
<flux:menu.item icon="clock" href="{{ route('legacy.projekt', $project->id) }}">
6761
{{ __('project.view.header.old-view') }}
6862
</flux:menu.item>
@@ -83,13 +77,13 @@
8377
<div>
8478
<p class="text-xs font-medium text-gray-500 uppercase">{{ __('project.view.summary_cards.state') }}</p>
8579
<p @class([
86-
"font-bold mt-1",
87-
"text-zinc-600" => $project->state->color() === "zinc",
88-
"text-sky-600" => $project->state->color() === "sky",
89-
"text-yellow-600" => $project->state->color() === "yellow",
90-
"text-green-600" => $project->state->color() === "green",
91-
"text-rose-600" => $project->state->color() === "rose",
92-
])>
80+
"font-bold mt-1",
81+
"text-zinc-600" => $project->state->color() === "zinc",
82+
"text-sky-600" => $project->state->color() === "sky",
83+
"text-yellow-600" => $project->state->color() === "yellow",
84+
"text-green-600" => $project->state->color() === "green",
85+
"text-rose-600" => $project->state->color() === "rose",
86+
])>
9387
{{ $project->state->label() }}
9488
</p>
9589
</div>
@@ -266,37 +260,39 @@
266260

267261
</div>
268262

269-
<!-- Approval Section -->
270-
<div class="bg-white rounded-2xl shadow-accent border border-gray-200 p-6">
271-
<h2 class="text-xl font-bold text-gray-900 mb-4">{{ __('project.view.approval.heading') }}</h2>
272-
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
273-
<div>
274-
<label
275-
class="block text-sm font-medium text-gray-700 mb-1">{{ __('project.view.approval.legal_basis') }}</label>
276-
@empty($project->recht)
277-
<p class="text-gray-500 italic">{{ __('project.view.approval.none') }}</p>
278-
@else
279-
<p class="text-gray-900">{{ $project->getLegal()['label'] }}</p>
280-
@endisset
281-
</div>
282-
<div>
283-
@if($project->getLegal())
263+
@if($showApproval)
264+
<!-- Approval Section -->
265+
<div class="bg-white rounded-2xl shadow-accent border border-gray-200 p-6">
266+
<h2 class="text-xl font-bold text-gray-900 mb-4">{{ __('project.view.approval.heading') }}</h2>
267+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
268+
<div>
284269
<label
285-
class="block text-sm font-medium text-gray-700 mb-1">{{ $project->getLegal()['label-additional'] }}</label>
286-
@if($project->recht_additional)
287-
<p class="text-gray-900">{{ $project->recht_additional }}</p>
288-
@else
270+
class="block text-sm font-medium text-gray-700 mb-1">{{ __('project.view.approval.legal_basis') }}</label>
271+
@empty($project->recht)
289272
<p class="text-gray-500 italic">{{ __('project.view.approval.none') }}</p>
273+
@else
274+
<p class="text-gray-900">{{ $project->getLegal()['label'] }}</p>
275+
@endisset
276+
</div>
277+
<div>
278+
@if($project->getLegal())
279+
<label
280+
class="block text-sm font-medium text-gray-700 mb-1">{{ $project->getLegal()['label-additional'] }}</label>
281+
@if($project->recht_additional)
282+
<p class="text-gray-900">{{ $project->recht_additional }}</p>
283+
@else
284+
<p class="text-gray-500 italic">{{ __('project.view.approval.none') }}</p>
285+
@endif
290286
@endif
287+
</div>
288+
@if(!empty($project->getLegal()['hint-text']))
289+
<div class="lg:col-span-2 mt-2">
290+
<p class="text-sm text-gray-500 mt-1">{{ $project->getLegal()['hint-text'] ?? '' }}</p>
291+
</div>
291292
@endif
292293
</div>
293-
@if(!empty($project->getLegal()['hint-text']))
294-
<div class="lg:col-span-2 mt-2">
295-
<p class="text-sm text-gray-500 mt-1">{{ $project->getLegal()['hint-text'] ?? '' }}</p>
296-
</div>
297-
@endif
298294
</div>
299-
</div>
295+
@endif
300296

301297
<!-- Project Details -->
302298
<div class="bg-white rounded-2xl shadow-accent border border-gray-200 p-6">
@@ -318,7 +314,7 @@ class="block text-sm font-medium text-gray-700 mb-1">{{ __('project.view.details
318314
<div>
319315
<label
320316
class="block text-sm font-medium text-gray-700 mb-1">{{ __('project.view.details.responsible') }}</label>
321-
@if(empty($project->responsible))
317+
@if(empty($project->responsible))
322318
<x-no-content/>
323319
@else
324320
<a href="mailto:{{ $project->responsible }}"
@@ -490,7 +486,17 @@ class="inline-flex items-center text-indigo-600 hover:text-indigo-800 transition
490486
{!! Str::markdown($project->beschreibung) !!}
491487
</p>
492488
@endempty
493-
489+
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mt-6">
490+
@foreach($project->attachments as $attachment)
491+
<x-file-card
492+
:href="route('project.attachment', [$attachment->id, $attachment->name])"
493+
:heading="$attachment->name"
494+
:size="$attachment->size"
495+
:url="$attachment->url"
496+
:icon="$attachment->mime_type"
497+
/>
498+
@endforeach
499+
</div>
494500
</div>
495501

496502
<!-- Expenses Section -->

routes/web.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
Route::get('project/{project_id}', \App\Livewire\Project\ShowProject::class)->name('project.show');
3535
Route::get('project/{project_id}/history', \App\Livewire\Project\ShowProject::class)->name('project.history');
3636
Route::get('project/{project_id}/edit', \App\Livewire\Project\EditProject::class)->name('project.edit');
37+
Route::get('project/attachment/{attachment}/{fileName}', [\App\Http\Controllers\ProjectController::class, 'showAttachment'])->name('project.attachment');
3738

3839
});
3940

0 commit comments

Comments
 (0)