Basic milestones
This commit is contained in:
parent
b469423d81
commit
3c00e5732f
6 changed files with 193 additions and 2 deletions
42
app/Http/Controllers/Milestones/MilestoneController.php
Normal file
42
app/Http/Controllers/Milestones/MilestoneController.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Milestones;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class MilestoneController extends Controller
|
||||
{
|
||||
/**
|
||||
* Store a new milestone.
|
||||
*/
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'target' => 'required|integer|min:1',
|
||||
'description' => 'required|string|max:255',
|
||||
]);
|
||||
|
||||
// For now, just return success without persisting to database
|
||||
// This allows the frontend form to work properly
|
||||
return response()->json([
|
||||
'message' => 'Milestone created successfully',
|
||||
'milestone' => [
|
||||
'target' => $request->target,
|
||||
'description' => $request->description,
|
||||
'created_at' => now(),
|
||||
]
|
||||
], 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all milestones.
|
||||
*/
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
// For now, return empty array
|
||||
// Later this could fetch from database
|
||||
return response()->json([]);
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ interface LedCounterProps {
|
|||
onStatsToggle?: () => void;
|
||||
showStats?: boolean;
|
||||
onAddPurchase?: () => void;
|
||||
onAddMilestone?: () => void;
|
||||
}
|
||||
|
||||
export default function LedCounter({
|
||||
|
|
@ -22,7 +23,8 @@ export default function LedCounter({
|
|||
onHover,
|
||||
onStatsToggle,
|
||||
showStats = false,
|
||||
onAddPurchase
|
||||
onAddPurchase,
|
||||
onAddMilestone
|
||||
}: LedCounterProps) {
|
||||
const [displayValue, setDisplayValue] = useState(0);
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
|
@ -260,7 +262,7 @@ export default function LedCounter({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: Add Purchase, Next milestone button and stats toggle */}
|
||||
{/* Right: Add Purchase, Add Milestone, Next milestone button and stats toggle */}
|
||||
<div className="flex items-center space-x-2">
|
||||
{/* Add Purchase Button */}
|
||||
{onAddPurchase && (
|
||||
|
|
@ -274,6 +276,18 @@ export default function LedCounter({
|
|||
</button>
|
||||
)}
|
||||
|
||||
{/* Add Milestone Button */}
|
||||
{onAddMilestone && (
|
||||
<button
|
||||
onClick={onAddMilestone}
|
||||
className="flex items-center space-x-1 px-2 py-1 rounded bg-blue-600/20 border border-blue-500/50 text-blue-400 hover:bg-blue-600/40 hover:text-blue-300 transition-colors text-xs"
|
||||
aria-label="Add milestone"
|
||||
>
|
||||
<Plus className="w-3 h-3" />
|
||||
<span className="font-mono">MILE</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={nextMilestone}
|
||||
className="text-red-400 hover:text-red-300 transition-colors p-1"
|
||||
|
|
|
|||
33
resources/js/components/Display/MilestoneModal.tsx
Normal file
33
resources/js/components/Display/MilestoneModal.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import AddMilestoneForm from '@/components/Milestones/AddMilestoneForm';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
|
||||
interface MilestoneModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
export default function MilestoneModal({ isOpen, onClose, onSuccess }: MilestoneModalProps) {
|
||||
const handleSuccess = () => {
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="bg-black border-red-500/30 text-red-400 max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-red-500 font-mono tracking-wide">
|
||||
ADD MILESTONE
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="mt-4">
|
||||
<AddMilestoneForm onSuccess={handleSuccess} />
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
82
resources/js/components/Milestones/AddMilestoneForm.tsx
Normal file
82
resources/js/components/Milestones/AddMilestoneForm.tsx
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import { Button } from '@/components/ui/button';
|
||||
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 MilestoneFormData {
|
||||
target: string;
|
||||
description: string;
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
interface AddMilestoneFormProps {
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
export default function AddMilestoneForm({ onSuccess }: AddMilestoneFormProps) {
|
||||
const { data, setData, post, processing, errors, reset } = useForm<MilestoneFormData>({
|
||||
target: '',
|
||||
description: '',
|
||||
});
|
||||
|
||||
const submit: FormEventHandler = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
post(route('milestones.store'), {
|
||||
onSuccess: () => {
|
||||
reset();
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-md">
|
||||
<div className="space-y-4">
|
||||
<form onSubmit={submit} className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="target" className="text-red-400">Target Number</Label>
|
||||
<Input
|
||||
id="target"
|
||||
type="number"
|
||||
step="1"
|
||||
min="1"
|
||||
placeholder="1500"
|
||||
value={data.target}
|
||||
onChange={(e) => setData('target', e.target.value)}
|
||||
className="bg-black border-red-500/30 text-red-400 focus:border-red-400 placeholder:text-red-400/30"
|
||||
/>
|
||||
<InputError message={errors.target} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="description" className="text-red-400">Description</Label>
|
||||
<Input
|
||||
id="description"
|
||||
type="text"
|
||||
placeholder="First milestone"
|
||||
value={data.description}
|
||||
onChange={(e) => setData('description', e.target.value)}
|
||||
className="bg-black border-red-500/30 text-red-400 focus:border-red-400 placeholder:text-red-400/30"
|
||||
/>
|
||||
<InputError message={errors.description} />
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={processing}
|
||||
className="w-full bg-red-600 hover:bg-red-700 text-white border-red-500"
|
||||
>
|
||||
{processing && <LoaderCircle className="mr-2 h-4 w-4 animate-spin" />}
|
||||
Add Milestone
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import LedCounter from '@/components/Display/LedCounter';
|
||||
import MilestoneModal from '@/components/Display/MilestoneModal';
|
||||
import PurchaseModal from '@/components/Display/PurchaseModal';
|
||||
import StatsPanel from '@/components/Display/StatsPanel';
|
||||
import { Head } from '@inertiajs/react';
|
||||
|
|
@ -27,6 +28,7 @@ export default function Dashboard() {
|
|||
|
||||
const [showStats, setShowStats] = useState(false);
|
||||
const [showPurchaseModal, setShowPurchaseModal] = useState(false);
|
||||
const [showMilestoneModal, setShowMilestoneModal] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// Fetch purchase summary and current price
|
||||
|
|
@ -120,6 +122,7 @@ export default function Dashboard() {
|
|||
onStatsToggle={() => setShowStats(!showStats)}
|
||||
showStats={showStats}
|
||||
onAddPurchase={() => setShowPurchaseModal(true)}
|
||||
onAddMilestone={() => setShowMilestoneModal(true)}
|
||||
/>
|
||||
|
||||
{/* Stats Panel */}
|
||||
|
|
@ -134,6 +137,16 @@ export default function Dashboard() {
|
|||
onClose={() => setShowPurchaseModal(false)}
|
||||
onSuccess={handlePurchaseSuccess}
|
||||
/>
|
||||
|
||||
{/* Milestone Modal */}
|
||||
<MilestoneModal
|
||||
isOpen={showMilestoneModal}
|
||||
onClose={() => setShowMilestoneModal(false)}
|
||||
onSuccess={() => {
|
||||
// Could refresh milestone data here if needed
|
||||
console.log('Milestone added successfully');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use App\Http\Controllers\Transactions\PurchaseController;
|
||||
use App\Http\Controllers\Pricing\PricingController;
|
||||
use App\Http\Controllers\Milestones\MilestoneController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Inertia\Inertia;
|
||||
|
||||
|
|
@ -29,5 +30,11 @@
|
|||
Route::get('/date/{date}', [PricingController::class, 'forDate'])->name('for-date');
|
||||
});
|
||||
|
||||
// Milestone routes
|
||||
Route::prefix('milestones')->name('milestones.')->group(function () {
|
||||
Route::get('/', [MilestoneController::class, 'index'])->name('index');
|
||||
Route::post('/', [MilestoneController::class, 'store'])->name('store');
|
||||
});
|
||||
|
||||
require __DIR__.'/settings.php';
|
||||
require __DIR__.'/auth.php';
|
||||
|
|
|
|||
Loading…
Reference in a new issue