Browse Source

dashboard JORGE

cree la vista de jorge que es el de mantenimiento
Rubi 1 week ago
parent
commit
89022967ea
  1. 82
      app/Http/Controllers/MaintenanceController.php
  2. 32
      app/Models/Maintenance.php
  3. 27
      database/migrations/2025_03_07_000000_add_plate_to_tipos_veiculos.php
  4. 28
      database/migrations/2025_03_08_000000_create_maintenances_table.php
  5. 22
      database/migrations/2025_05_07_000000_add_rol_to_users_table.php
  6. 347
      package-lock.json
  7. 7
      package.json
  8. 143
      resources/js/components/dashboard/JorgeDashboard.jsx
  9. 151
      resources/js/components/dashboard/NewMaintenanceForm.jsx
  10. 10
      resources/js/maintenance-dashboard.js
  11. 8
      resources/views/layouts/dashboard.blade.php
  12. 9
      resources/views/maintenance/dashboard.blade.php
  13. 10
      routes/api.php
  14. 2
      routes/web.php

82
app/Http/Controllers/MaintenanceController.php

@ -0,0 +1,82 @@
<?php
namespace App\Http\Controllers;
use App\Models\Maintenance;
use App\Models\Vehicle;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class MaintenanceController extends Controller
{
public function index()
{
return view('maintenance.dashboard');
}
public function getMaintenanceHistory()
{
$maintenances = Maintenance::with('vehicle')
->orderBy('date', 'desc')
->get();
return response()->json($maintenances);
}
public function getPendingAlerts()
{
$alerts = Maintenance::with('vehicle')
->where('status', 'Pendiente')
->where('next_maintenance_date', '<=', now()->addDays(30))
->orderBy('next_maintenance_date', 'asc')
->get();
return response()->json($alerts);
}
public function store(Request $request)
{
$validated = $request->validate([
'vehicle_id' => 'required|exists:vehicles,id',
'maintenance_type' => 'required|string',
'date' => 'required|date',
'description' => 'required|string',
'cost' => 'required|numeric|min:0',
'status' => 'required|in:Pendiente,En Proceso,Completado',
'next_maintenance_date' => 'nullable|date'
]);
$maintenance = Maintenance::create($validated);
return response()->json([
'message' => 'Mantenimiento registrado exitosamente',
'maintenance' => $maintenance
], 201);
}
public function update(Request $request, Maintenance $maintenance)
{
$validated = $request->validate([
'vehicle_id' => 'required|exists:vehicles,id',
'maintenance_type' => 'required|string',
'date' => 'required|date',
'description' => 'required|string',
'cost' => 'required|numeric|min:0',
'status' => 'required|in:Pendiente,En Proceso,Completado',
'next_maintenance_date' => 'nullable|date'
]);
$maintenance->update($validated);
return response()->json([
'message' => 'Mantenimiento actualizado exitosamente',
'maintenance' => $maintenance
]);
}
public function getVehicles()
{
$vehicles = Vehicle::where('status', 'Activo')->get();
return response()->json($vehicles);
}
}

32
app/Models/Maintenance.php

@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Maintenance extends Model
{
use HasFactory;
protected $fillable = [
'vehicle_id',
'maintenance_type',
'date',
'description',
'cost',
'status',
'next_maintenance_date'
];
protected $casts = [
'date' => 'date',
'next_maintenance_date' => 'date',
'cost' => 'decimal:2'
];
public function vehicle()
{
return $this->belongsTo(TiposVeiculo::class, 'vehicle_id');
}
}

27
database/migrations/2025_03_07_000000_add_plate_to_tipos_veiculos.php

@ -0,0 +1,27 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::table('tipos_veiculos', function (Blueprint $table) {
if (!Schema::hasColumn('tipos_veiculos', 'plate')) {
$table->string('plate')->after('nombre')->nullable();
}
if (!Schema::hasColumn('tipos_veiculos', 'model')) {
$table->string('model')->after('plate')->nullable();
}
});
}
public function down()
{
Schema::table('tipos_veiculos', function (Blueprint $table) {
$table->dropColumn(['plate', 'model']);
});
}
};

28
database/migrations/2025_03_08_000000_create_maintenances_table.php

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::create('maintenances', function (Blueprint $table) {
$table->id();
$table->foreignId('vehicle_id')->constrained('tipos_veiculos')->onDelete('cascade');
$table->string('maintenance_type');
$table->date('date');
$table->text('description');
$table->decimal('cost', 10, 2);
$table->enum('status', ['Pendiente', 'En Proceso', 'Completado'])->default('Pendiente');
$table->date('next_maintenance_date')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('maintenances');
}
};

22
database/migrations/2025_05_07_000000_add_rol_to_users_table.php

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('rol')->default('usuario')->after('email');
});
}
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('rol');
});
}
};

347
package-lock.json

@ -4,15 +4,27 @@
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"dependencies": {
"react-bootstrap": "^2.10.9"
},
"devDependencies": { "devDependencies": {
"@popperjs/core": "^2.11.6", "@popperjs/core": "^2.11.6",
"axios": "^1.1.2", "axios": "^1.9.0",
"bootstrap": "^5.2.3", "bootstrap": "^5.3.6",
"laravel-vite-plugin": "^0.7.2", "laravel-vite-plugin": "^0.7.2",
"sass": "^1.56.1", "sass": "^1.56.1",
"vite": "^4.0.0" "vite": "^4.0.0"
} }
}, },
"node_modules/@babel/runtime": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz",
"integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@esbuild/android-arm": { "node_modules/@esbuild/android-arm": {
"version": "0.18.20", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
@ -701,13 +713,120 @@
"version": "2.11.8", "version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"dev": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
"url": "https://opencollective.com/popperjs" "url": "https://opencollective.com/popperjs"
} }
}, },
"node_modules/@react-aria/ssr": {
"version": "3.9.8",
"resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.8.tgz",
"integrity": "sha512-lQDE/c9uTfBSDOjaZUJS8xP2jCKVk4zjQeIlCH90xaLhHDgbpCdns3xvFpJJujfj3nI4Ll9K7A+ONUBDCASOuw==",
"license": "Apache-2.0",
"dependencies": {
"@swc/helpers": "^0.5.0"
},
"engines": {
"node": ">= 12"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@restart/hooks": {
"version": "0.4.16",
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz",
"integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==",
"license": "MIT",
"dependencies": {
"dequal": "^2.0.3"
},
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/@restart/ui": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.9.4.tgz",
"integrity": "sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
"@popperjs/core": "^2.11.8",
"@react-aria/ssr": "^3.5.0",
"@restart/hooks": "^0.5.0",
"@types/warning": "^3.0.3",
"dequal": "^2.0.3",
"dom-helpers": "^5.2.0",
"uncontrollable": "^8.0.4",
"warning": "^4.0.3"
},
"peerDependencies": {
"react": ">=16.14.0",
"react-dom": ">=16.14.0"
}
},
"node_modules/@restart/ui/node_modules/@restart/hooks": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.1.tgz",
"integrity": "sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q==",
"license": "MIT",
"dependencies": {
"dequal": "^2.0.3"
},
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/@restart/ui/node_modules/uncontrollable": {
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz",
"integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==",
"license": "MIT",
"peerDependencies": {
"react": ">=16.14.0"
}
},
"node_modules/@swc/helpers": {
"version": "0.5.17",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
"integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.8.0"
}
},
"node_modules/@types/prop-types": {
"version": "15.7.14",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
"integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
"license": "MIT"
},
"node_modules/@types/react": {
"version": "19.1.3",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.3.tgz",
"integrity": "sha512-dLWQ+Z0CkIvK1J8+wrDPwGxEYFA4RAyHoZPxHVGspYmFVnwGSNT24cGIhFJrtfRnWVuW8X7NO52gCXmhkVUWGQ==",
"license": "MIT",
"dependencies": {
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-transition-group": {
"version": "4.4.12",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
"integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*"
}
},
"node_modules/@types/warning": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz",
"integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==",
"license": "MIT"
},
"node_modules/asynckit": { "node_modules/asynckit": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@ -716,9 +835,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.8.1", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
"integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==", "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -728,9 +847,9 @@
} }
}, },
"node_modules/bootstrap": { "node_modules/bootstrap": {
"version": "5.3.3", "version": "5.3.6",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.6.tgz",
"integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", "integrity": "sha512-jX0GAcRzvdwISuvArXn3m7KZscWWFAf1MKBcnzaN02qWMb3jpMoUX4/qgeiGzqyIb4ojulRzs89UCUmGcFSzTA==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -791,6 +910,12 @@
"url": "https://paulmillr.com/funding/" "url": "https://paulmillr.com/funding/"
} }
}, },
"node_modules/classnames": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
"license": "MIT"
},
"node_modules/combined-stream": { "node_modules/combined-stream": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@ -804,6 +929,12 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT"
},
"node_modules/delayed-stream": { "node_modules/delayed-stream": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@ -814,6 +945,15 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/detect-libc": { "node_modules/detect-libc": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
@ -828,6 +968,16 @@
"node": ">=0.10" "node": ">=0.10"
} }
}, },
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"node_modules/dunder-proto": { "node_modules/dunder-proto": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@ -1107,6 +1257,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/is-extglob": { "node_modules/is-extglob": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@ -1143,6 +1302,12 @@
"node": ">=0.12.0" "node": ">=0.12.0"
} }
}, },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT"
},
"node_modules/laravel-vite-plugin": { "node_modules/laravel-vite-plugin": {
"version": "0.7.8", "version": "0.7.8",
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.7.8.tgz", "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.7.8.tgz",
@ -1160,6 +1325,18 @@
"vite": "^3.0.0 || ^4.0.0" "vite": "^3.0.0 || ^4.0.0"
} }
}, },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/math-intrinsics": { "node_modules/math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@ -1235,6 +1412,15 @@
"license": "MIT", "license": "MIT",
"optional": true "optional": true
}, },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@ -1284,6 +1470,30 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
"node_modules/prop-types-extra": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz",
"integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==",
"license": "MIT",
"dependencies": {
"react-is": "^16.3.2",
"warning": "^4.0.0"
},
"peerDependencies": {
"react": ">=0.14.0"
}
},
"node_modules/proxy-from-env": { "node_modules/proxy-from-env": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@ -1291,6 +1501,88 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/react": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-bootstrap": {
"version": "2.10.9",
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.9.tgz",
"integrity": "sha512-TJUCuHcxdgYpOqeWmRApM/Dy0+hVsxNRFvq2aRFQuxhNi/+ivOxC5OdWIeHS3agxvzJ4Ev4nDw2ZdBl9ymd/JQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.24.7",
"@restart/hooks": "^0.4.9",
"@restart/ui": "^1.9.4",
"@types/prop-types": "^15.7.12",
"@types/react-transition-group": "^4.4.6",
"classnames": "^2.3.2",
"dom-helpers": "^5.2.1",
"invariant": "^2.2.4",
"prop-types": "^15.8.1",
"prop-types-extra": "^1.1.0",
"react-transition-group": "^4.4.5",
"uncontrollable": "^7.2.1",
"warning": "^4.0.3"
},
"peerDependencies": {
"@types/react": ">=16.14.8",
"react": ">=16.14.0",
"react-dom": ">=16.14.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/react-dom": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.26.0"
},
"peerDependencies": {
"react": "^19.1.0"
}
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
},
"node_modules/react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
"license": "MIT"
},
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
"license": "BSD-3-Clause",
"dependencies": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": ">=16.6.0",
"react-dom": ">=16.6.0"
}
},
"node_modules/readdirp": { "node_modules/readdirp": {
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
@ -1343,6 +1635,13 @@
"@parcel/watcher": "^2.4.1" "@parcel/watcher": "^2.4.1"
} }
}, },
"node_modules/scheduler": {
"version": "0.26.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
"license": "MIT",
"peer": true
},
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@ -1367,6 +1666,27 @@
"node": ">=8.0" "node": ">=8.0"
} }
}, },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/uncontrollable": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz",
"integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.6.3",
"@types/react": ">=16.9.11",
"invariant": "^2.2.4",
"react-lifecycles-compat": "^3.0.4"
},
"peerDependencies": {
"react": ">=15.0.0"
}
},
"node_modules/vite": { "node_modules/vite": {
"version": "4.5.9", "version": "4.5.9",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.9.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.9.tgz",
@ -1433,6 +1753,15 @@
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"picomatch": "^2.3.1" "picomatch": "^2.3.1"
} }
},
"node_modules/warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.0.0"
}
} }
} }
} }

7
package.json

@ -6,10 +6,13 @@
}, },
"devDependencies": { "devDependencies": {
"@popperjs/core": "^2.11.6", "@popperjs/core": "^2.11.6",
"axios": "^1.1.2", "axios": "^1.9.0",
"bootstrap": "^5.2.3", "bootstrap": "^5.3.6",
"laravel-vite-plugin": "^0.7.2", "laravel-vite-plugin": "^0.7.2",
"sass": "^1.56.1", "sass": "^1.56.1",
"vite": "^4.0.0" "vite": "^4.0.0"
},
"dependencies": {
"react-bootstrap": "^2.10.9"
} }
} }

143
resources/js/components/dashboard/JorgeDashboard.jsx

@ -0,0 +1,143 @@
import React, { useState, useEffect } from 'react';
import { Card, Row, Col, Button, Table, Alert } from 'react-bootstrap';
import NewMaintenanceForm from './NewMaintenanceForm';
import axios from 'axios';
const JorgeDashboard = () => {
const [maintenanceHistory, setMaintenanceHistory] = useState([]);
const [pendingAlerts, setPendingAlerts] = useState([]);
const [showNewMaintenanceForm, setShowNewMaintenanceForm] = useState(false);
const [vehicles, setVehicles] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
try {
setLoading(true);
const [historyRes, alertsRes, vehiclesRes] = await Promise.all([
axios.get('/api/maintenance/history'),
axios.get('/api/maintenance/alerts'),
axios.get('/api/maintenance/vehicles')
]);
setMaintenanceHistory(historyRes.data);
setPendingAlerts(alertsRes.data);
setVehicles(vehiclesRes.data);
setError(null);
} catch (err) {
setError('Error al cargar los datos. Por favor, intente nuevamente.');
console.error('Error fetching data:', err);
} finally {
setLoading(false);
}
};
const handleNewMaintenance = async (formData) => {
try {
const response = await axios.post('/api/maintenance', formData);
setMaintenanceHistory(prev => [response.data.maintenance, ...prev]);
setShowNewMaintenanceForm(false);
} catch (err) {
setError('Error al guardar el mantenimiento. Por favor, intente nuevamente.');
console.error('Error saving maintenance:', err);
}
};
if (loading) {
return <div className="text-center p-5">Cargando...</div>;
}
if (error) {
return <Alert variant="danger">{error}</Alert>;
}
return (
<div className="container-fluid">
<h2 className="mb-4">Panel de Control - Mantenimiento de Vehículos</h2>
{/* Alertas Pendientes */}
<Row className="mb-4">
<Col>
<Card>
<Card.Header>
<h4>Alertas de Mantenimiento Pendiente</h4>
</Card.Header>
<Card.Body>
{pendingAlerts.length === 0 ? (
<Alert variant="success">No hay alertas pendientes</Alert>
) : (
pendingAlerts.map(alert => (
<Alert key={alert.id} variant="warning">
<strong>{alert.vehicle.plate}</strong> - {alert.maintenance_type} pendiente para: {new Date(alert.next_maintenance_date).toLocaleDateString()}
</Alert>
))
)}
</Card.Body>
</Card>
</Col>
</Row>
{/* Historial de Mantenimientos */}
<Row>
<Col>
<Card>
<Card.Header className="d-flex justify-content-between align-items-center">
<h4>Historial de Mantenimientos</h4>
<Button
variant="primary"
onClick={() => setShowNewMaintenanceForm(true)}
>
Nuevo Mantenimiento
</Button>
</Card.Header>
<Card.Body>
<Table striped bordered hover>
<thead>
<tr>
<th>Vehículo</th>
<th>Fecha</th>
<th>Tipo</th>
<th>Estado</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
{maintenanceHistory.map(record => (
<tr key={record.id}>
<td>{record.vehicle.plate}</td>
<td>{new Date(record.date).toLocaleDateString()}</td>
<td>{record.maintenance_type}</td>
<td>{record.status}</td>
<td>
<Button variant="info" size="sm" className="me-2">
Ver Detalles
</Button>
<Button variant="secondary" size="sm">
Editar
</Button>
</td>
</tr>
))}
</tbody>
</Table>
</Card.Body>
</Card>
</Col>
</Row>
{/* Modal de Nuevo Mantenimiento */}
<NewMaintenanceForm
show={showNewMaintenanceForm}
handleClose={() => setShowNewMaintenanceForm(false)}
onSubmit={handleNewMaintenance}
vehicles={vehicles}
/>
</div>
);
};
export default JorgeDashboard;

151
resources/js/components/dashboard/NewMaintenanceForm.jsx

@ -0,0 +1,151 @@
import React, { useState } from 'react';
import { Modal, Form, Button } from 'react-bootstrap';
const NewMaintenanceForm = ({ show, handleClose, onSubmit, vehicles }) => {
const [formData, setFormData] = useState({
vehicle_id: '',
maintenance_type: '',
date: '',
description: '',
cost: '',
status: 'Pendiente',
next_maintenance_date: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
onSubmit(formData);
// Resetear el formulario
setFormData({
vehicle_id: '',
maintenance_type: '',
date: '',
description: '',
cost: '',
status: 'Pendiente',
next_maintenance_date: ''
});
};
return (
<Modal show={show} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Nuevo Registro de Mantenimiento</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form onSubmit={handleSubmit}>
<Form.Group className="mb-3">
<Form.Label>Vehículo</Form.Label>
<Form.Select
name="vehicle_id"
value={formData.vehicle_id}
onChange={handleChange}
required
>
<option value="">Seleccione un vehículo</option>
{vehicles.map(vehicle => (
<option key={vehicle.id} value={vehicle.id}>
{vehicle.plate} - {vehicle.model}
</option>
))}
</Form.Select>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Tipo de Mantenimiento</Form.Label>
<Form.Select
name="maintenance_type"
value={formData.maintenance_type}
onChange={handleChange}
required
>
<option value="">Seleccione un tipo</option>
<option value="Preventivo">Preventivo</option>
<option value="Correctivo">Correctivo</option>
<option value="Cambio de Aceite">Cambio de Aceite</option>
<option value="Revisión General">Revisión General</option>
</Form.Select>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Fecha</Form.Label>
<Form.Control
type="date"
name="date"
value={formData.date}
onChange={handleChange}
required
/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Descripción</Form.Label>
<Form.Control
as="textarea"
rows={3}
name="description"
value={formData.description}
onChange={handleChange}
required
/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Costo</Form.Label>
<Form.Control
type="number"
step="0.01"
name="cost"
value={formData.cost}
onChange={handleChange}
required
/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Estado</Form.Label>
<Form.Select
name="status"
value={formData.status}
onChange={handleChange}
required
>
<option value="Pendiente">Pendiente</option>
<option value="En Proceso">En Proceso</option>
<option value="Completado">Completado</option>
</Form.Select>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Próxima Fecha de Mantenimiento</Form.Label>
<Form.Control
type="date"
name="next_maintenance_date"
value={formData.next_maintenance_date}
onChange={handleChange}
/>
</Form.Group>
<div className="d-flex justify-content-end gap-2">
<Button variant="secondary" onClick={handleClose}>
Cancelar
</Button>
<Button variant="primary" type="submit">
Guardar
</Button>
</div>
</Form>
</Modal.Body>
</Modal>
);
};
export default NewMaintenanceForm;

10
resources/js/maintenance-dashboard.js

@ -0,0 +1,10 @@
import 'bootstrap/dist/css/bootstrap.min.css';
import React from 'react';
import { createRoot } from 'react-dom/client';
import JorgeDashboard from './components/dashboard/JorgeDashboard';
const container = document.getElementById('maintenance-dashboard');
if (container) {
const root = createRoot(container);
root.render(<JorgeDashboard />);
}

8
resources/views/layouts/dashboard.blade.php

@ -113,6 +113,14 @@
</a> </a>
</li> </li>
<!-- Mantenimiento -->
<li>
<a href="/maintenance" class="nav-item-hover flex items-center space-x-3 px-4 py-3 rounded-lg hover:bg-white/10 backdrop-blur-sm {{ request()->is('maintenance') ? 'bg-white/20' : '' }}">
<i class="fas fa-tools text-white/80"></i>
<span class="font-light">Mantenimiento</span>
</a>
</li>
<!-- Configuración con Submenú --> <!-- Configuración con Submenú -->
<li x-data="{ open: false }" class="relative"> <li x-data="{ open: false }" class="relative">
<button @click="open = !open" <button @click="open = !open"

9
resources/views/maintenance/dashboard.blade.php

@ -0,0 +1,9 @@
@extends('layouts.dashboard')
@section('content')
<div id="maintenance-dashboard"></div>
@endsection
@push('scripts')
@vite('resources/js/maintenance-dashboard.js')
@endpush

10
routes/api.php

@ -2,6 +2,7 @@
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use App\Http\Controllers\MaintenanceController;
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -17,3 +18,12 @@ use Illuminate\Support\Facades\Route;
Route::middleware('auth:sanctum')->get('/user', function (Request $request) { Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user(); return $request->user();
}); });
Route::middleware('auth:sanctum')->group(function () {
// Rutas de mantenimiento
Route::get('/maintenance/history', [MaintenanceController::class, 'getMaintenanceHistory']);
Route::get('/maintenance/alerts', [MaintenanceController::class, 'getPendingAlerts']);
Route::get('/maintenance/vehicles', [MaintenanceController::class, 'getVehicles']);
Route::post('/maintenance', [MaintenanceController::class, 'store']);
Route::put('/maintenance/{maintenance}', [MaintenanceController::class, 'update']);
});

2
routes/web.php

@ -9,6 +9,7 @@ use App\Http\Controllers\TiposVeiculosController;
use App\Http\Controllers\TiposLicenciasController; use App\Http\Controllers\TiposLicenciasController;
use App\Http\Controllers\CapacidadController; use App\Http\Controllers\CapacidadController;
use App\Http\Controllers\PrestamoController; use App\Http\Controllers\PrestamoController;
use App\Http\Controllers\MaintenanceController;
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -68,6 +69,7 @@ use App\Http\Controllers\PrestamoController;
Route::middleware(['auth'])->group(function () { Route::middleware(['auth'])->group(function () {
Route::get('/dashboard', [HomeController::class, 'index'])->name('dashboard'); Route::get('/dashboard', [HomeController::class, 'index'])->name('dashboard');
Route::get('/maintenance', [MaintenanceController::class, 'index'])->name('maintenance.dashboard');
Route::get('/usuarios', [usuariosController::class,'index'])->name('usuarios'); Route::get('/usuarios', [usuariosController::class,'index'])->name('usuarios');
Route::get('/usuarios/nuevo', [usuariosController::class, 'create'])->name('usuarios.create'); Route::get('/usuarios/nuevo', [usuariosController::class, 'create'])->name('usuarios.create');
Route::post('/usuarios/store', [usuariosController::class,'store'])->name('usuarios.store'); Route::post('/usuarios/store', [usuariosController::class,'store'])->name('usuarios.store');

Loading…
Cancel
Save