diff --git a/Dockerfile.dev b/Dockerfile.dev index f2a67c5..dcdd28c 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,15 +1,17 @@ # Development Dockerfile with FrankenPHP FROM dunglas/frankenphp:latest-php8.3-alpine -# Install system dependencies + development tools +# Install system dependencies + development tools (except nodejs/npm) RUN apk add --no-cache \ - nodejs \ - npm \ git \ mysql-client \ vim \ bash \ - nano + nano \ + curl + +# Install Node.js 20.19.0+ from NodeSource (for compatibility with Vite 7) +RUN curl -fsSL https://unofficial-builds.nodejs.org/download/release/v20.19.0/node-v20.19.0-linux-x64-musl.tar.xz | tar -xJ -C /usr/local --strip-components=1 # Install PHP extensions including xdebug for development RUN install-php-extensions \ @@ -68,29 +70,21 @@ RUN cat > /start.sh <<'EOF' #!/bin/sh set -e +# Fix DNS resolution by adding db host entry +# This is a workaround for podman-compose DNS issues +echo "10.89.1.2 db" >> /etc/hosts 2>/dev/null || true + # Create .env file if it doesn't exist if [ ! -f ".env" ]; then echo "Creating .env file from .env.example..." cp .env.example .env fi -# Install dependencies if volumes are empty -if [ ! -f "vendor/autoload.php" ]; then - echo "Installing composer dependencies..." - composer install -fi +# Skip composer install - use mounted vendor directory +echo "Using mounted vendor directory, skipping composer install" -# Handle node_modules with care - clean install if having issues -if [ ! -f "node_modules/.bin/vite" ]; then - echo "Installing npm dependencies..." - # Clean any remnants first - rm -rf node_modules/.* 2>/dev/null || true - rm -rf /app/.npm 2>/dev/null || true - # Fresh install with cache in tmp to avoid permission issues - npm install --cache /tmp/.npm -else - echo "Node modules already installed, skipping npm install" -fi +# Skip npm install - use mounted node_modules +echo "Using mounted node_modules directory, skipping npm install" # Clear Laravel caches php artisan config:clear || true @@ -101,10 +95,6 @@ echo "Waiting for database..." sleep 5 php artisan migrate --force || echo "Migration failed or not needed" -# Run development seeder (only in dev environment) -echo "Running development seeder..." -php artisan db:seed --class=DevelopmentSeeder --force || echo "Seeding skipped or already done" - # Generate app key if not set if [ -z "$APP_KEY" ] || [ "$APP_KEY" = "base64:YOUR_KEY_HERE" ]; then echo "Generating application key..." diff --git a/app/Http/Controllers/ScenarioController.php b/app/Http/Controllers/ScenarioController.php index 4e3054e..fa8172d 100644 --- a/app/Http/Controllers/ScenarioController.php +++ b/app/Http/Controllers/ScenarioController.php @@ -44,6 +44,11 @@ public function show(Scenario $scenario): Response ]); } + public function create(): Response + { + return Inertia::render('Scenarios/Create'); + } + public function store(Request $request): RedirectResponse { $request->validate([ @@ -60,4 +65,34 @@ public function store(Request $request): RedirectResponse return redirect()->route('scenarios.show', $scenario); } + + public function edit(Scenario $scenario): Response + { + return Inertia::render('Scenarios/Edit', [ + 'scenario' => $scenario + ]); + } + + public function update(Request $request, Scenario $scenario): RedirectResponse + { + $request->validate([ + 'name' => 'required|string|max:255', + 'description' => 'nullable|string', + 'start_date' => 'nullable|date', + 'end_date' => 'nullable|date|after_or_equal:start_date', + ]); + + $scenario->update($request->only(['name', 'description', 'start_date', 'end_date'])); + + return redirect()->route('scenarios.show', $scenario) + ->with('success', 'Scenario updated successfully'); + } + + public function destroy(Scenario $scenario): RedirectResponse + { + $scenario->delete(); + + return redirect()->route('scenarios.index') + ->with('success', 'Scenario deleted successfully'); + } } diff --git a/docker-compose.yml b/docker-compose.yml index 8e22f26..fdc44b7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -54,6 +54,7 @@ services: db: image: mariadb:11 container_name: buckets_db + hostname: db restart: unless-stopped ports: - "3307:3306" diff --git a/resources/js/pages/Scenarios/Index.tsx b/resources/js/pages/Scenarios/Index.tsx index c917956..6495d37 100644 --- a/resources/js/pages/Scenarios/Index.tsx +++ b/resources/js/pages/Scenarios/Index.tsx @@ -66,7 +66,7 @@ export default function Index({ scenarios }: Props) { data-testid="scenario-name" value={name} onChange={(e) => setName(e.target.value)} - className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-blue-500" + className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-blue-500" placeholder="e.g., 2025 Budget" /> {errors.name && ( diff --git a/routes/web.php b/routes/web.php index dc66e76..c3bccdd 100644 --- a/routes/web.php +++ b/routes/web.php @@ -8,8 +8,12 @@ // Scenario routes (no auth required for MVP) Route::get('/', [ScenarioController::class, 'index'])->name('scenarios.index'); -Route::get('/scenarios/{scenario}', [ScenarioController::class, 'show'])->name('scenarios.show'); +Route::get('/scenarios/create', [ScenarioController::class, 'create'])->name('scenarios.create'); Route::post('/scenarios', [ScenarioController::class, 'store'])->name('scenarios.store'); +Route::get('/scenarios/{scenario}', [ScenarioController::class, 'show'])->name('scenarios.show'); +Route::get('/scenarios/{scenario}/edit', [ScenarioController::class, 'edit'])->name('scenarios.edit'); +Route::patch('/scenarios/{scenario}', [ScenarioController::class, 'update'])->name('scenarios.update'); +Route::delete('/scenarios/{scenario}', [ScenarioController::class, 'destroy'])->name('scenarios.destroy'); // Bucket routes (no auth required for MVP) Route::get('/scenarios/{scenario}/buckets', [BucketController::class, 'index'])->name('buckets.index'); diff --git a/shell.nix b/shell.nix index a522393..9e3ad63 100644 --- a/shell.nix +++ b/shell.nix @@ -35,7 +35,49 @@ pkgs.mkShell { PODMAN_USERNS=keep-id podman-compose down -v PODMAN_USERNS=keep-id podman-compose build --no-cache app PODMAN_USERNS=keep-id podman-compose up -d + + echo "📦 Installing dependencies..." + # Wait for containers to be ready + echo " Waiting for containers to be ready..." + sleep 5 + + # Wait for database to be healthy + echo " Waiting for database to be ready..." + for i in {1..30}; do + if podman-compose exec db mariadb -u root -proot -e "SELECT 1" > /dev/null 2>&1; then + echo " Database is ready!" + break + fi + if [ $i -eq 30 ]; then + echo " ⚠️ Database not ready after 30 seconds" + fi + sleep 1 + done + + # Install composer dependencies + echo " Installing composer packages..." + podman-compose exec app composer install --no-interaction || echo "⚠️ Composer install failed" + + # Install npm dependencies + echo " Installing npm packages..." + podman-compose exec app npm install || echo "⚠️ NPM install failed" + + # Run migrations + echo " Running database migrations..." + sleep 2 # Extra wait to ensure DB is fully ready + podman-compose exec app php artisan migrate --force || echo "⚠️ Migrations failed" + + # Restart app container to ensure Vite picks up all changes + echo " Restarting app container..." + podman-compose restart app + echo "✅ Rebuild complete! Check logs with: dev-logs" + echo "" + echo "📍 Services available at:" + echo " App: http://localhost:8100" + echo " Vite: http://localhost:5174" + echo " Mailhog: http://localhost:8026" + echo " MariaDB: localhost:3307" } dev-rebuild-quick() { diff --git a/vite.config.ts b/vite.config.ts index 2e5db53..802db19 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -29,6 +29,7 @@ export default defineConfig({ port: 5173, strictPort: true, hmr: { + host: 'localhost', clientPort: 5174, }, },