Bilder
This commit is contained in:
parent
182b751ced
commit
b0feb07a62
@ -98,11 +98,37 @@ class ShoppingListController extends Controller
|
||||
$request->user()->id
|
||||
);
|
||||
|
||||
$shoppingItem->update([
|
||||
'product_name' => $request->string('product_name')->toString(),
|
||||
'quantity' => $request->filled('quantity') ? $request->string('quantity')->toString() : null,
|
||||
'store_id' => $storeId,
|
||||
]);
|
||||
$isDone = $request->boolean('is_done');
|
||||
|
||||
DB::transaction(function () use ($request, $shoppingItem, $storeId, $isDone): void {
|
||||
$shoppingItem->update([
|
||||
'product_name' => $request->string('product_name')->toString(),
|
||||
'quantity' => $request->filled('quantity') ? $request->string('quantity')->toString() : null,
|
||||
'store_id' => $storeId,
|
||||
'is_done' => $isDone,
|
||||
'done_at' => $isDone ? Carbon::now() : null,
|
||||
]);
|
||||
|
||||
if (! $isDone) {
|
||||
return;
|
||||
}
|
||||
|
||||
$photoPath = $request->file('photo')?->store('price-photos', 'public');
|
||||
|
||||
if (! $request->filled('price_decimal') && $photoPath === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ItemPriceLog::query()->create([
|
||||
'shopping_item_id' => $shoppingItem->id,
|
||||
'store_id' => $storeId,
|
||||
'price_decimal' => $request->input('price_decimal') ?? 0,
|
||||
'currency' => 'EUR',
|
||||
'logged_at' => Carbon::now(),
|
||||
'photo_path' => $photoPath,
|
||||
'source' => 'manual',
|
||||
]);
|
||||
});
|
||||
|
||||
return back()->with('status', 'Eintrag wurde gespeichert.');
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rules\File;
|
||||
|
||||
class UpdateShoppingItemRequest extends FormRequest
|
||||
{
|
||||
@ -28,6 +29,13 @@ class UpdateShoppingItemRequest extends FormRequest
|
||||
'quantity' => ['nullable', 'string', 'max:255'],
|
||||
'store_id' => ['nullable', 'integer', 'exists:stores,id'],
|
||||
'new_store_name' => ['nullable', 'string', 'max:255'],
|
||||
'is_done' => ['sometimes', 'boolean'],
|
||||
'price_decimal' => ['nullable', 'numeric', 'min:0', 'max:999999.99'],
|
||||
'photo' => [
|
||||
'nullable',
|
||||
File::types(['jpg', 'jpeg', 'png', 'gif', 'webp', 'heic', 'heif'])
|
||||
->max(15 * 1024),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@ -39,6 +47,10 @@ class UpdateShoppingItemRequest extends FormRequest
|
||||
'quantity.max' => 'Die Menge darf maximal 255 Zeichen lang sein.',
|
||||
'store_id.exists' => 'Das ausgewaehlte Geschaeft existiert nicht.',
|
||||
'new_store_name.max' => 'Der neue Geschaeftsname darf maximal 255 Zeichen lang sein.',
|
||||
'price_decimal.numeric' => 'Der Preis muss eine Zahl sein.',
|
||||
'price_decimal.min' => 'Der Preis darf nicht negativ sein.',
|
||||
'price_decimal.max' => 'Der Preis ist zu gross.',
|
||||
'photo.max' => 'Das Foto darf maximal 15 MB gross sein.',
|
||||
];
|
||||
}
|
||||
|
||||
@ -49,6 +61,8 @@ class UpdateShoppingItemRequest extends FormRequest
|
||||
'quantity' => 'Menge',
|
||||
'store_id' => 'Geschaeft',
|
||||
'new_store_name' => 'neues Geschaeft',
|
||||
'price_decimal' => 'Preis',
|
||||
'photo' => 'Foto',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,7 +101,6 @@
|
||||
@include('shopping-list.partials.item-pencil-panel', [
|
||||
'item' => $item,
|
||||
'stores' => $stores,
|
||||
'showToggleExtras' => true,
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
@ -158,7 +157,6 @@
|
||||
@include('shopping-list.partials.item-pencil-panel', [
|
||||
'item' => $item,
|
||||
'stores' => $stores,
|
||||
'showToggleExtras' => true,
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
{{-- Ein Bleistift oeffnet alle Aenderungsoptionen: Stammdaten (update) + optional Preis/Foto beim Abhaken (nur wenn $showToggleExtras) --}}
|
||||
@php
|
||||
$showToggleExtras = $showToggleExtras ?? false;
|
||||
@endphp
|
||||
|
||||
{{-- Ein Bleistift oeffnet ein Formular: Stammdaten, Erledigt-Status, optional Preis/Foto --}}
|
||||
<details class="group relative shrink-0">
|
||||
<summary
|
||||
class="list-none cursor-pointer flex h-11 w-11 items-center justify-center rounded-lg border border-gray-200 bg-white text-gray-600 shadow-sm hover:bg-gray-50 hover:text-gray-900 [&::-webkit-details-marker]:hidden"
|
||||
@ -14,7 +10,7 @@
|
||||
</summary>
|
||||
<div class="absolute right-0 z-20 mt-2 w-[min(calc(100vw-2rem),22rem)] rounded-xl border border-gray-200 bg-white p-4 shadow-lg sm:w-80">
|
||||
<p class="mb-1 text-sm font-semibold text-gray-900">Eintrag bearbeiten</p>
|
||||
<p class="mb-3 text-xs text-gray-500">Zwei getrennte Aktionen – bitte den passenden Button nutzen.</p>
|
||||
<p class="mb-3 text-xs text-gray-500">Produkt, Geschaeft, Erledigt-Status sowie optional Preis und Foto in einem Schritt speichern.</p>
|
||||
|
||||
@if($item->latestPhotoLog)
|
||||
<div class="mb-4 rounded-lg border border-emerald-200 bg-emerald-50/80 p-3">
|
||||
@ -31,11 +27,12 @@
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="mb-2">
|
||||
<p class="text-xs font-semibold text-gray-800">1. Produkt & Geschaeft</p>
|
||||
<p class="text-xs text-gray-500 mt-0.5">Nur Name, Menge und Geschaeft. Der Status (offen/erledigt) bleibt dabei gleich.</p>
|
||||
</div>
|
||||
<form method="POST" action="{{ route('shopping-items.update', $item) }}" class="grid grid-cols-1 gap-2">
|
||||
<form
|
||||
method="POST"
|
||||
action="{{ route('shopping-items.update', $item) }}"
|
||||
enctype="multipart/form-data"
|
||||
class="grid grid-cols-1 gap-2"
|
||||
>
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<label class="text-xs font-medium text-gray-600">Produktname</label>
|
||||
@ -72,85 +69,41 @@
|
||||
list="store-options"
|
||||
class="rounded-md border-gray-300 text-sm min-h-[44px]"
|
||||
>
|
||||
<label class="flex items-start gap-2 mt-1 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="is_done"
|
||||
value="1"
|
||||
@checked($item->is_done)
|
||||
class="mt-1 h-5 w-5 rounded border-gray-300 text-blue-600 shadow-sm focus:ring-blue-500"
|
||||
>
|
||||
<span class="text-xs text-gray-700">Als erledigt markieren</span>
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 -mt-1">Preis und Foto werden nur gespeichert, wenn der Eintrag als erledigt markiert ist.</p>
|
||||
<input
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0"
|
||||
name="price_decimal"
|
||||
placeholder="Preis in EUR (optional)"
|
||||
class="rounded-md border-gray-300 text-sm min-h-[44px]"
|
||||
>
|
||||
<label class="text-xs text-gray-600">Foto Kassenbon (optional)</label>
|
||||
<input type="file" name="photo" accept="image/*" class="text-sm min-h-[44px]">
|
||||
@if($item->latestPriceLog)
|
||||
<div class="text-xs text-gray-500">
|
||||
Letzter Preis:
|
||||
{{ number_format((float) $item->latestPriceLog->price_decimal, 2, ',', '.') }} EUR
|
||||
bei {{ $item->latestPriceLog->store?->name ?: 'ohne Geschaeft' }}
|
||||
({{ optional($item->latestPriceLog->logged_at)->format('d.m.Y H:i') }})
|
||||
</div>
|
||||
@endif
|
||||
<button
|
||||
type="submit"
|
||||
class="mt-1 rounded-lg bg-blue-600 text-white px-4 py-2.5 min-h-[44px] text-sm font-medium hover:bg-blue-700"
|
||||
>
|
||||
Nur Produkt speichern
|
||||
Speichern
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@if($showToggleExtras)
|
||||
<div class="my-4 border-t border-gray-200"></div>
|
||||
<div class="mb-2">
|
||||
@if($item->is_done)
|
||||
<p class="text-xs font-semibold text-gray-800">2. Neuer Preis oder Kassenbon</p>
|
||||
<p class="text-xs text-gray-500 mt-0.5">Fuer die Statistik: weiterer Einkauf desselben Artikels. Alles optional.</p>
|
||||
@else
|
||||
<p class="text-xs font-semibold text-gray-800">2. Erledigen & Kasse erfassen</p>
|
||||
<p class="text-xs text-gray-500 mt-0.5">Hakt die Zeile ab und speichert optional Preis und Foto. Oder nutze nur die Checkbox in der Liste zum Abhaken ohne Kasse.</p>
|
||||
@endif
|
||||
</div>
|
||||
<form
|
||||
method="POST"
|
||||
action="{{ route('shopping-items.toggle', $item) }}"
|
||||
enctype="multipart/form-data"
|
||||
class="grid grid-cols-1 gap-2"
|
||||
>
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<input type="hidden" name="is_done" value="1">
|
||||
<input
|
||||
type="text"
|
||||
name="quantity"
|
||||
value="{{ $item->quantity }}"
|
||||
placeholder="Menge (optional)"
|
||||
class="rounded-md border-gray-300 text-sm min-h-[44px]"
|
||||
>
|
||||
<select name="store_id" class="rounded-md border-gray-300 text-sm min-h-[44px]">
|
||||
<option value="">Geschaeft (optional)</option>
|
||||
@foreach($stores as $store)
|
||||
<option value="{{ $store->id }}" @selected($item->store_id === $store->id)>
|
||||
{{ $store->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<input
|
||||
type="text"
|
||||
name="new_store_name"
|
||||
placeholder="Geschaeft frei eingeben (optional)"
|
||||
list="store-options"
|
||||
class="rounded-md border-gray-300 text-sm min-h-[44px]"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0"
|
||||
name="price_decimal"
|
||||
placeholder="Preis in EUR (optional)"
|
||||
class="rounded-md border-gray-300 text-sm min-h-[44px]"
|
||||
>
|
||||
<label class="text-xs text-gray-600">Foto Kassenbon (optional)</label>
|
||||
<input type="file" name="photo" accept="image/*" class="text-sm min-h-[44px]">
|
||||
@if($item->latestPriceLog)
|
||||
<div class="text-xs text-gray-500">
|
||||
Letzter Preis:
|
||||
{{ number_format((float) $item->latestPriceLog->price_decimal, 2, ',', '.') }} EUR
|
||||
bei {{ $item->latestPriceLog->store?->name ?: 'ohne Geschaeft' }}
|
||||
({{ optional($item->latestPriceLog->logged_at)->format('d.m.Y H:i') }})
|
||||
</div>
|
||||
@endif
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-lg bg-emerald-600 text-white px-4 py-2.5 min-h-[44px] text-sm font-medium hover:bg-emerald-700"
|
||||
>
|
||||
@if($item->is_done)
|
||||
Preis & Foto zur Statistik
|
||||
@else
|
||||
Abhaken & Kasse speichern
|
||||
@endif
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</details>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user