diff --git a/app/Http/Controllers/Pricing/PricingController.php b/app/Http/Controllers/Pricing/PricingController.php new file mode 100644 index 0000000..cdd34a3 --- /dev/null +++ b/app/Http/Controllers/Pricing/PricingController.php @@ -0,0 +1,54 @@ +json([ + 'current_price' => $price, + ]); + } + + public function update(Request $request): JsonResponse + { + $validated = $request->validate([ + 'date' => 'required|date|before_or_equal:today', + 'price' => 'required|numeric|min:0.0001', + ]); + + $assetPrice = AssetPrice::updatePrice($validated['date'], $validated['price']); + + return response()->json([ + 'success' => true, + 'message' => 'Asset price updated successfully!', + 'data' => $assetPrice, + ]); + } + + public function history(Request $request): JsonResponse + { + $limit = $request->get('limit', 30); + $history = AssetPrice::history($limit); + + return response()->json($history); + } + + public function forDate(Request $request, string $date): JsonResponse + { + $price = AssetPrice::forDate($date); + + return response()->json([ + 'date' => $date, + 'price' => $price, + ]); + } +} diff --git a/app/Models/Pricing/AssetPrice.php b/app/Models/Pricing/AssetPrice.php new file mode 100644 index 0000000..2d3b6cf --- /dev/null +++ b/app/Models/Pricing/AssetPrice.php @@ -0,0 +1,60 @@ + 'date', + 'price' => 'decimal:4', + ]; + + public static function current(): ?float + { + $latestPrice = static::latest('date')->first(); + + return $latestPrice ? $latestPrice->price : null; + } + + public static function forDate(string $date): ?float + { + $price = static::where('date', '<=', $date) + ->orderBy('date', 'desc') + ->first(); + + return $price ? $price->price : null; + } + + public static function updatePrice(string $date, float $price): self + { + return static::updateOrCreate( + ['date' => $date], + ['price' => $price] + ); + } + + public static function history(int $limit = 30): Collection + { + return static::orderBy('date', 'desc')->limit($limit)->get(); + } +} diff --git a/database/migrations/2025_07_10_152716_create_asset_prices_table.php b/database/migrations/2025_07_10_152716_create_asset_prices_table.php new file mode 100644 index 0000000..94ebab3 --- /dev/null +++ b/database/migrations/2025_07_10_152716_create_asset_prices_table.php @@ -0,0 +1,26 @@ +id(); + $table->date('date'); + $table->decimal('price', 10, 4); + $table->timestamps(); + + $table->unique('date'); + $table->index('date'); + }); + } + + public function down(): void + { + Schema::dropIfExists('asset_prices'); + } +}; diff --git a/resources/js/components/Pricing/UpdatePriceForm.tsx b/resources/js/components/Pricing/UpdatePriceForm.tsx new file mode 100644 index 0000000..b4944e5 --- /dev/null +++ b/resources/js/components/Pricing/UpdatePriceForm.tsx @@ -0,0 +1,91 @@ +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import InputError from '@/components/InputError'; +import { useForm } from '@inertiajs/react'; +import { LoaderCircle } from 'lucide-react'; +import { FormEventHandler } from 'react'; + +interface PriceUpdateFormData { + date: string; + price: string; + [key: string]: string; +} + +interface UpdatePriceFormProps { + currentPrice?: number; + className?: string; +} + +export default function UpdatePriceForm({ currentPrice, className }: UpdatePriceFormProps) { + const { data, setData, post, processing, errors } = useForm({ + date: new Date().toISOString().split('T')[0], // Today's date in YYYY-MM-DD format + price: currentPrice?.toString() || '', + }); + + const submit: FormEventHandler = (e) => { + e.preventDefault(); + + post(route('pricing.update'), { + onSuccess: () => { + // Keep the date, reset only price if needed + // User might want to update same day multiple times + }, + }); + }; + + return ( + + + Update Asset Price + {currentPrice && ( +

+ Current price: €{currentPrice.toFixed(4)} +

+ )} +
+ +
+
+ + setData('date', e.target.value)} + max={new Date().toISOString().split('T')[0]} + /> + +
+ +
+ + setData('price', e.target.value)} + /> +

+ Price per unit/share of the asset +

+ +
+ + +
+
+
+ ); +} diff --git a/routes/web.php b/routes/web.php index 2743eaa..59d6907 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,6 +1,7 @@ name('destroy'); }); +// Pricing routes +Route::prefix('pricing')->name('pricing.')->group(function () { + Route::get('/current', [PricingController::class, 'current'])->name('current'); + Route::post('/update', [PricingController::class, 'update'])->name('update'); + Route::get('/history', [PricingController::class, 'history'])->name('history'); + Route::get('/date/{date}', [PricingController::class, 'forDate'])->name('for-date'); +}); + require __DIR__.'/settings.php'; require __DIR__.'/auth.php';