From e839d40a99a13f2f667dbe09a5c4bdb0f8cde6ab Mon Sep 17 00:00:00 2001 From: sackey Date: Wed, 30 Jul 2025 17:15:50 +0000 Subject: [PATCH] Initial commit --- .editorconfig | 18 + .env.example | 65 + .gitattributes | 10 + .github/workflows/lint.yml | 46 + .github/workflows/tests.yml | 54 + .gitignore | 23 + PERMISSIONS.md | 292 + THEME_STANDARD.md | 49 + app/Console/Commands/AssignPermissions.php | 79 + app/Console/Commands/AssignRoleCommand.php | 54 + app/Console/Commands/GeneratePartHistory.php | 128 + app/Console/Commands/TestUserPermissions.php | 49 + .../Auth/VerifyEmailController.php | 30 + app/Http/Controllers/Controller.php | 8 + app/Http/Controllers/CustomerController.php | 71 + app/Http/Controllers/InventoryController.php | 13 + .../Controllers/ServiceOrderController.php | 76 + app/Http/Controllers/SettingsController.php | 288 + app/Http/Controllers/VehicleController.php | 71 + app/Http/Middleware/PermissionMiddleware.php | 36 + app/Http/Middleware/RoleMiddleware.php | 35 + app/Livewire/Actions/Logout.php | 22 + app/Livewire/Appointments/Calendar.php | 282 + app/Livewire/Appointments/Create.php | 229 + app/Livewire/Appointments/Form.php | 204 + app/Livewire/Appointments/Index.php | 237 + app/Livewire/Appointments/TimeSlots.php | 284 + app/Livewire/CustomerPortal/EstimateView.php | 73 + app/Livewire/CustomerPortal/JobStatus.php | 44 + app/Livewire/Customers/Create.php | 75 + app/Livewire/Customers/Edit.php | 102 + app/Livewire/Customers/Index.php | 83 + app/Livewire/Customers/Show.php | 21 + app/Livewire/Dashboard/DailySchedule.php | 33 + app/Livewire/Dashboard/Overview.php | 65 + app/Livewire/Dashboard/PerformanceMetrics.php | 75 + app/Livewire/Dashboard/WorkflowOverview.php | 130 + app/Livewire/Diagnosis/Create.php | 707 ++ app/Livewire/Diagnosis/Edit.php | 13 + app/Livewire/Diagnosis/Index.php | 15 + app/Livewire/Diagnosis/Show.php | 31 + app/Livewire/Estimates/Create.php | 193 + app/Livewire/Estimates/Edit.php | 13 + app/Livewire/Estimates/Index.php | 48 + app/Livewire/Estimates/PDF.php | 13 + app/Livewire/Estimates/Show.php | 13 + app/Livewire/GlobalSearch.php | 138 + app/Livewire/Inspections/Create.php | 135 + app/Livewire/Inspections/Edit.php | 13 + app/Livewire/Inspections/Index.php | 49 + app/Livewire/Inspections/Show.php | 13 + app/Livewire/Inventory/Dashboard.php | 64 + app/Livewire/Inventory/Parts/Create.php | 104 + app/Livewire/Inventory/Parts/Edit.php | 134 + app/Livewire/Inventory/Parts/History.php | 86 + app/Livewire/Inventory/Parts/Index.php | 134 + app/Livewire/Inventory/Parts/Show.php | 56 + .../Inventory/PurchaseOrders/Create.php | 172 + .../Inventory/PurchaseOrders/Edit.php | 157 + .../Inventory/PurchaseOrders/Index.php | 93 + .../Inventory/PurchaseOrders/Show.php | 116 + .../Inventory/StockMovements/Create.php | 68 + .../Inventory/StockMovements/Index.php | 93 + app/Livewire/Inventory/Suppliers/Create.php | 66 + app/Livewire/Inventory/Suppliers/Edit.php | 88 + app/Livewire/Inventory/Suppliers/Index.php | 79 + app/Livewire/JobCards/Create.php | 184 + app/Livewire/JobCards/Edit.php | 185 + app/Livewire/JobCards/Index.php | 118 + app/Livewire/JobCards/Show.php | 31 + app/Livewire/JobCards/WorkflowStatus.php | 37 + app/Livewire/Reports/Dashboard.php | 190 + app/Livewire/ServiceItems/Manage.php | 169 + app/Livewire/ServiceOrders/Create.php | 312 + app/Livewire/ServiceOrders/Edit.php | 244 + app/Livewire/ServiceOrders/Index.php | 163 + app/Livewire/ServiceOrders/Invoice.php | 27 + app/Livewire/ServiceOrders/Show.php | 46 + app/Livewire/TechnicianManagement/Index.php | 108 + .../PerformanceTracking.php | 251 + .../TechnicianManagement/SkillsManagement.php | 146 + .../TechnicianManagement/TechnicianForm.php | 183 + .../WorkloadManagement.php | 256 + app/Livewire/Timesheets/Create.php | 108 + app/Livewire/Timesheets/Edit.php | 13 + app/Livewire/Timesheets/Index.php | 56 + app/Livewire/Timesheets/Show.php | 13 + app/Livewire/UserManagement.php | 0 app/Livewire/Users/Create.php | 359 + app/Livewire/Users/Edit.php | 467 + app/Livewire/Users/Index.php | 376 + app/Livewire/Users/ManageRolesPermissions.php | 393 + app/Livewire/Users/Show.php | 506 + app/Livewire/Vehicles/Create.php | 181 + app/Livewire/Vehicles/Edit.php | 211 + app/Livewire/Vehicles/Index.php | 114 + app/Livewire/Vehicles/Show.php | 26 + app/Livewire/WorkOrders/Create.php | 150 + app/Livewire/WorkOrders/Edit.php | 13 + app/Livewire/WorkOrders/Index.php | 54 + app/Livewire/WorkOrders/Show.php | 13 + app/Models/Appointment.php | 214 + app/Models/Branch.php | 44 + app/Models/Customer.php | 57 + app/Models/Diagnosis.php | 68 + app/Models/Estimate.php | 115 + app/Models/EstimateLineItem.php | 48 + app/Models/JobCard.php | 118 + app/Models/Part.php | 142 + app/Models/PartHistory.php | 127 + app/Models/Permission.php | 59 + app/Models/PurchaseOrder.php | 94 + app/Models/PurchaseOrderItem.php | 54 + app/Models/Report.php | 284 + app/Models/Role.php | 99 + app/Models/ServiceItem.php | 38 + app/Models/ServiceOrder.php | 114 + app/Models/ServiceOrderPart.php | 12 + app/Models/StockMovement.php | 129 + app/Models/Supplier.php | 62 + app/Models/Technician.php | 144 + app/Models/TechnicianPerformance.php | 71 + app/Models/TechnicianSkill.php | 131 + app/Models/TechnicianWorkload.php | 83 + app/Models/Timesheet.php | 96 + app/Models/User.php | 135 + app/Models/Vehicle.php | 65 + app/Models/VehicleInspection.php | 93 + app/Models/WorkOrder.php | 107 + app/Models/WorkOrderTask.php | 47 + app/Notifications/EstimateNotification.php | 55 + .../WorkflowStatusNotification.php | 103 + app/Policies/JobCardPolicy.php | 160 + app/Providers/AppServiceProvider.php | 24 + app/Providers/AuthServiceProvider.php | 76 + app/Providers/BladeServiceProvider.php | 60 + app/Providers/VoltServiceProvider.php | 28 + .../AppointmentNotificationService.php | 370 + app/Services/NhtsaVehicleService.php | 0 app/Services/NotificationService.php | 160 + app/Services/VinDecoderService.php | 216 + app/Services/WorkflowService.php | 298 + app/Settings/GeneralSettings.php | 42 + app/Settings/InventorySettings.php | 61 + app/Settings/NotificationSettings.php | 43 + app/Settings/SecuritySettings.php | 43 + app/Settings/ServiceSettings.php | 42 + app/Traits/HasRolesAndPermissions.php | 249 + app/Traits/LogsPartHistory.php | 110 + .../Components/Flux/Icon/CalendarPlus.php | 26 + app/View/Components/PermissionCheck.php | 60 + artisan | 18 + bootstrap/app.php | 21 + bootstrap/cache/.gitignore | 2 + bootstrap/providers.php | 8 + composer.json | 82 + composer.lock | 8820 +++++++++++++++++ config/app.php | 126 + config/auth.php | 115 + config/cache.php | 108 + config/database.php | 174 + config/filesystems.php | 80 + config/logging.php | 132 + config/mail.php | 116 + config/queue.php | 112 + config/services.php | 38 + config/session.php | 217 + config/settings.php | 98 + database/.gitignore | 1 + database/factories/AppointmentFactory.php | 23 + database/factories/CustomerFactory.php | 34 + database/factories/PartFactory.php | 23 + database/factories/ReportFactory.php | 138 + database/factories/ServiceItemFactory.php | 23 + database/factories/ServiceOrderFactory.php | 23 + .../factories/ServiceOrderPartFactory.php | 23 + database/factories/TechnicianFactory.php | 43 + database/factories/UserFactory.php | 44 + database/factories/VehicleFactory.php | 48 + .../factories/VehicleInspectionFactory.php | 23 + .../0001_01_01_000000_create_users_table.php | 49 + .../0001_01_01_000001_create_cache_table.php | 35 + .../0001_01_01_000002_create_jobs_table.php | 57 + ...022_12_14_083707_create_settings_table.php | 24 + ...25_07_21_092201_create_customers_table.php | 39 + ...025_07_21_092218_create_vehicles_table.php | 40 + ..._07_21_092230_create_technicians_table.php | 39 + ..._21_092241_create_service_orders_table.php | 47 + ...7_21_092255_create_service_items_table.php | 37 + .../2025_07_21_092302_create_parts_table.php | 42 + ...92312_create_service_order_parts_table.php | 36 + ...07_21_092328_create_appointments_table.php | 40 + ...92337_create_vehicle_inspections_table.php | 40 + ...33_add_vehicle_image_to_vehicles_table.php | 28 + ...25_07_21_173209_create_suppliers_table.php | 47 + ...21_173243_create_purchase_orders_table.php | 44 + ...3253_create_purchase_order_items_table.php | 36 + ...21_173315_create_stock_movements_table.php | 42 + ...73331_update_parts_table_for_inventory.php | 52 + ..._21_185721_create_part_histories_table.php | 47 + ..._200911_create_technician_skills_table.php | 39 + ...8_create_technician_performances_table.php | 38 + ...1149_create_technician_workloads_table.php | 42 + ...2025_07_22_101906_create_reports_table.php | 36 + ...25_07_22_114738_create_job_cards_table.php | 52 + ...25_07_22_115022_create_diagnoses_table.php | 50 + ...25_07_22_115125_create_estimates_table.php | 67 + ...15126_create_estimate_line_items_table.php | 50 + ..._07_22_115127_create_work_orders_table.php | 64 + ...2_115128_create_work_order_tasks_table.php | 55 + ...5_07_22_115129_create_timesheets_table.php | 64 + ...2_120746_create_work_order_parts_table.php | 37 + ...810_add_workflow_fields_to_users_table.php | 54 + ...ow_fields_to_vehicle_inspections_table.php | 49 + ...26_create_roles_and_permissions_tables.php | 85 + ..._additional_user_fields_to_users_table.php | 52 + ...0001_update_status_enum_in_users_table.php | 30 + ...07_30_094950_create_activity_log_table.php | 27 + ...add_event_column_to_activity_log_table.php | 22 + ...atch_uuid_column_to_activity_log_table.php | 22 + ...025_07_30_095042_create_branches_table.php | 39 + ...add_password_changed_at_to_users_table.php | 28 + database/seeders/AppointmentSeeder.php | 137 + database/seeders/BranchSeeder.php | 68 + database/seeders/CustomerSeeder.php | 17 + database/seeders/DatabaseSeeder.php | 85 + database/seeders/PartSeeder.php | 17 + database/seeders/ReportSeeder.php | 66 + .../seeders/RolesAndPermissionsSeeder.php | 386 + database/seeders/ServiceItemSeeder.php | 17 + database/seeders/ServiceOrderPartSeeder.php | 17 + database/seeders/ServiceOrderSeeder.php | 17 + database/seeders/TechnicianSeeder.php | 17 + database/seeders/VehicleInspectionSeeder.php | 17 + database/seeders/VehicleSeeder.php | 17 + ...5_07_29_090420_create_general_settings.php | 48 + ...5_07_29_090444_create_general_settings.php | 11 + ...5_07_29_090755_create_service_settings.php | 60 + ...07_29_090830_create_inventory_settings.php | 59 + ...29_090850_create_notification_settings.php | 51 + ..._07_29_090902_create_security_settings.php | 41 + package-lock.json | 2194 ++++ package.json | 22 + phpunit.xml | 33 + public/.htaccess | 25 + public/apple-touch-icon.png | Bin 0 -> 1662 bytes public/favicon.ico | Bin 0 -> 4286 bytes public/favicon.svg | 3 + public/images/logo-safe.png | Bin 0 -> 500910 bytes public/index.php | 20 + public/robots.txt | 2 + resources/css/app.css | 66 + resources/js/app.js | 0 resources/views/appointments/index.blade.php | 3 + .../views/components/action-message.blade.php | 14 + .../views/components/app-logo-icon.blade.php | 1 + resources/views/components/app-logo.blade.php | 6 + .../views/components/auth-header.blade.php | 9 + .../components/auth-session-status.blade.php | 9 + .../views/components/flux/card.blade.php | 3 + .../views/components/layouts/app.blade.php | 5 + .../components/layouts/app/header.blade.php | 106 + .../layouts/app/sidebar-new.blade.php | 157 + .../layouts/app/sidebar-old.blade.php | 335 + .../components/layouts/app/sidebar.blade.php | 479 + .../views/components/layouts/auth.blade.php | 13 + .../components/layouts/auth/card.blade.php | 26 + .../components/layouts/auth/simple.blade.php | 22 + .../components/layouts/auth/split.blade.php | 43 + .../components/permission-check.blade.php | 1 + .../components/placeholder-pattern.blade.php | 12 + .../components/settings-navigation.blade.php | 47 + .../components/settings/layout.blade.php | 19 + resources/views/customers/create.blade.php | 3 + resources/views/customers/edit.blade.php | 3 + resources/views/customers/index.blade.php | 3 + resources/views/customers/show.blade.php | 3 + resources/views/dashboard.blade.php | 51 + resources/views/flux/accent.blade.php | 36 + resources/views/flux/aside.blade.php | 19 + resources/views/flux/avatar/group.blade.php | 12 + resources/views/flux/avatar/index.blade.php | 187 + resources/views/flux/badge/close.blade.php | 23 + resources/views/flux/badge/index.blade.php | 96 + resources/views/flux/brand.blade.php | 52 + .../views/flux/breadcrumbs/index.blade.php | 4 + .../views/flux/breadcrumbs/item.blade.php | 67 + resources/views/flux/button/group.blade.php | 45 + resources/views/flux/button/index.blade.php | 198 + resources/views/flux/checkbox/all.blade.php | 2 + .../views/flux/checkbox/group/index.blade.php | 5 + .../checkbox/group/variants/default.blade.php | 22 + resources/views/flux/checkbox/index.blade.php | 14 + .../views/flux/checkbox/indicator.blade.php | 31 + .../flux/checkbox/variants/default.blade.php | 23 + resources/views/flux/container.blade.php | 9 + resources/views/flux/description.blade.php | 4 + resources/views/flux/dropdown.blade.php | 19 + resources/views/flux/error.blade.php | 24 + resources/views/flux/field.blade.php | 40 + resources/views/flux/fieldset.blade.php | 38 + resources/views/flux/footer.blade.php | 11 + resources/views/flux/header.blade.php | 28 + resources/views/flux/heading.blade.php | 40 + .../views/flux/icon/academic-cap.blade.php | 48 + .../icon/adjustments-horizontal.blade.php | 45 + .../flux/icon/adjustments-vertical.blade.php | 45 + .../icon/archive-box-arrow-down.blade.php | 47 + .../flux/icon/archive-box-x-mark.blade.php | 48 + .../views/flux/icon/archive-box.blade.php | 48 + .../flux/icon/arrow-down-circle.blade.php | 45 + .../views/flux/icon/arrow-down-left.blade.php | 45 + .../icon/arrow-down-on-square-stack.blade.php | 47 + .../flux/icon/arrow-down-on-square.blade.php | 45 + .../flux/icon/arrow-down-right.blade.php | 45 + .../views/flux/icon/arrow-down-tray.blade.php | 47 + .../views/flux/icon/arrow-down.blade.php | 45 + .../flux/icon/arrow-left-circle.blade.php | 45 + .../arrow-left-end-on-rectangle.blade.php | 47 + .../arrow-left-start-on-rectangle.blade.php | 46 + .../views/flux/icon/arrow-left.blade.php | 45 + .../views/flux/icon/arrow-long-down.blade.php | 45 + .../views/flux/icon/arrow-long-left.blade.php | 45 + .../flux/icon/arrow-long-right.blade.php | 45 + .../views/flux/icon/arrow-long-up.blade.php | 45 + .../icon/arrow-path-rounded-square.blade.php | 45 + .../views/flux/icon/arrow-path.blade.php | 45 + .../flux/icon/arrow-right-circle.blade.php | 45 + .../arrow-right-end-on-rectangle.blade.php | 47 + .../arrow-right-start-on-rectangle.blade.php | 46 + .../views/flux/icon/arrow-right.blade.php | 45 + .../icon/arrow-top-right-on-square.blade.php | 47 + .../flux/icon/arrow-trending-down.blade.php | 45 + .../flux/icon/arrow-trending-up.blade.php | 45 + .../flux/icon/arrow-turn-down-left.blade.php | 45 + .../flux/icon/arrow-turn-down-right.blade.php | 45 + .../flux/icon/arrow-turn-left-down.blade.php | 45 + .../flux/icon/arrow-turn-left-up.blade.php | 45 + .../flux/icon/arrow-turn-right-down.blade.php | 45 + .../flux/icon/arrow-turn-right-up.blade.php | 45 + .../flux/icon/arrow-turn-up-left.blade.php | 45 + .../flux/icon/arrow-turn-up-right.blade.php | 45 + .../views/flux/icon/arrow-up-circle.blade.php | 45 + .../views/flux/icon/arrow-up-left.blade.php | 45 + .../icon/arrow-up-on-square-stack.blade.php | 47 + .../flux/icon/arrow-up-on-square.blade.php | 45 + .../views/flux/icon/arrow-up-right.blade.php | 45 + .../views/flux/icon/arrow-up-tray.blade.php | 47 + resources/views/flux/icon/arrow-up.blade.php | 45 + .../flux/icon/arrow-uturn-down.blade.php | 45 + .../flux/icon/arrow-uturn-left.blade.php | 45 + .../flux/icon/arrow-uturn-right.blade.php | 45 + .../views/flux/icon/arrow-uturn-up.blade.php | 45 + .../flux/icon/arrows-pointing-in.blade.php | 45 + .../flux/icon/arrows-pointing-out.blade.php | 45 + .../flux/icon/arrows-right-left.blade.php | 45 + .../views/flux/icon/arrows-up-down.blade.php | 45 + resources/views/flux/icon/at-symbol.blade.php | 45 + resources/views/flux/icon/backspace.blade.php | 45 + resources/views/flux/icon/backward.blade.php | 45 + resources/views/flux/icon/banknotes.blade.php | 48 + resources/views/flux/icon/bars-2.blade.php | 45 + .../flux/icon/bars-3-bottom-left.blade.php | 45 + .../flux/icon/bars-3-bottom-right.blade.php | 45 + .../flux/icon/bars-3-center-left.blade.php | 45 + resources/views/flux/icon/bars-3.blade.php | 45 + resources/views/flux/icon/bars-4.blade.php | 45 + .../views/flux/icon/bars-arrow-down.blade.php | 45 + .../views/flux/icon/bars-arrow-up.blade.php | 45 + resources/views/flux/icon/battery-0.blade.php | 45 + .../views/flux/icon/battery-100.blade.php | 47 + .../views/flux/icon/battery-50.blade.php | 48 + resources/views/flux/icon/beaker.blade.php | 45 + .../views/flux/icon/bell-alert.blade.php | 48 + .../views/flux/icon/bell-slash.blade.php | 47 + .../views/flux/icon/bell-snooze.blade.php | 45 + resources/views/flux/icon/bell.blade.php | 45 + resources/views/flux/icon/bold.blade.php | 45 + .../views/flux/icon/bolt-slash.blade.php | 46 + resources/views/flux/icon/bolt.blade.php | 45 + .../views/flux/icon/book-open-text.blade.php | 47 + resources/views/flux/icon/book-open.blade.php | 45 + .../views/flux/icon/bookmark-slash.blade.php | 45 + .../views/flux/icon/bookmark-square.blade.php | 45 + resources/views/flux/icon/bookmark.blade.php | 45 + resources/views/flux/icon/briefcase.blade.php | 48 + resources/views/flux/icon/bug-ant.blade.php | 45 + .../flux/icon/building-library.blade.php | 47 + .../flux/icon/building-office-2.blade.php | 45 + .../views/flux/icon/building-office.blade.php | 45 + .../flux/icon/building-storefront.blade.php | 46 + resources/views/flux/icon/cake.blade.php | 45 + .../views/flux/icon/calculator.blade.php | 45 + .../flux/icon/calendar-date-range.blade.php | 48 + .../views/flux/icon/calendar-days.blade.php | 48 + resources/views/flux/icon/calendar.blade.php | 45 + resources/views/flux/icon/camera.blade.php | 48 + .../flux/icon/chart-bar-square.blade.php | 45 + resources/views/flux/icon/chart-bar.blade.php | 45 + resources/views/flux/icon/chart-pie.blade.php | 49 + .../chat-bubble-bottom-center-text.blade.php | 45 + .../icon/chat-bubble-bottom-center.blade.php | 45 + .../icon/chat-bubble-left-ellipsis.blade.php | 45 + .../icon/chat-bubble-left-right.blade.php | 48 + .../flux/icon/chat-bubble-left.blade.php | 45 + .../chat-bubble-oval-left-ellipsis.blade.php | 45 + .../flux/icon/chat-bubble-oval-left.blade.php | 45 + .../views/flux/icon/check-badge.blade.php | 45 + .../views/flux/icon/check-circle.blade.php | 45 + resources/views/flux/icon/check.blade.php | 45 + .../flux/icon/chevron-double-down.blade.php | 46 + .../flux/icon/chevron-double-left.blade.php | 46 + .../flux/icon/chevron-double-right.blade.php | 46 + .../flux/icon/chevron-double-up.blade.php | 47 + .../views/flux/icon/chevron-down.blade.php | 45 + .../views/flux/icon/chevron-left.blade.php | 45 + .../views/flux/icon/chevron-right.blade.php | 45 + .../views/flux/icon/chevron-up-down.blade.php | 45 + .../views/flux/icon/chevron-up.blade.php | 45 + .../flux/icon/chevrons-up-down.blade.php | 43 + .../views/flux/icon/circle-stack.blade.php | 50 + .../icon/clipboard-document-check.blade.php | 48 + .../icon/clipboard-document-list.blade.php | 48 + .../flux/icon/clipboard-document.blade.php | 49 + resources/views/flux/icon/clipboard.blade.php | 45 + resources/views/flux/icon/clock.blade.php | 45 + .../flux/icon/cloud-arrow-down.blade.php | 45 + .../views/flux/icon/cloud-arrow-up.blade.php | 45 + resources/views/flux/icon/cloud.blade.php | 45 + .../flux/icon/code-bracket-square.blade.php | 45 + .../views/flux/icon/code-bracket.blade.php | 45 + .../views/flux/icon/cog-6-tooth.blade.php | 46 + .../views/flux/icon/cog-8-tooth.blade.php | 46 + resources/views/flux/icon/cog.blade.php | 47 + .../views/flux/icon/command-line.blade.php | 45 + .../flux/icon/computer-desktop.blade.php | 45 + resources/views/flux/icon/cpu-chip.blade.php | 48 + .../views/flux/icon/credit-card.blade.php | 47 + .../flux/icon/cube-transparent.blade.php | 45 + resources/views/flux/icon/cube.blade.php | 45 + .../flux/icon/currency-bangladeshi.blade.php | 45 + .../views/flux/icon/currency-dollar.blade.php | 48 + .../views/flux/icon/currency-euro.blade.php | 45 + .../views/flux/icon/currency-pound.blade.php | 45 + .../views/flux/icon/currency-rupee.blade.php | 45 + .../views/flux/icon/currency-yen.blade.php | 45 + .../flux/icon/cursor-arrow-rays.blade.php | 45 + .../flux/icon/cursor-arrow-ripple.blade.php | 49 + .../flux/icon/device-phone-mobile.blade.php | 48 + .../views/flux/icon/device-tablet.blade.php | 47 + resources/views/flux/icon/divide.blade.php | 46 + .../flux/icon/document-arrow-down.blade.php | 46 + .../flux/icon/document-arrow-up.blade.php | 46 + .../flux/icon/document-chart-bar.blade.php | 46 + .../views/flux/icon/document-check.blade.php | 46 + .../document-currency-bangladeshi.blade.php | 45 + .../icon/document-currency-dollar.blade.php | 46 + .../icon/document-currency-euro.blade.php | 46 + .../icon/document-currency-pound.blade.php | 45 + .../icon/document-currency-rupee.blade.php | 45 + .../flux/icon/document-currency-yen.blade.php | 45 + .../flux/icon/document-duplicate.blade.php | 48 + .../icon/document-magnifying-glass.blade.php | 49 + .../views/flux/icon/document-minus.blade.php | 46 + .../views/flux/icon/document-plus.blade.php | 46 + .../views/flux/icon/document-text.blade.php | 46 + resources/views/flux/icon/document.blade.php | 46 + .../icon/ellipsis-horizontal-circle.blade.php | 45 + .../flux/icon/ellipsis-horizontal.blade.php | 45 + .../flux/icon/ellipsis-vertical.blade.php | 45 + .../views/flux/icon/envelope-open.blade.php | 46 + resources/views/flux/icon/envelope.blade.php | 48 + resources/views/flux/icon/equals.blade.php | 45 + .../flux/icon/exclamation-circle.blade.php | 45 + .../flux/icon/exclamation-triangle.blade.php | 45 + .../views/flux/icon/eye-dropper.blade.php | 45 + resources/views/flux/icon/eye-slash.blade.php | 49 + resources/views/flux/icon/eye.blade.php | 49 + .../views/flux/icon/face-frown.blade.php | 45 + .../views/flux/icon/face-smile.blade.php | 45 + resources/views/flux/icon/film.blade.php | 45 + .../views/flux/icon/finger-print.blade.php | 45 + resources/views/flux/icon/fire.blade.php | 46 + resources/views/flux/icon/flag.blade.php | 45 + .../flux/icon/folder-arrow-down.blade.php | 45 + .../views/flux/icon/folder-git-2.blade.php | 45 + .../views/flux/icon/folder-minus.blade.php | 45 + .../views/flux/icon/folder-open.blade.php | 45 + .../views/flux/icon/folder-plus.blade.php | 45 + resources/views/flux/icon/folder.blade.php | 45 + resources/views/flux/icon/forward.blade.php | 45 + resources/views/flux/icon/funnel.blade.php | 45 + resources/views/flux/icon/gif.blade.php | 45 + resources/views/flux/icon/gift-top.blade.php | 47 + resources/views/flux/icon/gift.blade.php | 47 + resources/views/flux/icon/globe-alt.blade.php | 45 + .../views/flux/icon/globe-americas.blade.php | 45 + .../flux/icon/globe-asia-australia.blade.php | 46 + .../flux/icon/globe-europe-africa.blade.php | 45 + resources/views/flux/icon/h1.blade.php | 45 + resources/views/flux/icon/h2.blade.php | 45 + resources/views/flux/icon/h3.blade.php | 45 + .../views/flux/icon/hand-raised.blade.php | 45 + .../views/flux/icon/hand-thumb-down.blade.php | 45 + .../views/flux/icon/hand-thumb-up.blade.php | 45 + resources/views/flux/icon/hashtag.blade.php | 45 + resources/views/flux/icon/heart.blade.php | 45 + .../views/flux/icon/home-modern.blade.php | 46 + resources/views/flux/icon/home.blade.php | 46 + .../views/flux/icon/identification.blade.php | 45 + .../flux/icon/inbox-arrow-down.blade.php | 48 + .../views/flux/icon/inbox-stack.blade.php | 48 + resources/views/flux/icon/inbox.blade.php | 45 + resources/views/flux/icon/index.blade.php | 12 + .../flux/icon/information-circle.blade.php | 45 + resources/views/flux/icon/italic.blade.php | 45 + resources/views/flux/icon/key.blade.php | 45 + resources/views/flux/icon/language.blade.php | 46 + .../views/flux/icon/layout-grid.blade.php | 45 + resources/views/flux/icon/lifebuoy.blade.php | 45 + .../views/flux/icon/light-bulb.blade.php | 46 + .../views/flux/icon/link-slash.blade.php | 45 + resources/views/flux/icon/link.blade.php | 47 + .../views/flux/icon/list-bullet.blade.php | 45 + resources/views/flux/icon/loading.blade.php | 45 + .../views/flux/icon/lock-closed.blade.php | 45 + resources/views/flux/icon/lock-open.blade.php | 45 + .../icon/magnifying-glass-circle.blade.php | 48 + .../icon/magnifying-glass-minus.blade.php | 47 + .../flux/icon/magnifying-glass-plus.blade.php | 47 + .../flux/icon/magnifying-glass.blade.php | 45 + resources/views/flux/icon/map-pin.blade.php | 46 + resources/views/flux/icon/map.blade.php | 45 + resources/views/flux/icon/megaphone.blade.php | 45 + .../views/flux/icon/microphone.blade.php | 48 + .../views/flux/icon/minus-circle.blade.php | 45 + resources/views/flux/icon/minus.blade.php | 45 + resources/views/flux/icon/moon.blade.php | 45 + .../views/flux/icon/musical-note.blade.php | 45 + resources/views/flux/icon/newspaper.blade.php | 48 + resources/views/flux/icon/no-symbol.blade.php | 45 + .../views/flux/icon/numbered-list.blade.php | 45 + .../views/flux/icon/paint-brush.blade.php | 45 + .../views/flux/icon/paper-airplane.blade.php | 45 + .../views/flux/icon/paper-clip.blade.php | 45 + .../views/flux/icon/pause-circle.blade.php | 45 + resources/views/flux/icon/pause.blade.php | 45 + .../views/flux/icon/pencil-square.blade.php | 48 + resources/views/flux/icon/pencil.blade.php | 45 + .../views/flux/icon/percent-badge.blade.php | 45 + .../flux/icon/phone-arrow-down-left.blade.php | 47 + .../flux/icon/phone-arrow-up-right.blade.php | 47 + .../views/flux/icon/phone-x-mark.blade.php | 46 + resources/views/flux/icon/phone.blade.php | 45 + resources/views/flux/icon/photo.blade.php | 45 + .../views/flux/icon/play-circle.blade.php | 46 + .../views/flux/icon/play-pause.blade.php | 45 + resources/views/flux/icon/play.blade.php | 45 + .../views/flux/icon/plus-circle.blade.php | 45 + resources/views/flux/icon/plus.blade.php | 45 + resources/views/flux/icon/power.blade.php | 45 + .../icon/presentation-chart-bar.blade.php | 45 + .../icon/presentation-chart-line.blade.php | 45 + resources/views/flux/icon/printer.blade.php | 45 + .../views/flux/icon/puzzle-piece.blade.php | 45 + resources/views/flux/icon/qr-code.blade.php | 52 + .../flux/icon/question-mark-circle.blade.php | 45 + .../views/flux/icon/queue-list.blade.php | 45 + resources/views/flux/icon/radio.blade.php | 45 + .../views/flux/icon/receipt-percent.blade.php | 45 + .../views/flux/icon/receipt-refund.blade.php | 45 + .../views/flux/icon/rectangle-group.blade.php | 45 + .../views/flux/icon/rectangle-stack.blade.php | 45 + .../views/flux/icon/rocket-launch.blade.php | 48 + resources/views/flux/icon/rss.blade.php | 46 + resources/views/flux/icon/scale.blade.php | 45 + resources/views/flux/icon/scissors.blade.php | 48 + .../views/flux/icon/server-stack.blade.php | 48 + resources/views/flux/icon/server.blade.php | 48 + resources/views/flux/icon/share.blade.php | 45 + .../views/flux/icon/shield-check.blade.php | 45 + .../flux/icon/shield-exclamation.blade.php | 45 + .../views/flux/icon/shopping-bag.blade.php | 45 + .../views/flux/icon/shopping-cart.blade.php | 45 + .../views/flux/icon/signal-slash.blade.php | 45 + resources/views/flux/icon/signal.blade.php | 48 + resources/views/flux/icon/slash.blade.php | 45 + resources/views/flux/icon/sparkles.blade.php | 45 + .../views/flux/icon/speaker-wave.blade.php | 48 + .../views/flux/icon/speaker-x-mark.blade.php | 45 + .../views/flux/icon/square-2-stack.blade.php | 48 + .../flux/icon/square-3-stack-3d.blade.php | 51 + .../views/flux/icon/squares-2x2.blade.php | 45 + .../views/flux/icon/squares-plus.blade.php | 45 + resources/views/flux/icon/star.blade.php | 45 + .../views/flux/icon/stop-circle.blade.php | 46 + resources/views/flux/icon/stop.blade.php | 45 + .../views/flux/icon/strikethrough.blade.php | 45 + resources/views/flux/icon/sun.blade.php | 45 + resources/views/flux/icon/swatch.blade.php | 47 + .../views/flux/icon/table-cells.blade.php | 45 + resources/views/flux/icon/tag.blade.php | 46 + resources/views/flux/icon/ticket.blade.php | 45 + resources/views/flux/icon/trash.blade.php | 45 + resources/views/flux/icon/trophy.blade.php | 45 + resources/views/flux/icon/truck.blade.php | 50 + resources/views/flux/icon/tv.blade.php | 48 + resources/views/flux/icon/underline.blade.php | 45 + .../views/flux/icon/user-circle.blade.php | 45 + .../views/flux/icon/user-group.blade.php | 46 + .../views/flux/icon/user-minus.blade.php | 45 + resources/views/flux/icon/user-plus.blade.php | 45 + resources/views/flux/icon/user.blade.php | 45 + resources/views/flux/icon/users.blade.php | 45 + resources/views/flux/icon/variable.blade.php | 45 + .../flux/icon/video-camera-slash.blade.php | 45 + .../views/flux/icon/video-camera.blade.php | 45 + .../views/flux/icon/view-columns.blade.php | 45 + .../flux/icon/viewfinder-circle.blade.php | 45 + resources/views/flux/icon/wallet.blade.php | 45 + resources/views/flux/icon/wifi.blade.php | 45 + resources/views/flux/icon/window.blade.php | 45 + .../flux/icon/wrench-screwdriver.blade.php | 49 + resources/views/flux/icon/wrench.blade.php | 46 + resources/views/flux/icon/x-circle.blade.php | 45 + resources/views/flux/icon/x-mark.blade.php | 45 + .../views/flux/input/clearable.blade.php | 20 + resources/views/flux/input/copyable.blade.php | 20 + .../views/flux/input/expandable.blade.php | 16 + resources/views/flux/input/file.blade.php | 68 + .../views/flux/input/group/affix.blade.php | 13 + .../views/flux/input/group/index.blade.php | 52 + .../views/flux/input/group/prefix.blade.php | 14 + .../views/flux/input/group/suffix.blade.php | 14 + resources/views/flux/input/index.blade.php | 225 + resources/views/flux/input/viewable.blade.php | 35 + resources/views/flux/label.blade.php | 32 + resources/views/flux/legend.blade.php | 4 + resources/views/flux/link.blade.php | 28 + resources/views/flux/main.blade.php | 15 + .../views/flux/menu/checkbox/group.blade.php | 4 + .../views/flux/menu/checkbox/index.blade.php | 60 + resources/views/flux/menu/group.blade.php | 22 + resources/views/flux/menu/heading.blade.php | 14 + resources/views/flux/menu/index.blade.php | 17 + resources/views/flux/menu/item.blade.php | 79 + .../views/flux/menu/radio/group.blade.php | 4 + .../views/flux/menu/radio/index.blade.php | 60 + resources/views/flux/menu/separator.blade.php | 4 + resources/views/flux/menu/submenu.blade.php | 38 + resources/views/flux/modal/close.blade.php | 4 + resources/views/flux/modal/index.blade.php | 94 + resources/views/flux/modal/trigger.blade.php | 16 + resources/views/flux/navbar/badge.blade.php | 30 + resources/views/flux/navbar/index.blade.php | 15 + resources/views/flux/navbar/item.blade.php | 81 + resources/views/flux/navlist/badge.blade.php | 30 + resources/views/flux/navlist/group.blade.php | 38 + resources/views/flux/navlist/index.blade.php | 14 + resources/views/flux/navlist/item.blade.php | 89 + resources/views/flux/navmenu/index.blade.php | 12 + resources/views/flux/navmenu/item.blade.php | 77 + .../views/flux/navmenu/separator.blade.php | 4 + resources/views/flux/profile.blade.php | 58 + .../views/flux/radio/group/index.blade.php | 5 + .../radio/group/variants/default.blade.php | 26 + .../radio/group/variants/segmented.blade.php | 28 + resources/views/flux/radio/index.blade.php | 14 + .../views/flux/radio/indicator.blade.php | 27 + .../flux/radio/variants/default.blade.php | 12 + .../flux/radio/variants/segmented.blade.php | 52 + resources/views/flux/select/index.blade.php | 7 + .../views/flux/select/option/index.blade.php | 14 + .../select/option/variants/default.blade.php | 9 + .../flux/select/variants/default.blade.php | 48 + resources/views/flux/separator.blade.php | 34 + .../views/flux/sidebar/backdrop.blade.php | 5 + resources/views/flux/sidebar/index.blade.php | 37 + resources/views/flux/sidebar/toggle.blade.php | 12 + resources/views/flux/spacer.blade.php | 1 + resources/views/flux/subheading.blade.php | 19 + resources/views/flux/switch.blade.php | 49 + resources/views/flux/text.blade.php | 42 + resources/views/flux/textarea.blade.php | 38 + .../views/flux/tooltip/content.blade.php | 21 + resources/views/flux/tooltip/index.blade.php | 37 + resources/views/layouts/app.blade.php | 57 + .../livewire/appointments/calendar.blade.php | 430 + .../livewire/appointments/create.blade.php | 185 + .../livewire/appointments/form.blade.php | 157 + .../livewire/appointments/index.blade.php | 471 + .../appointments/time-slots.blade.php | 242 + .../livewire/auth/confirm-password.blade.php | 58 + .../livewire/auth/forgot-password.blade.php | 49 + resources/views/livewire/auth/login.blade.php | 127 + .../views/livewire/auth/register.blade.php | 99 + .../livewire/auth/reset-password.blade.php | 115 + .../livewire/auth/verify-email.blade.php | 57 + .../customer-portal/estimate-view.blade.php | 3 + .../customer-portal/job-status.blade.php | 3 + .../views/livewire/customers/create.blade.php | 138 + .../views/livewire/customers/edit.blade.php | 157 + .../views/livewire/customers/index.blade.php | 181 + .../views/livewire/customers/show.blade.php | 224 + .../dashboard/daily-schedule.blade.php | 133 + .../livewire/dashboard/overview.blade.php | 151 + .../dashboard/performance-metrics.blade.php | 98 + .../workflow-overview-backup.blade.php | 90 + .../dashboard/workflow-overview.blade.php | 190 + .../views/livewire/diagnosis/create.blade.php | 897 ++ .../views/livewire/diagnosis/edit.blade.php | 3 + .../views/livewire/diagnosis/index.blade.php | 3 + .../views/livewire/diagnosis/show.blade.php | 3 + .../views/livewire/estimates/create.blade.php | 3 + .../views/livewire/estimates/edit.blade.php | 3 + .../views/livewire/estimates/index.blade.php | 134 + .../views/livewire/estimates/p-d-f.blade.php | 3 + .../views/livewire/estimates/show.blade.php | 3 + .../views/livewire/global-search.blade.php | 58 + .../livewire/inspections/create.blade.php | 3 + .../views/livewire/inspections/edit.blade.php | 3 + .../livewire/inspections/index.blade.php | 125 + .../views/livewire/inspections/show.blade.php | 3 + .../livewire/inventory/dashboard.blade.php | 308 + .../livewire/inventory/parts/create.blade.php | 242 + .../livewire/inventory/parts/edit.blade.php | 247 + .../inventory/parts/history.blade.php | 166 + .../livewire/inventory/parts/index.blade.php | 395 + .../livewire/inventory/parts/show.blade.php | 297 + .../purchase-orders/create.blade.php | 201 + .../inventory/purchase-orders/edit.blade.php | 192 + .../inventory/purchase-orders/index.blade.php | 291 + .../inventory/purchase-orders/show.blade.php | 226 + .../stock-movements/create.blade.php | 104 + .../inventory/stock-movements/index.blade.php | 252 + .../inventory/suppliers/create.blade.php | 147 + .../inventory/suppliers/edit.blade.php | 147 + .../inventory/suppliers/index.blade.php | 217 + .../views/livewire/job-cards/create.blade.php | 250 + .../views/livewire/job-cards/edit.blade.php | 300 + .../views/livewire/job-cards/index.blade.php | 197 + .../views/livewire/job-cards/show.blade.php | 506 + .../livewire/job-cards/workflow.blade.php | 328 + .../livewire/reports/dashboard.blade.php | 177 + .../partials/customer-analytics.blade.php | 154 + .../partials/performance-metrics.blade.php | 251 + .../partials/revenue-analysis.blade.php | 100 + .../reports/partials/service-trends.blade.php | 175 + .../livewire/service-items/manage.blade.php | 286 + .../livewire/service-orders/create.blade.php | 396 + .../livewire/service-orders/edit.blade.php | 266 + .../livewire/service-orders/index.blade.php | 281 + .../livewire/service-orders/invoice.blade.php | 218 + .../livewire/service-orders/show.blade.php | 443 + .../livewire/settings/appearance.blade.php | 19 + .../settings/delete-user-form.blade.php | 58 + .../livewire/settings/password.blade.php | 78 + .../views/livewire/settings/profile.blade.php | 114 + .../technician-management/index.blade.php | 270 + .../performance-tracking.blade.php | 276 + .../skills-management.blade.php | 229 + .../technician-form.blade.php | 161 + .../workload-management.blade.php | 345 + .../livewire/timesheets/create.blade.php | 3 + .../views/livewire/timesheets/edit.blade.php | 3 + .../views/livewire/timesheets/index.blade.php | 3 + .../views/livewire/timesheets/show.blade.php | 3 + .../views/livewire/user-management.blade.php | 0 .../views/livewire/users/create.blade.php | 318 + resources/views/livewire/users/edit.blade.php | 311 + .../views/livewire/users/index.blade.php | 360 + .../users/manage-roles-permissions.blade.php | 169 + resources/views/livewire/users/show.blade.php | 344 + .../views/livewire/vehicles/create.blade.php | 260 + .../views/livewire/vehicles/edit.blade.php | 277 + .../views/livewire/vehicles/index.blade.php | 223 + .../views/livewire/vehicles/show.blade.php | 288 + .../livewire/work-orders/create.blade.php | 3 + .../views/livewire/work-orders/edit.blade.php | 3 + .../livewire/work-orders/index.blade.php | 135 + .../views/livewire/work-orders/show.blade.php | 3 + resources/views/partials/head.blade.php | 14 + .../views/partials/settings-heading.blade.php | 5 + resources/views/partials/theme.blade.php | 14 + resources/views/reports.blade.php | 3 + resources/views/service-items/index.blade.php | 13 + .../views/service-orders/create.blade.php | 3 + resources/views/service-orders/edit.blade.php | 3 + .../views/service-orders/index.blade.php | 3 + .../views/service-orders/invoice.blade.php | 3 + resources/views/service-orders/show.blade.php | 3 + resources/views/settings/general.blade.php | 229 + resources/views/settings/inventory.blade.php | 334 + .../views/settings/inventory_fixed.blade.php | 320 + .../views/settings/notifications.blade.php | 283 + resources/views/settings/security.blade.php | 425 + resources/views/settings/service.blade.php | 268 + .../views/technician-management.blade.php | 49 + resources/views/technician-reports.blade.php | 61 + resources/views/technician-skills.blade.php | 46 + resources/views/technicians/index.blade.php | 139 + resources/views/vehicles/create.blade.php | 3 + resources/views/vehicles/edit.blade.php | 3 + resources/views/vehicles/index.blade.php | 3 + resources/views/vehicles/show.blade.php | 3 + resources/views/welcome.blade.php | 278 + routes/auth.php | 35 + routes/console.php | 8 + routes/test.php | 23 + routes/web.php | 212 + storage/app/.gitignore | 4 + storage/app/private/.gitignore | 2 + storage/app/public/.gitignore | 2 + storage/framework/.gitignore | 9 + storage/framework/cache/.gitignore | 3 + storage/framework/cache/data/.gitignore | 2 + storage/framework/sessions/.gitignore | 2 + storage/framework/testing/.gitignore | 2 + storage/framework/views/.gitignore | 2 + storage/logs/.gitignore | 2 + tests/Feature/Auth/AuthenticationTest.php | 61 + tests/Feature/Auth/EmailVerificationTest.php | 59 + .../Feature/Auth/PasswordConfirmationTest.php | 50 + tests/Feature/Auth/PasswordResetTest.php | 79 + tests/Feature/Auth/RegistrationTest.php | 35 + tests/Feature/DashboardTest.php | 27 + tests/Feature/ExampleTest.php | 18 + tests/Feature/Settings/PasswordUpdateTest.php | 50 + tests/Feature/Settings/ProfileUpdateTest.php | 89 + tests/TestCase.php | 10 + tests/Unit/ExampleTest.php | 16 + vite.config.js | 18 + 832 files changed, 72253 insertions(+) create mode 100644 .editorconfig create mode 100644 .env.example create mode 100644 .gitattributes create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/tests.yml create mode 100644 .gitignore create mode 100644 PERMISSIONS.md create mode 100644 THEME_STANDARD.md create mode 100644 app/Console/Commands/AssignPermissions.php create mode 100644 app/Console/Commands/AssignRoleCommand.php create mode 100644 app/Console/Commands/GeneratePartHistory.php create mode 100644 app/Console/Commands/TestUserPermissions.php create mode 100644 app/Http/Controllers/Auth/VerifyEmailController.php create mode 100644 app/Http/Controllers/Controller.php create mode 100644 app/Http/Controllers/CustomerController.php create mode 100644 app/Http/Controllers/InventoryController.php create mode 100644 app/Http/Controllers/ServiceOrderController.php create mode 100644 app/Http/Controllers/SettingsController.php create mode 100644 app/Http/Controllers/VehicleController.php create mode 100644 app/Http/Middleware/PermissionMiddleware.php create mode 100644 app/Http/Middleware/RoleMiddleware.php create mode 100644 app/Livewire/Actions/Logout.php create mode 100644 app/Livewire/Appointments/Calendar.php create mode 100644 app/Livewire/Appointments/Create.php create mode 100644 app/Livewire/Appointments/Form.php create mode 100644 app/Livewire/Appointments/Index.php create mode 100644 app/Livewire/Appointments/TimeSlots.php create mode 100644 app/Livewire/CustomerPortal/EstimateView.php create mode 100644 app/Livewire/CustomerPortal/JobStatus.php create mode 100644 app/Livewire/Customers/Create.php create mode 100644 app/Livewire/Customers/Edit.php create mode 100644 app/Livewire/Customers/Index.php create mode 100644 app/Livewire/Customers/Show.php create mode 100644 app/Livewire/Dashboard/DailySchedule.php create mode 100644 app/Livewire/Dashboard/Overview.php create mode 100644 app/Livewire/Dashboard/PerformanceMetrics.php create mode 100644 app/Livewire/Dashboard/WorkflowOverview.php create mode 100644 app/Livewire/Diagnosis/Create.php create mode 100644 app/Livewire/Diagnosis/Edit.php create mode 100644 app/Livewire/Diagnosis/Index.php create mode 100644 app/Livewire/Diagnosis/Show.php create mode 100644 app/Livewire/Estimates/Create.php create mode 100644 app/Livewire/Estimates/Edit.php create mode 100644 app/Livewire/Estimates/Index.php create mode 100644 app/Livewire/Estimates/PDF.php create mode 100644 app/Livewire/Estimates/Show.php create mode 100644 app/Livewire/GlobalSearch.php create mode 100644 app/Livewire/Inspections/Create.php create mode 100644 app/Livewire/Inspections/Edit.php create mode 100644 app/Livewire/Inspections/Index.php create mode 100644 app/Livewire/Inspections/Show.php create mode 100644 app/Livewire/Inventory/Dashboard.php create mode 100644 app/Livewire/Inventory/Parts/Create.php create mode 100644 app/Livewire/Inventory/Parts/Edit.php create mode 100644 app/Livewire/Inventory/Parts/History.php create mode 100644 app/Livewire/Inventory/Parts/Index.php create mode 100644 app/Livewire/Inventory/Parts/Show.php create mode 100644 app/Livewire/Inventory/PurchaseOrders/Create.php create mode 100644 app/Livewire/Inventory/PurchaseOrders/Edit.php create mode 100644 app/Livewire/Inventory/PurchaseOrders/Index.php create mode 100644 app/Livewire/Inventory/PurchaseOrders/Show.php create mode 100644 app/Livewire/Inventory/StockMovements/Create.php create mode 100644 app/Livewire/Inventory/StockMovements/Index.php create mode 100644 app/Livewire/Inventory/Suppliers/Create.php create mode 100644 app/Livewire/Inventory/Suppliers/Edit.php create mode 100644 app/Livewire/Inventory/Suppliers/Index.php create mode 100644 app/Livewire/JobCards/Create.php create mode 100644 app/Livewire/JobCards/Edit.php create mode 100644 app/Livewire/JobCards/Index.php create mode 100644 app/Livewire/JobCards/Show.php create mode 100644 app/Livewire/JobCards/WorkflowStatus.php create mode 100644 app/Livewire/Reports/Dashboard.php create mode 100644 app/Livewire/ServiceItems/Manage.php create mode 100644 app/Livewire/ServiceOrders/Create.php create mode 100644 app/Livewire/ServiceOrders/Edit.php create mode 100644 app/Livewire/ServiceOrders/Index.php create mode 100644 app/Livewire/ServiceOrders/Invoice.php create mode 100644 app/Livewire/ServiceOrders/Show.php create mode 100644 app/Livewire/TechnicianManagement/Index.php create mode 100644 app/Livewire/TechnicianManagement/PerformanceTracking.php create mode 100644 app/Livewire/TechnicianManagement/SkillsManagement.php create mode 100644 app/Livewire/TechnicianManagement/TechnicianForm.php create mode 100644 app/Livewire/TechnicianManagement/WorkloadManagement.php create mode 100644 app/Livewire/Timesheets/Create.php create mode 100644 app/Livewire/Timesheets/Edit.php create mode 100644 app/Livewire/Timesheets/Index.php create mode 100644 app/Livewire/Timesheets/Show.php create mode 100644 app/Livewire/UserManagement.php create mode 100644 app/Livewire/Users/Create.php create mode 100644 app/Livewire/Users/Edit.php create mode 100644 app/Livewire/Users/Index.php create mode 100644 app/Livewire/Users/ManageRolesPermissions.php create mode 100644 app/Livewire/Users/Show.php create mode 100644 app/Livewire/Vehicles/Create.php create mode 100644 app/Livewire/Vehicles/Edit.php create mode 100644 app/Livewire/Vehicles/Index.php create mode 100644 app/Livewire/Vehicles/Show.php create mode 100644 app/Livewire/WorkOrders/Create.php create mode 100644 app/Livewire/WorkOrders/Edit.php create mode 100644 app/Livewire/WorkOrders/Index.php create mode 100644 app/Livewire/WorkOrders/Show.php create mode 100644 app/Models/Appointment.php create mode 100644 app/Models/Branch.php create mode 100644 app/Models/Customer.php create mode 100644 app/Models/Diagnosis.php create mode 100644 app/Models/Estimate.php create mode 100644 app/Models/EstimateLineItem.php create mode 100644 app/Models/JobCard.php create mode 100644 app/Models/Part.php create mode 100644 app/Models/PartHistory.php create mode 100644 app/Models/Permission.php create mode 100644 app/Models/PurchaseOrder.php create mode 100644 app/Models/PurchaseOrderItem.php create mode 100644 app/Models/Report.php create mode 100644 app/Models/Role.php create mode 100644 app/Models/ServiceItem.php create mode 100644 app/Models/ServiceOrder.php create mode 100644 app/Models/ServiceOrderPart.php create mode 100644 app/Models/StockMovement.php create mode 100644 app/Models/Supplier.php create mode 100644 app/Models/Technician.php create mode 100644 app/Models/TechnicianPerformance.php create mode 100644 app/Models/TechnicianSkill.php create mode 100644 app/Models/TechnicianWorkload.php create mode 100644 app/Models/Timesheet.php create mode 100644 app/Models/User.php create mode 100644 app/Models/Vehicle.php create mode 100644 app/Models/VehicleInspection.php create mode 100644 app/Models/WorkOrder.php create mode 100644 app/Models/WorkOrderTask.php create mode 100644 app/Notifications/EstimateNotification.php create mode 100644 app/Notifications/WorkflowStatusNotification.php create mode 100644 app/Policies/JobCardPolicy.php create mode 100644 app/Providers/AppServiceProvider.php create mode 100644 app/Providers/AuthServiceProvider.php create mode 100644 app/Providers/BladeServiceProvider.php create mode 100644 app/Providers/VoltServiceProvider.php create mode 100644 app/Services/AppointmentNotificationService.php create mode 100644 app/Services/NhtsaVehicleService.php create mode 100644 app/Services/NotificationService.php create mode 100644 app/Services/VinDecoderService.php create mode 100644 app/Services/WorkflowService.php create mode 100644 app/Settings/GeneralSettings.php create mode 100644 app/Settings/InventorySettings.php create mode 100644 app/Settings/NotificationSettings.php create mode 100644 app/Settings/SecuritySettings.php create mode 100644 app/Settings/ServiceSettings.php create mode 100644 app/Traits/HasRolesAndPermissions.php create mode 100644 app/Traits/LogsPartHistory.php create mode 100644 app/View/Components/Flux/Icon/CalendarPlus.php create mode 100644 app/View/Components/PermissionCheck.php create mode 100755 artisan create mode 100644 bootstrap/app.php create mode 100644 bootstrap/cache/.gitignore create mode 100644 bootstrap/providers.php create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 config/app.php create mode 100644 config/auth.php create mode 100644 config/cache.php create mode 100644 config/database.php create mode 100644 config/filesystems.php create mode 100644 config/logging.php create mode 100644 config/mail.php create mode 100644 config/queue.php create mode 100644 config/services.php create mode 100644 config/session.php create mode 100644 config/settings.php create mode 100644 database/.gitignore create mode 100644 database/factories/AppointmentFactory.php create mode 100644 database/factories/CustomerFactory.php create mode 100644 database/factories/PartFactory.php create mode 100644 database/factories/ReportFactory.php create mode 100644 database/factories/ServiceItemFactory.php create mode 100644 database/factories/ServiceOrderFactory.php create mode 100644 database/factories/ServiceOrderPartFactory.php create mode 100644 database/factories/TechnicianFactory.php create mode 100644 database/factories/UserFactory.php create mode 100644 database/factories/VehicleFactory.php create mode 100644 database/factories/VehicleInspectionFactory.php create mode 100644 database/migrations/0001_01_01_000000_create_users_table.php create mode 100644 database/migrations/0001_01_01_000001_create_cache_table.php create mode 100644 database/migrations/0001_01_01_000002_create_jobs_table.php create mode 100644 database/migrations/2022_12_14_083707_create_settings_table.php create mode 100644 database/migrations/2025_07_21_092201_create_customers_table.php create mode 100644 database/migrations/2025_07_21_092218_create_vehicles_table.php create mode 100644 database/migrations/2025_07_21_092230_create_technicians_table.php create mode 100644 database/migrations/2025_07_21_092241_create_service_orders_table.php create mode 100644 database/migrations/2025_07_21_092255_create_service_items_table.php create mode 100644 database/migrations/2025_07_21_092302_create_parts_table.php create mode 100644 database/migrations/2025_07_21_092312_create_service_order_parts_table.php create mode 100644 database/migrations/2025_07_21_092328_create_appointments_table.php create mode 100644 database/migrations/2025_07_21_092337_create_vehicle_inspections_table.php create mode 100644 database/migrations/2025_07_21_113333_add_vehicle_image_to_vehicles_table.php create mode 100644 database/migrations/2025_07_21_173209_create_suppliers_table.php create mode 100644 database/migrations/2025_07_21_173243_create_purchase_orders_table.php create mode 100644 database/migrations/2025_07_21_173253_create_purchase_order_items_table.php create mode 100644 database/migrations/2025_07_21_173315_create_stock_movements_table.php create mode 100644 database/migrations/2025_07_21_173331_update_parts_table_for_inventory.php create mode 100644 database/migrations/2025_07_21_185721_create_part_histories_table.php create mode 100644 database/migrations/2025_07_21_200911_create_technician_skills_table.php create mode 100644 database/migrations/2025_07_21_200928_create_technician_performances_table.php create mode 100644 database/migrations/2025_07_21_201149_create_technician_workloads_table.php create mode 100644 database/migrations/2025_07_22_101906_create_reports_table.php create mode 100644 database/migrations/2025_07_22_114738_create_job_cards_table.php create mode 100644 database/migrations/2025_07_22_115022_create_diagnoses_table.php create mode 100644 database/migrations/2025_07_22_115125_create_estimates_table.php create mode 100644 database/migrations/2025_07_22_115126_create_estimate_line_items_table.php create mode 100644 database/migrations/2025_07_22_115127_create_work_orders_table.php create mode 100644 database/migrations/2025_07_22_115128_create_work_order_tasks_table.php create mode 100644 database/migrations/2025_07_22_115129_create_timesheets_table.php create mode 100644 database/migrations/2025_07_22_120746_create_work_order_parts_table.php create mode 100644 database/migrations/2025_07_22_120810_add_workflow_fields_to_users_table.php create mode 100644 database/migrations/2025_07_22_120843_add_workflow_fields_to_vehicle_inspections_table.php create mode 100644 database/migrations/2025_07_24_135226_create_roles_and_permissions_tables.php create mode 100644 database/migrations/2025_07_25_080805_add_additional_user_fields_to_users_table.php create mode 100644 database/migrations/2025_07_30_000001_update_status_enum_in_users_table.php create mode 100644 database/migrations/2025_07_30_094950_create_activity_log_table.php create mode 100644 database/migrations/2025_07_30_094951_add_event_column_to_activity_log_table.php create mode 100644 database/migrations/2025_07_30_094952_add_batch_uuid_column_to_activity_log_table.php create mode 100644 database/migrations/2025_07_30_095042_create_branches_table.php create mode 100644 database/migrations/2025_07_30_095441_add_password_changed_at_to_users_table.php create mode 100644 database/seeders/AppointmentSeeder.php create mode 100644 database/seeders/BranchSeeder.php create mode 100644 database/seeders/CustomerSeeder.php create mode 100644 database/seeders/DatabaseSeeder.php create mode 100644 database/seeders/PartSeeder.php create mode 100644 database/seeders/ReportSeeder.php create mode 100644 database/seeders/RolesAndPermissionsSeeder.php create mode 100644 database/seeders/ServiceItemSeeder.php create mode 100644 database/seeders/ServiceOrderPartSeeder.php create mode 100644 database/seeders/ServiceOrderSeeder.php create mode 100644 database/seeders/TechnicianSeeder.php create mode 100644 database/seeders/VehicleInspectionSeeder.php create mode 100644 database/seeders/VehicleSeeder.php create mode 100644 database/settings/2025_07_29_090420_create_general_settings.php create mode 100644 database/settings/2025_07_29_090444_create_general_settings.php create mode 100644 database/settings/2025_07_29_090755_create_service_settings.php create mode 100644 database/settings/2025_07_29_090830_create_inventory_settings.php create mode 100644 database/settings/2025_07_29_090850_create_notification_settings.php create mode 100644 database/settings/2025_07_29_090902_create_security_settings.php create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 phpunit.xml create mode 100644 public/.htaccess create mode 100644 public/apple-touch-icon.png create mode 100644 public/favicon.ico create mode 100644 public/favicon.svg create mode 100644 public/images/logo-safe.png create mode 100644 public/index.php create mode 100644 public/robots.txt create mode 100644 resources/css/app.css create mode 100644 resources/js/app.js create mode 100644 resources/views/appointments/index.blade.php create mode 100644 resources/views/components/action-message.blade.php create mode 100644 resources/views/components/app-logo-icon.blade.php create mode 100644 resources/views/components/app-logo.blade.php create mode 100644 resources/views/components/auth-header.blade.php create mode 100644 resources/views/components/auth-session-status.blade.php create mode 100644 resources/views/components/flux/card.blade.php create mode 100644 resources/views/components/layouts/app.blade.php create mode 100644 resources/views/components/layouts/app/header.blade.php create mode 100644 resources/views/components/layouts/app/sidebar-new.blade.php create mode 100644 resources/views/components/layouts/app/sidebar-old.blade.php create mode 100644 resources/views/components/layouts/app/sidebar.blade.php create mode 100644 resources/views/components/layouts/auth.blade.php create mode 100644 resources/views/components/layouts/auth/card.blade.php create mode 100644 resources/views/components/layouts/auth/simple.blade.php create mode 100644 resources/views/components/layouts/auth/split.blade.php create mode 100644 resources/views/components/permission-check.blade.php create mode 100644 resources/views/components/placeholder-pattern.blade.php create mode 100644 resources/views/components/settings-navigation.blade.php create mode 100644 resources/views/components/settings/layout.blade.php create mode 100644 resources/views/customers/create.blade.php create mode 100644 resources/views/customers/edit.blade.php create mode 100644 resources/views/customers/index.blade.php create mode 100644 resources/views/customers/show.blade.php create mode 100644 resources/views/dashboard.blade.php create mode 100644 resources/views/flux/accent.blade.php create mode 100644 resources/views/flux/aside.blade.php create mode 100644 resources/views/flux/avatar/group.blade.php create mode 100644 resources/views/flux/avatar/index.blade.php create mode 100644 resources/views/flux/badge/close.blade.php create mode 100644 resources/views/flux/badge/index.blade.php create mode 100644 resources/views/flux/brand.blade.php create mode 100644 resources/views/flux/breadcrumbs/index.blade.php create mode 100644 resources/views/flux/breadcrumbs/item.blade.php create mode 100644 resources/views/flux/button/group.blade.php create mode 100644 resources/views/flux/button/index.blade.php create mode 100644 resources/views/flux/checkbox/all.blade.php create mode 100644 resources/views/flux/checkbox/group/index.blade.php create mode 100644 resources/views/flux/checkbox/group/variants/default.blade.php create mode 100644 resources/views/flux/checkbox/index.blade.php create mode 100644 resources/views/flux/checkbox/indicator.blade.php create mode 100644 resources/views/flux/checkbox/variants/default.blade.php create mode 100644 resources/views/flux/container.blade.php create mode 100644 resources/views/flux/description.blade.php create mode 100644 resources/views/flux/dropdown.blade.php create mode 100644 resources/views/flux/error.blade.php create mode 100644 resources/views/flux/field.blade.php create mode 100644 resources/views/flux/fieldset.blade.php create mode 100644 resources/views/flux/footer.blade.php create mode 100644 resources/views/flux/header.blade.php create mode 100644 resources/views/flux/heading.blade.php create mode 100644 resources/views/flux/icon/academic-cap.blade.php create mode 100644 resources/views/flux/icon/adjustments-horizontal.blade.php create mode 100644 resources/views/flux/icon/adjustments-vertical.blade.php create mode 100644 resources/views/flux/icon/archive-box-arrow-down.blade.php create mode 100644 resources/views/flux/icon/archive-box-x-mark.blade.php create mode 100644 resources/views/flux/icon/archive-box.blade.php create mode 100644 resources/views/flux/icon/arrow-down-circle.blade.php create mode 100644 resources/views/flux/icon/arrow-down-left.blade.php create mode 100644 resources/views/flux/icon/arrow-down-on-square-stack.blade.php create mode 100644 resources/views/flux/icon/arrow-down-on-square.blade.php create mode 100644 resources/views/flux/icon/arrow-down-right.blade.php create mode 100644 resources/views/flux/icon/arrow-down-tray.blade.php create mode 100644 resources/views/flux/icon/arrow-down.blade.php create mode 100644 resources/views/flux/icon/arrow-left-circle.blade.php create mode 100644 resources/views/flux/icon/arrow-left-end-on-rectangle.blade.php create mode 100644 resources/views/flux/icon/arrow-left-start-on-rectangle.blade.php create mode 100644 resources/views/flux/icon/arrow-left.blade.php create mode 100644 resources/views/flux/icon/arrow-long-down.blade.php create mode 100644 resources/views/flux/icon/arrow-long-left.blade.php create mode 100644 resources/views/flux/icon/arrow-long-right.blade.php create mode 100644 resources/views/flux/icon/arrow-long-up.blade.php create mode 100644 resources/views/flux/icon/arrow-path-rounded-square.blade.php create mode 100644 resources/views/flux/icon/arrow-path.blade.php create mode 100644 resources/views/flux/icon/arrow-right-circle.blade.php create mode 100644 resources/views/flux/icon/arrow-right-end-on-rectangle.blade.php create mode 100644 resources/views/flux/icon/arrow-right-start-on-rectangle.blade.php create mode 100644 resources/views/flux/icon/arrow-right.blade.php create mode 100644 resources/views/flux/icon/arrow-top-right-on-square.blade.php create mode 100644 resources/views/flux/icon/arrow-trending-down.blade.php create mode 100644 resources/views/flux/icon/arrow-trending-up.blade.php create mode 100644 resources/views/flux/icon/arrow-turn-down-left.blade.php create mode 100644 resources/views/flux/icon/arrow-turn-down-right.blade.php create mode 100644 resources/views/flux/icon/arrow-turn-left-down.blade.php create mode 100644 resources/views/flux/icon/arrow-turn-left-up.blade.php create mode 100644 resources/views/flux/icon/arrow-turn-right-down.blade.php create mode 100644 resources/views/flux/icon/arrow-turn-right-up.blade.php create mode 100644 resources/views/flux/icon/arrow-turn-up-left.blade.php create mode 100644 resources/views/flux/icon/arrow-turn-up-right.blade.php create mode 100644 resources/views/flux/icon/arrow-up-circle.blade.php create mode 100644 resources/views/flux/icon/arrow-up-left.blade.php create mode 100644 resources/views/flux/icon/arrow-up-on-square-stack.blade.php create mode 100644 resources/views/flux/icon/arrow-up-on-square.blade.php create mode 100644 resources/views/flux/icon/arrow-up-right.blade.php create mode 100644 resources/views/flux/icon/arrow-up-tray.blade.php create mode 100644 resources/views/flux/icon/arrow-up.blade.php create mode 100644 resources/views/flux/icon/arrow-uturn-down.blade.php create mode 100644 resources/views/flux/icon/arrow-uturn-left.blade.php create mode 100644 resources/views/flux/icon/arrow-uturn-right.blade.php create mode 100644 resources/views/flux/icon/arrow-uturn-up.blade.php create mode 100644 resources/views/flux/icon/arrows-pointing-in.blade.php create mode 100644 resources/views/flux/icon/arrows-pointing-out.blade.php create mode 100644 resources/views/flux/icon/arrows-right-left.blade.php create mode 100644 resources/views/flux/icon/arrows-up-down.blade.php create mode 100644 resources/views/flux/icon/at-symbol.blade.php create mode 100644 resources/views/flux/icon/backspace.blade.php create mode 100644 resources/views/flux/icon/backward.blade.php create mode 100644 resources/views/flux/icon/banknotes.blade.php create mode 100644 resources/views/flux/icon/bars-2.blade.php create mode 100644 resources/views/flux/icon/bars-3-bottom-left.blade.php create mode 100644 resources/views/flux/icon/bars-3-bottom-right.blade.php create mode 100644 resources/views/flux/icon/bars-3-center-left.blade.php create mode 100644 resources/views/flux/icon/bars-3.blade.php create mode 100644 resources/views/flux/icon/bars-4.blade.php create mode 100644 resources/views/flux/icon/bars-arrow-down.blade.php create mode 100644 resources/views/flux/icon/bars-arrow-up.blade.php create mode 100644 resources/views/flux/icon/battery-0.blade.php create mode 100644 resources/views/flux/icon/battery-100.blade.php create mode 100644 resources/views/flux/icon/battery-50.blade.php create mode 100644 resources/views/flux/icon/beaker.blade.php create mode 100644 resources/views/flux/icon/bell-alert.blade.php create mode 100644 resources/views/flux/icon/bell-slash.blade.php create mode 100644 resources/views/flux/icon/bell-snooze.blade.php create mode 100644 resources/views/flux/icon/bell.blade.php create mode 100644 resources/views/flux/icon/bold.blade.php create mode 100644 resources/views/flux/icon/bolt-slash.blade.php create mode 100644 resources/views/flux/icon/bolt.blade.php create mode 100644 resources/views/flux/icon/book-open-text.blade.php create mode 100644 resources/views/flux/icon/book-open.blade.php create mode 100644 resources/views/flux/icon/bookmark-slash.blade.php create mode 100644 resources/views/flux/icon/bookmark-square.blade.php create mode 100644 resources/views/flux/icon/bookmark.blade.php create mode 100644 resources/views/flux/icon/briefcase.blade.php create mode 100644 resources/views/flux/icon/bug-ant.blade.php create mode 100644 resources/views/flux/icon/building-library.blade.php create mode 100644 resources/views/flux/icon/building-office-2.blade.php create mode 100644 resources/views/flux/icon/building-office.blade.php create mode 100644 resources/views/flux/icon/building-storefront.blade.php create mode 100644 resources/views/flux/icon/cake.blade.php create mode 100644 resources/views/flux/icon/calculator.blade.php create mode 100644 resources/views/flux/icon/calendar-date-range.blade.php create mode 100644 resources/views/flux/icon/calendar-days.blade.php create mode 100644 resources/views/flux/icon/calendar.blade.php create mode 100644 resources/views/flux/icon/camera.blade.php create mode 100644 resources/views/flux/icon/chart-bar-square.blade.php create mode 100644 resources/views/flux/icon/chart-bar.blade.php create mode 100644 resources/views/flux/icon/chart-pie.blade.php create mode 100644 resources/views/flux/icon/chat-bubble-bottom-center-text.blade.php create mode 100644 resources/views/flux/icon/chat-bubble-bottom-center.blade.php create mode 100644 resources/views/flux/icon/chat-bubble-left-ellipsis.blade.php create mode 100644 resources/views/flux/icon/chat-bubble-left-right.blade.php create mode 100644 resources/views/flux/icon/chat-bubble-left.blade.php create mode 100644 resources/views/flux/icon/chat-bubble-oval-left-ellipsis.blade.php create mode 100644 resources/views/flux/icon/chat-bubble-oval-left.blade.php create mode 100644 resources/views/flux/icon/check-badge.blade.php create mode 100644 resources/views/flux/icon/check-circle.blade.php create mode 100644 resources/views/flux/icon/check.blade.php create mode 100644 resources/views/flux/icon/chevron-double-down.blade.php create mode 100644 resources/views/flux/icon/chevron-double-left.blade.php create mode 100644 resources/views/flux/icon/chevron-double-right.blade.php create mode 100644 resources/views/flux/icon/chevron-double-up.blade.php create mode 100644 resources/views/flux/icon/chevron-down.blade.php create mode 100644 resources/views/flux/icon/chevron-left.blade.php create mode 100644 resources/views/flux/icon/chevron-right.blade.php create mode 100644 resources/views/flux/icon/chevron-up-down.blade.php create mode 100644 resources/views/flux/icon/chevron-up.blade.php create mode 100644 resources/views/flux/icon/chevrons-up-down.blade.php create mode 100644 resources/views/flux/icon/circle-stack.blade.php create mode 100644 resources/views/flux/icon/clipboard-document-check.blade.php create mode 100644 resources/views/flux/icon/clipboard-document-list.blade.php create mode 100644 resources/views/flux/icon/clipboard-document.blade.php create mode 100644 resources/views/flux/icon/clipboard.blade.php create mode 100644 resources/views/flux/icon/clock.blade.php create mode 100644 resources/views/flux/icon/cloud-arrow-down.blade.php create mode 100644 resources/views/flux/icon/cloud-arrow-up.blade.php create mode 100644 resources/views/flux/icon/cloud.blade.php create mode 100644 resources/views/flux/icon/code-bracket-square.blade.php create mode 100644 resources/views/flux/icon/code-bracket.blade.php create mode 100644 resources/views/flux/icon/cog-6-tooth.blade.php create mode 100644 resources/views/flux/icon/cog-8-tooth.blade.php create mode 100644 resources/views/flux/icon/cog.blade.php create mode 100644 resources/views/flux/icon/command-line.blade.php create mode 100644 resources/views/flux/icon/computer-desktop.blade.php create mode 100644 resources/views/flux/icon/cpu-chip.blade.php create mode 100644 resources/views/flux/icon/credit-card.blade.php create mode 100644 resources/views/flux/icon/cube-transparent.blade.php create mode 100644 resources/views/flux/icon/cube.blade.php create mode 100644 resources/views/flux/icon/currency-bangladeshi.blade.php create mode 100644 resources/views/flux/icon/currency-dollar.blade.php create mode 100644 resources/views/flux/icon/currency-euro.blade.php create mode 100644 resources/views/flux/icon/currency-pound.blade.php create mode 100644 resources/views/flux/icon/currency-rupee.blade.php create mode 100644 resources/views/flux/icon/currency-yen.blade.php create mode 100644 resources/views/flux/icon/cursor-arrow-rays.blade.php create mode 100644 resources/views/flux/icon/cursor-arrow-ripple.blade.php create mode 100644 resources/views/flux/icon/device-phone-mobile.blade.php create mode 100644 resources/views/flux/icon/device-tablet.blade.php create mode 100644 resources/views/flux/icon/divide.blade.php create mode 100644 resources/views/flux/icon/document-arrow-down.blade.php create mode 100644 resources/views/flux/icon/document-arrow-up.blade.php create mode 100644 resources/views/flux/icon/document-chart-bar.blade.php create mode 100644 resources/views/flux/icon/document-check.blade.php create mode 100644 resources/views/flux/icon/document-currency-bangladeshi.blade.php create mode 100644 resources/views/flux/icon/document-currency-dollar.blade.php create mode 100644 resources/views/flux/icon/document-currency-euro.blade.php create mode 100644 resources/views/flux/icon/document-currency-pound.blade.php create mode 100644 resources/views/flux/icon/document-currency-rupee.blade.php create mode 100644 resources/views/flux/icon/document-currency-yen.blade.php create mode 100644 resources/views/flux/icon/document-duplicate.blade.php create mode 100644 resources/views/flux/icon/document-magnifying-glass.blade.php create mode 100644 resources/views/flux/icon/document-minus.blade.php create mode 100644 resources/views/flux/icon/document-plus.blade.php create mode 100644 resources/views/flux/icon/document-text.blade.php create mode 100644 resources/views/flux/icon/document.blade.php create mode 100644 resources/views/flux/icon/ellipsis-horizontal-circle.blade.php create mode 100644 resources/views/flux/icon/ellipsis-horizontal.blade.php create mode 100644 resources/views/flux/icon/ellipsis-vertical.blade.php create mode 100644 resources/views/flux/icon/envelope-open.blade.php create mode 100644 resources/views/flux/icon/envelope.blade.php create mode 100644 resources/views/flux/icon/equals.blade.php create mode 100644 resources/views/flux/icon/exclamation-circle.blade.php create mode 100644 resources/views/flux/icon/exclamation-triangle.blade.php create mode 100644 resources/views/flux/icon/eye-dropper.blade.php create mode 100644 resources/views/flux/icon/eye-slash.blade.php create mode 100644 resources/views/flux/icon/eye.blade.php create mode 100644 resources/views/flux/icon/face-frown.blade.php create mode 100644 resources/views/flux/icon/face-smile.blade.php create mode 100644 resources/views/flux/icon/film.blade.php create mode 100644 resources/views/flux/icon/finger-print.blade.php create mode 100644 resources/views/flux/icon/fire.blade.php create mode 100644 resources/views/flux/icon/flag.blade.php create mode 100644 resources/views/flux/icon/folder-arrow-down.blade.php create mode 100644 resources/views/flux/icon/folder-git-2.blade.php create mode 100644 resources/views/flux/icon/folder-minus.blade.php create mode 100644 resources/views/flux/icon/folder-open.blade.php create mode 100644 resources/views/flux/icon/folder-plus.blade.php create mode 100644 resources/views/flux/icon/folder.blade.php create mode 100644 resources/views/flux/icon/forward.blade.php create mode 100644 resources/views/flux/icon/funnel.blade.php create mode 100644 resources/views/flux/icon/gif.blade.php create mode 100644 resources/views/flux/icon/gift-top.blade.php create mode 100644 resources/views/flux/icon/gift.blade.php create mode 100644 resources/views/flux/icon/globe-alt.blade.php create mode 100644 resources/views/flux/icon/globe-americas.blade.php create mode 100644 resources/views/flux/icon/globe-asia-australia.blade.php create mode 100644 resources/views/flux/icon/globe-europe-africa.blade.php create mode 100644 resources/views/flux/icon/h1.blade.php create mode 100644 resources/views/flux/icon/h2.blade.php create mode 100644 resources/views/flux/icon/h3.blade.php create mode 100644 resources/views/flux/icon/hand-raised.blade.php create mode 100644 resources/views/flux/icon/hand-thumb-down.blade.php create mode 100644 resources/views/flux/icon/hand-thumb-up.blade.php create mode 100644 resources/views/flux/icon/hashtag.blade.php create mode 100644 resources/views/flux/icon/heart.blade.php create mode 100644 resources/views/flux/icon/home-modern.blade.php create mode 100644 resources/views/flux/icon/home.blade.php create mode 100644 resources/views/flux/icon/identification.blade.php create mode 100644 resources/views/flux/icon/inbox-arrow-down.blade.php create mode 100644 resources/views/flux/icon/inbox-stack.blade.php create mode 100644 resources/views/flux/icon/inbox.blade.php create mode 100644 resources/views/flux/icon/index.blade.php create mode 100644 resources/views/flux/icon/information-circle.blade.php create mode 100644 resources/views/flux/icon/italic.blade.php create mode 100644 resources/views/flux/icon/key.blade.php create mode 100644 resources/views/flux/icon/language.blade.php create mode 100644 resources/views/flux/icon/layout-grid.blade.php create mode 100644 resources/views/flux/icon/lifebuoy.blade.php create mode 100644 resources/views/flux/icon/light-bulb.blade.php create mode 100644 resources/views/flux/icon/link-slash.blade.php create mode 100644 resources/views/flux/icon/link.blade.php create mode 100644 resources/views/flux/icon/list-bullet.blade.php create mode 100644 resources/views/flux/icon/loading.blade.php create mode 100644 resources/views/flux/icon/lock-closed.blade.php create mode 100644 resources/views/flux/icon/lock-open.blade.php create mode 100644 resources/views/flux/icon/magnifying-glass-circle.blade.php create mode 100644 resources/views/flux/icon/magnifying-glass-minus.blade.php create mode 100644 resources/views/flux/icon/magnifying-glass-plus.blade.php create mode 100644 resources/views/flux/icon/magnifying-glass.blade.php create mode 100644 resources/views/flux/icon/map-pin.blade.php create mode 100644 resources/views/flux/icon/map.blade.php create mode 100644 resources/views/flux/icon/megaphone.blade.php create mode 100644 resources/views/flux/icon/microphone.blade.php create mode 100644 resources/views/flux/icon/minus-circle.blade.php create mode 100644 resources/views/flux/icon/minus.blade.php create mode 100644 resources/views/flux/icon/moon.blade.php create mode 100644 resources/views/flux/icon/musical-note.blade.php create mode 100644 resources/views/flux/icon/newspaper.blade.php create mode 100644 resources/views/flux/icon/no-symbol.blade.php create mode 100644 resources/views/flux/icon/numbered-list.blade.php create mode 100644 resources/views/flux/icon/paint-brush.blade.php create mode 100644 resources/views/flux/icon/paper-airplane.blade.php create mode 100644 resources/views/flux/icon/paper-clip.blade.php create mode 100644 resources/views/flux/icon/pause-circle.blade.php create mode 100644 resources/views/flux/icon/pause.blade.php create mode 100644 resources/views/flux/icon/pencil-square.blade.php create mode 100644 resources/views/flux/icon/pencil.blade.php create mode 100644 resources/views/flux/icon/percent-badge.blade.php create mode 100644 resources/views/flux/icon/phone-arrow-down-left.blade.php create mode 100644 resources/views/flux/icon/phone-arrow-up-right.blade.php create mode 100644 resources/views/flux/icon/phone-x-mark.blade.php create mode 100644 resources/views/flux/icon/phone.blade.php create mode 100644 resources/views/flux/icon/photo.blade.php create mode 100644 resources/views/flux/icon/play-circle.blade.php create mode 100644 resources/views/flux/icon/play-pause.blade.php create mode 100644 resources/views/flux/icon/play.blade.php create mode 100644 resources/views/flux/icon/plus-circle.blade.php create mode 100644 resources/views/flux/icon/plus.blade.php create mode 100644 resources/views/flux/icon/power.blade.php create mode 100644 resources/views/flux/icon/presentation-chart-bar.blade.php create mode 100644 resources/views/flux/icon/presentation-chart-line.blade.php create mode 100644 resources/views/flux/icon/printer.blade.php create mode 100644 resources/views/flux/icon/puzzle-piece.blade.php create mode 100644 resources/views/flux/icon/qr-code.blade.php create mode 100644 resources/views/flux/icon/question-mark-circle.blade.php create mode 100644 resources/views/flux/icon/queue-list.blade.php create mode 100644 resources/views/flux/icon/radio.blade.php create mode 100644 resources/views/flux/icon/receipt-percent.blade.php create mode 100644 resources/views/flux/icon/receipt-refund.blade.php create mode 100644 resources/views/flux/icon/rectangle-group.blade.php create mode 100644 resources/views/flux/icon/rectangle-stack.blade.php create mode 100644 resources/views/flux/icon/rocket-launch.blade.php create mode 100644 resources/views/flux/icon/rss.blade.php create mode 100644 resources/views/flux/icon/scale.blade.php create mode 100644 resources/views/flux/icon/scissors.blade.php create mode 100644 resources/views/flux/icon/server-stack.blade.php create mode 100644 resources/views/flux/icon/server.blade.php create mode 100644 resources/views/flux/icon/share.blade.php create mode 100644 resources/views/flux/icon/shield-check.blade.php create mode 100644 resources/views/flux/icon/shield-exclamation.blade.php create mode 100644 resources/views/flux/icon/shopping-bag.blade.php create mode 100644 resources/views/flux/icon/shopping-cart.blade.php create mode 100644 resources/views/flux/icon/signal-slash.blade.php create mode 100644 resources/views/flux/icon/signal.blade.php create mode 100644 resources/views/flux/icon/slash.blade.php create mode 100644 resources/views/flux/icon/sparkles.blade.php create mode 100644 resources/views/flux/icon/speaker-wave.blade.php create mode 100644 resources/views/flux/icon/speaker-x-mark.blade.php create mode 100644 resources/views/flux/icon/square-2-stack.blade.php create mode 100644 resources/views/flux/icon/square-3-stack-3d.blade.php create mode 100644 resources/views/flux/icon/squares-2x2.blade.php create mode 100644 resources/views/flux/icon/squares-plus.blade.php create mode 100644 resources/views/flux/icon/star.blade.php create mode 100644 resources/views/flux/icon/stop-circle.blade.php create mode 100644 resources/views/flux/icon/stop.blade.php create mode 100644 resources/views/flux/icon/strikethrough.blade.php create mode 100644 resources/views/flux/icon/sun.blade.php create mode 100644 resources/views/flux/icon/swatch.blade.php create mode 100644 resources/views/flux/icon/table-cells.blade.php create mode 100644 resources/views/flux/icon/tag.blade.php create mode 100644 resources/views/flux/icon/ticket.blade.php create mode 100644 resources/views/flux/icon/trash.blade.php create mode 100644 resources/views/flux/icon/trophy.blade.php create mode 100644 resources/views/flux/icon/truck.blade.php create mode 100644 resources/views/flux/icon/tv.blade.php create mode 100644 resources/views/flux/icon/underline.blade.php create mode 100644 resources/views/flux/icon/user-circle.blade.php create mode 100644 resources/views/flux/icon/user-group.blade.php create mode 100644 resources/views/flux/icon/user-minus.blade.php create mode 100644 resources/views/flux/icon/user-plus.blade.php create mode 100644 resources/views/flux/icon/user.blade.php create mode 100644 resources/views/flux/icon/users.blade.php create mode 100644 resources/views/flux/icon/variable.blade.php create mode 100644 resources/views/flux/icon/video-camera-slash.blade.php create mode 100644 resources/views/flux/icon/video-camera.blade.php create mode 100644 resources/views/flux/icon/view-columns.blade.php create mode 100644 resources/views/flux/icon/viewfinder-circle.blade.php create mode 100644 resources/views/flux/icon/wallet.blade.php create mode 100644 resources/views/flux/icon/wifi.blade.php create mode 100644 resources/views/flux/icon/window.blade.php create mode 100644 resources/views/flux/icon/wrench-screwdriver.blade.php create mode 100644 resources/views/flux/icon/wrench.blade.php create mode 100644 resources/views/flux/icon/x-circle.blade.php create mode 100644 resources/views/flux/icon/x-mark.blade.php create mode 100644 resources/views/flux/input/clearable.blade.php create mode 100644 resources/views/flux/input/copyable.blade.php create mode 100644 resources/views/flux/input/expandable.blade.php create mode 100644 resources/views/flux/input/file.blade.php create mode 100644 resources/views/flux/input/group/affix.blade.php create mode 100644 resources/views/flux/input/group/index.blade.php create mode 100644 resources/views/flux/input/group/prefix.blade.php create mode 100644 resources/views/flux/input/group/suffix.blade.php create mode 100644 resources/views/flux/input/index.blade.php create mode 100644 resources/views/flux/input/viewable.blade.php create mode 100644 resources/views/flux/label.blade.php create mode 100644 resources/views/flux/legend.blade.php create mode 100644 resources/views/flux/link.blade.php create mode 100644 resources/views/flux/main.blade.php create mode 100644 resources/views/flux/menu/checkbox/group.blade.php create mode 100644 resources/views/flux/menu/checkbox/index.blade.php create mode 100644 resources/views/flux/menu/group.blade.php create mode 100644 resources/views/flux/menu/heading.blade.php create mode 100644 resources/views/flux/menu/index.blade.php create mode 100644 resources/views/flux/menu/item.blade.php create mode 100644 resources/views/flux/menu/radio/group.blade.php create mode 100644 resources/views/flux/menu/radio/index.blade.php create mode 100644 resources/views/flux/menu/separator.blade.php create mode 100644 resources/views/flux/menu/submenu.blade.php create mode 100644 resources/views/flux/modal/close.blade.php create mode 100644 resources/views/flux/modal/index.blade.php create mode 100644 resources/views/flux/modal/trigger.blade.php create mode 100644 resources/views/flux/navbar/badge.blade.php create mode 100644 resources/views/flux/navbar/index.blade.php create mode 100644 resources/views/flux/navbar/item.blade.php create mode 100644 resources/views/flux/navlist/badge.blade.php create mode 100644 resources/views/flux/navlist/group.blade.php create mode 100644 resources/views/flux/navlist/index.blade.php create mode 100644 resources/views/flux/navlist/item.blade.php create mode 100644 resources/views/flux/navmenu/index.blade.php create mode 100644 resources/views/flux/navmenu/item.blade.php create mode 100644 resources/views/flux/navmenu/separator.blade.php create mode 100644 resources/views/flux/profile.blade.php create mode 100644 resources/views/flux/radio/group/index.blade.php create mode 100644 resources/views/flux/radio/group/variants/default.blade.php create mode 100644 resources/views/flux/radio/group/variants/segmented.blade.php create mode 100644 resources/views/flux/radio/index.blade.php create mode 100644 resources/views/flux/radio/indicator.blade.php create mode 100644 resources/views/flux/radio/variants/default.blade.php create mode 100644 resources/views/flux/radio/variants/segmented.blade.php create mode 100644 resources/views/flux/select/index.blade.php create mode 100644 resources/views/flux/select/option/index.blade.php create mode 100644 resources/views/flux/select/option/variants/default.blade.php create mode 100644 resources/views/flux/select/variants/default.blade.php create mode 100644 resources/views/flux/separator.blade.php create mode 100644 resources/views/flux/sidebar/backdrop.blade.php create mode 100644 resources/views/flux/sidebar/index.blade.php create mode 100644 resources/views/flux/sidebar/toggle.blade.php create mode 100644 resources/views/flux/spacer.blade.php create mode 100644 resources/views/flux/subheading.blade.php create mode 100644 resources/views/flux/switch.blade.php create mode 100644 resources/views/flux/text.blade.php create mode 100644 resources/views/flux/textarea.blade.php create mode 100644 resources/views/flux/tooltip/content.blade.php create mode 100644 resources/views/flux/tooltip/index.blade.php create mode 100644 resources/views/layouts/app.blade.php create mode 100644 resources/views/livewire/appointments/calendar.blade.php create mode 100644 resources/views/livewire/appointments/create.blade.php create mode 100644 resources/views/livewire/appointments/form.blade.php create mode 100644 resources/views/livewire/appointments/index.blade.php create mode 100644 resources/views/livewire/appointments/time-slots.blade.php create mode 100644 resources/views/livewire/auth/confirm-password.blade.php create mode 100644 resources/views/livewire/auth/forgot-password.blade.php create mode 100644 resources/views/livewire/auth/login.blade.php create mode 100644 resources/views/livewire/auth/register.blade.php create mode 100644 resources/views/livewire/auth/reset-password.blade.php create mode 100644 resources/views/livewire/auth/verify-email.blade.php create mode 100644 resources/views/livewire/customer-portal/estimate-view.blade.php create mode 100644 resources/views/livewire/customer-portal/job-status.blade.php create mode 100644 resources/views/livewire/customers/create.blade.php create mode 100644 resources/views/livewire/customers/edit.blade.php create mode 100644 resources/views/livewire/customers/index.blade.php create mode 100644 resources/views/livewire/customers/show.blade.php create mode 100644 resources/views/livewire/dashboard/daily-schedule.blade.php create mode 100644 resources/views/livewire/dashboard/overview.blade.php create mode 100644 resources/views/livewire/dashboard/performance-metrics.blade.php create mode 100644 resources/views/livewire/dashboard/workflow-overview-backup.blade.php create mode 100644 resources/views/livewire/dashboard/workflow-overview.blade.php create mode 100644 resources/views/livewire/diagnosis/create.blade.php create mode 100644 resources/views/livewire/diagnosis/edit.blade.php create mode 100644 resources/views/livewire/diagnosis/index.blade.php create mode 100644 resources/views/livewire/diagnosis/show.blade.php create mode 100644 resources/views/livewire/estimates/create.blade.php create mode 100644 resources/views/livewire/estimates/edit.blade.php create mode 100644 resources/views/livewire/estimates/index.blade.php create mode 100644 resources/views/livewire/estimates/p-d-f.blade.php create mode 100644 resources/views/livewire/estimates/show.blade.php create mode 100644 resources/views/livewire/global-search.blade.php create mode 100644 resources/views/livewire/inspections/create.blade.php create mode 100644 resources/views/livewire/inspections/edit.blade.php create mode 100644 resources/views/livewire/inspections/index.blade.php create mode 100644 resources/views/livewire/inspections/show.blade.php create mode 100644 resources/views/livewire/inventory/dashboard.blade.php create mode 100644 resources/views/livewire/inventory/parts/create.blade.php create mode 100644 resources/views/livewire/inventory/parts/edit.blade.php create mode 100644 resources/views/livewire/inventory/parts/history.blade.php create mode 100644 resources/views/livewire/inventory/parts/index.blade.php create mode 100644 resources/views/livewire/inventory/parts/show.blade.php create mode 100644 resources/views/livewire/inventory/purchase-orders/create.blade.php create mode 100644 resources/views/livewire/inventory/purchase-orders/edit.blade.php create mode 100644 resources/views/livewire/inventory/purchase-orders/index.blade.php create mode 100644 resources/views/livewire/inventory/purchase-orders/show.blade.php create mode 100644 resources/views/livewire/inventory/stock-movements/create.blade.php create mode 100644 resources/views/livewire/inventory/stock-movements/index.blade.php create mode 100644 resources/views/livewire/inventory/suppliers/create.blade.php create mode 100644 resources/views/livewire/inventory/suppliers/edit.blade.php create mode 100644 resources/views/livewire/inventory/suppliers/index.blade.php create mode 100644 resources/views/livewire/job-cards/create.blade.php create mode 100644 resources/views/livewire/job-cards/edit.blade.php create mode 100644 resources/views/livewire/job-cards/index.blade.php create mode 100644 resources/views/livewire/job-cards/show.blade.php create mode 100644 resources/views/livewire/job-cards/workflow.blade.php create mode 100644 resources/views/livewire/reports/dashboard.blade.php create mode 100644 resources/views/livewire/reports/partials/customer-analytics.blade.php create mode 100644 resources/views/livewire/reports/partials/performance-metrics.blade.php create mode 100644 resources/views/livewire/reports/partials/revenue-analysis.blade.php create mode 100644 resources/views/livewire/reports/partials/service-trends.blade.php create mode 100644 resources/views/livewire/service-items/manage.blade.php create mode 100644 resources/views/livewire/service-orders/create.blade.php create mode 100644 resources/views/livewire/service-orders/edit.blade.php create mode 100644 resources/views/livewire/service-orders/index.blade.php create mode 100644 resources/views/livewire/service-orders/invoice.blade.php create mode 100644 resources/views/livewire/service-orders/show.blade.php create mode 100644 resources/views/livewire/settings/appearance.blade.php create mode 100644 resources/views/livewire/settings/delete-user-form.blade.php create mode 100644 resources/views/livewire/settings/password.blade.php create mode 100644 resources/views/livewire/settings/profile.blade.php create mode 100644 resources/views/livewire/technician-management/index.blade.php create mode 100644 resources/views/livewire/technician-management/performance-tracking.blade.php create mode 100644 resources/views/livewire/technician-management/skills-management.blade.php create mode 100644 resources/views/livewire/technician-management/technician-form.blade.php create mode 100644 resources/views/livewire/technician-management/workload-management.blade.php create mode 100644 resources/views/livewire/timesheets/create.blade.php create mode 100644 resources/views/livewire/timesheets/edit.blade.php create mode 100644 resources/views/livewire/timesheets/index.blade.php create mode 100644 resources/views/livewire/timesheets/show.blade.php create mode 100644 resources/views/livewire/user-management.blade.php create mode 100644 resources/views/livewire/users/create.blade.php create mode 100644 resources/views/livewire/users/edit.blade.php create mode 100644 resources/views/livewire/users/index.blade.php create mode 100644 resources/views/livewire/users/manage-roles-permissions.blade.php create mode 100644 resources/views/livewire/users/show.blade.php create mode 100644 resources/views/livewire/vehicles/create.blade.php create mode 100644 resources/views/livewire/vehicles/edit.blade.php create mode 100644 resources/views/livewire/vehicles/index.blade.php create mode 100644 resources/views/livewire/vehicles/show.blade.php create mode 100644 resources/views/livewire/work-orders/create.blade.php create mode 100644 resources/views/livewire/work-orders/edit.blade.php create mode 100644 resources/views/livewire/work-orders/index.blade.php create mode 100644 resources/views/livewire/work-orders/show.blade.php create mode 100644 resources/views/partials/head.blade.php create mode 100644 resources/views/partials/settings-heading.blade.php create mode 100644 resources/views/partials/theme.blade.php create mode 100644 resources/views/reports.blade.php create mode 100644 resources/views/service-items/index.blade.php create mode 100644 resources/views/service-orders/create.blade.php create mode 100644 resources/views/service-orders/edit.blade.php create mode 100644 resources/views/service-orders/index.blade.php create mode 100644 resources/views/service-orders/invoice.blade.php create mode 100644 resources/views/service-orders/show.blade.php create mode 100644 resources/views/settings/general.blade.php create mode 100644 resources/views/settings/inventory.blade.php create mode 100644 resources/views/settings/inventory_fixed.blade.php create mode 100644 resources/views/settings/notifications.blade.php create mode 100644 resources/views/settings/security.blade.php create mode 100644 resources/views/settings/service.blade.php create mode 100644 resources/views/technician-management.blade.php create mode 100644 resources/views/technician-reports.blade.php create mode 100644 resources/views/technician-skills.blade.php create mode 100644 resources/views/technicians/index.blade.php create mode 100644 resources/views/vehicles/create.blade.php create mode 100644 resources/views/vehicles/edit.blade.php create mode 100644 resources/views/vehicles/index.blade.php create mode 100644 resources/views/vehicles/show.blade.php create mode 100644 resources/views/welcome.blade.php create mode 100644 routes/auth.php create mode 100644 routes/console.php create mode 100644 routes/test.php create mode 100644 routes/web.php create mode 100644 storage/app/.gitignore create mode 100644 storage/app/private/.gitignore create mode 100644 storage/app/public/.gitignore create mode 100644 storage/framework/.gitignore create mode 100644 storage/framework/cache/.gitignore create mode 100644 storage/framework/cache/data/.gitignore create mode 100644 storage/framework/sessions/.gitignore create mode 100644 storage/framework/testing/.gitignore create mode 100644 storage/framework/views/.gitignore create mode 100644 storage/logs/.gitignore create mode 100644 tests/Feature/Auth/AuthenticationTest.php create mode 100644 tests/Feature/Auth/EmailVerificationTest.php create mode 100644 tests/Feature/Auth/PasswordConfirmationTest.php create mode 100644 tests/Feature/Auth/PasswordResetTest.php create mode 100644 tests/Feature/Auth/RegistrationTest.php create mode 100644 tests/Feature/DashboardTest.php create mode 100644 tests/Feature/ExampleTest.php create mode 100644 tests/Feature/Settings/PasswordUpdateTest.php create mode 100644 tests/Feature/Settings/ProfileUpdateTest.php create mode 100644 tests/TestCase.php create mode 100644 tests/Unit/ExampleTest.php create mode 100644 vite.config.js diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8f0de65 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 + +[docker-compose.yml] +indent_size = 4 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..35db1dd --- /dev/null +++ b/.env.example @@ -0,0 +1,65 @@ +APP_NAME=Laravel +APP_ENV=local +APP_KEY= +APP_DEBUG=true +APP_URL=http://localhost + +APP_LOCALE=en +APP_FALLBACK_LOCALE=en +APP_FAKER_LOCALE=en_US + +APP_MAINTENANCE_DRIVER=file +# APP_MAINTENANCE_STORE=database + +PHP_CLI_SERVER_WORKERS=4 + +BCRYPT_ROUNDS=12 + +LOG_CHANNEL=stack +LOG_STACK=single +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +DB_CONNECTION=sqlite +# DB_HOST=127.0.0.1 +# DB_PORT=3306 +# DB_DATABASE=laravel +# DB_USERNAME=root +# DB_PASSWORD= + +SESSION_DRIVER=database +SESSION_LIFETIME=120 +SESSION_ENCRYPT=false +SESSION_PATH=/ +SESSION_DOMAIN=null + +BROADCAST_CONNECTION=log +FILESYSTEM_DISK=local +QUEUE_CONNECTION=database + +CACHE_STORE=database +# CACHE_PREFIX= + +MEMCACHED_HOST=127.0.0.1 + +REDIS_CLIENT=phpredis +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_MAILER=log +MAIL_SCHEME=null +MAIL_HOST=127.0.0.1 +MAIL_PORT=2525 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_FROM_ADDRESS="hello@example.com" +MAIL_FROM_NAME="${APP_NAME}" + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET= +AWS_USE_PATH_STYLE_ENDPOINT=false + +VITE_APP_NAME="${APP_NAME}" diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f50f803 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +* text=auto eol=lf + +*.blade.php diff=html +*.css diff=css +*.html diff=html +*.md diff=markdown +*.php diff=php + +CHANGELOG.md export-ignore +README.md export-ignore diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..c3a441b --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,46 @@ +name: linter + +on: + push: + branches: + - develop + - main + pull_request: + branches: + - develop + - main + +permissions: + contents: write + +jobs: + quality: + runs-on: ubuntu-latest + environment: Testing + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + + - name: Add Flux Credentials Loaded From ENV + run: composer config http-basic.composer.fluxui.dev "${{ secrets.FLUX_USERNAME }}" "${{ secrets.FLUX_LICENSE_KEY }}" + + - name: Install Dependencies + run: | + composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist + npm install + + - name: Run Pint + run: vendor/bin/pint + + # - name: Commit Changes + # uses: stefanzweifel/git-auto-commit-action@v5 + # with: + # commit_message: fix code style + # commit_options: '--no-verify' + # file_pattern: | + # **/* + # !.github/workflows/* diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..e70fa2f --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,54 @@ +name: tests + +on: + push: + branches: + - develop + - main + pull_request: + branches: + - develop + - main + +jobs: + ci: + runs-on: ubuntu-latest + environment: Testing + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.4 + tools: composer:v2 + coverage: xdebug + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'npm' + + - name: Install Node Dependencies + run: npm i + + - name: Add Flux Credentials Loaded From ENV + run: composer config http-basic.composer.fluxui.dev "${{ secrets.FLUX_USERNAME }}" "${{ secrets.FLUX_LICENSE_KEY }}" + + - name: Install Dependencies + run: composer install --no-interaction --prefer-dist --optimize-autoloader + + - name: Copy Environment File + run: cp .env.example .env + + - name: Generate Application Key + run: php artisan key:generate + + - name: Build Assets + run: npm run build + + - name: Run Tests + run: ./vendor/bin/phpunit \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c7cf1fa --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +/.phpunit.cache +/node_modules +/public/build +/public/hot +/public/storage +/storage/*.key +/storage/pail +/vendor +.env +.env.backup +.env.production +.phpactor.json +.phpunit.result.cache +Homestead.json +Homestead.yaml +npm-debug.log +yarn-error.log +/auth.json +/.fleet +/.idea +/.nova +/.vscode +/.zed diff --git a/PERMISSIONS.md b/PERMISSIONS.md new file mode 100644 index 0000000..1bcc1ce --- /dev/null +++ b/PERMISSIONS.md @@ -0,0 +1,292 @@ +# Role-Based User Permission System Documentation + +## Overview + +This car repairs shop application now includes a comprehensive role-based permission system that provides fine-grained access control across all modules. The system supports: + +- **Hierarchical Roles**: Users can have multiple roles with different permissions +- **Direct Permissions**: Users can have permissions assigned directly, bypassing roles +- **Branch-Based Access**: Permissions can be scoped to specific branches +- **Module-Based Organization**: Permissions are organized by functional modules + +## Database Structure + +### Core Tables + +1. **roles** - Defines available roles in the system +2. **permissions** - Defines granular permissions organized by modules +3. **role_permissions** - Links roles to their permissions +4. **user_roles** - Assigns roles to users (with optional branch scoping) +5. **user_permissions** - Assigns direct permissions to users + +## Available Roles + +| Role | Display Name | Description | +|------|-------------|-------------| +| `admin` | Administrator | Full system access across all branches | +| `manager` | Branch Manager | Full access within assigned branch | +| `service_supervisor` | Service Supervisor | Supervises service operations and technicians | +| `service_coordinator` | Service Coordinator | Coordinates service workflow and scheduling | +| `service_advisor` | Service Advisor | Interfaces with customers and manages service requests | +| `parts_manager` | Parts Manager | Manages inventory and parts ordering | +| `technician` | Technician | Performs vehicle service and repairs | +| `quality_inspector` | Quality Inspector | Performs quality inspections and audits | +| `customer_service` | Customer Service | Handles customer inquiries and support | + +## Permission Modules + +### Job Cards +- `job-cards.view` - View job cards in own branch +- `job-cards.view-all` - View job cards across all branches +- `job-cards.view-own` - View only assigned job cards +- `job-cards.create` - Create new job cards +- `job-cards.update` - Update job cards in own branch +- `job-cards.update-all` - Update job cards across all branches +- `job-cards.update-own` - Update only assigned job cards +- `job-cards.delete` - Delete job cards +- `job-cards.approve` - Approve job cards for processing +- `job-cards.assign-technician` - Assign technicians to job cards + +### Customers +- `customers.view` - View customer information +- `customers.create` - Create new customers +- `customers.update` - Update customer information +- `customers.delete` - Delete customers + +### Vehicles +- `vehicles.view` - View vehicle information +- `vehicles.create` - Register new vehicles +- `vehicles.update` - Update vehicle information +- `vehicles.delete` - Delete vehicles + +### Inventory +- `inventory.view` - View inventory items +- `inventory.create` - Add new inventory items +- `inventory.update` - Update inventory items +- `inventory.delete` - Delete inventory items +- `inventory.stock-movements` - Manage stock movements +- `inventory.purchase-orders` - Manage purchase orders + +### Service Orders +- `service-orders.view` - View service orders +- `service-orders.create` - Create new service orders +- `service-orders.update` - Update service orders +- `service-orders.delete` - Delete service orders +- `service-orders.approve` - Approve service orders + +### Appointments +- `appointments.view` - View appointments +- `appointments.create` - Schedule new appointments +- `appointments.update` - Update appointments +- `appointments.delete` - Cancel appointments + +### Technicians +- `technicians.view` - View technician information +- `technicians.create` - Add new technicians +- `technicians.update` - Update technician information +- `technicians.delete` - Remove technicians +- `technicians.assign-work` - Assign work to technicians +- `technicians.view-performance` - View technician performance reports + +### Reports +- `reports.view` - View all reports +- `reports.financial` - View financial and revenue reports +- `reports.operational` - View operational and performance reports +- `reports.export` - Export reports to various formats + +### User Management +- `users.view` - View user accounts +- `users.create` - Create new user accounts +- `users.update` - Update user information +- `users.delete` - Delete user accounts +- `users.manage-roles` - Assign and remove user roles + +### System Administration +- `system.settings` - Configure system settings +- `system.maintenance` - Perform system maintenance tasks + +## Usage Examples + +### In Controllers/Livewire Components + +```php +// Check permission in component mount method +public function mount() +{ + $this->authorize('create', JobCard::class); +} + +// Check permission in methods +public function save() +{ + if (!auth()->user()->hasPermission('job-cards.create')) { + abort(403, 'You do not have permission to create job cards.'); + } + // ... rest of the method +} + +// Filter data based on permissions +public function loadData() +{ + $user = auth()->user(); + + if ($user->hasPermission('job-cards.view-all')) { + $this->jobCards = JobCard::all(); + } elseif ($user->hasPermission('job-cards.view')) { + $this->jobCards = JobCard::where('branch_code', $user->branch_code)->get(); + } else { + $this->jobCards = JobCard::where('service_advisor_id', $user->id)->get(); + } +} +``` + +### In Routes + +```php +// Protect routes with permission middleware +Route::get('/job-cards', JobCardIndex::class) + ->middleware('permission:job-cards.view'); + +Route::get('/job-cards/create', JobCardCreate::class) + ->middleware('permission:job-cards.create'); + +// Protect with role middleware +Route::prefix('admin')->middleware('role:admin,manager')->group(function () { + Route::get('/settings', AdminSettings::class); +}); +``` + +### In Blade Templates + +```blade +{{-- Using Blade directives --}} +@hasPermission('job-cards.create') + + Create Job Card + +@endhasPermission + +@hasRole('admin|manager') +
+ {{-- Admin content --}} +
+@endhasRole + +@hasAnyPermission('reports.view|reports.financial') + View Reports +@endhasAnyPermission + +{{-- Using permission component --}} + + + + + +
+ {{-- Supervisor-only tools --}} +
+
+``` + +### In Policies + +```php +class JobCardPolicy +{ + public function view(User $user, JobCard $jobCard): bool + { + // Admin can view all + if ($user->hasPermission('job-cards.view-all')) { + return true; + } + + // Branch-level access + if ($user->hasPermission('job-cards.view') && + $jobCard->branch_code === $user->branch_code) { + return true; + } + + // Own records only + if ($user->hasPermission('job-cards.view-own') && + $jobCard->service_advisor_id === $user->id) { + return true; + } + + return false; + } +} +``` + +## Artisan Commands + +### Assign Role to User +```bash +# Assign role without branch restriction +php artisan user:assign-role user@example.com admin + +# Assign role with branch restriction +php artisan user:assign-role user@example.com service_advisor --branch=ACC +``` + +### Seed Roles and Permissions +```bash +php artisan db:seed --class=RolesAndPermissionsSeeder +``` + +## User Management Interface + +The system includes a web interface for managing user roles and permissions: + +- **URL**: `/user-management` +- **Permission Required**: `users.view` +- **Features**: + - Search and filter users + - Assign/remove roles + - Grant/revoke direct permissions + - Set branch-specific access + - Activate/deactivate users + +## Permission Hierarchy + +1. **Super Admin**: `admin` role bypasses all permission checks +2. **Direct Permissions**: Override role-based permissions +3. **Role Permissions**: Standard role-based access +4. **Branch Scoping**: Permissions can be limited to specific branches + +## Security Features + +- **Branch Isolation**: Users can only access data within their assigned branch (unless granted cross-branch permissions) +- **Temporal Permissions**: Roles and permissions can have expiration dates +- **Audit Trail**: All role and permission changes are timestamped +- **Middleware Protection**: Routes are protected at the middleware level +- **Policy-Based Authorization**: Model operations use Laravel policies for fine-grained control + +## Best Practices + +1. **Use Roles for Common Patterns**: Assign permissions to roles rather than directly to users +2. **Branch Scoping**: Always consider branch-level access when designing features +3. **Least Privilege**: Grant only the minimum permissions required for a user's job function +4. **Regular Audits**: Periodically review user permissions and remove unnecessary access +5. **Policy Classes**: Use Laravel policies for complex authorization logic +6. **Middleware First**: Apply middleware protection to routes before implementing view-level checks + +## Troubleshooting + +### Common Issues + +1. **Permission Denied Errors**: Check that the user has the required permission and that their role is active +2. **Branch Access Issues**: Verify that the user's branch_code matches the resource's branch_code +3. **Middleware Conflicts**: Ensure middleware is applied in the correct order +4. **Cache Issues**: Clear application cache after changing permissions: `php artisan cache:clear` + +### Debug Commands + +```bash +# Check user's roles and permissions +php artisan tinker +>>> $user = User::find(1); +>>> $user->getAllPermissions(); +>>> $user->roles; +``` + +This permission system provides a robust foundation for controlling access to all features in your car repairs shop application while maintaining flexibility for different organizational structures and workflows. diff --git a/THEME_STANDARD.md b/THEME_STANDARD.md new file mode 100644 index 0000000..ea14750 --- /dev/null +++ b/THEME_STANDARD.md @@ -0,0 +1,49 @@ +# 🎨 Car Repair Shop - Theme Standardization + +## Color Palette Standard +Based on your app.css configuration where zinc maps to slate colors. + +### Primary Colors (Use ZINC everywhere) +- **Backgrounds**: `bg-white dark:bg-zinc-800` +- **Cards/Containers**: `bg-white dark:bg-zinc-800` with `border border-zinc-200 dark:border-zinc-700` +- **Secondary Backgrounds**: `bg-zinc-50 dark:bg-zinc-900` +- **Borders**: `border-zinc-200 dark:border-zinc-700` + +### Typography +- **Primary Text**: `text-zinc-900 dark:text-white` +- **Secondary Text**: `text-zinc-600 dark:text-zinc-400` +- **Muted Text**: `text-zinc-500 dark:text-zinc-500` + +### Interactive Elements +- **Accent**: `text-accent` / `bg-accent` (indigo-500/300) +- **Links**: `text-accent hover:text-accent-content` +- **Buttons**: Use Flux components (`flux:button`) + +### Form Elements +- **Inputs**: Use Flux components (`flux:input`, `flux:select`) +- **Focus States**: Handled by Flux automatically + +### Table Elements +- **Headers**: `bg-zinc-50 dark:bg-zinc-900` +- **Borders**: `divide-zinc-200 dark:divide-zinc-700` +- **Hover**: `hover:bg-zinc-50 dark:hover:bg-zinc-700` + +### Status Colors (Keep these for badges) +- **Success**: `bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200` +- **Warning**: `bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200` +- **Error**: `bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200` +- **Info**: `bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200` + +## Components to Standardize +1. Job Cards - Currently using gray colors +2. Work Orders - Currently using zinc (good) +3. Customers - Mixed gray/zinc colors +4. Service Orders - Using zinc (good) +5. Users - Recently updated to stone (needs zinc) +6. All other modules + +## Implementation Priority +1. Fix color inconsistencies (gray → zinc) +2. Standardize form components to use Flux +3. Ensure dark mode compatibility +4. Update status badges for consistency diff --git a/app/Console/Commands/AssignPermissions.php b/app/Console/Commands/AssignPermissions.php new file mode 100644 index 0000000..cffe833 --- /dev/null +++ b/app/Console/Commands/AssignPermissions.php @@ -0,0 +1,79 @@ +argument('role'); + + $role = Role::where('name', $roleName)->first(); + + if (!$role) { + $this->error("Role '{$roleName}' not found!"); + return 1; + } + + $permissions = Permission::all(); + + $this->info("Found {$permissions->count()} permissions"); + $this->info("Assigning to role: {$role->display_name}"); + + $assigned = 0; + foreach ($permissions as $permission) { + $exists = DB::table('role_permissions') + ->where('role_id', $role->id) + ->where('permission_id', $permission->id) + ->exists(); + + if (!$exists) { + DB::table('role_permissions')->insert([ + 'role_id' => $role->id, + 'permission_id' => $permission->id, + 'created_at' => now(), + 'updated_at' => now() + ]); + $assigned++; + } + } + + $total = DB::table('role_permissions')->where('role_id', $role->id)->count(); + + $this->info("Assigned {$assigned} new permissions"); + $this->info("Role now has {$total} total permissions"); + + // Check for users.view specifically + $usersView = DB::table('role_permissions') + ->join('permissions', 'role_permissions.permission_id', '=', 'permissions.id') + ->where('role_permissions.role_id', $role->id) + ->where('permissions.name', 'users.view') + ->exists(); + + $this->info("users.view permission: " . ($usersView ? "✓ ASSIGNED" : "✗ MISSING")); + + return 0; + } +} diff --git a/app/Console/Commands/AssignRoleCommand.php b/app/Console/Commands/AssignRoleCommand.php new file mode 100644 index 0000000..e77a9de --- /dev/null +++ b/app/Console/Commands/AssignRoleCommand.php @@ -0,0 +1,54 @@ +argument('email'); + $roleName = $this->argument('role'); + $branchCode = $this->option('branch'); + + $user = User::where('email', $email)->first(); + if (!$user) { + $this->error("User with email {$email} not found."); + return Command::FAILURE; + } + + $role = Role::where('name', $roleName)->first(); + if (!$role) { + $this->error("Role {$roleName} not found."); + return Command::FAILURE; + } + + // Assign role to user + $user->assignRole($role, $branchCode); + + $this->info("Role '{$role->display_name}' assigned to user '{$user->name}'" . + ($branchCode ? " for branch '{$branchCode}'" : '') . "."); + + return Command::SUCCESS; + } +} diff --git a/app/Console/Commands/GeneratePartHistory.php b/app/Console/Commands/GeneratePartHistory.php new file mode 100644 index 0000000..01aacbd --- /dev/null +++ b/app/Console/Commands/GeneratePartHistory.php @@ -0,0 +1,128 @@ +option('part_id'); + + if ($partId) { + $parts = Part::where('id', $partId)->get(); + } else { + $parts = Part::take(5)->get(); + } + + if ($parts->isEmpty()) { + $this->error('No parts found'); + return; + } + + foreach ($parts as $part) { + $this->info("Generating history for part: {$part->name}"); + + // Generate creation history + PartHistory::create([ + 'part_id' => $part->id, + 'event_type' => PartHistory::EVENT_CREATED, + 'new_values' => [ + 'name' => $part->name, + 'part_number' => $part->part_number, + 'cost_price' => $part->cost_price, + 'sell_price' => $part->sell_price, + ], + 'notes' => 'Part initially created', + 'ip_address' => '127.0.0.1', + 'user_agent' => 'Console Command', + 'created_by' => 1, + 'created_at' => $part->created_at, + 'updated_at' => $part->created_at, + ]); + + // Generate some stock movements + $movements = [ + ['type' => 'in', 'qty' => 50, 'note' => 'Initial stock'], + ['type' => 'in', 'qty' => 25, 'note' => 'Restocked inventory'], + ['type' => 'out', 'qty' => 10, 'note' => 'Used in service'], + ['type' => 'adjustment', 'qty' => -2, 'note' => 'Inventory correction'], + ]; + + $currentStock = $part->quantity_on_hand; + $runningTotal = 0; + + foreach ($movements as $index => $movement) { + // Create dates that span from a week ago to today to ensure some records show up + $daysAgo = max(0, 7 - $index); // This will create records from 7 days ago to today + $createdAt = now()->subDays($daysAgo)->subHours(rand(1, 12)); + + $quantityBefore = $runningTotal; + $quantityChange = $movement['type'] === 'out' ? -$movement['qty'] : $movement['qty']; + $quantityAfter = $quantityBefore + $quantityChange; + $runningTotal = $quantityAfter; + + PartHistory::create([ + 'part_id' => $part->id, + 'event_type' => $movement['type'] === 'in' ? PartHistory::EVENT_STOCK_IN : + ($movement['type'] === 'out' ? PartHistory::EVENT_STOCK_OUT : PartHistory::EVENT_ADJUSTMENT), + 'quantity_change' => $quantityChange, + 'quantity_before' => $quantityBefore, + 'quantity_after' => $quantityAfter, + 'reference_type' => 'manual_adjustment', + 'notes' => $movement['note'], + 'ip_address' => '127.0.0.1', + 'user_agent' => 'Console Command', + 'created_by' => 1, + 'created_at' => $createdAt, + 'updated_at' => $createdAt, + ]); + } + + // Generate price change + $oldPrice = $part->cost_price; + $newPrice = $oldPrice * 1.1; // 10% increase + + PartHistory::create([ + 'part_id' => $part->id, + 'event_type' => PartHistory::EVENT_PRICE_CHANGE, + 'old_values' => ['cost_price' => $oldPrice], + 'new_values' => ['cost_price' => $newPrice], + 'cost_before' => $oldPrice, + 'cost_after' => $newPrice, + 'notes' => 'Price updated due to supplier cost increase', + 'ip_address' => '127.0.0.1', + 'user_agent' => 'Console Command', + 'created_by' => 1, + 'created_at' => now(), + 'updated_at' => now(), + ]); + + $this->info("✓ Generated " . PartHistory::where('part_id', $part->id)->count() . " history records for {$part->name}"); + } + + $totalHistory = PartHistory::count(); + $this->info("\nTotal part history records in database: {$totalHistory}"); + } +} diff --git a/app/Console/Commands/TestUserPermissions.php b/app/Console/Commands/TestUserPermissions.php new file mode 100644 index 0000000..1e92b26 --- /dev/null +++ b/app/Console/Commands/TestUserPermissions.php @@ -0,0 +1,49 @@ +argument('email'); + $user = User::where('email', $email)->first(); + + if (!$user) { + $this->error("User with email {$email} not found"); + return 1; + } + + $this->info("Testing permissions for: {$user->name} ({$user->email})"); + $this->info("User branch_code: " . ($user->branch_code ?? 'NULL')); + + // Test role assignment + $roles = $user->roles()->where('user_roles.is_active', true)->get(); + $this->info("Active roles: " . $roles->pluck('name')->join(', ')); + + // Test hasRole + $hasSuperAdmin = $user->hasRole('super_admin'); + $this->info("Has super_admin role: " . ($hasSuperAdmin ? 'YES' : 'NO')); + + // Test hasPermission with and without branch code + $hasUsersViewWithBranch = $user->hasPermission('users.view', $user->branch_code); + $hasUsersViewWithoutBranch = $user->hasPermission('users.view'); + + $this->info("Has users.view with branch code: " . ($hasUsersViewWithBranch ? 'YES' : 'NO')); + $this->info("Has users.view without branch code: " . ($hasUsersViewWithoutBranch ? 'YES' : 'NO')); + + // Check role permissions + foreach ($roles as $role) { + $rolePermissions = $role->permissions()->where('name', 'users.view')->count(); + $this->info("Role '{$role->name}' has users.view permission: " . ($rolePermissions > 0 ? 'YES' : 'NO')); + } + + return 0; + } +} diff --git a/app/Http/Controllers/Auth/VerifyEmailController.php b/app/Http/Controllers/Auth/VerifyEmailController.php new file mode 100644 index 0000000..a300bfa --- /dev/null +++ b/app/Http/Controllers/Auth/VerifyEmailController.php @@ -0,0 +1,30 @@ +user()->hasVerifiedEmail()) { + return redirect()->intended(route('dashboard', absolute: false).'?verified=1'); + } + + if ($request->user()->markEmailAsVerified()) { + /** @var \Illuminate\Contracts\Auth\MustVerifyEmail $user */ + $user = $request->user(); + + event(new Verified($user)); + } + + return redirect()->intended(route('dashboard', absolute: false).'?verified=1'); + } +} diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php new file mode 100644 index 0000000..8677cd5 --- /dev/null +++ b/app/Http/Controllers/Controller.php @@ -0,0 +1,8 @@ +route('customers.index'); + } + + /** + * Display the specified resource. + */ + public function show(Customer $customer) + { + // Load relationships for the show page + $customer->load(['vehicles', 'serviceOrders.vehicle', 'serviceOrders.assignedTechnician', 'appointments']); + + return view('customers.show', compact('customer')); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(Customer $customer) + { + return view('customers.edit', compact('customer')); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, Customer $customer) + { + // This is handled by the Livewire component + return redirect()->route('customers.show', $customer); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(Customer $customer) + { + $customer->delete(); + return redirect()->route('customers.index')->with('success', 'Customer deleted successfully.'); + } +} diff --git a/app/Http/Controllers/InventoryController.php b/app/Http/Controllers/InventoryController.php new file mode 100644 index 0000000..ad637ed --- /dev/null +++ b/app/Http/Controllers/InventoryController.php @@ -0,0 +1,13 @@ +route('inventory.dashboard'); + } +} diff --git a/app/Http/Controllers/ServiceOrderController.php b/app/Http/Controllers/ServiceOrderController.php new file mode 100644 index 0000000..1bd1ba6 --- /dev/null +++ b/app/Http/Controllers/ServiceOrderController.php @@ -0,0 +1,76 @@ +route('service-orders.index'); + } + + /** + * Display the specified resource. + */ + public function show(ServiceOrder $serviceOrder) + { + return view('service-orders.show', compact('serviceOrder')); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(ServiceOrder $serviceOrder) + { + return view('service-orders.edit', compact('serviceOrder')); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, ServiceOrder $serviceOrder) + { + // This will be handled by Livewire component + return redirect()->route('service-orders.show', $serviceOrder); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(ServiceOrder $serviceOrder) + { + // This will be handled by Livewire component + return redirect()->route('service-orders.index'); + } + + /** + * Generate invoice for the service order. + */ + public function invoice(ServiceOrder $serviceOrder) + { + return view('service-orders.invoice', compact('serviceOrder')); + } +} diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php new file mode 100644 index 0000000..91d6e08 --- /dev/null +++ b/app/Http/Controllers/SettingsController.php @@ -0,0 +1,288 @@ +validate([ + 'shop_name' => 'required|string|max:255', + 'shop_address' => 'required|string|max:255', + 'shop_city' => 'required|string|max:100', + 'shop_state' => 'required|string|max:100', + 'shop_zip_code' => 'required|string|max:20', + 'shop_phone' => 'required|string|max:20', + 'shop_email' => 'required|email|max:255', + 'shop_website' => 'nullable|url|max:255', + 'default_tax_rate' => 'required|numeric|min:0|max:100', + 'currency' => 'required|string|max:10', + 'currency_symbol' => 'required|string|max:5', + 'timezone' => 'required|string', + 'date_format' => 'required|string', + 'time_format' => 'required|string', + 'enable_notifications' => 'nullable|boolean', + 'enable_sms_notifications' => 'nullable|boolean', + 'enable_email_notifications' => 'nullable|boolean', + 'is_open_weekends' => 'nullable|boolean', + 'business_hours' => 'nullable|array', + 'holiday_hours' => 'nullable|array', + ]); + + // Handle boolean fields that might not be present in request + $validated['enable_notifications'] = $request->has('enable_notifications'); + $validated['enable_sms_notifications'] = $request->has('enable_sms_notifications'); + $validated['enable_email_notifications'] = $request->has('enable_email_notifications'); + $validated['is_open_weekends'] = $request->has('is_open_weekends'); + + // Ensure arrays have default values if not provided + $validated['business_hours'] = $validated['business_hours'] ?? $settings->business_hours ?? []; + $validated['holiday_hours'] = $validated['holiday_hours'] ?? $settings->holiday_hours ?? []; + + foreach ($validated as $key => $value) { + $settings->$key = $value; + } + + $settings->save(); + + return redirect()->back()->with('success', 'General settings updated successfully!'); + } + + public function service(ServiceSettings $settings) + { + return view('settings.service', compact('settings')); + } + + public function updateService(Request $request, ServiceSettings $settings) + { + $validated = $request->validate([ + 'standard_labor_rate' => 'required|numeric|min:0', + 'overtime_labor_rate' => 'required|numeric|min:0', + 'weekend_labor_rate' => 'required|numeric|min:0', + 'holiday_labor_rate' => 'required|numeric|min:0', + 'oil_change_interval' => 'required|integer|min:1000', + 'tire_rotation_interval' => 'required|integer|min:1000', + 'brake_inspection_interval' => 'required|integer|min:1000', + 'general_inspection_interval' => 'required|integer|min:1000', + 'enable_service_reminders' => 'nullable|boolean', + 'reminder_advance_days' => 'required|integer|min:1|max:90', + 'default_parts_warranty_days' => 'required|integer|min:1', + 'default_labor_warranty_days' => 'required|integer|min:1', + 'enable_extended_warranty' => 'nullable|boolean', + 'require_quality_inspection' => 'nullable|boolean', + 'require_technician_signature' => 'nullable|boolean', + 'require_customer_signature' => 'nullable|boolean', + 'enable_photo_documentation' => 'nullable|boolean', + 'service_categories' => 'nullable|array', + 'priority_levels' => 'nullable|array', + ]); + + // Handle boolean fields that might not be present in request + $validated['enable_service_reminders'] = $request->has('enable_service_reminders'); + $validated['enable_extended_warranty'] = $request->has('enable_extended_warranty'); + $validated['require_quality_inspection'] = $request->has('require_quality_inspection'); + $validated['require_technician_signature'] = $request->has('require_technician_signature'); + $validated['require_customer_signature'] = $request->has('require_customer_signature'); + $validated['enable_photo_documentation'] = $request->has('enable_photo_documentation'); + + // Ensure arrays have default values if not provided + $validated['service_categories'] = $validated['service_categories'] ?? $settings->service_categories ?? []; + $validated['priority_levels'] = $validated['priority_levels'] ?? $settings->priority_levels ?? []; + + foreach ($validated as $key => $value) { + $settings->$key = $value; + } + + $settings->save(); + + return redirect()->back()->with('success', 'Service settings updated successfully!'); + } + + public function inventory(InventorySettings $settings) + { + return view('settings.inventory', compact('settings')); + } + + public function updateInventory(Request $request, InventorySettings $settings) + { + $validated = $request->validate([ + 'low_stock_threshold' => 'required|integer|min:1', + 'critical_stock_threshold' => 'required|integer|min:1', + 'default_reorder_quantity' => 'required|integer|min:1', + 'default_lead_time_days' => 'required|integer|min:1', + 'default_markup_percentage' => 'required|numeric|min:0', + 'preferred_supplier_count' => 'required|integer|min:1', + 'minimum_order_amount' => 'required|numeric|min:0', + 'default_part_markup' => 'required|numeric|min:0', + 'core_charge_percentage' => 'required|numeric|min:0', + 'shop_supply_fee' => 'required|numeric|min:0', + 'environmental_fee' => 'required|numeric|min:0', + 'waste_oil_fee' => 'required|numeric|min:0', + 'tire_disposal_fee' => 'required|numeric|min:0', + 'default_payment_terms' => 'required|string', + 'preferred_ordering_method' => 'required|string', + 'free_shipping_threshold' => 'nullable|numeric|min:0', + 'enable_low_stock_alerts' => 'nullable|boolean', + 'enable_automatic_reorder' => 'nullable|boolean', + 'track_serial_numbers' => 'nullable|boolean', + 'enable_barcode_scanning' => 'nullable|boolean', + 'enable_volume_discounts' => 'nullable|boolean', + 'enable_seasonal_pricing' => 'nullable|boolean', + 'enable_customer_specific_pricing' => 'nullable|boolean', + 'require_po_approval' => 'nullable|boolean', + 'enable_dropship' => 'nullable|boolean', + 'enable_backorders' => 'nullable|boolean', + ]); + + // Handle boolean fields that might not be present in request + $validated['enable_low_stock_alerts'] = $request->has('enable_low_stock_alerts'); + $validated['enable_auto_reorder'] = $request->has('enable_automatic_reorder'); // Map form field to DB field + $validated['track_serial_numbers'] = $request->has('track_serial_numbers'); + $validated['enable_barcode_scanning'] = $request->has('enable_barcode_scanning'); + $validated['enable_volume_discounts'] = $request->has('enable_volume_discounts'); + $validated['enable_seasonal_pricing'] = $request->has('enable_seasonal_pricing'); + $validated['enable_customer_specific_pricing'] = $request->has('enable_customer_specific_pricing'); + $validated['require_po_approval'] = $request->has('require_po_approval'); + $validated['enable_dropship'] = $request->has('enable_dropship'); + $validated['enable_backorders'] = $request->has('enable_backorders'); + + foreach ($validated as $key => $value) { + $settings->$key = $value; + } + + $settings->save(); + + return redirect()->back()->with('success', 'Inventory settings updated successfully!'); + } + + public function notifications(NotificationSettings $settings) + { + return view('settings.notifications', compact('settings')); + } + + public function updateNotifications(Request $request, NotificationSettings $settings) + { + $validated = $request->validate([ + 'from_email' => 'required|email', + 'from_name' => 'required|string|max:255', + 'manager_email' => 'required|email', + 'enable_customer_notifications' => 'nullable|boolean', + 'enable_technician_notifications' => 'nullable|boolean', + 'enable_manager_notifications' => 'nullable|boolean', + 'enable_sms' => 'nullable|boolean', + 'sms_provider' => 'nullable|string', + 'sms_api_key' => 'nullable|string', + 'sms_from_number' => 'nullable|string', + 'customer_notification_types' => 'nullable|array', + 'notification_timing' => 'nullable|array', + 'notify_on_new_job' => 'nullable|boolean', + 'notify_on_job_completion' => 'nullable|boolean', + 'notify_on_low_stock' => 'nullable|boolean', + 'notify_on_overdue_inspection' => 'nullable|boolean', + 'notify_on_warranty_expiry' => 'nullable|boolean', + 'enable_escalation' => 'nullable|boolean', + 'escalation_hours' => 'required|integer|min:1', + 'escalation_contacts' => 'nullable|array', + ]); + + // Handle boolean fields that might not be present in request + $validated['enable_customer_notifications'] = $request->has('enable_customer_notifications'); + $validated['enable_technician_notifications'] = $request->has('enable_technician_notifications'); + $validated['enable_manager_notifications'] = $request->has('enable_manager_notifications'); + $validated['enable_sms'] = $request->has('enable_sms'); + $validated['notify_on_new_job'] = $request->has('notify_on_new_job'); + $validated['notify_on_job_completion'] = $request->has('notify_on_job_completion'); + $validated['notify_on_low_stock'] = $request->has('notify_on_low_stock'); + $validated['notify_on_overdue_inspection'] = $request->has('notify_on_overdue_inspection'); + $validated['notify_on_warranty_expiry'] = $request->has('notify_on_warranty_expiry'); + $validated['enable_escalation'] = $request->has('enable_escalation'); + + // Ensure arrays have default values if not provided + $validated['customer_notification_types'] = $validated['customer_notification_types'] ?? $settings->customer_notification_types ?? []; + $validated['notification_timing'] = $validated['notification_timing'] ?? $settings->notification_timing ?? []; + $validated['escalation_contacts'] = $validated['escalation_contacts'] ?? $settings->escalation_contacts ?? []; + + foreach ($validated as $key => $value) { + $settings->$key = $value; + } + + $settings->save(); + + return redirect()->back()->with('success', 'Notification settings updated successfully!'); + } + + public function security(SecuritySettings $settings) + { + return view('settings.security', compact('settings')); + } + + public function updateSecurity(Request $request, SecuritySettings $settings) + { + $validated = $request->validate([ + 'enable_two_factor_auth' => 'nullable|boolean', + 'session_timeout_minutes' => 'required|integer|min:5', + 'password_expiry_days' => 'required|integer|min:1', + 'max_login_attempts' => 'required|integer|min:1', + 'lockout_duration_minutes' => 'required|integer|min:1', + 'min_password_length' => 'required|integer|min:6', + 'require_uppercase' => 'nullable|boolean', + 'require_lowercase' => 'nullable|boolean', + 'require_numbers' => 'nullable|boolean', + 'require_special_characters' => 'nullable|boolean', + 'enable_data_encryption' => 'nullable|boolean', + 'enable_audit_logging' => 'nullable|boolean', + 'audit_log_retention_days' => 'required|integer|min:1', + 'enable_backup_alerts' => 'nullable|boolean', + 'enable_api_rate_limiting' => 'nullable|boolean', + 'api_requests_per_minute' => 'required|integer|min:1', + 'allowed_ip_addresses' => 'nullable|string', + 'allow_customer_portal' => 'nullable|boolean', + 'allow_customer_data_download' => 'nullable|boolean', + 'customer_session_timeout_minutes' => 'required|integer|min:5', + ]); + + // Handle boolean fields that might not be present in request + $validated['enable_two_factor_auth'] = $request->has('enable_two_factor_auth'); + $validated['require_uppercase'] = $request->has('require_uppercase'); + $validated['require_lowercase'] = $request->has('require_lowercase'); + $validated['require_numbers'] = $request->has('require_numbers'); + $validated['require_special_characters'] = $request->has('require_special_characters'); + $validated['enable_data_encryption'] = $request->has('enable_data_encryption'); + $validated['enable_audit_logging'] = $request->has('enable_audit_logging'); + $validated['enable_backup_alerts'] = $request->has('enable_backup_alerts'); + $validated['enable_api_rate_limiting'] = $request->has('enable_api_rate_limiting'); + $validated['allow_customer_portal'] = $request->has('allow_customer_portal'); + $validated['allow_customer_data_download'] = $request->has('allow_customer_data_download'); + + // Convert IP addresses from textarea to array + if (!empty($validated['allowed_ip_addresses'])) { + $validated['allowed_ip_addresses'] = array_filter( + array_map('trim', explode("\n", $validated['allowed_ip_addresses'])), + function($ip) { return !empty($ip); } + ); + } else { + $validated['allowed_ip_addresses'] = []; + } + + foreach ($validated as $key => $value) { + $settings->$key = $value; + } + + $settings->save(); + + return redirect()->back()->with('success', 'Security settings updated successfully!'); + } +} diff --git a/app/Http/Controllers/VehicleController.php b/app/Http/Controllers/VehicleController.php new file mode 100644 index 0000000..5b59773 --- /dev/null +++ b/app/Http/Controllers/VehicleController.php @@ -0,0 +1,71 @@ +route('vehicles.index'); + } + + /** + * Display the specified resource. + */ + public function show(Vehicle $vehicle) + { + // Load relationships for the show page + $vehicle->load(['customer', 'serviceOrders.assignedTechnician', 'appointments', 'inspections']); + + return view('vehicles.show', compact('vehicle')); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(Vehicle $vehicle) + { + return view('vehicles.edit', compact('vehicle')); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, Vehicle $vehicle) + { + // This is handled by the Livewire component + return redirect()->route('vehicles.show', $vehicle); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(Vehicle $vehicle) + { + $vehicle->delete(); + return redirect()->route('vehicles.index')->with('success', 'Vehicle deleted successfully.'); + } +} diff --git a/app/Http/Middleware/PermissionMiddleware.php b/app/Http/Middleware/PermissionMiddleware.php new file mode 100644 index 0000000..e5cefe1 --- /dev/null +++ b/app/Http/Middleware/PermissionMiddleware.php @@ -0,0 +1,36 @@ +check()) { + return redirect()->route('login'); + } + + $user = auth()->user(); + + // Check for super admin role first (bypass all restrictions) + if ($user->hasRole('super_admin')) { + return $next($request); + } + + $branchCode = $user->branch_code; + + // Check if user has any of the required permissions + if ($user->hasAnyPermission($permissions, $branchCode)) { + return $next($request); + } + + abort(403, 'Access denied. Required permission: ' . implode(' or ', $permissions)); + } +} diff --git a/app/Http/Middleware/RoleMiddleware.php b/app/Http/Middleware/RoleMiddleware.php new file mode 100644 index 0000000..a53a5c3 --- /dev/null +++ b/app/Http/Middleware/RoleMiddleware.php @@ -0,0 +1,35 @@ +check()) { + return redirect()->route('login'); + } + + $user = auth()->user(); + $branchCode = $user->branch_code; + + // Check if user has any of the required roles + if ($user->hasAnyRole($roles, $branchCode)) { + return $next($request); + } + + // Check for super admin role (bypass branch restrictions) + if ($user->hasRole('admin')) { + return $next($request); + } + + abort(403, 'Access denied. Required role: ' . implode(' or ', $roles)); + } +} diff --git a/app/Livewire/Actions/Logout.php b/app/Livewire/Actions/Logout.php new file mode 100644 index 0000000..45993bb --- /dev/null +++ b/app/Livewire/Actions/Logout.php @@ -0,0 +1,22 @@ +logout(); + + Session::invalidate(); + Session::regenerateToken(); + + return redirect('/'); + } +} diff --git a/app/Livewire/Appointments/Calendar.php b/app/Livewire/Appointments/Calendar.php new file mode 100644 index 0000000..ad4d189 --- /dev/null +++ b/app/Livewire/Appointments/Calendar.php @@ -0,0 +1,282 @@ +currentDate = now(); + $this->currentMonth = $this->currentDate->month; + $this->currentYear = $this->currentDate->year; + $this->selectedDate = $this->currentDate->format('Y-m-d'); + $this->technicians = Technician::where('status', 'active')->orderBy('first_name')->get(); + $this->generateCalendar(); + $this->loadAppointments(); + } + + public function updatedSelectedTechnician() + { + $this->loadAppointments(); + } + + public function setViewType($type) + { + $this->viewType = $type; + $this->generateCalendar(); + $this->loadAppointments(); + } + + public function previousPeriod() + { + switch ($this->viewType) { + case 'month': + $this->currentDate = $this->currentDate->subMonth(); + break; + case 'week': + $this->currentDate = $this->currentDate->subWeek(); + break; + case 'day': + $this->currentDate = $this->currentDate->subDay(); + break; + } + + $this->currentMonth = $this->currentDate->month; + $this->currentYear = $this->currentDate->year; + $this->generateCalendar(); + $this->loadAppointments(); + } + + public function nextPeriod() + { + switch ($this->viewType) { + case 'month': + $this->currentDate = $this->currentDate->addMonth(); + break; + case 'week': + $this->currentDate = $this->currentDate->addWeek(); + break; + case 'day': + $this->currentDate = $this->currentDate->addDay(); + break; + } + + $this->currentMonth = $this->currentDate->month; + $this->currentYear = $this->currentDate->year; + $this->generateCalendar(); + $this->loadAppointments(); + } + + public function today() + { + $this->currentDate = now(); + $this->currentMonth = $this->currentDate->month; + $this->currentYear = $this->currentDate->year; + $this->selectedDate = $this->currentDate->format('Y-m-d'); + $this->generateCalendar(); + $this->loadAppointments(); + } + + public function selectDate($date) + { + $this->selectedDate = $date; + $this->currentDate = Carbon::parse($date); + $this->dispatch('date-selected', date: $date); + } + + public function showAppointmentDetails($appointmentId) + { + $this->selectedAppointment = Appointment::with(['customer', 'vehicle', 'assignedTechnician']) + ->find($appointmentId); + $this->showAppointmentModal = true; + } + + public function closeAppointmentModal() + { + $this->showAppointmentModal = false; + $this->selectedAppointment = null; + } + + private function generateCalendar() + { + $this->calendarDays = []; + + switch ($this->viewType) { + case 'month': + $this->generateMonthCalendar(); + break; + case 'week': + $this->generateWeekCalendar(); + break; + case 'day': + $this->generateDayCalendar(); + break; + } + } + + private function generateMonthCalendar() + { + $startOfMonth = $this->currentDate->copy()->startOfMonth(); + $endOfMonth = $this->currentDate->copy()->endOfMonth(); + $startOfCalendar = $startOfMonth->copy()->startOfWeek(); + $endOfCalendar = $endOfMonth->copy()->endOfWeek(); + + $current = $startOfCalendar->copy(); + $this->calendarDays = []; + + while ($current <= $endOfCalendar) { + $this->calendarDays[] = [ + 'date' => $current->format('Y-m-d'), + 'day' => $current->day, + 'isCurrentMonth' => $current->month === $this->currentMonth, + 'isToday' => $current->isToday(), + 'isSelected' => $current->format('Y-m-d') === $this->selectedDate, + 'dayName' => $current->format('D'), + ]; + $current->addDay(); + } + } + + private function generateWeekCalendar() + { + $startOfWeek = $this->currentDate->copy()->startOfWeek(); + $this->calendarDays = []; + + for ($i = 0; $i < 7; $i++) { + $date = $startOfWeek->copy()->addDays($i); + $this->calendarDays[] = [ + 'date' => $date->format('Y-m-d'), + 'day' => $date->day, + 'isCurrentMonth' => true, + 'isToday' => $date->isToday(), + 'isSelected' => $date->format('Y-m-d') === $this->selectedDate, + 'dayName' => $date->format('l'), + 'fullDate' => $date->format('M j'), + ]; + } + } + + private function generateDayCalendar() + { + $this->calendarDays = [[ + 'date' => $this->currentDate->format('Y-m-d'), + 'day' => $this->currentDate->day, + 'isCurrentMonth' => true, + 'isToday' => $this->currentDate->isToday(), + 'isSelected' => true, + 'dayName' => $this->currentDate->format('l'), + 'fullDate' => $this->currentDate->format('F j, Y'), + ]]; + } + + private function loadAppointments() + { + $query = Appointment::with(['customer', 'vehicle', 'assignedTechnician']); + + switch ($this->viewType) { + case 'month': + $startDate = $this->currentDate->copy()->startOfMonth(); + $endDate = $this->currentDate->copy()->endOfMonth(); + break; + case 'week': + $startDate = $this->currentDate->copy()->startOfWeek(); + $endDate = $this->currentDate->copy()->endOfWeek(); + break; + case 'day': + $startDate = $this->currentDate->copy()->startOfDay(); + $endDate = $this->currentDate->copy()->endOfDay(); + break; + } + + $query->whereBetween('scheduled_datetime', [$startDate, $endDate]); + + if ($this->selectedTechnician) { + $query->where('assigned_technician_id', $this->selectedTechnician); + } + + $appointments = $query->orderBy('scheduled_datetime')->get(); + + // Group appointments by date and convert to array for blade template + $this->appointments = $appointments->groupBy(function ($appointment) { + return $appointment->scheduled_datetime->format('Y-m-d'); + })->map(function ($dayAppointments) { + return $dayAppointments->map(function ($appointment) { + return [ + 'id' => $appointment->id, + 'scheduled_datetime' => $appointment->scheduled_datetime->toISOString(), + 'service_requested' => $appointment->service_requested, + 'status' => $appointment->status, + 'status_color' => $appointment->status_color, + 'customer' => [ + 'first_name' => $appointment->customer->first_name ?? '', + 'last_name' => $appointment->customer->last_name ?? '', + ], + 'assigned_technician' => [ + 'first_name' => $appointment->assignedTechnician->first_name ?? '', + 'last_name' => $appointment->assignedTechnician->last_name ?? '', + ], + ]; + })->toArray(); + })->toArray(); + } + + public function getTimeSlots() + { + $slots = []; + $start = 8; // 8 AM + $end = 18; // 6 PM + + for ($hour = $start; $hour < $end; $hour++) { + $slots[] = [ + 'time' => sprintf('%02d:00', $hour), + 'label' => Carbon::createFromTime($hour, 0)->format('g:i A'), + ]; + $slots[] = [ + 'time' => sprintf('%02d:30', $hour), + 'label' => Carbon::createFromTime($hour, 30)->format('g:i A'), + ]; + } + + return $slots; + } + + public function getCurrentPeriodLabel() + { + switch ($this->viewType) { + case 'month': + return $this->currentDate->format('F Y'); + case 'week': + $start = $this->currentDate->copy()->startOfWeek(); + $end = $this->currentDate->copy()->endOfWeek(); + return $start->format('M j') . ' - ' . $end->format('M j, Y'); + case 'day': + return $this->currentDate->format('l, F j, Y'); + } + } + + public function render() + { + return view('livewire.appointments.calendar', [ + 'currentPeriodLabel' => $this->getCurrentPeriodLabel(), + 'timeSlots' => $this->getTimeSlots(), + 'appointmentCount' => collect($this->appointments)->flatten(1)->count(), + ]); + } +} diff --git a/app/Livewire/Appointments/Create.php b/app/Livewire/Appointments/Create.php new file mode 100644 index 0000000..c87e9a5 --- /dev/null +++ b/app/Livewire/Appointments/Create.php @@ -0,0 +1,229 @@ + 'Maintenance', + 'repair' => 'Repair', + 'inspection' => 'Inspection', + 'estimate' => 'Estimate', + 'pickup' => 'Pickup', + 'delivery' => 'Delivery' + ]; + + public $durationOptions = [ + 30 => '30 minutes', + 60 => '1 hour', + 90 => '1.5 hours', + 120 => '2 hours', + 150 => '2.5 hours', + 180 => '3 hours', + 240 => '4 hours', + ]; + + protected $rules = [ + 'customer_id' => 'required|exists:customers,id', + 'vehicle_id' => 'required|exists:vehicles,id', + 'scheduled_date' => 'required|date|after_or_equal:today', + 'scheduled_time' => 'required', + 'estimated_duration_minutes' => 'required|integer|min:15|max:480', + 'appointment_type' => 'required|in:maintenance,repair,inspection,estimate,pickup,delivery', + 'service_requested' => 'required|string|max:500', + 'customer_notes' => 'nullable|string|max:1000', + 'internal_notes' => 'nullable|string|max:1000', + ]; + + protected $messages = [ + 'customer_id.required' => 'Please select a customer.', + 'vehicle_id.required' => 'Please select a vehicle.', + 'scheduled_date.required' => 'Please select an appointment date.', + 'scheduled_date.after_or_equal' => 'Appointment date cannot be in the past.', + 'scheduled_time.required' => 'Please select an appointment time.', + 'service_requested.required' => 'Please describe the service requested.', + ]; + + public function mount() + { + $this->loadInitialData(); + $this->scheduled_date = Carbon::tomorrow()->format('Y-m-d'); + } + + public function loadInitialData() + { + $this->customers = Customer::orderBy('first_name')->orderBy('last_name')->get(); + $this->technicians = Technician::where('status', 'active')->orderBy('first_name')->orderBy('last_name')->get(); + } + + public function updatedCustomerId() + { + if ($this->customer_id) { + $this->vehicles = Vehicle::where('customer_id', $this->customer_id)->get(); + $this->vehicle_id = ''; + } else { + $this->vehicles = []; + $this->vehicle_id = ''; + } + } + + public function updatedScheduledDate() + { + if ($this->scheduled_date) { + $this->loadAvailableTimeSlots(); + } + } + + public function updatedAssignedTechnicianId() + { + if ($this->scheduled_date) { + $this->loadAvailableTimeSlots(); + } + } + + public function loadAvailableTimeSlots() + { + $date = Carbon::parse($this->scheduled_date); + $technicianId = $this->assigned_technician_id; + + // Generate time slots from 8 AM to 5 PM + $slots = []; + $startTime = $date->copy()->setTime(8, 0); + $endTime = $date->copy()->setTime(17, 0); + + while ($startTime->lt($endTime)) { + $timeSlot = $startTime->format('H:i'); + + // Check if this time slot is available for the technician + $isAvailable = true; + if ($technicianId) { + $startDateTime = Carbon::parse($this->scheduled_date . ' ' . $timeSlot); + $endDateTime = $startDateTime->copy()->addMinutes($this->estimated_duration_minutes); + + $conflictingAppointments = Appointment::where('assigned_technician_id', $technicianId) + ->where(function($query) use ($startDateTime, $endDateTime) { + $query->where(function($q) use ($startDateTime, $endDateTime) { + // Check if new appointment overlaps with existing ones + $q->where(function($subQ) use ($startDateTime, $endDateTime) { + // New appointment starts during existing appointment + $subQ->where('scheduled_datetime', '<=', $startDateTime) + ->whereRaw('DATE_ADD(scheduled_datetime, INTERVAL estimated_duration_minutes MINUTE) > ?', [$startDateTime]); + })->orWhere(function($subQ) use ($startDateTime, $endDateTime) { + // New appointment ends during existing appointment + $subQ->where('scheduled_datetime', '<', $endDateTime) + ->where('scheduled_datetime', '>=', $startDateTime); + }); + }); + }) + ->exists(); + + $isAvailable = !$conflictingAppointments; + } + + if ($isAvailable) { + $slots[] = [ + 'value' => $timeSlot, + 'label' => $startTime->format('g:i A'), + ]; + } + + $startTime->addMinutes(30); + } + + $this->availableTimeSlots = $slots; + } + + public function save() + { + $this->validate(); + + try { + // Check for conflicts one more time + $scheduledDateTime = Carbon::parse($this->scheduled_date . ' ' . $this->scheduled_time); + $endDateTime = $scheduledDateTime->copy()->addMinutes($this->estimated_duration_minutes); + + if ($this->assigned_technician_id) { + $conflicts = Appointment::where('assigned_technician_id', $this->assigned_technician_id) + ->where(function($query) use ($scheduledDateTime, $endDateTime) { + $query->where(function($q) use ($scheduledDateTime, $endDateTime) { + // Check if new appointment overlaps with existing ones + $q->where(function($subQ) use ($scheduledDateTime, $endDateTime) { + // New appointment starts during existing appointment + $subQ->where('scheduled_datetime', '<=', $scheduledDateTime) + ->whereRaw('DATE_ADD(scheduled_datetime, INTERVAL estimated_duration_minutes MINUTE) > ?', [$scheduledDateTime]); + })->orWhere(function($subQ) use ($scheduledDateTime, $endDateTime) { + // New appointment ends during existing appointment + $subQ->where('scheduled_datetime', '<', $endDateTime) + ->where('scheduled_datetime', '>=', $scheduledDateTime); + })->orWhere(function($subQ) use ($scheduledDateTime, $endDateTime) { + // New appointment completely contains existing appointment + $subQ->where('scheduled_datetime', '>=', $scheduledDateTime) + ->whereRaw('DATE_ADD(scheduled_datetime, INTERVAL estimated_duration_minutes MINUTE) <= ?', [$endDateTime]); + }); + }); + }) + ->exists(); + + if ($conflicts) { + $this->addError('scheduled_time', 'This time slot conflicts with another appointment for the selected technician.'); + return; + } + } + + // Combine date and time into scheduled_datetime + $scheduledDateTime = Carbon::parse($this->scheduled_date . ' ' . $this->scheduled_time); + + $appointment = Appointment::create([ + 'customer_id' => $this->customer_id, + 'vehicle_id' => $this->vehicle_id, + 'assigned_technician_id' => $this->assigned_technician_id ?: null, + 'scheduled_datetime' => $scheduledDateTime, + 'estimated_duration_minutes' => $this->estimated_duration_minutes, + 'appointment_type' => $this->appointment_type, + 'service_requested' => $this->service_requested, + 'customer_notes' => $this->customer_notes, + 'internal_notes' => $this->internal_notes, + 'status' => 'scheduled', + 'created_by' => auth()->id(), + ]); + + session()->flash('message', 'Appointment scheduled successfully!'); + return redirect()->route('appointments.index'); + + } catch (\Exception $e) { + $this->addError('general', 'Error creating appointment: ' . $e->getMessage()); + } + } + + public function render() + { + return view('livewire.appointments.create')->layout('components.layouts.app', [ + 'title' => 'Schedule Appointment' + ]); + } +} diff --git a/app/Livewire/Appointments/Form.php b/app/Livewire/Appointments/Form.php new file mode 100644 index 0000000..abcd97b --- /dev/null +++ b/app/Livewire/Appointments/Form.php @@ -0,0 +1,204 @@ + 'Maintenance', + 'repair' => 'Repair', + 'inspection' => 'Inspection', + 'estimate' => 'Estimate', + 'pickup' => 'Pickup', + 'delivery' => 'Delivery' + ]; + + public $durationOptions = [ + 30 => '30 minutes', + 60 => '1 hour', + 90 => '1.5 hours', + 120 => '2 hours', + 180 => '3 hours', + 240 => '4 hours', + 300 => '5 hours', + 360 => '6 hours', + 480 => '8 hours' + ]; + + protected $rules = [ + 'customer_id' => 'required|exists:customers,id', + 'vehicle_id' => 'required|exists:vehicles,id', + 'assigned_technician_id' => 'nullable|exists:technicians,id', + 'scheduled_date' => 'required|date|after_or_equal:today', + 'scheduled_time' => 'required|date_format:H:i', + 'estimated_duration_minutes' => 'required|integer|min:15|max:480', + 'appointment_type' => 'required|in:maintenance,repair,inspection,estimate,pickup,delivery', + 'service_requested' => 'required|string|max:1000', + 'customer_notes' => 'nullable|string|max:1000', + 'internal_notes' => 'nullable|string|max:1000' + ]; + + public function mount($appointment = null) + { + $this->customers = Customer::orderBy('first_name')->get(); + $this->technicians = Technician::where('status', 'active')->orderBy('first_name')->get(); + + if ($appointment) { + $this->appointment = $appointment; + $this->editing = true; + $this->loadAppointmentData(); + } else { + $this->scheduled_date = now()->addDay()->format('Y-m-d'); + $this->scheduled_time = '09:00'; + } + } + + public function loadAppointmentData() + { + $this->customer_id = $this->appointment->customer_id; + $this->vehicle_id = $this->appointment->vehicle_id; + $this->assigned_technician_id = $this->appointment->assigned_technician_id; + $this->scheduled_date = $this->appointment->scheduled_datetime->format('Y-m-d'); + $this->scheduled_time = $this->appointment->scheduled_datetime->format('H:i'); + $this->estimated_duration_minutes = $this->appointment->estimated_duration_minutes; + $this->appointment_type = $this->appointment->appointment_type; + $this->service_requested = $this->appointment->service_requested; + $this->customer_notes = $this->appointment->customer_notes; + $this->internal_notes = $this->appointment->internal_notes; + + $this->loadVehicles(); + } + + public function updatedCustomerId() + { + $this->vehicle_id = ''; + $this->loadVehicles(); + } + + public function updatedScheduledDate() + { + $this->checkTimeSlotAvailability(); + } + + public function updatedScheduledTime() + { + $this->checkTimeSlotAvailability(); + } + + public function updatedAssignedTechnicianId() + { + $this->checkTimeSlotAvailability(); + } + + public function loadVehicles() + { + if ($this->customer_id) { + $this->vehicles = Vehicle::where('customer_id', $this->customer_id)->get(); + } else { + $this->vehicles = []; + } + } + + public function checkTimeSlotAvailability() + { + if (!$this->scheduled_date || !$this->scheduled_time || !$this->assigned_technician_id) { + return; + } + + $scheduledDateTime = Carbon::parse($this->scheduled_date . ' ' . $this->scheduled_time); + $endDateTime = $scheduledDateTime->copy()->addMinutes($this->estimated_duration_minutes); + + // Check for conflicts + $conflicts = Appointment::where('assigned_technician_id', $this->assigned_technician_id) + ->where('status', '!=', 'cancelled') + ->where(function ($query) use ($scheduledDateTime, $endDateTime) { + $query->whereBetween('scheduled_datetime', [$scheduledDateTime, $endDateTime]) + ->orWhere(function ($q) use ($scheduledDateTime, $endDateTime) { + $q->where('scheduled_datetime', '<=', $scheduledDateTime) + ->whereRaw('DATE_ADD(scheduled_datetime, INTERVAL estimated_duration_minutes MINUTE) > ?', [$scheduledDateTime]); + }); + }); + + if ($this->editing && $this->appointment) { + $conflicts->where('id', '!=', $this->appointment->id); + } + + if ($conflicts->exists()) { + $this->addError('scheduled_time', 'This time slot conflicts with another appointment for the selected technician.'); + } else { + $this->resetErrorBag('scheduled_time'); + } + } + + public function save() + { + $this->validate(); + + $scheduledDateTime = Carbon::parse($this->scheduled_date . ' ' . $this->scheduled_time); + + $data = [ + 'customer_id' => $this->customer_id, + 'vehicle_id' => $this->vehicle_id, + 'assigned_technician_id' => $this->assigned_technician_id ?: null, + 'scheduled_datetime' => $scheduledDateTime, + 'estimated_duration_minutes' => $this->estimated_duration_minutes, + 'appointment_type' => $this->appointment_type, + 'service_requested' => $this->service_requested, + 'customer_notes' => $this->customer_notes, + 'internal_notes' => $this->internal_notes, + 'status' => 'scheduled' + ]; + + if ($this->editing && $this->appointment) { + $this->appointment->update($data); + session()->flash('message', 'Appointment updated successfully!'); + } else { + Appointment::create($data); + session()->flash('message', 'Appointment scheduled successfully!'); + } + + $this->dispatch('appointment-saved'); + $this->closeModal(); + } + + public function closeModal() + { + $this->showModal = false; + $this->dispatch('close-appointment-form'); + } + + public function render() + { + return view('livewire.appointments.form'); + } +} diff --git a/app/Livewire/Appointments/Index.php b/app/Livewire/Appointments/Index.php new file mode 100644 index 0000000..66d7635 --- /dev/null +++ b/app/Livewire/Appointments/Index.php @@ -0,0 +1,237 @@ + ['except' => ''], + 'statusFilter' => ['except' => ''], + 'technicianFilter' => ['except' => ''], + 'dateFilter' => ['except' => ''], + 'typeFilter' => ['except' => ''], + 'view' => ['except' => 'list'] + ]; + + public function mount() + { + $this->selectedDate = now()->format('Y-m-d'); + } + + public function updatingSearch() + { + $this->resetPage(); + } + + public function updatingStatusFilter() + { + $this->resetPage(); + } + + public function updatingTechnicianFilter() + { + $this->resetPage(); + } + + public function updatingDateFilter() + { + $this->resetPage(); + } + + public function updatingTypeFilter() + { + $this->resetPage(); + } + + public function setView($view) + { + $this->view = $view; + } + + public function clearFilters() + { + $this->search = ''; + $this->statusFilter = ''; + $this->technicianFilter = ''; + $this->dateFilter = ''; + $this->typeFilter = ''; + $this->resetPage(); + } + + public function createAppointment() + { + $this->selectedAppointment = null; + $this->showForm = true; + } + + public function editAppointment($appointmentId) + { + $this->selectedAppointment = Appointment::find($appointmentId); + $this->showForm = true; + } + + #[On('appointment-saved')] + public function refreshAppointments() + { + $this->showForm = false; + $this->resetPage(); + } + + #[On('close-appointment-form')] + public function closeForm() + { + $this->showForm = false; + } + + #[On('appointment-updated')] + public function refreshData() + { + // This will trigger a re-render + } + + public function confirmAppointment($appointmentId) + { + $appointment = Appointment::find($appointmentId); + if ($appointment && $appointment->confirm()) { + session()->flash('message', 'Appointment confirmed successfully!'); + $this->dispatch('appointment-updated'); + } + } + + public function checkInAppointment($appointmentId) + { + $appointment = Appointment::find($appointmentId); + if ($appointment && $appointment->checkIn()) { + session()->flash('message', 'Customer checked in successfully!'); + $this->dispatch('appointment-updated'); + } + } + + public function completeAppointment($appointmentId) + { + $appointment = Appointment::find($appointmentId); + if ($appointment && $appointment->complete()) { + session()->flash('message', 'Appointment completed successfully!'); + $this->dispatch('appointment-updated'); + } + } + + public function cancelAppointment($appointmentId) + { + $appointment = Appointment::find($appointmentId); + if ($appointment && $appointment->cancel()) { + session()->flash('message', 'Appointment cancelled successfully!'); + $this->dispatch('appointment-updated'); + } + } + + public function markNoShow($appointmentId) + { + $appointment = Appointment::find($appointmentId); + if ($appointment && $appointment->markNoShow()) { + session()->flash('message', 'Appointment marked as no-show.'); + $this->dispatch('appointment-updated'); + } + } + + public function getAppointmentsProperty() + { + $query = Appointment::query() + ->with(['customer', 'vehicle', 'assignedTechnician']) + ->when($this->search, function ($q) { + $q->whereHas('customer', function ($customer) { + $customer->where('first_name', 'like', '%' . $this->search . '%') + ->orWhere('last_name', 'like', '%' . $this->search . '%') + ->orWhere('email', 'like', '%' . $this->search . '%'); + })->orWhereHas('vehicle', function ($vehicle) { + $vehicle->where('make', 'like', '%' . $this->search . '%') + ->orWhere('model', 'like', '%' . $this->search . '%') + ->orWhere('license_plate', 'like', '%' . $this->search . '%'); + })->orWhere('service_requested', 'like', '%' . $this->search . '%'); + }) + ->when($this->statusFilter, function ($q) { + $q->where('status', $this->statusFilter); + }) + ->when($this->technicianFilter, function ($q) { + $q->where('assigned_technician_id', $this->technicianFilter); + }) + ->when($this->typeFilter, function ($q) { + $q->where('appointment_type', $this->typeFilter); + }) + ->when($this->dateFilter, function ($q) { + switch ($this->dateFilter) { + case 'today': + $q->whereDate('scheduled_datetime', today()); + break; + case 'tomorrow': + $q->whereDate('scheduled_datetime', today()->addDay()); + break; + case 'this_week': + $q->whereBetween('scheduled_datetime', [ + now()->startOfWeek(), + now()->endOfWeek() + ]); + break; + case 'next_week': + $q->whereBetween('scheduled_datetime', [ + now()->addWeek()->startOfWeek(), + now()->addWeek()->endOfWeek() + ]); + break; + case 'overdue': + $q->where('status', 'scheduled') + ->where('scheduled_datetime', '<', now()); + break; + } + }) + ->orderBy('scheduled_datetime', 'asc'); + + return $query->paginate(15); + } + + public function getTechniciansProperty() + { + return Technician::where('status', 'active') + ->orderBy('first_name') + ->get(); + } + + public function getTodayStatsProperty() + { + $today = today(); + return [ + 'total' => Appointment::whereDate('scheduled_datetime', $today)->count(), + 'confirmed' => Appointment::whereDate('scheduled_datetime', $today)->where('status', 'confirmed')->count(), + 'in_progress' => Appointment::whereDate('scheduled_datetime', $today)->where('status', 'in_progress')->count(), + 'completed' => Appointment::whereDate('scheduled_datetime', $today)->where('status', 'completed')->count(), + ]; + } + + public function render() + { + return view('livewire.appointments.index', [ + 'appointments' => $this->appointments, + 'technicians' => $this->technicians, + 'todayStats' => $this->todayStats, + ]); + } +} diff --git a/app/Livewire/Appointments/TimeSlots.php b/app/Livewire/Appointments/TimeSlots.php new file mode 100644 index 0000000..5796f24 --- /dev/null +++ b/app/Livewire/Appointments/TimeSlots.php @@ -0,0 +1,284 @@ +selectedDate = $date ?: now()->addDay()->format('Y-m-d'); + $this->selectedTechnician = $technicianId ?: ''; + $this->serviceDuration = $duration; + $this->technicians = Technician::where('status', 'active')->orderBy('first_name')->get(); + + $this->generateTimeSlots(); + $this->loadBookedSlots(); + $this->calculateAvailableSlots(); + } + + public function updatedSelectedDate() + { + $this->selectedSlot = ''; + $this->generateTimeSlots(); + $this->loadBookedSlots(); + $this->calculateAvailableSlots(); + $this->dispatch('date-changed', date: $this->selectedDate); + } + + public function updatedSelectedTechnician() + { + $this->selectedSlot = ''; + $this->loadBookedSlots(); + $this->calculateAvailableSlots(); + $this->dispatch('technician-changed', technicianId: $this->selectedTechnician); + } + + public function updatedServiceDuration() + { + $this->selectedSlot = ''; + $this->calculateAvailableSlots(); + } + + public function selectSlot($time) + { + $this->selectedSlot = $time; + $this->dispatch('slot-selected', [ + 'date' => $this->selectedDate, + 'time' => $time, + 'technician_id' => $this->selectedTechnician, + 'duration' => $this->serviceDuration + ]); + } + + public function clearSelection() + { + $this->selectedSlot = ''; + $this->dispatch('slot-cleared'); + } + + private function generateTimeSlots() + { + $this->timeSlots = []; + $date = Carbon::parse($this->selectedDate); + + // Don't show slots for past dates + if ($date->isPast() && !$date->isToday()) { + return; + } + + $start = $date->copy()->setTimeFromTimeString($this->businessStart); + $end = $date->copy()->setTimeFromTimeString($this->businessEnd); + $lunchStart = $date->copy()->setTimeFromTimeString($this->lunchStart); + $lunchEnd = $date->copy()->setTimeFromTimeString($this->lunchEnd); + + $current = $start->copy(); + + while ($current < $end) { + $timeString = $current->format('H:i'); + + // Skip lunch time + if ($current >= $lunchStart && $current < $lunchEnd) { + $current->addMinutes($this->slotInterval); + continue; + } + + // Skip past times for today + if ($date->isToday() && $current <= now()) { + $current->addMinutes($this->slotInterval); + continue; + } + + $this->timeSlots[] = [ + 'time' => $timeString, + 'label' => $current->format('g:i A'), + 'datetime' => $current->copy(), + ]; + + $current->addMinutes($this->slotInterval); + } + } + + private function loadBookedSlots() + { + $query = Appointment::whereDate('scheduled_datetime', $this->selectedDate) + ->whereNotIn('status', ['cancelled', 'no_show']); + + if ($this->selectedTechnician) { + $query->where('assigned_technician_id', $this->selectedTechnician); + } + + $appointments = $query->get(); + + $this->bookedSlots = []; + + foreach ($appointments as $appointment) { + $startTime = Carbon::parse($appointment->scheduled_datetime); + $endTime = $startTime->copy()->addMinutes($appointment->estimated_duration_minutes); + + // Mark all slots that overlap with this appointment as booked + $current = $startTime->copy(); + while ($current < $endTime) { + $this->bookedSlots[] = [ + 'time' => $current->format('H:i'), + 'appointment_id' => $appointment->id, + 'customer_name' => $appointment->customer->first_name . ' ' . $appointment->customer->last_name, + 'service' => $appointment->service_requested, + 'status' => $appointment->status, + ]; + $current->addMinutes($this->slotInterval); + } + } + } + + private function calculateAvailableSlots() + { + $this->availableSlots = []; + $bookedTimes = collect($this->bookedSlots)->pluck('time')->toArray(); + + foreach ($this->timeSlots as $slot) { + $isAvailable = true; + $slotStart = Carbon::parse($this->selectedDate . ' ' . $slot['time']); + $slotEnd = $slotStart->copy()->addMinutes($this->serviceDuration); + + // Check if this slot and required duration would conflict with any booked slot + $checkTime = $slotStart->copy(); + while ($checkTime < $slotEnd) { + if (in_array($checkTime->format('H:i'), $bookedTimes)) { + $isAvailable = false; + break; + } + $checkTime->addMinutes($this->slotInterval); + } + + // Check if slot extends beyond business hours + $businessEnd = Carbon::parse($this->selectedDate . ' ' . $this->businessEnd); + if ($slotEnd > $businessEnd) { + $isAvailable = false; + } + + // Check if slot conflicts with lunch time + $lunchStart = Carbon::parse($this->selectedDate . ' ' . $this->lunchStart); + $lunchEnd = Carbon::parse($this->selectedDate . ' ' . $this->lunchEnd); + if ($slotStart < $lunchEnd && $slotEnd > $lunchStart) { + $isAvailable = false; + } + + if ($isAvailable) { + $this->availableSlots[] = $slot['time']; + } + } + } + + public function getSlotStatus($time) + { + $bookedSlot = collect($this->bookedSlots)->firstWhere('time', $time); + + if ($bookedSlot) { + return [ + 'status' => 'booked', + 'data' => $bookedSlot + ]; + } + + if (in_array($time, $this->availableSlots)) { + return [ + 'status' => 'available', + 'data' => null + ]; + } + + return [ + 'status' => 'unavailable', + 'data' => null + ]; + } + + public function getAvailableSlotsForApi() + { + return collect($this->timeSlots) + ->filter(function ($slot) { + return in_array($slot['time'], $this->availableSlots); + }) + ->values() + ->toArray(); + } + + public function getBookedSlotsInfo() + { + return collect($this->bookedSlots) + ->groupBy('time') + ->map(function ($slots) { + return $slots->first(); + }) + ->values() + ->toArray(); + } + + public function isSlotSelected($time) + { + return $this->selectedSlot === $time; + } + + public function getNextAvailableDate() + { + $date = Carbon::parse($this->selectedDate); + $maxDays = 30; // Look ahead 30 days + + for ($i = 1; $i <= $maxDays; $i++) { + $checkDate = $date->copy()->addDays($i); + + // Skip weekends (assuming business doesn't operate on weekends) + if ($checkDate->isWeekend()) { + continue; + } + + // Generate slots for this date + $tempDate = $this->selectedDate; + $this->selectedDate = $checkDate->format('Y-m-d'); + $this->generateTimeSlots(); + $this->loadBookedSlots(); + $this->calculateAvailableSlots(); + + if (!empty($this->availableSlots)) { + $nextDate = $this->selectedDate; + $this->selectedDate = $tempDate; // Restore original date + $this->generateTimeSlots(); + $this->loadBookedSlots(); + $this->calculateAvailableSlots(); + return $nextDate; + } + + $this->selectedDate = $tempDate; // Restore original date + } + + return null; + } + + public function render() + { + return view('livewire.appointments.time-slots', [ + 'nextAvailableDate' => $this->getNextAvailableDate(), + ]); + } +} diff --git a/app/Livewire/CustomerPortal/EstimateView.php b/app/Livewire/CustomerPortal/EstimateView.php new file mode 100644 index 0000000..cb37279 --- /dev/null +++ b/app/Livewire/CustomerPortal/EstimateView.php @@ -0,0 +1,73 @@ +jobCard = $jobCard->load(['customer', 'vehicle']); + $this->estimate = $estimate->load(['lineItems', 'diagnosis']); + + // Mark estimate as viewed + if ($estimate->status === 'sent') { + $estimate->update(['status' => 'viewed']); + } + } + + public function approveEstimate() + { + $workflowService = app(WorkflowService::class); + + $this->estimate->update([ + 'customer_approval_status' => 'approved', + 'customer_approved_at' => now(), + 'customer_approval_method' => 'portal', + 'status' => 'approved', + ]); + + $this->jobCard->update(['status' => 'estimate_approved']); + + // Notify relevant staff + $workflowService->notifyStaffOfApproval($this->estimate); + + session()->flash('message', 'Estimate approved successfully! We will begin work on your vehicle soon.'); + + return redirect()->route('customer-portal.status', $this->jobCard); + } + + public function rejectEstimate() + { + $this->validate([ + 'customerComments' => 'required|string|max:1000' + ]); + + $this->estimate->update([ + 'customer_approval_status' => 'rejected', + 'customer_approved_at' => now(), + 'customer_approval_method' => 'portal', + 'status' => 'rejected', + 'notes' => $this->customerComments, + ]); + + $this->jobCard->update(['status' => 'estimate_rejected']); + + session()->flash('message', 'Estimate rejected. Our team will contact you to discuss alternatives.'); + + return redirect()->route('customer-portal.status', $this->jobCard); + } + + public function render() + { + return view('livewire.customer-portal.estimate-view'); + } +} diff --git a/app/Livewire/CustomerPortal/JobStatus.php b/app/Livewire/CustomerPortal/JobStatus.php new file mode 100644 index 0000000..fe4c749 --- /dev/null +++ b/app/Livewire/CustomerPortal/JobStatus.php @@ -0,0 +1,44 @@ +jobCard = $jobCard->load([ + 'customer', + 'vehicle', + 'serviceAdvisor', + 'vehicleInspections', + 'diagnoses', + 'estimates', + 'workOrders.tasks', + 'timesheets' + ]); + + $workflowService = app(WorkflowService::class); + $this->workflowProgress = $workflowService->getWorkflowProgress($this->jobCard); + } + + public function refreshStatus() + { + $this->jobCard->refresh(); + $workflowService = app(WorkflowService::class); + $this->workflowProgress = $workflowService->getWorkflowProgress($this->jobCard); + + session()->flash('message', 'Status updated!'); + } + + public function render() + { + return view('livewire.customer-portal.job-status'); + } +} diff --git a/app/Livewire/Customers/Create.php b/app/Livewire/Customers/Create.php new file mode 100644 index 0000000..de03f9d --- /dev/null +++ b/app/Livewire/Customers/Create.php @@ -0,0 +1,75 @@ + 'required|string|max:255', + 'last_name' => 'required|string|max:255', + 'email' => 'required|email|unique:customers,email', + 'phone' => 'required|string|max:255', + 'secondary_phone' => 'nullable|string|max:255', + 'address' => 'required|string|max:500', + 'city' => 'required|string|max:255', + 'state' => 'required|string|max:2', + 'zip_code' => 'required|string|max:10', + 'notes' => 'nullable|string|max:1000', + 'status' => 'required|in:active,inactive', + ]; + + protected $messages = [ + 'first_name.required' => 'First name is required.', + 'last_name.required' => 'Last name is required.', + 'email.required' => 'Email address is required.', + 'email.email' => 'Please enter a valid email address.', + 'email.unique' => 'This email address is already registered.', + 'phone.required' => 'Phone number is required.', + 'address.required' => 'Address is required.', + 'city.required' => 'City is required.', + 'state.required' => 'State is required.', + 'zip_code.required' => 'ZIP code is required.', + ]; + + public function save() + { + $this->validate(); + + $customer = Customer::create([ + 'first_name' => $this->first_name, + 'last_name' => $this->last_name, + 'email' => $this->email, + 'phone' => $this->phone, + 'secondary_phone' => $this->secondary_phone, + 'address' => $this->address, + 'city' => $this->city, + 'state' => $this->state, + 'zip_code' => $this->zip_code, + 'notes' => $this->notes, + 'status' => $this->status, + ]); + + session()->flash('success', 'Customer created successfully!'); + return redirect()->route('customers.show', $customer); + } + + public function render() + { + return view('livewire.customers.create'); + } +} diff --git a/app/Livewire/Customers/Edit.php b/app/Livewire/Customers/Edit.php new file mode 100644 index 0000000..e6d4517 --- /dev/null +++ b/app/Livewire/Customers/Edit.php @@ -0,0 +1,102 @@ +customer = $customer; + $this->first_name = $customer->first_name; + $this->last_name = $customer->last_name; + $this->email = $customer->email; + $this->phone = $customer->phone; + $this->secondary_phone = $customer->secondary_phone; + $this->address = $customer->address; + $this->city = $customer->city; + $this->state = $customer->state; + $this->zip_code = $customer->zip_code; + $this->notes = $customer->notes; + $this->status = $customer->status; + } + + public function updateCustomer() + { + // Update validation rules to exclude current customer's email + $this->validate([ + 'first_name' => 'required|string|max:255', + 'last_name' => 'required|string|max:255', + 'email' => 'required|email|max:255|unique:customers,email,' . $this->customer->id, + 'phone' => 'required|string|max:20', + 'secondary_phone' => 'nullable|string|max:20', + 'address' => 'required|string|max:500', + 'city' => 'required|string|max:255', + 'state' => 'required|string|max:255', + 'zip_code' => 'required|string|max:10', + 'notes' => 'nullable|string|max:1000', + 'status' => 'required|in:active,inactive', + ]); + + $this->customer->update([ + 'first_name' => $this->first_name, + 'last_name' => $this->last_name, + 'email' => $this->email, + 'phone' => $this->phone, + 'secondary_phone' => $this->secondary_phone, + 'address' => $this->address, + 'city' => $this->city, + 'state' => $this->state, + 'zip_code' => $this->zip_code, + 'notes' => $this->notes, + 'status' => $this->status, + ]); + + session()->flash('success', 'Customer updated successfully!'); + + return $this->redirect('/customers/' . $this->customer->id, navigate: true); + } + + public function render() + { + return view('livewire.customers.edit'); + } +} diff --git a/app/Livewire/Customers/Index.php b/app/Livewire/Customers/Index.php new file mode 100644 index 0000000..1136b3e --- /dev/null +++ b/app/Livewire/Customers/Index.php @@ -0,0 +1,83 @@ + ['except' => ''], + 'status' => ['except' => ''], + 'sortBy' => ['except' => 'created_at'], + 'sortDirection' => ['except' => 'desc'], + ]; + + public function updatingSearch() + { + $this->resetPage(); + } + + public function updatingStatus() + { + $this->resetPage(); + } + + public function sortBy($field) + { + if ($this->sortBy === $field) { + $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc'; + } else { + $this->sortBy = $field; + $this->sortDirection = 'asc'; + } + } + + public function deleteCustomer($customerId) + { + $customer = Customer::findOrFail($customerId); + + // Check if customer has any service orders or vehicles + if ($customer->serviceOrders()->count() > 0 || $customer->vehicles()->count() > 0) { + session()->flash('error', 'Cannot delete customer with existing vehicles or service orders. Please remove or transfer them first.'); + return; + } + + $customerName = $customer->full_name; + $customer->delete(); + + session()->flash('success', "Customer {$customerName} has been deleted successfully."); + } + + public function render() + { + $customers = Customer::query() + ->with(['vehicles']) + ->when($this->search, function ($query) { + $query->where(function ($q) { + $q->where('first_name', 'like', '%' . $this->search . '%') + ->orWhere('last_name', 'like', '%' . $this->search . '%') + ->orWhere('email', 'like', '%' . $this->search . '%') + ->orWhere('phone', 'like', '%' . $this->search . '%'); + }); + }) + ->when($this->status, function ($query) { + $query->where('status', $this->status); + }) + ->orderBy($this->sortBy, $this->sortDirection) + ->paginate(15); + + return view('livewire.customers.index', [ + 'customers' => $customers, + ]); + } +} diff --git a/app/Livewire/Customers/Show.php b/app/Livewire/Customers/Show.php new file mode 100644 index 0000000..3642746 --- /dev/null +++ b/app/Livewire/Customers/Show.php @@ -0,0 +1,21 @@ +customer = $customer->load(['vehicles', 'serviceOrders.assignedTechnician', 'appointments']); + } + + public function render() + { + return view('livewire.customers.show'); + } +} diff --git a/app/Livewire/Dashboard/DailySchedule.php b/app/Livewire/Dashboard/DailySchedule.php new file mode 100644 index 0000000..f55b078 --- /dev/null +++ b/app/Livewire/Dashboard/DailySchedule.php @@ -0,0 +1,33 @@ +startOfDay(); + + $schedule = [ + 'appointments' => Appointment::whereDate('scheduled_datetime', $today) + ->with(['customer', 'vehicle']) + ->orderBy('scheduled_datetime') + ->get(), + 'pickups' => JobCard::where('status', 'completed') + ->whereDate('expected_completion_date', $today) + ->with(['customer', 'vehicle']) + ->get(), + 'overdue' => JobCard::where('expected_completion_date', '<', now()) + ->whereNotIn('status', ['completed', 'delivered', 'cancelled']) + ->with(['customer', 'vehicle']) + ->limit(5) + ->get(), + ]; + + return view('livewire.dashboard.daily-schedule', compact('schedule')); + } +} diff --git a/app/Livewire/Dashboard/Overview.php b/app/Livewire/Dashboard/Overview.php new file mode 100644 index 0000000..de2d6c7 --- /dev/null +++ b/app/Livewire/Dashboard/Overview.php @@ -0,0 +1,65 @@ +loadStats(); + $this->loadRecentData(); + } + + public function loadStats() + { + $this->stats = [ + 'total_customers' => Customer::where('status', 'active')->count(), + 'total_vehicles' => Vehicle::where('status', 'active')->count(), + 'pending_orders' => ServiceOrder::whereIn('status', ['pending', 'in_progress'])->count(), + 'today_appointments' => Appointment::whereDate('scheduled_datetime', today())->count(), + 'monthly_revenue' => ServiceOrder::where('status', 'completed') + ->whereMonth('completed_at', now()->month) + ->sum('total_amount'), + 'orders_this_week' => ServiceOrder::whereBetween('created_at', [ + now()->startOfWeek(), + now()->endOfWeek() + ])->count(), + ]; + } + + public function loadRecentData() + { + $this->recentServiceOrders = ServiceOrder::with(['customer', 'vehicle', 'assignedTechnician']) + ->latest() + ->take(5) + ->get(); + + $this->todayAppointments = Appointment::with(['customer', 'vehicle']) + ->whereDate('scheduled_datetime', today()) + ->orderBy('scheduled_datetime') + ->get(); + + $this->pendingOrders = ServiceOrder::with(['customer', 'vehicle']) + ->whereIn('status', ['pending', 'waiting_approval']) + ->orderBy('created_at', 'desc') + ->take(5) + ->get(); + } + + public function render() + { + return view('livewire.dashboard.overview'); + } +} diff --git a/app/Livewire/Dashboard/PerformanceMetrics.php b/app/Livewire/Dashboard/PerformanceMetrics.php new file mode 100644 index 0000000..d053c14 --- /dev/null +++ b/app/Livewire/Dashboard/PerformanceMetrics.php @@ -0,0 +1,75 @@ +startOfWeek(); + $lastWeek = now()->subWeek()->startOfWeek(); + + $metrics = [ + 'this_week' => [ + 'jobs_completed' => JobCard::whereBetween('completion_datetime', [$currentWeek, $currentWeek->copy()->endOfWeek()]) + ->where('status', 'completed') + ->count(), + 'revenue' => Estimate::whereBetween('customer_approved_at', [$currentWeek, $currentWeek->copy()->endOfWeek()]) + ->where('customer_approval_status', 'approved') + ->sum('total_amount'), + 'avg_completion_time' => $this->getAverageCompletionTime($currentWeek, $currentWeek->copy()->endOfWeek()), + 'customer_satisfaction' => 4.2, // This would come from a customer feedback system + ], + 'last_week' => [ + 'jobs_completed' => JobCard::whereBetween('completion_datetime', [$lastWeek, $lastWeek->copy()->endOfWeek()]) + ->where('status', 'completed') + ->count(), + 'revenue' => Estimate::whereBetween('customer_approved_at', [$lastWeek, $lastWeek->copy()->endOfWeek()]) + ->where('customer_approval_status', 'approved') + ->sum('total_amount'), + ], + ]; + + // Calculate growth percentages + $metrics['growth'] = [ + 'jobs' => $this->calculateGrowth($metrics['last_week']['jobs_completed'], $metrics['this_week']['jobs_completed']), + 'revenue' => $this->calculateGrowth($metrics['last_week']['revenue'], $metrics['this_week']['revenue']), + ]; + + return view('livewire.dashboard.performance-metrics', compact('metrics')); + } + + private function getAverageCompletionTime($start, $end) + { + $completedJobs = JobCard::whereBetween('completion_datetime', [$start, $end]) + ->where('status', 'completed') + ->whereNotNull('arrival_datetime') + ->whereNotNull('completion_datetime') + ->get(); + + if ($completedJobs->isEmpty()) { + return 0; + } + + $totalHours = $completedJobs->sum(function ($job) { + return $job->arrival_datetime->diffInHours($job->completion_datetime); + }); + + return round($totalHours / $completedJobs->count(), 1); + } + + private function calculateGrowth($previous, $current) + { + if ($previous == 0) { + return $current > 0 ? 100 : 0; + } + + return round((($current - $previous) / $previous) * 100, 1); + } +} diff --git a/app/Livewire/Dashboard/WorkflowOverview.php b/app/Livewire/Dashboard/WorkflowOverview.php new file mode 100644 index 0000000..b6720b7 --- /dev/null +++ b/app/Livewire/Dashboard/WorkflowOverview.php @@ -0,0 +1,130 @@ +user(); + + // Get counts based on user role + $stats = [ + 'pending_inspection' => JobCard::where('status', 'pending_inspection')->count(), + 'assigned_for_diagnosis' => JobCard::where('status', 'assigned_for_diagnosis')->count(), + 'diagnosis_in_progress' => JobCard::where('status', 'diagnosis_in_progress')->count(), + 'estimates_pending_approval' => Estimate::where('customer_approval_status', 'pending')->count(), + 'work_orders_active' => WorkOrder::whereIn('status', ['scheduled', 'in_progress'])->count(), + 'quality_inspections_pending' => JobCard::where('status', 'quality_inspection')->count(), + ]; + + // Get role-specific data + $roleSpecificData = $this->getRoleSpecificData($user); + + // Get recent activity + $recentJobCards = JobCard::with(['customer', 'vehicle']) + ->latest() + ->limit(5) + ->get(); + + return view('livewire.dashboard.workflow-overview', [ + 'stats' => $stats, + 'roleSpecificData' => $roleSpecificData, + 'recentJobCards' => $recentJobCards, + ]); + } + + private function getRoleSpecificData($user) + { + // Use the RBAC system instead of hardcoded roles + $userRoles = $user->roles->pluck('name')->toArray(); + + if ($user->hasPermission('dashboard.supervisor-view')) { + return [ + 'title' => 'Service Supervisor Dashboard', + 'tasks' => [ + 'Pending Inspections' => JobCard::where('status', 'pending_inspection')->count(), + 'Quality Inspections' => JobCard::where('status', 'quality_inspection')->count(), + 'Jobs Awaiting Assignment' => JobCard::whereNull('assigned_technician_id')->count(), + 'Overdue Jobs' => JobCard::where('estimated_completion', '<', now())->whereNotIn('status', ['completed', 'delivered'])->count(), + ], + 'priority_jobs' => JobCard::where('priority', 'urgent')->where('status', '!=', 'completed')->with(['customer', 'vehicle'])->limit(5)->get(), + ]; + } + + if ($user->hasPermission('dashboard.technician-view')) { + return [ + 'title' => 'Technician Dashboard', + 'tasks' => [ + 'My Assigned Jobs' => JobCard::where('assigned_technician_id', $user->id)->whereNotIn('status', ['completed', 'delivered'])->count(), + 'Diagnosis Pending' => JobCard::where('assigned_technician_id', $user->id)->where('status', 'assigned_for_diagnosis')->count(), + 'Work in Progress' => JobCard::where('assigned_technician_id', $user->id)->where('status', 'work_in_progress')->count(), + 'Quality Checks' => JobCard::where('assigned_technician_id', $user->id)->where('status', 'quality_inspection')->count(), + ], + 'my_jobs' => JobCard::where('assigned_technician_id', $user->id)->whereNotIn('status', ['completed', 'delivered'])->with(['customer', 'vehicle'])->limit(5)->get(), + ]; + } + + if ($user->hasPermission('dashboard.parts-view')) { + return [ + 'title' => 'Parts Manager Dashboard', + 'tasks' => [ + 'Parts Orders Pending' => Estimate::where('customer_approval_status', 'approved')->whereHas('jobCard', function($q) { + $q->where('status', 'estimate_approved'); + })->count(), + 'Estimates Awaiting Parts' => Estimate::where('status', 'pending_parts')->count(), + 'Purchase Orders Active' => 0, // Would connect to purchase order system + 'Stock Alerts' => 0, // Would connect to inventory system + ], + 'approved_estimates' => Estimate::where('customer_approval_status', 'approved')->with(['jobCard.customer', 'jobCard.vehicle'])->limit(5)->get(), + ]; + } + + if ($user->hasPermission('dashboard.advisor-view')) { + return [ + 'title' => 'Service Advisor Dashboard', + 'tasks' => [ + 'My Job Cards' => JobCard::where('service_advisor_id', $user->id)->whereNotIn('status', ['completed', 'delivered'])->count(), + 'Customer Follow-ups' => Estimate::where('status', 'sent')->whereHas('jobCard', function($q) use ($user) { + $q->where('service_advisor_id', $user->id); + })->count(), + 'Ready for Pickup' => JobCard::where('service_advisor_id', $user->id)->where('status', 'completed')->count(), + 'Overdue Estimates' => Estimate::where('created_at', '<', now()->subDays(3))->where('customer_approval_status', 'pending')->count(), + ], + 'my_customers' => JobCard::where('service_advisor_id', $user->id)->with(['customer', 'vehicle'])->limit(5)->get(), + ]; + } + + // Default dashboard for general users + return [ + 'title' => 'Dashboard Overview', + 'tasks' => [ + 'Today\'s Jobs' => JobCard::whereDate('created_at', today())->count(), + 'This Week\'s Jobs' => JobCard::whereBetween('created_at', [now()->startOfWeek(), now()->endOfWeek()])->count(), + 'Active Customers' => JobCard::distinct('customer_id')->whereMonth('created_at', now()->month)->count(), + ], + 'items' => JobCard::latest()->with(['customer', 'vehicle'])->limit(3)->get(), + ]; + } + + public function getStatusVariant($status) + { + return match($status) { + 'pending_inspection' => 'info', + 'assigned_for_diagnosis', 'diagnosis_in_progress' => 'warning', + 'estimate_sent', 'estimate_pending' => 'neutral', + 'work_in_progress' => 'primary', + 'quality_inspection' => 'secondary', + 'completed' => 'success', + 'delivered' => 'success', + 'cancelled' => 'danger', + default => 'neutral' + }; + } +} diff --git a/app/Livewire/Diagnosis/Create.php b/app/Livewire/Diagnosis/Create.php new file mode 100644 index 0000000..9c8e8f1 --- /dev/null +++ b/app/Livewire/Diagnosis/Create.php @@ -0,0 +1,707 @@ + 'General Inspection', + 'electrical_diagnosis' => 'Electrical Diagnosis', + 'engine_diagnosis' => 'Engine Diagnosis', + 'transmission_diagnosis' => 'Transmission Diagnosis', + 'brake_diagnosis' => 'Brake System Diagnosis', + 'suspension_diagnosis' => 'Suspension Diagnosis', + 'air_conditioning' => 'Air Conditioning Diagnosis', + 'computer_diagnosis' => 'Computer/ECU Diagnosis', + 'emissions_diagnosis' => 'Emissions Diagnosis', + 'noise_diagnosis' => 'Noise/Vibration Diagnosis', + ]; + + // Parts integration + public $availableParts = []; + public $partSearch = ''; + public $partSearchTerm = ''; + public $partCategoryFilter = ''; + public $filteredParts; + + // Service items integration + public $availableServiceItems = []; + public $serviceItemSearch = ''; + public $serviceSearchTerm = ''; + public $serviceCategoryFilter = ''; + public $filteredServiceItems; + + // UI state + public $showPartsSection = false; + public $showLaborSection = false; + public $showDiagnosticCodesSection = false; + public $showTestResultsSection = false; + public $showAdvancedOptions = false; + public $showTimesheetSection = true; + public $createEstimateAutomatically = true; + + protected $rules = [ + 'customer_reported_issues' => 'required|string', + 'diagnostic_findings' => 'required|string|min:20', + 'root_cause_analysis' => 'required|string|min:20', + 'recommended_repairs' => 'required|string|min:10', + 'priority_level' => 'required|in:low,medium,high,urgent', + 'estimated_repair_time' => 'required|numeric|min:0.5|max:40', + 'safety_concerns' => 'nullable|string', + 'environmental_impact' => 'nullable|string', + 'notes' => 'nullable|string', + 'photos.*' => 'nullable|image|max:5120', + 'selectedDiagnosisType' => 'required|string', + ]; + + protected $messages = [ + 'diagnostic_findings.min' => 'Please provide detailed diagnostic findings (at least 20 characters).', + 'root_cause_analysis.min' => 'Please provide a thorough root cause analysis (at least 20 characters).', + 'recommended_repairs.min' => 'Please specify the recommended repairs (at least 10 characters).', + 'estimated_repair_time.max' => 'Estimated repair time cannot exceed 40 hours. For longer repairs, consider breaking into phases.', + 'photos.*.max' => 'Each photo must be less than 5MB.', + 'selectedDiagnosisType.required' => 'Please select a diagnosis type.', + ]; + + public function mount(JobCard $jobCard) + { + $this->jobCard = $jobCard->load(['customer', 'vehicle', 'timesheets']); + $this->customer_reported_issues = $jobCard->customer_reported_issues ?? ''; + + // Initialize arrays to prevent null issues - load from session if available + $this->parts_required = session()->get("diagnosis_parts_{$jobCard->id}", []); + $this->labor_operations = session()->get("diagnosis_labor_{$jobCard->id}", []); + $this->timesheets = []; + $this->diagnostic_codes = session()->get("diagnosis_codes_{$jobCard->id}", []); + $this->test_results = []; + $this->special_tools_required = []; + + // Initialize filtered collections + $this->filteredParts = collect(); + $this->filteredServiceItems = collect(); + + // Load existing timesheets for this job card related to diagnosis + $this->loadTimesheets(); + + // Load available parts and service items + $this->loadAvailableParts(); + $this->loadAvailableServiceItems(); + + // Initialize with one empty part and labor operation for convenience if none exist + if (empty($this->parts_required)) { + $this->addPart(); + } + if (empty($this->labor_operations)) { + $this->addLaborOperation(); + } + } + + public function updatedPartsRequired() + { + // Save parts to session whenever they change + session()->put("diagnosis_parts_{$this->jobCard->id}", $this->parts_required); + } + + public function updatedLaborOperations() + { + // Save labor operations to session whenever they change + session()->put("diagnosis_labor_{$this->jobCard->id}", $this->labor_operations); + } + + public function updatedDiagnosticCodes() + { + // Save diagnostic codes to session whenever they change + session()->put("diagnosis_codes_{$this->jobCard->id}", $this->diagnostic_codes); + } + + public function loadTimesheets() + { + $this->timesheets = Timesheet::where('job_card_id', $this->jobCard->id) + ->where('entry_type', 'manual') + ->where('description', 'like', '%diagnosis%') + ->with('user') + ->orderBy('created_at', 'desc') + ->get() + ->toArray(); + } + + public function loadAvailableParts() + { + $query = Part::where('status', 'active'); + + if (!empty($this->partSearch)) { + $query->where(function ($q) { + $q->where('name', 'like', '%' . $this->partSearch . '%') + ->orWhere('part_number', 'like', '%' . $this->partSearch . '%') + ->orWhere('description', 'like', '%' . $this->partSearch . '%'); + }); + } + + $this->availableParts = $query->limit(20)->get()->toArray(); + } + + public function loadAvailableServiceItems() + { + $query = ServiceItem::query(); + + if (!empty($this->serviceItemSearch)) { + $query->where(function ($q) { + $q->where('service_name', 'like', '%' . $this->serviceItemSearch . '%') + ->orWhere('description', 'like', '%' . $this->serviceItemSearch . '%') + ->orWhere('category', 'like', '%' . $this->serviceItemSearch . '%'); + }); + } + + $this->availableServiceItems = $query->limit(20)->get()->toArray(); + } + + // Computed properties for filtered collections + public function updatedPartSearchTerm() + { + $this->updateFilteredParts(); + } + + public function updatedPartCategoryFilter() + { + $this->updateFilteredParts(); + } + + public function updateFilteredParts() + { + try { + // If no search criteria provided, return empty collection + if (empty($this->partSearchTerm) && empty($this->partCategoryFilter)) { + $this->filteredParts = collect(); + return; + } + + // Start with active parts only + $query = Part::where('status', 'active'); + + // Add search term filter if provided + if (!empty($this->partSearchTerm)) { + $searchTerm = trim($this->partSearchTerm); + $query->where(function ($q) use ($searchTerm) { + $q->where('name', 'like', '%' . $searchTerm . '%') + ->orWhere('part_number', 'like', '%' . $searchTerm . '%') + ->orWhere('description', 'like', '%' . $searchTerm . '%') + ->orWhere('manufacturer', 'like', '%' . $searchTerm . '%'); + }); + } + + // Add category filter if provided + if (!empty($this->partCategoryFilter)) { + $query->where('category', $this->partCategoryFilter); + } + + // Order by name for consistent results + $query->orderBy('name'); + + // Get results and assign to property + $this->filteredParts = $query->limit(20)->get(); + + // Log for debugging + \Log::info('Parts search executed', [ + 'search_term' => $this->partSearchTerm, + 'category_filter' => $this->partCategoryFilter, + 'results_count' => $this->filteredParts->count(), + 'results' => $this->filteredParts->pluck('name')->toArray() + ]); + + } catch (\Exception $e) { + // Log error and return empty collection + \Log::error('Error in updateFilteredParts', [ + 'error' => $e->getMessage(), + 'search_term' => $this->partSearchTerm, + 'category_filter' => $this->partCategoryFilter + ]); + + $this->filteredParts = collect(); + } + } + + public function getFilteredServiceItemsProperty() + { + $query = ServiceItem::query(); + + if (!empty($this->serviceSearchTerm)) { + $query->where(function ($q) { + $q->where('name', 'like', '%' . $this->serviceSearchTerm . '%') + ->orWhere('description', 'like', '%' . $this->serviceSearchTerm . '%'); + }); + } + + if (!empty($this->serviceCategoryFilter)) { + $query->where('category', $this->serviceCategoryFilter); + } + + return $query->limit(20)->get(); + } + + public function updatedPartSearch() + { + $this->loadAvailableParts(); + } + + public function updatedServiceSearchTerm() + { + $this->updateFilteredServiceItems(); + } + + public function updatedServiceCategoryFilter() + { + $this->updateFilteredServiceItems(); + } + + public function updateFilteredServiceItems() + { + try { + // If no search criteria provided, return empty collection + if (empty($this->serviceSearchTerm) && empty($this->serviceCategoryFilter)) { + $this->filteredServiceItems = collect(); + return; + } + + // Start with active service items only + $query = ServiceItem::where('status', 'active'); + + // Add search term filter if provided + if (!empty($this->serviceSearchTerm)) { + $searchTerm = trim($this->serviceSearchTerm); + $query->where(function ($q) use ($searchTerm) { + $q->where('service_name', 'like', '%' . $searchTerm . '%') + ->orWhere('description', 'like', '%' . $searchTerm . '%'); + }); + } + + // Add category filter if provided + if (!empty($this->serviceCategoryFilter)) { + $query->where('category', $this->serviceCategoryFilter); + } + + // Order by name for consistent results + $query->orderBy('service_name'); + + // Get results + $results = $query->limit(20)->get(); + + $this->filteredServiceItems = $results; + } catch (\Exception $e) { + // Log error and return empty collection + \Log::error('Error in updateFilteredServiceItems', [ + 'error' => $e->getMessage(), + 'search_term' => $this->serviceSearchTerm, + 'category_filter' => $this->serviceCategoryFilter + ]); + + $this->filteredServiceItems = collect(); + } + } + + public function startTimesheet() + { + // End any currently running timesheet + if ($this->currentTimesheet) { + $this->endTimesheet(); + } + + $this->currentTimesheet = Timesheet::create([ + 'job_card_id' => $this->jobCard->id, + 'user_id' => auth()->id(), + 'entry_type' => 'manual', + 'description' => 'Diagnosis: ' . ($this->diagnosisTypes[$this->selectedDiagnosisType] ?? 'General Diagnosis'), + 'date' => now()->toDateString(), + 'start_time' => now(), + 'hourly_rate' => auth()->user()->hourly_rate ?? 85.00, + 'status' => 'draft', + ]); + + $this->loadTimesheets(); + session()->flash('timesheet_message', 'Timesheet started for ' . ($this->diagnosisTypes[$this->selectedDiagnosisType] ?? 'Diagnosis')); + } + + public function endTimesheet() + { + if (!$this->currentTimesheet) { + return; + } + + $timesheet = Timesheet::find($this->currentTimesheet['id']); + if ($timesheet && !$timesheet->end_time) { + $endTime = now(); + $totalMinutes = $timesheet->start_time->diffInMinutes($endTime); + $billableHours = round($totalMinutes / 60, 2); + + $timesheet->update([ + 'end_time' => $endTime, + 'hours_worked' => $billableHours, + 'billable_hours' => $billableHours, + 'total_amount' => $billableHours * $timesheet->hourly_rate, + 'status' => 'submitted', + ]); + + session()->flash('timesheet_message', 'Timesheet ended. Total time: ' . $billableHours . ' hours'); + } + + $this->currentTimesheet = null; + $this->loadTimesheets(); + } + + public function addPartFromCatalog($partId) + { + $part = Part::find($partId); + if ($part) { + $this->parts_required[] = [ + 'part_id' => $part->id, + 'part_name' => $part->name, + 'part_number' => $part->part_number, + 'quantity' => 1, + 'estimated_cost' => $part->sell_price, + 'availability' => $part->quantity_on_hand > 0 ? 'in_stock' : 'out_of_stock' + ]; + $this->updatedPartsRequired(); // Save to session + } + } + + public function addServiceItemFromCatalog($serviceItemId) + { + $serviceItem = ServiceItem::find($serviceItemId); + if ($serviceItem) { + $this->labor_operations[] = [ + 'service_item_id' => $serviceItem->id, + 'operation' => $serviceItem->service_name, + 'description' => $serviceItem->description, + 'estimated_hours' => $serviceItem->estimated_hours, + 'labor_rate' => $serviceItem->labor_rate, + 'category' => $serviceItem->category, + 'complexity' => 'medium' + ]; + $this->updatedLaborOperations(); // Save to session + } + } + + public function addPart() + { + $this->parts_required[] = [ + 'part_id' => null, + 'part_name' => '', + 'part_number' => '', + 'quantity' => 1, + 'estimated_cost' => 0, + 'availability' => 'in_stock' + ]; + $this->updatedPartsRequired(); // Save to session + } + + public function removePart($index) + { + unset($this->parts_required[$index]); + $this->parts_required = array_values($this->parts_required); + $this->updatedPartsRequired(); // Save to session + } + + public function addLaborOperation() + { + $this->labor_operations[] = [ + 'service_item_id' => null, + 'operation' => '', + 'description' => '', + 'estimated_hours' => 0, + 'labor_rate' => 85.00, + 'category' => '', + 'complexity' => 'medium' + ]; + $this->updatedLaborOperations(); // Save to session + } + + public function removeLaborOperation($index) + { + unset($this->labor_operations[$index]); + $this->labor_operations = array_values($this->labor_operations); + $this->updatedLaborOperations(); // Save to session + } + + public function saveProgress() + { + // Manually save current progress to session + $this->updatedPartsRequired(); + $this->updatedLaborOperations(); + $this->updatedDiagnosticCodes(); + + session()->flash('progress_saved', 'Progress saved successfully!'); + } + + public function addDiagnosticCode() + { + $this->diagnostic_codes[] = [ + 'code' => '', + 'description' => '', + 'system' => '', + 'severity' => 'medium' + ]; + } + + public function removeDiagnosticCode($index) + { + unset($this->diagnostic_codes[$index]); + $this->diagnostic_codes = array_values($this->diagnostic_codes); + } + + public function addTestResult() + { + $this->test_results[] = [ + 'test_name' => '', + 'result' => '', + 'specification' => '', + 'status' => 'pass' + ]; + } + + public function removeTestResult($index) + { + unset($this->test_results[$index]); + $this->test_results = array_values($this->test_results); + } + + public function addSpecialTool() + { + $this->special_tools_required[] = [ + 'tool_name' => '', + 'tool_type' => '', + 'availability' => 'available' + ]; + } + + public function removeSpecialTool($index) + { + unset($this->special_tools_required[$index]); + $this->special_tools_required = array_values($this->special_tools_required); + } + + public function togglePartsSection() + { + $this->showPartsSection = !$this->showPartsSection; + } + + public function toggleLaborSection() + { + $this->showLaborSection = !$this->showLaborSection; + } + + public function toggleDiagnosticCodesSection() + { + $this->showDiagnosticCodesSection = !$this->showDiagnosticCodesSection; + } + + public function toggleTestResultsSection() + { + $this->showTestResultsSection = !$this->showTestResultsSection; + } + + public function toggleAdvancedOptions() + { + $this->showAdvancedOptions = !$this->showAdvancedOptions; + } + + public function toggleTimesheetSection() + { + $this->showTimesheetSection = !$this->showTimesheetSection; + } + + public function calculateTotalEstimatedCost() + { + $partsCost = array_sum(array_map(function($part) { + return ($part['quantity'] ?? 1) * ($part['estimated_cost'] ?? 0); + }, $this->parts_required)); + + $laborCost = 0; + foreach ($this->labor_operations as $operation) { + $laborCost += ($operation['estimated_hours'] ?? 0) * ($operation['labor_rate'] ?? 85); + } + + // Include diagnostic time costs + $diagnosticCost = collect($this->timesheets)->sum('total_amount'); + + return $partsCost + $laborCost + $diagnosticCost; + } + + private function createEstimateFromDiagnosis($diagnosis) + { + // Calculate totals + $partsCost = array_sum(array_map(function($part) { + return ($part['quantity'] ?? 1) * ($part['estimated_cost'] ?? 0); + }, $this->parts_required)); + + $laborCost = 0; + foreach ($this->labor_operations as $operation) { + $laborCost += ($operation['estimated_hours'] ?? 0) * ($operation['labor_rate'] ?? 85); + } + + $subtotal = $partsCost + $laborCost; + $taxRate = 0.0875; // 8.75% tax rate - should be configurable + $taxAmount = $subtotal * $taxRate; + $totalAmount = $subtotal + $taxAmount; + + // Create the estimate + $estimate = Estimate::create([ + 'job_card_id' => $this->jobCard->id, + 'diagnosis_id' => $diagnosis->id, + 'estimate_number' => 'EST-' . str_pad(Estimate::max('id') + 1, 6, '0', STR_PAD_LEFT), + 'customer_id' => $this->jobCard->customer_id, + 'vehicle_id' => $this->jobCard->vehicle_id, + 'prepared_by_id' => auth()->id(), + 'status' => 'draft', + 'priority_level' => $this->priority_level, + 'estimated_completion_date' => now()->addHours($this->estimated_repair_time), + 'subtotal' => $subtotal, + 'tax_rate' => $taxRate, + 'tax_amount' => $taxAmount, + 'total_amount' => $totalAmount, + 'notes' => 'Auto-generated from diagnosis: ' . $diagnosis->id, + 'valid_until' => now()->addDays(30), + ]); + + // Create line items for parts + foreach ($this->parts_required as $part) { + if (!empty($part['part_name'])) { + EstimateLineItem::create([ + 'estimate_id' => $estimate->id, + 'type' => 'part', + 'part_id' => $part['part_id'] ?? null, + 'description' => $part['part_name'], + 'part_number' => $part['part_number'] ?? null, + 'quantity' => $part['quantity'] ?? 1, + 'unit_price' => $part['estimated_cost'] ?? 0, + 'total_price' => ($part['quantity'] ?? 1) * ($part['estimated_cost'] ?? 0), + ]); + } + } + + // Create line items for labor + foreach ($this->labor_operations as $operation) { + if (!empty($operation['operation'])) { + EstimateLineItem::create([ + 'estimate_id' => $estimate->id, + 'type' => 'labor', + 'service_item_id' => $operation['service_item_id'] ?? null, + 'description' => $operation['operation'], + 'labor_hours' => $operation['estimated_hours'] ?? 0, + 'labor_rate' => $operation['labor_rate'] ?? 85, + 'total_price' => ($operation['estimated_hours'] ?? 0) * ($operation['labor_rate'] ?? 85), + ]); + } + } + + return $estimate; + } + + public function save() + { + $this->validate(); + + // End any active timesheet + if ($this->currentTimesheet) { + $this->endTimesheet(); + } + + // Handle photo uploads + $photoUrls = []; + if ($this->photos) { + foreach ($this->photos as $photo) { + $photoUrls[] = $photo->store('diagnosis', 'public'); + } + } + + $diagnosis = Diagnosis::create([ + 'job_card_id' => $this->jobCard->id, + 'service_coordinator_id' => auth()->id(), + 'customer_reported_issues' => $this->customer_reported_issues, + 'diagnostic_findings' => $this->diagnostic_findings, + 'root_cause_analysis' => $this->root_cause_analysis, + 'recommended_repairs' => $this->recommended_repairs, + 'additional_issues_found' => $this->additional_issues_found, + 'priority_level' => $this->priority_level, + 'estimated_repair_time' => $this->estimated_repair_time, + 'parts_required' => array_filter($this->parts_required, function($part) { + return !empty($part['part_name']); + }), + 'labor_operations' => array_filter($this->labor_operations, function($operation) { + return !empty($operation['operation']); + }), + 'special_tools_required' => array_filter($this->special_tools_required, function($tool) { + return !empty($tool['tool_name']); + }), + 'safety_concerns' => $this->safety_concerns, + 'diagnostic_codes' => array_filter($this->diagnostic_codes, function($code) { + return !empty($code['code']); + }), + 'test_results' => array_filter($this->test_results, function($result) { + return !empty($result['test_name']); + }), + 'photos' => $photoUrls, + 'notes' => $this->notes, + 'environmental_impact' => $this->environmental_impact, + 'customer_authorization_required' => $this->customer_authorization_required, + 'diagnosis_status' => 'completed', + 'diagnosis_date' => now(), + ]); + + // Update job card status + $this->jobCard->update(['status' => 'diagnosis_completed']); + + // Create estimate automatically + $estimate = $this->createEstimateFromDiagnosis($diagnosis); + + // Clear session data after successful diagnosis creation + session()->forget([ + "diagnosis_parts_{$this->jobCard->id}", + "diagnosis_labor_{$this->jobCard->id}", + "diagnosis_codes_{$this->jobCard->id}" + ]); + + session()->flash('message', 'Diagnosis completed successfully! Estimate #' . $estimate->estimate_number . ' has been created automatically.'); + return redirect()->route('estimates.show', $estimate); + } + + public function render() + { + return view('livewire.diagnosis.create'); + } +} diff --git a/app/Livewire/Diagnosis/Edit.php b/app/Livewire/Diagnosis/Edit.php new file mode 100644 index 0000000..add54a2 --- /dev/null +++ b/app/Livewire/Diagnosis/Edit.php @@ -0,0 +1,13 @@ +paginate(20); + return view('livewire.diagnosis.index', compact('diagnoses')); + } +} diff --git a/app/Livewire/Diagnosis/Show.php b/app/Livewire/Diagnosis/Show.php new file mode 100644 index 0000000..637a97c --- /dev/null +++ b/app/Livewire/Diagnosis/Show.php @@ -0,0 +1,31 @@ +diagnosis = $diagnosis->load([ + 'jobCard.customer', + 'jobCard.vehicle', + 'serviceCoordinator', + 'estimate' + ]); + } + + public function createEstimate() + { + return redirect()->route('estimates.create', $this->diagnosis); + } + + public function render() + { + return view('livewire.diagnosis.show'); + } +} diff --git a/app/Livewire/Estimates/Create.php b/app/Livewire/Estimates/Create.php new file mode 100644 index 0000000..a94d806 --- /dev/null +++ b/app/Livewire/Estimates/Create.php @@ -0,0 +1,193 @@ + 'required|string', + 'validity_period_days' => 'required|integer|min:1|max:365', + 'tax_rate' => 'required|numeric|min:0|max:50', + 'discount_amount' => 'nullable|numeric|min:0', + 'lineItems.*.type' => 'required|in:labor,parts,miscellaneous', + 'lineItems.*.description' => 'required|string', + 'lineItems.*.quantity' => 'required|numeric|min:0.01', + 'lineItems.*.unit_price' => 'required|numeric|min:0', + ]; + + public function mount(Diagnosis $diagnosis) + { + $this->diagnosis = $diagnosis->load([ + 'jobCard.customer', + 'jobCard.vehicle' + ]); + + // Pre-populate from diagnosis + $this->initializeLineItems(); + $this->terms_and_conditions = config('app.default_estimate_terms', + 'This estimate is valid for 30 days. All work will be performed according to industry standards.' + ); + } + + public function initializeLineItems() + { + // Add labor operations from diagnosis + foreach ($this->diagnosis->labor_operations as $labor) { + $this->lineItems[] = [ + 'type' => 'labor', + 'description' => $labor['operation'], + 'quantity' => $labor['estimated_hours'], + 'unit_price' => $labor['labor_rate'], + 'total_amount' => $labor['estimated_hours'] * $labor['labor_rate'], + 'labor_hours' => $labor['estimated_hours'], + 'labor_rate' => $labor['labor_rate'], + 'required' => true, + ]; + } + + // Add parts from diagnosis + foreach ($this->diagnosis->parts_required as $part) { + $this->lineItems[] = [ + 'type' => 'parts', + 'part_id' => null, + 'description' => $part['part_name'] . ' (' . $part['part_number'] . ')', + 'quantity' => $part['quantity'], + 'unit_price' => $part['estimated_cost'], + 'total_amount' => $part['quantity'] * $part['estimated_cost'], + 'markup_percentage' => 20, + 'required' => true, + ]; + } + + $this->calculateTotals(); + } + + public function addLineItem() + { + $this->lineItems[] = [ + 'type' => 'labor', + 'description' => '', + 'quantity' => 1, + 'unit_price' => 0, + 'total_amount' => 0, + 'required' => true, + ]; + } + + public function removeLineItem($index) + { + unset($this->lineItems[$index]); + $this->lineItems = array_values($this->lineItems); + $this->calculateTotals(); + } + + public function updatedLineItems() + { + $this->calculateTotals(); + } + + public function calculateTotals() + { + $this->subtotal = collect($this->lineItems)->sum(function ($item) { + return ($item['quantity'] ?? 0) * ($item['unit_price'] ?? 0); + }); + + $this->tax_amount = ($this->subtotal - $this->discount_amount) * ($this->tax_rate / 100); + $this->total_amount = $this->subtotal - $this->discount_amount + $this->tax_amount; + + // Update individual line item totals + foreach ($this->lineItems as $index => &$item) { + $item['total_amount'] = ($item['quantity'] ?? 0) * ($item['unit_price'] ?? 0); + } + } + + public function save() + { + $this->validate(); + $this->calculateTotals(); + + // Generate estimate number + $branchCode = $this->diagnosis->jobCard->branch_code; + $lastEstimateNumber = Estimate::where('estimate_number', 'like', $branchCode . '/EST%') + ->whereYear('created_at', now()->year) + ->count(); + $estimateNumber = $branchCode . '/EST' . str_pad($lastEstimateNumber + 1, 4, '0', STR_PAD_LEFT); + + $estimate = Estimate::create([ + 'estimate_number' => $estimateNumber, + 'job_card_id' => $this->diagnosis->job_card_id, + 'diagnosis_id' => $this->diagnosis->id, + 'prepared_by_id' => auth()->id(), + 'labor_cost' => collect($this->lineItems)->where('type', 'labor')->sum('total_amount'), + 'parts_cost' => collect($this->lineItems)->where('type', 'parts')->sum('total_amount'), + 'miscellaneous_cost' => collect($this->lineItems)->where('type', 'miscellaneous')->sum('total_amount'), + 'subtotal' => $this->subtotal, + 'tax_rate' => $this->tax_rate, + 'tax_amount' => $this->tax_amount, + 'discount_amount' => $this->discount_amount, + 'total_amount' => $this->total_amount, + 'validity_period_days' => $this->validity_period_days, + 'terms_and_conditions' => $this->terms_and_conditions, + 'notes' => $this->notes, + 'internal_notes' => $this->internal_notes, + 'status' => 'draft', + ]); + + // Create line items + foreach ($this->lineItems as $item) { + EstimateLineItem::create([ + 'estimate_id' => $estimate->id, + 'type' => $item['type'], + 'part_id' => $item['part_id'] ?? null, + 'description' => $item['description'], + 'quantity' => $item['quantity'], + 'unit_price' => $item['unit_price'], + 'total_amount' => $item['total_amount'], + 'labor_hours' => $item['labor_hours'] ?? null, + 'labor_rate' => $item['labor_rate'] ?? null, + 'markup_percentage' => $item['markup_percentage'] ?? 0, + 'required' => $item['required'] ?? true, + ]); + } + + // Update job card status + $this->diagnosis->jobCard->update(['status' => 'estimate_prepared']); + + session()->flash('message', 'Estimate created successfully!'); + return redirect()->route('estimates.show', $estimate); + } + + public function sendToCustomer() + { + // This would be called after saving + $customer = $this->diagnosis->jobCard->customer; + $customer->notify(new EstimateNotification($estimate)); + + session()->flash('message', 'Estimate sent to customer successfully!'); + } + + public function render() + { + return view('livewire.estimates.create'); + } +} diff --git a/app/Livewire/Estimates/Edit.php b/app/Livewire/Estimates/Edit.php new file mode 100644 index 0000000..7b83f10 --- /dev/null +++ b/app/Livewire/Estimates/Edit.php @@ -0,0 +1,13 @@ +resetPage(); + } + + public function render() + { + $estimates = Estimate::with(['jobCard.customer', 'jobCard.vehicle', 'preparedBy']) + ->when($this->search, function ($query) { + $query->where(function ($q) { + $q->where('estimate_number', 'like', '%' . $this->search . '%') + ->orWhereHas('jobCard', function ($jobQuery) { + $jobQuery->where('job_number', 'like', '%' . $this->search . '%') + ->orWhereHas('customer', function ($customerQuery) { + $customerQuery->where('first_name', 'like', '%' . $this->search . '%') + ->orWhere('last_name', 'like', '%' . $this->search . '%'); + }); + }); + }); + }) + ->when($this->statusFilter, function ($query) { + $query->where('status', $this->statusFilter); + }) + ->when($this->approvalStatusFilter, function ($query) { + $query->where('customer_approval_status', $this->approvalStatusFilter); + }) + ->latest() + ->paginate(15); + + return view('livewire.estimates.index', compact('estimates')); + } +} diff --git a/app/Livewire/Estimates/PDF.php b/app/Livewire/Estimates/PDF.php new file mode 100644 index 0000000..16ca681 --- /dev/null +++ b/app/Livewire/Estimates/PDF.php @@ -0,0 +1,13 @@ +search) < 2) { + $this->results = []; + $this->showResults = false; + return; + } + + $this->showResults = true; + $this->searchAll(); + } + + public function searchAll() + { + $this->results = []; + + // Search Customers + $customers = Customer::where('first_name', 'like', "%{$this->search}%") + ->orWhere('last_name', 'like', "%{$this->search}%") + ->orWhere('email', 'like', "%{$this->search}%") + ->orWhere('phone', 'like', "%{$this->search}%") + ->limit(5) + ->get() + ->map(function ($customer) { + return [ + 'type' => 'customer', + 'title' => $customer->full_name, + 'subtitle' => $customer->email ?? $customer->phone, + 'url' => route('customers.show', $customer), + 'icon' => 'user' + ]; + }) + ->toArray(); + + // Search Vehicles + $vehicles = Vehicle::where('license_plate', 'like', "%{$this->search}%") + ->orWhere('make', 'like', "%{$this->search}%") + ->orWhere('model', 'like', "%{$this->search}%") + ->orWhere('vin', 'like', "%{$this->search}%") + ->with('customer') + ->limit(5) + ->get() + ->map(function ($vehicle) { + return [ + 'type' => 'vehicle', + 'title' => "{$vehicle->make} {$vehicle->model} - {$vehicle->license_plate}", + 'subtitle' => $vehicle->customer?->full_name ?? 'No customer assigned', + 'url' => "/vehicles/{$vehicle->id}", + 'icon' => 'truck' + ]; + }) + ->toArray(); + + // Search Job Cards + $jobCards = JobCard::where('job_card_number', 'like', "%{$this->search}%") + ->orWhereHas('customer', function ($query) { + $query->where('first_name', 'like', "%{$this->search}%") + ->orWhere('last_name', 'like', "%{$this->search}%"); + }) + ->orWhereHas('vehicle', function ($query) { + $query->where('license_plate', 'like', "%{$this->search}%"); + }) + ->with(['customer', 'vehicle']) + ->limit(5) + ->get() + ->map(function ($jobCard) { + return [ + 'type' => 'job_card', + 'title' => "Job #{$jobCard->job_card_number}", + 'subtitle' => "{$jobCard->customer->full_name} - {$jobCard->vehicle->license_plate}", + 'url' => route('job-cards.show', $jobCard), + 'icon' => 'clipboard-document-list' + ]; + }) + ->toArray(); + + // Search Appointments (if the table exists) + try { + $appointments = Appointment::whereHas('customer', function ($query) { + $query->where('first_name', 'like', "%{$this->search}%") + ->orWhere('last_name', 'like', "%{$this->search}%"); + }) + ->orWhereHas('vehicle', function ($query) { + $query->where('license_plate', 'like', "%{$this->search}%"); + }) + ->with(['customer', 'vehicle']) + ->limit(5) + ->get() + ->map(function ($appointment) { + return [ + 'type' => 'appointment', + 'title' => "Appointment - {$appointment->scheduled_date}", + 'subtitle' => "{$appointment->customer->full_name} - {$appointment->vehicle->license_plate}", + 'url' => "/appointments/{$appointment->id}", + 'icon' => 'calendar' + ]; + }) + ->toArray(); + } catch (\Exception $e) { + $appointments = []; + } + + // Combine all results and limit to 10 + $this->results = array_slice( + array_merge($customers, $vehicles, $jobCards, $appointments), + 0, + 10 + ); + } + + public function clearSearch() + { + $this->search = ''; + $this->results = []; + $this->showResults = false; + } + + public function render() + { + return view('livewire.global-search'); + } +} diff --git a/app/Livewire/Inspections/Create.php b/app/Livewire/Inspections/Create.php new file mode 100644 index 0000000..5f3bfac --- /dev/null +++ b/app/Livewire/Inspections/Create.php @@ -0,0 +1,135 @@ + [ + 'body_condition' => '', + 'paint_condition' => '', + 'lights_working' => '', + 'mirrors_intact' => '', + 'windshield_condition' => '', + ], + 'interior' => [ + 'seats_condition' => '', + 'dashboard_condition' => '', + 'electronics_working' => '', + 'upholstery_condition' => '', + ], + 'mechanical' => [ + 'engine_condition' => '', + 'transmission_condition' => '', + 'brakes_condition' => '', + 'suspension_condition' => '', + 'tires_condition' => '', + ], + 'fluids' => [ + 'oil_level' => '', + 'coolant_level' => '', + 'brake_fluid_level' => '', + 'power_steering_fluid' => '', + ] + ]; + + protected $rules = [ + 'current_mileage' => 'required|numeric|min:0', + 'fuel_level' => 'required|string', + 'overall_condition' => 'required|in:excellent,good,fair,poor,damaged', + 'cleanliness_rating' => 'required|integer|min:1|max:10', + ]; + + public function mount(JobCard $jobCard, $type) + { + $this->jobCard = $jobCard->load(['customer', 'vehicle']); + $this->type = $type; + $this->current_mileage = $jobCard->vehicle->current_mileage ?? ''; + + if ($type === 'outgoing') { + $this->rules['quality_rating'] = 'required|integer|min:1|max:10'; + } + } + + public function save() + { + $this->validate(); + + // Handle file uploads + $photoUrls = []; + foreach ($this->photos as $photo) { + $photoUrls[] = $photo->store('inspections', 'public'); + } + + $videoUrls = []; + foreach ($this->videos as $video) { + $videoUrls[] = $video->store('inspections', 'public'); + } + + $inspection = VehicleInspection::create([ + 'job_card_id' => $this->jobCard->id, + 'vehicle_id' => $this->jobCard->vehicle_id, + 'inspector_id' => auth()->id(), + 'inspection_type' => $this->type, + 'current_mileage' => $this->current_mileage, + 'fuel_level' => $this->fuel_level, + 'inspection_checklist' => $this->checklist, + 'photos' => $photoUrls, + 'videos' => $videoUrls, + 'overall_condition' => $this->overall_condition, + 'recommendations' => $this->recommendations, + 'damage_notes' => $this->damage_notes, + 'cleanliness_rating' => $this->cleanliness_rating, + 'quality_rating' => $this->quality_rating, + 'follow_up_required' => $this->follow_up_required, + 'notes' => $this->notes, + 'inspection_date' => now(), + ]); + + // Update job card status based on inspection type + if ($this->type === 'incoming') { + $this->jobCard->update([ + 'status' => 'inspection_completed', + 'mileage_in' => $this->current_mileage, + 'fuel_level_in' => $this->fuel_level, + ]); + } else { + $this->jobCard->update([ + 'status' => 'quality_check_completed', + 'mileage_out' => $this->current_mileage, + 'fuel_level_out' => $this->fuel_level, + ]); + } + + session()->flash('message', ucfirst($this->type) . ' inspection completed successfully!'); + return redirect()->route('inspections.show', $inspection); + } + + public function render() + { + return view('livewire.inspections.create'); + } +} diff --git a/app/Livewire/Inspections/Edit.php b/app/Livewire/Inspections/Edit.php new file mode 100644 index 0000000..f903a04 --- /dev/null +++ b/app/Livewire/Inspections/Edit.php @@ -0,0 +1,13 @@ +resetPage(); + } + + public function render() + { + $inspections = VehicleInspection::with([ + 'jobCard.customer', + 'jobCard.vehicle', + 'inspector' + ]) + ->when($this->search, function ($query) { + $query->whereHas('jobCard', function ($jobQuery) { + $jobQuery->where('job_number', 'like', '%' . $this->search . '%') + ->orWhereHas('customer', function ($customerQuery) { + $customerQuery->where('first_name', 'like', '%' . $this->search . '%') + ->orWhere('last_name', 'like', '%' . $this->search . '%'); + }); + }); + }) + ->when($this->typeFilter, function ($query) { + $query->where('inspection_type', $this->typeFilter); + }) + ->when($this->statusFilter, function ($query) { + $query->where('overall_condition', $this->statusFilter); + }) + ->latest() + ->paginate(15); + + return view('livewire.inspections.index', compact('inspections')); + } +} diff --git a/app/Livewire/Inspections/Show.php b/app/Livewire/Inspections/Show.php new file mode 100644 index 0000000..1014189 --- /dev/null +++ b/app/Livewire/Inspections/Show.php @@ -0,0 +1,13 @@ +count(); + $outOfStockParts = Part::outOfStock()->count(); + $totalStockValue = Part::selectRaw('SUM(quantity_on_hand * cost_price) as total')->value('total') ?? 0; + + // Get recent stock movements + $recentMovements = StockMovement::with(['part', 'createdBy']) + ->orderBy('created_at', 'desc') + ->limit(10) + ->get(); + + // Get pending purchase orders + $pendingOrders = PurchaseOrder::with('supplier') + ->whereIn('status', ['pending', 'ordered']) + ->orderBy('order_date', 'desc') + ->limit(5) + ->get(); + + // Get low stock parts + $lowStockPartsList = Part::with('supplier') + ->lowStock() + ->orderBy('quantity_on_hand', 'asc') + ->limit(10) + ->get(); + + // Get stock by category + $stockByCategory = Part::selectRaw('category, SUM(quantity_on_hand * cost_price) as total_value') + ->groupBy('category') + ->orderBy('total_value', 'desc') + ->get(); // Get top suppliers by parts count + $topSuppliers = Supplier::withCount('parts') + ->having('parts_count', '>', 0) + ->orderBy('parts_count', 'desc') + ->limit(5) + ->get(); + + return view('livewire.inventory.dashboard', [ + 'totalParts' => $totalParts, + 'lowStockParts' => $lowStockParts, + 'outOfStockParts' => $outOfStockParts, + 'totalStockValue' => $totalStockValue, + 'recentMovements' => $recentMovements, + 'pendingOrders' => $pendingOrders, + 'lowStockPartsList' => $lowStockPartsList, + 'stockByCategory' => $stockByCategory, + 'topSuppliers' => $topSuppliers, + ]); + } +} diff --git a/app/Livewire/Inventory/Parts/Create.php b/app/Livewire/Inventory/Parts/Create.php new file mode 100644 index 0000000..38b7b98 --- /dev/null +++ b/app/Livewire/Inventory/Parts/Create.php @@ -0,0 +1,104 @@ + 'required|string|max:255|unique:parts', + 'name' => 'required|string|max:255', + 'description' => 'nullable|string', + 'manufacturer' => 'nullable|string|max:255', + 'category' => 'nullable|string|max:255', + 'cost_price' => 'required|numeric|min:0', + 'sell_price' => 'required|numeric|min:0', + 'quantity_on_hand' => 'required|integer|min:0', + 'minimum_stock_level' => 'required|integer|min:0', + 'maximum_stock_level' => 'required|integer|min:0', + 'location' => 'nullable|string|max:255', + 'supplier_id' => 'nullable|exists:suppliers,id', + 'supplier_part_number' => 'nullable|string|max:255', + 'lead_time_days' => 'nullable|integer|min:0', + 'status' => 'required|in:active,inactive', + 'barcode' => 'nullable|string|max:255', + 'weight' => 'nullable|numeric|min:0', + 'dimensions' => 'nullable|string|max:255', + 'warranty_period' => 'nullable|integer|min:0', + 'image' => 'nullable|image|max:2048', + ]; + + public function save() + { + $this->validate(); + + $data = [ + 'part_number' => $this->part_number, + 'name' => $this->name, + 'description' => $this->description, + 'manufacturer' => $this->manufacturer, + 'category' => $this->category, + 'cost_price' => $this->cost_price, + 'sell_price' => $this->sell_price, + 'quantity_on_hand' => $this->quantity_on_hand, + 'minimum_stock_level' => $this->minimum_stock_level, + 'maximum_stock_level' => $this->maximum_stock_level, + 'location' => $this->location, + 'supplier_id' => $this->supplier_id ?: null, + 'supplier_part_number' => $this->supplier_part_number, + 'lead_time_days' => $this->lead_time_days, + 'status' => $this->status, + 'barcode' => $this->barcode, + 'weight' => $this->weight, + 'dimensions' => $this->dimensions, + 'warranty_period' => $this->warranty_period, + ]; + + // Handle image upload + if ($this->image) { + $data['image'] = $this->image->store('parts', 'public'); + } + + Part::create($data); + + session()->flash('success', 'Part created successfully.'); + + return $this->redirect(route('inventory.parts.index')); + } + + public function render() + { + $suppliers = Supplier::active()->orderBy('name')->get(); + + return view('livewire.inventory.parts.create', [ + 'suppliers' => $suppliers, + ]); + } +} diff --git a/app/Livewire/Inventory/Parts/Edit.php b/app/Livewire/Inventory/Parts/Edit.php new file mode 100644 index 0000000..edf35b1 --- /dev/null +++ b/app/Livewire/Inventory/Parts/Edit.php @@ -0,0 +1,134 @@ + 'required|string|max:255', + 'name' => 'required|string|max:255', + 'description' => 'nullable|string', + 'manufacturer' => 'nullable|string|max:255', + 'category' => 'nullable|string|max:255', + 'cost_price' => 'required|numeric|min:0', + 'sell_price' => 'required|numeric|min:0', + 'quantity_on_hand' => 'required|integer|min:0', + 'minimum_stock_level' => 'required|integer|min:0', + 'maximum_stock_level' => 'required|integer|min:0', + 'location' => 'nullable|string|max:255', + 'supplier_id' => 'nullable|exists:suppliers,id', + 'supplier_part_number' => 'nullable|string|max:255', + 'lead_time_days' => 'nullable|integer|min:0', + 'status' => 'required|in:active,inactive', + 'barcode' => 'nullable|string|max:255', + 'weight' => 'nullable|numeric|min:0', + 'dimensions' => 'nullable|string|max:255', + 'warranty_period' => 'nullable|integer|min:0', + 'image' => 'nullable|image|max:2048', + ]; + + public function mount() + { + $this->part_number = $this->part->part_number; + $this->name = $this->part->name; + $this->description = $this->part->description; + $this->manufacturer = $this->part->manufacturer; + $this->category = $this->part->category; + $this->cost_price = $this->part->cost_price; + $this->sell_price = $this->part->sell_price; + $this->quantity_on_hand = $this->part->quantity_on_hand; + $this->minimum_stock_level = $this->part->minimum_stock_level; + $this->maximum_stock_level = $this->part->maximum_stock_level; + $this->location = $this->part->location; + $this->supplier_id = $this->part->supplier_id; + $this->supplier_part_number = $this->part->supplier_part_number; + $this->lead_time_days = $this->part->lead_time_days; + $this->status = $this->part->status; + $this->barcode = $this->part->barcode; + $this->weight = $this->part->weight; + $this->dimensions = $this->part->dimensions; + $this->warranty_period = $this->part->warranty_period; + $this->currentImage = $this->part->image; + } + + public function save() + { + $rules = $this->rules; + $rules['part_number'] = 'required|string|max:255|unique:parts,part_number,' . $this->part->id; + + $this->validate($rules); + + $data = [ + 'part_number' => $this->part_number, + 'name' => $this->name, + 'description' => $this->description, + 'manufacturer' => $this->manufacturer, + 'category' => $this->category, + 'cost_price' => $this->cost_price, + 'sell_price' => $this->sell_price, + 'quantity_on_hand' => $this->quantity_on_hand, + 'minimum_stock_level' => $this->minimum_stock_level, + 'maximum_stock_level' => $this->maximum_stock_level, + 'location' => $this->location, + 'supplier_id' => $this->supplier_id ?: null, + 'supplier_part_number' => $this->supplier_part_number, + 'lead_time_days' => $this->lead_time_days, + 'status' => $this->status, + 'barcode' => $this->barcode, + 'weight' => $this->weight, + 'dimensions' => $this->dimensions, + 'warranty_period' => $this->warranty_period, + ]; + + // Handle image upload + if ($this->image) { + $data['image'] = $this->image->store('parts', 'public'); + } + + $this->part->update($data); + + session()->flash('success', 'Part updated successfully.'); + + return $this->redirect(route('inventory.parts.index')); + } + + public function render() + { + $suppliers = Supplier::active()->orderBy('name')->get(); + + return view('livewire.inventory.parts.edit', [ + 'suppliers' => $suppliers, + ]); + } +} diff --git a/app/Livewire/Inventory/Parts/History.php b/app/Livewire/Inventory/Parts/History.php new file mode 100644 index 0000000..ce11ee7 --- /dev/null +++ b/app/Livewire/Inventory/Parts/History.php @@ -0,0 +1,86 @@ + ['except' => ''], + 'dateFrom' => ['except' => ''], + 'dateTo' => ['except' => ''], + ]; + + public function mount() + { + // Clear date filters if they are set to today (which would exclude our test data) + if ($this->dateFrom === now()->format('Y-m-d') && $this->dateTo === now()->format('Y-m-d')) { + $this->dateFrom = null; + $this->dateTo = null; + } + } + + public function updatingEventTypeFilter() + { + $this->resetPage(); + } + + public function clearFilters() + { + $this->reset(['eventTypeFilter', 'dateFrom', 'dateTo']); + $this->resetPage(); + } + + public function goBack() + { + return $this->redirect(route('inventory.parts.show', $this->part), navigate: true); + } + + public function render() + { + $query = PartHistory::where('part_id', $this->part->id) + ->with(['createdBy']) + ->orderBy('created_at', 'desc'); + + // Apply filters + if ($this->eventTypeFilter !== '') { + $query->where('event_type', $this->eventTypeFilter); + } + + if ($this->dateFrom) { + $query->whereDate('created_at', '>=', $this->dateFrom); + } + + if ($this->dateTo) { + $query->whereDate('created_at', '<=', $this->dateTo); + } + + $histories = $query->paginate(20); + + $eventTypes = [ + PartHistory::EVENT_CREATED => 'Created', + PartHistory::EVENT_UPDATED => 'Updated', + PartHistory::EVENT_STOCK_IN => 'Stock In', + PartHistory::EVENT_STOCK_OUT => 'Stock Out', + PartHistory::EVENT_ADJUSTMENT => 'Adjustment', + PartHistory::EVENT_PRICE_CHANGE => 'Price Change', + PartHistory::EVENT_SUPPLIER_CHANGE => 'Supplier Change', + ]; + + return view('livewire.inventory.parts.history', [ + 'histories' => $histories, + 'eventTypes' => $eventTypes, + ]); + } +} diff --git a/app/Livewire/Inventory/Parts/Index.php b/app/Livewire/Inventory/Parts/Index.php new file mode 100644 index 0000000..1ed9333 --- /dev/null +++ b/app/Livewire/Inventory/Parts/Index.php @@ -0,0 +1,134 @@ + ['except' => ''], + 'categoryFilter' => ['except' => ''], + 'statusFilter' => ['except' => ''], + 'stockFilter' => ['except' => ''], + 'supplierFilter' => ['except' => ''], + 'sortBy' => ['except' => 'name'], + 'sortDirection' => ['except' => 'asc'], + ]; + + public function updatingSearch() + { + $this->resetPage(); + } + + public function updatingCategoryFilter() + { + $this->resetPage(); + } + + public function updatingStatusFilter() + { + $this->resetPage(); + } + + public function updatingStockFilter() + { + $this->resetPage(); + } + + public function updatingSupplierFilter() + { + $this->resetPage(); + } + + public function sortBy($field) + { + if ($this->sortBy === $field) { + $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc'; + } else { + $this->sortBy = $field; + $this->sortDirection = 'asc'; + } + } + + public function clearFilters() + { + $this->reset(['search', 'categoryFilter', 'statusFilter', 'stockFilter', 'supplierFilter']); + $this->resetPage(); + } + + public function render() + { + $query = Part::with('supplier'); + + // Apply search + if ($this->search) { + $query->where(function ($q) { + $q->where('name', 'like', '%' . $this->search . '%') + ->orWhere('part_number', 'like', '%' . $this->search . '%') + ->orWhere('description', 'like', '%' . $this->search . '%') + ->orWhere('manufacturer', 'like', '%' . $this->search . '%'); + }); + } + + // Apply filters + if ($this->categoryFilter) { + $query->where('category', $this->categoryFilter); + } + + if ($this->statusFilter) { + $query->where('status', $this->statusFilter); + } + + if ($this->supplierFilter) { + $query->where('supplier_id', $this->supplierFilter); + } + + if ($this->stockFilter) { + switch ($this->stockFilter) { + case 'low_stock': + $query->whereColumn('quantity_on_hand', '<=', 'minimum_stock_level'); + break; + case 'out_of_stock': + $query->where('quantity_on_hand', '<=', 0); + break; + case 'overstock': + $query->whereColumn('quantity_on_hand', '>=', 'maximum_stock_level'); + break; + case 'in_stock': + $query->where('quantity_on_hand', '>', 0) + ->whereColumn('quantity_on_hand', '>', 'minimum_stock_level') + ->whereColumn('quantity_on_hand', '<', 'maximum_stock_level'); + break; + } + } + + // Apply sorting + $query->orderBy($this->sortBy, $this->sortDirection); + + $parts = $query->paginate(15); + + // Get filter options + $categories = Part::distinct()->pluck('category')->filter()->sort(); + $suppliers = Supplier::active()->orderBy('name')->get(); + + return view('livewire.inventory.parts.index', [ + 'parts' => $parts, + 'categories' => $categories, + 'suppliers' => $suppliers, + ]); + } +} diff --git a/app/Livewire/Inventory/Parts/Show.php b/app/Livewire/Inventory/Parts/Show.php new file mode 100644 index 0000000..84aa6a0 --- /dev/null +++ b/app/Livewire/Inventory/Parts/Show.php @@ -0,0 +1,56 @@ +part = $part->load(['supplier', 'stockMovements.createdBy']); + $this->tab = request()->get('tab', 'details'); + } + + public function showHistory() + { + return $this->redirect(route('inventory.parts.show', $this->part) . '?tab=history', navigate: true); + } + + public function addStockMovement() + { + return $this->redirect(route('inventory.stock-movements.create') . '?part_id=' . $this->part->id, navigate: true); + } + + public function createPurchaseOrder() + { + return $this->redirect(route('inventory.purchase-orders.create') . '?part_id=' . $this->part->id, navigate: true); + } + + public function goBack() + { + return $this->redirect(route('inventory.parts.index'), navigate: true); + } + + public function edit() + { + return $this->redirect(route('inventory.parts.edit', $this->part), navigate: true); + } + + public function render() + { + if ($this->tab === 'history') { + return view('livewire.inventory.parts.show', [ + 'showHistory' => true + ]); + } + + return view('livewire.inventory.parts.show', [ + 'showHistory' => false + ]); + } +} diff --git a/app/Livewire/Inventory/PurchaseOrders/Create.php b/app/Livewire/Inventory/PurchaseOrders/Create.php new file mode 100644 index 0000000..3e85b55 --- /dev/null +++ b/app/Livewire/Inventory/PurchaseOrders/Create.php @@ -0,0 +1,172 @@ + 'required|exists:suppliers,id', + 'order_date' => 'required|date', + 'expected_date' => 'nullable|date|after:order_date', + 'notes' => 'nullable|string|max:1000', + 'status' => 'required|in:draft,pending,ordered', + 'items' => 'required|array|min:1', + 'items.*.part_id' => 'required|exists:parts,id', + 'items.*.quantity' => 'required|integer|min:1', + 'items.*.unit_cost' => 'required|numeric|min:0', + ]; + + public function mount() + { + $this->order_date = now()->format('Y-m-d'); + + // Pre-select part if passed in URL + if (request()->has('part_id')) { + $partId = request()->get('part_id'); + $part = Part::find($partId); + if ($part) { + $this->selectedPart = $partId; + $this->unitCost = $part->cost_price; + // Pre-fill supplier if part has one + if ($part->supplier_id) { + $this->supplier_id = $part->supplier_id; + } + } + } + } + + public function updatedSupplierId() + { + // Clear selected part when supplier changes + $this->selectedPart = ''; + $this->unitCost = ''; + } + + public function addItem() + { + $this->validate([ + 'selectedPart' => 'required|exists:parts,id', + 'quantity' => 'required|integer|min:1', + 'unitCost' => 'required|numeric|min:0', + ]); + + $part = Part::find($this->selectedPart); + + // Check if part already exists in items + $existingIndex = collect($this->items)->search(function ($item) { + return $item['part_id'] == $this->selectedPart; + }); + + if ($existingIndex !== false) { + // Update existing item + $this->items[$existingIndex]['quantity'] += $this->quantity; + $this->items[$existingIndex]['unit_cost'] = $this->unitCost; + } else { + // Add new item + $this->items[] = [ + 'part_id' => $this->selectedPart, + 'part_name' => $part->name, + 'part_number' => $part->part_number, + 'quantity' => $this->quantity, + 'unit_cost' => $this->unitCost, + 'total_cost' => $this->quantity * $this->unitCost, + ]; + } + + // Reset form + $this->reset(['selectedPart', 'quantity', 'unitCost']); + } + + public function removeItem($index) + { + unset($this->items[$index]); + $this->items = array_values($this->items); + } + + public function updatedSelectedPart() + { + if ($this->selectedPart) { + $part = Part::find($this->selectedPart); + $this->unitCost = $part->cost_price ?? ''; + } + } + + public function save() + { + $this->validate(); + + $purchaseOrder = PurchaseOrder::create([ + 'po_number' => $this->generateOrderNumber(), + 'supplier_id' => $this->supplier_id, + 'order_date' => $this->order_date, + 'expected_date' => $this->expected_date ?: null, + 'status' => $this->status, + 'notes' => $this->notes, + 'approved_by' => auth()->id(), + ]); + + foreach ($this->items as $item) { + $purchaseOrder->items()->create([ + 'part_id' => $item['part_id'], + 'quantity_ordered' => $item['quantity'], + 'unit_cost' => $item['unit_cost'], + 'total_cost' => $item['quantity'] * $item['unit_cost'], + ]); + } + + session()->flash('success', 'Purchase order created successfully!'); + + return $this->redirect(route('inventory.purchase-orders.show', $purchaseOrder), navigate: true); + } + + private function generateOrderNumber() + { + $year = date('Y'); + $lastOrder = PurchaseOrder::whereYear('created_at', $year) + ->orderBy('id', 'desc') + ->first(); + + $sequence = $lastOrder ? (int) substr($lastOrder->po_number, -4) + 1 : 1; + + return 'PO-' . $year . '-' . str_pad($sequence, 4, '0', STR_PAD_LEFT); + } + + public function getTotalAmount() + { + return collect($this->items)->sum('total_cost'); + } + + public function render() + { + $suppliers = Supplier::where('is_active', true)->orderBy('name')->get(); + + // Filter parts by selected supplier + $partsQuery = Part::orderBy('name'); + if ($this->supplier_id) { + $partsQuery->where('supplier_id', $this->supplier_id); + } + $parts = $partsQuery->get(); + + return view('livewire.inventory.purchase-orders.create', [ + 'suppliers' => $suppliers, + 'parts' => $parts, + ]); + } +} diff --git a/app/Livewire/Inventory/PurchaseOrders/Edit.php b/app/Livewire/Inventory/PurchaseOrders/Edit.php new file mode 100644 index 0000000..7c3cac0 --- /dev/null +++ b/app/Livewire/Inventory/PurchaseOrders/Edit.php @@ -0,0 +1,157 @@ + 'required|exists:suppliers,id', + 'order_date' => 'required|date', + 'expected_date' => 'nullable|date|after:order_date', + 'notes' => 'nullable|string|max:1000', + 'status' => 'required|in:draft,pending,ordered', + 'items' => 'required|array|min:1', + 'items.*.part_id' => 'required|exists:parts,id', + 'items.*.quantity' => 'required|integer|min:1', + 'items.*.unit_cost' => 'required|numeric|min:0', + ]; + + public function mount(PurchaseOrder $purchaseOrder) + { + $this->purchaseOrder = $purchaseOrder->load(['items.part']); + + $this->supplier_id = $purchaseOrder->supplier_id; + $this->order_date = $purchaseOrder->order_date->format('Y-m-d'); + $this->expected_date = $purchaseOrder->expected_date ? $purchaseOrder->expected_date->format('Y-m-d') : ''; + $this->notes = $purchaseOrder->notes; + $this->status = $purchaseOrder->status; + + // Load existing items + foreach ($purchaseOrder->items as $item) { + $this->items[] = [ + 'id' => $item->id, + 'part_id' => $item->part_id, + 'part_name' => $item->part->name, + 'part_number' => $item->part->part_number, + 'quantity' => $item->quantity_ordered, + 'unit_cost' => $item->unit_cost, + 'total_cost' => $item->total_cost, + ]; + } + } + + public function addItem() + { + $this->validate([ + 'selectedPart' => 'required|exists:parts,id', + 'quantity' => 'required|integer|min:1', + 'unitCost' => 'required|numeric|min:0', + ]); + + $part = Part::find($this->selectedPart); + + // Check if part already exists in items + $existingIndex = collect($this->items)->search(function ($item) { + return $item['part_id'] == $this->selectedPart; + }); + + if ($existingIndex !== false) { + // Update existing item + $this->items[$existingIndex]['quantity'] += $this->quantity; + $this->items[$existingIndex]['unit_cost'] = $this->unitCost; + $this->items[$existingIndex]['total_cost'] = $this->items[$existingIndex]['quantity'] * $this->unitCost; + } else { + // Add new item + $this->items[] = [ + 'id' => null, // New item + 'part_id' => $this->selectedPart, + 'part_name' => $part->name, + 'part_number' => $part->part_number, + 'quantity' => $this->quantity, + 'unit_cost' => $this->unitCost, + 'total_cost' => $this->quantity * $this->unitCost, + ]; + } + + // Reset form + $this->reset(['selectedPart', 'quantity', 'unitCost']); + } + + public function removeItem($index) + { + unset($this->items[$index]); + $this->items = array_values($this->items); + } + + public function updatedSelectedPart() + { + if ($this->selectedPart) { + $part = Part::find($this->selectedPart); + $this->unitCost = $part->cost_price ?? ''; + } + } + + public function save() + { + $this->validate(); + + $this->purchaseOrder->update([ + 'supplier_id' => $this->supplier_id, + 'order_date' => $this->order_date, + 'expected_date' => $this->expected_date ?: null, + 'status' => $this->status, + 'notes' => $this->notes, + ]); + + // Delete existing items and recreate + $this->purchaseOrder->items()->delete(); + + foreach ($this->items as $item) { + $this->purchaseOrder->items()->create([ + 'part_id' => $item['part_id'], + 'quantity_ordered' => $item['quantity'], + 'unit_cost' => $item['unit_cost'], + 'total_cost' => $item['quantity'] * $item['unit_cost'], + ]); + } + + session()->flash('success', 'Purchase order updated successfully!'); + + return $this->redirect(route('inventory.purchase-orders.show', $this->purchaseOrder), navigate: true); + } + + public function getTotalAmount() + { + return collect($this->items)->sum('total_cost'); + } + + public function render() + { + $suppliers = Supplier::where('is_active', true)->orderBy('name')->get(); + $parts = Part::orderBy('name')->get(); + + return view('livewire.inventory.purchase-orders.edit', [ + 'suppliers' => $suppliers, + 'parts' => $parts, + ]); + } +} diff --git a/app/Livewire/Inventory/PurchaseOrders/Index.php b/app/Livewire/Inventory/PurchaseOrders/Index.php new file mode 100644 index 0000000..7d25f38 --- /dev/null +++ b/app/Livewire/Inventory/PurchaseOrders/Index.php @@ -0,0 +1,93 @@ + ['except' => ''], + 'statusFilter' => ['except' => ''], + 'supplierFilter' => ['except' => ''], + 'sortBy' => ['except' => 'order_date'], + 'sortDirection' => ['except' => 'desc'], + ]; + + public function updatingSearch() + { + $this->resetPage(); + } + + public function updatingStatusFilter() + { + $this->resetPage(); + } + + public function updatingSupplierFilter() + { + $this->resetPage(); + } + + public function sortBy($field) + { + if ($this->sortBy === $field) { + $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc'; + } else { + $this->sortBy = $field; + $this->sortDirection = 'asc'; + } + } + + public function clearFilters() + { + $this->reset(['search', 'statusFilter', 'supplierFilter']); + $this->resetPage(); + } + + public function render() + { + $query = PurchaseOrder::with(['supplier', 'items']); + + // Apply search + if ($this->search) { + $query->where(function ($q) { + $q->where('po_number', 'like', '%' . $this->search . '%') + ->orWhereHas('supplier', function ($sq) { + $sq->where('name', 'like', '%' . $this->search . '%'); + }); + }); + } + + // Apply filters + if ($this->statusFilter !== '') { + $query->where('status', $this->statusFilter); + } + + if ($this->supplierFilter !== '') { + $query->where('supplier_id', $this->supplierFilter); + } + + // Apply sorting + $query->orderBy($this->sortBy, $this->sortDirection); + + $purchaseOrders = $query->paginate(15); + $suppliers = Supplier::where('is_active', true)->orderBy('name')->get(); + + return view('livewire.inventory.purchase-orders.index', [ + 'purchaseOrders' => $purchaseOrders, + 'suppliers' => $suppliers, + ]); + } +} diff --git a/app/Livewire/Inventory/PurchaseOrders/Show.php b/app/Livewire/Inventory/PurchaseOrders/Show.php new file mode 100644 index 0000000..c180246 --- /dev/null +++ b/app/Livewire/Inventory/PurchaseOrders/Show.php @@ -0,0 +1,116 @@ +purchaseOrder = $purchaseOrder->load(['supplier', 'items.part']); + + // Initialize received quantities + foreach ($this->purchaseOrder->items as $item) { + $this->receivedQuantities[$item->id] = $item->quantity_received ?? 0; + } + } + + public function startReceiving() + { + $this->receivingMode = true; + } + + public function cancelReceiving() + { + $this->receivingMode = false; + // Reset quantities + foreach ($this->purchaseOrder->items as $item) { + $this->receivedQuantities[$item->id] = $item->quantity_received ?? 0; + } + } + + public function receiveItems() + { + $this->validate([ + 'receivedQuantities.*' => 'required|integer|min:0', + ]); + + $totalReceived = 0; + $totalOrdered = 0; + + foreach ($this->purchaseOrder->items as $item) { + $receivedQty = $this->receivedQuantities[$item->id]; + $previouslyReceived = $item->quantity_received ?? 0; + $newlyReceived = $receivedQty - $previouslyReceived; + + if ($newlyReceived > 0) { + // Update item received quantity + $item->update(['quantity_received' => $receivedQty]); + + // Add to part stock + $item->part->increment('quantity_on_hand', $newlyReceived); + + // Create stock movement + StockMovement::create([ + 'part_id' => $item->part_id, + 'movement_type' => 'in', + 'quantity' => $newlyReceived, + 'reference_type' => 'purchase_order', + 'reference_id' => $this->purchaseOrder->id, + 'notes' => "Received from PO #{$this->purchaseOrder->po_number}", + 'created_by' => auth()->id(), + ]); + } + + $totalReceived += $receivedQty; + $totalOrdered += $item->quantity_ordered; + } + + // Update purchase order status + if ($totalReceived == 0) { + $status = $this->purchaseOrder->status; + } elseif ($totalReceived >= $totalOrdered) { + $status = 'received'; + } else { + $status = 'partial'; + } + + $this->purchaseOrder->update([ + 'status' => $status, + 'received_date' => $totalReceived > 0 ? now() : null, + ]); + + $this->receivingMode = false; + $this->purchaseOrder->refresh(); + + session()->flash('success', 'Items received successfully!'); + } + + public function markAsOrdered() + { + $this->purchaseOrder->update(['status' => 'ordered']); + $this->purchaseOrder->refresh(); + + session()->flash('success', 'Purchase order marked as ordered!'); + } + + public function cancelOrder() + { + $this->purchaseOrder->update(['status' => 'cancelled']); + $this->purchaseOrder->refresh(); + + session()->flash('success', 'Purchase order cancelled!'); + } + + public function render() + { + return view('livewire.inventory.purchase-orders.show'); + } +} diff --git a/app/Livewire/Inventory/StockMovements/Create.php b/app/Livewire/Inventory/StockMovements/Create.php new file mode 100644 index 0000000..8ef6b59 --- /dev/null +++ b/app/Livewire/Inventory/StockMovements/Create.php @@ -0,0 +1,68 @@ +has('part_id')) { + $this->part_id = request()->get('part_id'); + } + } + + protected $rules = [ + 'part_id' => 'required|exists:parts,id', + 'movement_type' => 'required|in:in,out,adjustment', + 'quantity' => 'required|integer|min:1', + 'notes' => 'required|string|max:500', + ]; + + public function save() + { + $this->validate(); + + $part = Part::find($this->part_id); + + // Create stock movement + StockMovement::create([ + 'part_id' => $this->part_id, + 'movement_type' => $this->movement_type, + 'quantity' => $this->quantity, + 'reference_type' => $this->reference_type, + 'notes' => $this->notes, + 'created_by' => auth()->id(), + ]); + + // Update part stock + if ($this->movement_type === 'in' || $this->movement_type === 'adjustment') { + $part->increment('quantity_on_hand', $this->quantity); + } elseif ($this->movement_type === 'out') { + $part->decrement('quantity_on_hand', $this->quantity); + } + + session()->flash('success', 'Stock movement recorded successfully!'); + + return $this->redirect(route('inventory.stock-movements.index'), navigate: true); + } + + public function render() + { + $parts = Part::orderBy('name')->get(); + + return view('livewire.inventory.stock-movements.create', [ + 'parts' => $parts, + ]); + } +} diff --git a/app/Livewire/Inventory/StockMovements/Index.php b/app/Livewire/Inventory/StockMovements/Index.php new file mode 100644 index 0000000..fdf6493 --- /dev/null +++ b/app/Livewire/Inventory/StockMovements/Index.php @@ -0,0 +1,93 @@ + ['except' => ''], + 'typeFilter' => ['except' => ''], + 'partFilter' => ['except' => ''], + 'dateFrom' => ['except' => ''], + 'dateTo' => ['except' => ''], + 'sortBy' => ['except' => 'created_at'], + 'sortDirection' => ['except' => 'desc'], + ]; + + public function updatingSearch() + { + $this->resetPage(); + } + + public function sortBy($field) + { + if ($this->sortBy === $field) { + $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc'; + } else { + $this->sortBy = $field; + $this->sortDirection = 'asc'; + } + } + + public function clearFilters() + { + $this->reset(['search', 'typeFilter', 'partFilter', 'dateFrom', 'dateTo']); + $this->resetPage(); + } + + public function render() + { + $query = StockMovement::with(['part', 'createdBy']); + + // Apply search + if ($this->search) { + $query->whereHas('part', function ($q) { + $q->where('name', 'like', '%' . $this->search . '%') + ->orWhere('part_number', 'like', '%' . $this->search . '%'); + }); + } + + // Apply filters + if ($this->typeFilter !== '') { + $query->where('movement_type', $this->typeFilter); + } + + if ($this->partFilter !== '') { + $query->where('part_id', $this->partFilter); + } + + if ($this->dateFrom) { + $query->whereDate('created_at', '>=', $this->dateFrom); + } + + if ($this->dateTo) { + $query->whereDate('created_at', '<=', $this->dateTo); + } + + // Apply sorting + $query->orderBy($this->sortBy, $this->sortDirection); + + $movements = $query->paginate(20); + $parts = Part::orderBy('name')->get(); + + return view('livewire.inventory.stock-movements.index', [ + 'movements' => $movements, + 'parts' => $parts, + ]); + } +} diff --git a/app/Livewire/Inventory/Suppliers/Create.php b/app/Livewire/Inventory/Suppliers/Create.php new file mode 100644 index 0000000..7eca6cf --- /dev/null +++ b/app/Livewire/Inventory/Suppliers/Create.php @@ -0,0 +1,66 @@ + 'required|string|max:255', + 'company_name' => 'nullable|string|max:255', + 'email' => 'required|email|unique:suppliers,email', + 'phone' => 'nullable|string|max:20', + 'address' => 'nullable|string|max:255', + 'city' => 'nullable|string|max:100', + 'state' => 'nullable|string|max:100', + 'zip_code' => 'nullable|string|max:20', + 'contact_person' => 'nullable|string|max:255', + 'payment_terms' => 'nullable|string|max:255', + 'rating' => 'nullable|numeric|min:0|max:5', + 'is_active' => 'boolean', + ]; + + public function save() + { + $this->validate(); + + Supplier::create([ + 'name' => $this->name, + 'company_name' => $this->company_name, + 'email' => $this->email, + 'phone' => $this->phone, + 'address' => $this->address, + 'city' => $this->city, + 'state' => $this->state, + 'zip_code' => $this->zip_code, + 'contact_person' => $this->contact_person, + 'payment_terms' => $this->payment_terms, + 'rating' => $this->rating ?: null, + 'is_active' => $this->is_active, + ]); + + session()->flash('success', 'Supplier created successfully!'); + + return $this->redirect(route('inventory.suppliers.index'), navigate: true); + } + + public function render() + { + return view('livewire.inventory.suppliers.create'); + } +} diff --git a/app/Livewire/Inventory/Suppliers/Edit.php b/app/Livewire/Inventory/Suppliers/Edit.php new file mode 100644 index 0000000..6f1d570 --- /dev/null +++ b/app/Livewire/Inventory/Suppliers/Edit.php @@ -0,0 +1,88 @@ +supplier = $supplier; + $this->name = $supplier->name; + $this->company_name = $supplier->company_name; + $this->email = $supplier->email; + $this->phone = $supplier->phone; + $this->address = $supplier->address; + $this->city = $supplier->city; + $this->state = $supplier->state; + $this->zip_code = $supplier->zip_code; + $this->contact_person = $supplier->contact_person; + $this->payment_terms = $supplier->payment_terms; + $this->rating = $supplier->rating ?: 0; + $this->is_active = $supplier->is_active; + } + + protected function rules() + { + return [ + 'name' => 'required|string|max:255', + 'company_name' => 'nullable|string|max:255', + 'email' => 'required|email|unique:suppliers,email,' . $this->supplier->id, + 'phone' => 'nullable|string|max:20', + 'address' => 'nullable|string|max:255', + 'city' => 'nullable|string|max:100', + 'state' => 'nullable|string|max:100', + 'zip_code' => 'nullable|string|max:20', + 'contact_person' => 'nullable|string|max:255', + 'payment_terms' => 'nullable|string|max:255', + 'rating' => 'nullable|numeric|min:0|max:5', + 'is_active' => 'boolean', + ]; + } + + public function save() + { + $this->validate(); + + $this->supplier->update([ + 'name' => $this->name, + 'company_name' => $this->company_name, + 'email' => $this->email, + 'phone' => $this->phone, + 'address' => $this->address, + 'city' => $this->city, + 'state' => $this->state, + 'zip_code' => $this->zip_code, + 'contact_person' => $this->contact_person, + 'payment_terms' => $this->payment_terms, + 'rating' => $this->rating ?: null, + 'is_active' => $this->is_active, + ]); + + session()->flash('success', 'Supplier updated successfully!'); + + return $this->redirect(route('inventory.suppliers.index'), navigate: true); + } + + public function render() + { + return view('livewire.inventory.suppliers.edit'); + } +} diff --git a/app/Livewire/Inventory/Suppliers/Index.php b/app/Livewire/Inventory/Suppliers/Index.php new file mode 100644 index 0000000..8e61502 --- /dev/null +++ b/app/Livewire/Inventory/Suppliers/Index.php @@ -0,0 +1,79 @@ + ['except' => ''], + 'statusFilter' => ['except' => ''], + 'sortBy' => ['except' => 'name'], + 'sortDirection' => ['except' => 'asc'], + ]; + + public function updatingSearch() + { + $this->resetPage(); + } + + public function updatingStatusFilter() + { + $this->resetPage(); + } + + public function sortBy($field) + { + if ($this->sortBy === $field) { + $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc'; + } else { + $this->sortBy = $field; + $this->sortDirection = 'asc'; + } + } + + public function clearFilters() + { + $this->reset(['search', 'statusFilter']); + $this->resetPage(); + } + + public function render() + { + $query = Supplier::withCount('parts'); + + // Apply search + if ($this->search) { + $query->where(function ($q) { + $q->where('name', 'like', '%' . $this->search . '%') + ->orWhere('company_name', 'like', '%' . $this->search . '%') + ->orWhere('email', 'like', '%' . $this->search . '%') + ->orWhere('phone', 'like', '%' . $this->search . '%'); + }); + } + + // Apply filters + if ($this->statusFilter !== '') { + $query->where('is_active', $this->statusFilter); + } + + // Apply sorting + $query->orderBy($this->sortBy, $this->sortDirection); + + $suppliers = $query->paginate(15); + + return view('livewire.inventory.suppliers.index', [ + 'suppliers' => $suppliers, + ]); + } +} diff --git a/app/Livewire/JobCards/Create.php b/app/Livewire/JobCards/Create.php new file mode 100644 index 0000000..d4a016a --- /dev/null +++ b/app/Livewire/JobCards/Create.php @@ -0,0 +1,184 @@ + 'required|exists:customers,id', + 'vehicle_id' => 'required|exists:vehicles,id', + 'service_advisor_id' => 'required|exists:users,id', + 'branch_code' => 'required|string|max:10', + 'arrival_datetime' => 'required|date', + 'expected_completion_date' => 'nullable|date|after:arrival_datetime', + 'mileage_in' => 'nullable|integer|min:0', + 'fuel_level_in' => 'nullable|string|max:20', + 'customer_reported_issues' => 'required|string|max:2000', + 'vehicle_condition_notes' => 'nullable|string|max:1000', + 'keys_location' => 'nullable|string|max:255', + 'personal_items_removed' => 'boolean', + 'photos_taken' => 'boolean', + 'priority' => 'required|in:low,medium,high,urgent', + 'notes' => 'nullable|string|max:2000', + 'inspector_id' => 'required_if:perform_inspection,true|exists:users,id', + 'overall_condition' => 'required_if:perform_inspection,true|string|max:500', + 'inspection_notes' => 'nullable|string|max:1000', + ]; + } + + public function mount() + { + // Check if user has permission to create job cards + $this->authorize('create', JobCard::class); + + $this->branch_code = auth()->user()->branch_code ?? config('app.default_branch_code', 'ACC'); + $this->arrival_datetime = now()->format('Y-m-d\TH:i'); + $this->loadData(); + $this->initializeInspectionChecklist(); + } + + public function loadData() + { + $user = auth()->user(); + + $this->customers = Customer::orderBy('first_name')->get(); + + // Filter service advisors based on user's permissions and branch + $this->serviceAdvisors = User::whereIn('role', ['service_advisor', 'service_supervisor']) + ->where('status', 'active') + ->when(!$user->hasPermission('job-cards.view-all'), function ($query) use ($user) { + return $query->where('branch_code', $user->branch_code); + }) + ->orderBy('name') + ->get(); + + $this->inspectors = User::whereIn('role', ['service_supervisor', 'quality_inspector']) + ->where('status', 'active') + ->when(!$user->hasPermission('job-cards.view-all'), function ($query) use ($user) { + return $query->where('branch_code', $user->branch_code); + }) + ->orderBy('name') + ->get(); + } + + public function updatedCustomerId() + { + if ($this->customer_id) { + $this->vehicles = Vehicle::where('customer_id', $this->customer_id) + ->orderBy('year', 'desc') + ->orderBy('make') + ->orderBy('model') + ->get(); + } else { + $this->vehicles = []; + $this->vehicle_id = ''; + } + } + + public function initializeInspectionChecklist() + { + $this->inspection_checklist = [ + 'exterior_damage' => false, + 'interior_condition' => false, + 'tire_condition' => false, + 'fluid_levels' => false, + 'lights_working' => false, + 'battery_condition' => false, + 'belts_hoses' => false, + 'air_filter' => false, + 'brake_condition' => false, + 'suspension' => false, + ]; + } + + public function save() + { + // Check if user still has permission to create job cards + $this->authorize('create', JobCard::class); + + $this->validate(); + + try { + $workflowService = app(WorkflowService::class); + + $data = [ + 'customer_id' => $this->customer_id, + 'vehicle_id' => $this->vehicle_id, + 'service_advisor_id' => $this->service_advisor_id, + 'branch_code' => $this->branch_code, + 'arrival_datetime' => $this->arrival_datetime, + 'expected_completion_date' => $this->expected_completion_date, + 'mileage_in' => $this->mileage_in, + 'fuel_level_in' => $this->fuel_level_in, + 'customer_reported_issues' => $this->customer_reported_issues, + 'vehicle_condition_notes' => $this->vehicle_condition_notes, + 'keys_location' => $this->keys_location, + 'personal_items_removed' => $this->personal_items_removed, + 'photos_taken' => $this->photos_taken, + 'priority' => $this->priority, + 'notes' => $this->notes, + ]; + + if ($this->perform_inspection) { + $data['inspector_id'] = $this->inspector_id; + $data['inspection_checklist'] = $this->inspection_checklist; + $data['overall_condition'] = $this->overall_condition; + $data['inspection_notes'] = $this->inspection_notes; + } + + $jobCard = $workflowService->createJobCard($data); + + session()->flash('success', 'Job card created successfully! Job Card #: ' . $jobCard->job_card_number); + + return redirect()->route('job-cards.show', $jobCard); + + } catch (\Exception $e) { + session()->flash('error', 'Failed to create job card: ' . $e->getMessage()); + } + } + + public function render() + { + return view('livewire.job-cards.create'); + } +} diff --git a/app/Livewire/JobCards/Edit.php b/app/Livewire/JobCards/Edit.php new file mode 100644 index 0000000..ef7a5cd --- /dev/null +++ b/app/Livewire/JobCards/Edit.php @@ -0,0 +1,185 @@ +jobCard = $jobCard; + $this->loadData(); + $this->initializeForm(); + } + + public function initializeForm() + { + $this->form = [ + 'customer_id' => $this->jobCard->customer_id, + 'vehicle_id' => $this->jobCard->vehicle_id, + 'service_advisor_id' => $this->jobCard->service_advisor_id, + 'status' => $this->jobCard->status, + 'arrival_datetime' => $this->jobCard->arrival_datetime ? $this->jobCard->arrival_datetime->format('Y-m-d\TH:i') : '', + 'expected_completion_date' => $this->jobCard->expected_completion_date ? $this->jobCard->expected_completion_date->format('Y-m-d\TH:i') : '', + 'completion_datetime' => $this->jobCard->completion_datetime ? $this->jobCard->completion_datetime->format('Y-m-d\TH:i') : '', + 'priority' => $this->jobCard->priority, + 'mileage_in' => $this->jobCard->mileage_in, + 'mileage_out' => $this->jobCard->mileage_out, + 'fuel_level_in' => $this->jobCard->fuel_level_in, + 'fuel_level_out' => $this->jobCard->fuel_level_out, + 'keys_location' => $this->jobCard->keys_location, + 'delivery_method' => $this->jobCard->delivery_method, + 'customer_reported_issues' => $this->jobCard->customer_reported_issues, + 'vehicle_condition_notes' => $this->jobCard->vehicle_condition_notes, + 'notes' => $this->jobCard->notes, + 'customer_satisfaction_rating' => $this->jobCard->customer_satisfaction_rating, + 'personal_items_removed' => (bool) $this->jobCard->personal_items_removed, + 'photos_taken' => (bool) $this->jobCard->photos_taken, + ]; + } + + public function loadData() + { + $user = auth()->user(); + + $this->customers = Customer::orderBy('first_name')->get(); + $this->vehicles = Vehicle::orderBy('make')->orderBy('model')->get(); + + // Filter service advisors based on user's permissions and branch + $this->serviceAdvisors = User::whereIn('role', ['service_advisor', 'service_supervisor']) + ->where('status', 'active') + ->when(!$user->hasRole(['admin', 'manager']), function ($query) use ($user) { + return $query->where('branch_code', $user->branch_code); + }) + ->orderBy('name') + ->get(); + } + + protected function rules() + { + return [ + 'form.customer_id' => 'required|exists:customers,id', + 'form.vehicle_id' => 'required|exists:vehicles,id', + 'form.service_advisor_id' => 'nullable|exists:users,id', + 'form.status' => 'required|string|in:received,in_diagnosis,estimate_sent,approved,in_progress,quality_check,completed,delivered,cancelled', + 'form.arrival_datetime' => 'required|date', + 'form.expected_completion_date' => 'nullable|date|after:form.arrival_datetime', + 'form.completion_datetime' => 'nullable|date', + 'form.priority' => 'required|in:low,medium,high,urgent', + 'form.mileage_in' => 'nullable|integer|min:0', + 'form.mileage_out' => 'nullable|integer|min:0|gte:form.mileage_in', + 'form.fuel_level_in' => 'nullable|string|in:empty,1/4,1/2,3/4,full', + 'form.fuel_level_out' => 'nullable|string|in:empty,1/4,1/2,3/4,full', + 'form.keys_location' => 'nullable|string|max:255', + 'form.delivery_method' => 'nullable|string|in:pickup,delivery,towing', + 'form.customer_reported_issues' => 'nullable|string|max:2000', + 'form.vehicle_condition_notes' => 'nullable|string|max:1000', + 'form.notes' => 'nullable|string|max:2000', + 'form.customer_satisfaction_rating' => 'nullable|integer|min:1|max:5', + 'form.personal_items_removed' => 'boolean', + 'form.photos_taken' => 'boolean', + ]; + } + + public function save() + { + try { + // Debug: Log form data + \Log::info('Form data before validation:', $this->form); + + $this->validate(); + + // Filter out empty values for optional fields + $updateData = [ + 'customer_id' => $this->form['customer_id'], + 'vehicle_id' => $this->form['vehicle_id'], + 'status' => $this->form['status'], + 'arrival_datetime' => $this->form['arrival_datetime'], + 'priority' => $this->form['priority'], + 'personal_items_removed' => (bool) ($this->form['personal_items_removed'] ?? false), + 'photos_taken' => (bool) ($this->form['photos_taken'] ?? false), + ]; + + // Add service advisor if provided + if (!empty($this->form['service_advisor_id'])) { + $updateData['service_advisor_id'] = $this->form['service_advisor_id']; + } + + // Add optional fields only if they have values + if (!empty($this->form['expected_completion_date'])) { + $updateData['expected_completion_date'] = $this->form['expected_completion_date']; + } + + if (!empty($this->form['completion_datetime'])) { + $updateData['completion_datetime'] = $this->form['completion_datetime']; + } + + if (!empty($this->form['mileage_in'])) { + $updateData['mileage_in'] = (int) $this->form['mileage_in']; + } + + if (!empty($this->form['mileage_out'])) { + $updateData['mileage_out'] = (int) $this->form['mileage_out']; + } + + if (!empty($this->form['fuel_level_in'])) { + $updateData['fuel_level_in'] = $this->form['fuel_level_in']; + } + + if (!empty($this->form['fuel_level_out'])) { + $updateData['fuel_level_out'] = $this->form['fuel_level_out']; + } + + if (!empty($this->form['keys_location'])) { + $updateData['keys_location'] = $this->form['keys_location']; + } + + if (!empty($this->form['delivery_method'])) { + $updateData['delivery_method'] = $this->form['delivery_method']; + } + + if (!empty($this->form['customer_satisfaction_rating'])) { + $updateData['customer_satisfaction_rating'] = (int) $this->form['customer_satisfaction_rating']; + } + + // Add text fields even if empty (they can be null) + $updateData['customer_reported_issues'] = $this->form['customer_reported_issues'] ?? null; + $updateData['vehicle_condition_notes'] = $this->form['vehicle_condition_notes'] ?? null; + $updateData['notes'] = $this->form['notes'] ?? null; + + \Log::info('Update data:', $updateData); + + $this->jobCard->update($updateData); + + session()->flash('success', 'Job card updated successfully!'); + + return redirect()->route('job-cards.show', $this->jobCard); + + } catch (\Illuminate\Validation\ValidationException $e) { + \Log::error('Validation error:', $e->errors()); + // Re-throw validation exceptions so they are handled by Livewire + throw $e; + } catch (\Exception $e) { + \Log::error('Update error:', ['message' => $e->getMessage(), 'trace' => $e->getTraceAsString()]); + session()->flash('error', 'Failed to update job card: ' . $e->getMessage()); + $this->dispatch('show-error', message: 'Failed to update job card: ' . $e->getMessage()); + } + } + + public function render() + { + return view('livewire.job-cards.edit'); + } +} diff --git a/app/Livewire/JobCards/Index.php b/app/Livewire/JobCards/Index.php new file mode 100644 index 0000000..25e86a3 --- /dev/null +++ b/app/Livewire/JobCards/Index.php @@ -0,0 +1,118 @@ + ['except' => ''], + 'statusFilter' => ['except' => ''], + 'branchFilter' => ['except' => ''], + 'priorityFilter' => ['except' => ''], + 'sortBy' => ['except' => 'created_at'], + 'sortDirection' => ['except' => 'desc'], + ]; + + public function updatingSearch() + { + $this->resetPage(); + } + + public function updatingStatusFilter() + { + $this->resetPage(); + } + + public function updatingBranchFilter() + { + $this->resetPage(); + } + + public function updatingPriorityFilter() + { + $this->resetPage(); + } + + public function sortBy($field) + { + if ($this->sortBy === $field) { + $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc'; + } else { + $this->sortBy = $field; + $this->sortDirection = 'asc'; + } + } + + public function render() + { + $jobCards = JobCard::query() + ->with(['customer', 'vehicle', 'serviceAdvisor']) + ->when($this->search, function ($query) { + $query->where(function ($q) { + $q->where('job_card_number', 'like', '%' . $this->search . '%') + ->orWhereHas('customer', function ($customerQuery) { + $customerQuery->where('first_name', 'like', '%' . $this->search . '%') + ->orWhere('last_name', 'like', '%' . $this->search . '%') + ->orWhere('email', 'like', '%' . $this->search . '%'); + }) + ->orWhereHas('vehicle', function ($vehicleQuery) { + $vehicleQuery->where('license_plate', 'like', '%' . $this->search . '%') + ->orWhere('vin', 'like', '%' . $this->search . '%'); + }); + }); + }) + ->when($this->statusFilter, function ($query) { + $query->where('status', $this->statusFilter); + }) + ->when($this->branchFilter, function ($query) { + $query->where('branch_code', $this->branchFilter); + }) + ->when($this->priorityFilter, function ($query) { + $query->where('priority', $this->priorityFilter); + }) + ->orderBy($this->sortBy, $this->sortDirection) + ->paginate(20); + + $statusOptions = [ + 'received' => 'Received', + 'in_diagnosis' => 'In Diagnosis', + 'estimate_sent' => 'Estimate Sent', + 'approved' => 'Approved', + 'in_progress' => 'In Progress', + 'quality_check' => 'Quality Check', + 'completed' => 'Completed', + 'delivered' => 'Delivered', + 'cancelled' => 'Cancelled', + ]; + + $priorityOptions = [ + 'low' => 'Low', + 'medium' => 'Medium', + 'high' => 'High', + 'urgent' => 'Urgent', + ]; + + $branchOptions = [ + 'ACC' => 'ACC Branch', + 'KSI' => 'KSI Branch', + // Add more branches as needed + ]; + + return view('livewire.job-cards.index', compact('jobCards', 'statusOptions', 'priorityOptions', 'branchOptions')); + } +} diff --git a/app/Livewire/JobCards/Show.php b/app/Livewire/JobCards/Show.php new file mode 100644 index 0000000..7132ed4 --- /dev/null +++ b/app/Livewire/JobCards/Show.php @@ -0,0 +1,31 @@ +jobCard = $jobCard->load([ + 'customer', + 'vehicle', + 'serviceAdvisor', + 'incomingInspection', + 'outgoingInspection', + 'diagnosis', + 'estimates', + 'workOrders', + 'timesheets' + ]); + } + + public function render() + { + return view('livewire.job-cards.show'); + } +} diff --git a/app/Livewire/JobCards/WorkflowStatus.php b/app/Livewire/JobCards/WorkflowStatus.php new file mode 100644 index 0000000..53823bc --- /dev/null +++ b/app/Livewire/JobCards/WorkflowStatus.php @@ -0,0 +1,37 @@ +jobCard = $jobCard->load([ + 'customer', + 'vehicle', + 'serviceAdvisor', + 'incomingInspection.inspector', + 'outgoingInspection.inspector', + 'diagnosis.serviceCoordinator', + 'estimates.preparedBy', + 'workOrders.assignedTechnician', + 'workOrders.serviceCoordinator', + 'timesheets.technician' + ]); + + $workflowService = app(WorkflowService::class); + $this->workflowData = $workflowService->getWorkflowStatus($this->jobCard); + } + + public function render() + { + return view('livewire.job-cards.workflow'); + } +} diff --git a/app/Livewire/Reports/Dashboard.php b/app/Livewire/Reports/Dashboard.php new file mode 100644 index 0000000..cd42be4 --- /dev/null +++ b/app/Livewire/Reports/Dashboard.php @@ -0,0 +1,190 @@ +setDateRange(); + $this->loadAllData(); + } + + public function updatedDateRange() + { + $this->setDateRange(); + $this->loadAllData(); + } + + public function updatedSelectedReport() + { + $this->loadAllData(); + } + + public function setDateRange() + { + switch ($this->dateRange) { + case 'today': + $this->startDate = now()->startOfDay(); + $this->endDate = now()->endOfDay(); + break; + case 'yesterday': + $this->startDate = now()->subDay()->startOfDay(); + $this->endDate = now()->subDay()->endOfDay(); + break; + case 'last_7_days': + $this->startDate = now()->subDays(7)->startOfDay(); + $this->endDate = now()->endOfDay(); + break; + case 'last_30_days': + $this->startDate = now()->subDays(30)->startOfDay(); + $this->endDate = now()->endOfDay(); + break; + case 'this_month': + $this->startDate = now()->startOfMonth(); + $this->endDate = now()->endOfMonth(); + break; + case 'last_month': + $this->startDate = now()->subMonth()->startOfMonth(); + $this->endDate = now()->subMonth()->endOfMonth(); + break; + case 'this_quarter': + $this->startDate = now()->startOfQuarter(); + $this->endDate = now()->endOfQuarter(); + break; + case 'this_year': + $this->startDate = now()->startOfYear(); + $this->endDate = now()->endOfYear(); + break; + case 'last_year': + $this->startDate = now()->subYear()->startOfYear(); + $this->endDate = now()->subYear()->endOfYear(); + break; + } + } + + public function loadAllData() + { + $this->loadOverviewStats(); + + if ($this->selectedReport === 'revenue' || $this->selectedReport === 'overview') { + $this->loadRevenueData(); + } + + if ($this->selectedReport === 'customer_analytics' || $this->selectedReport === 'overview') { + $this->loadCustomerAnalytics(); + } + + if ($this->selectedReport === 'service_trends' || $this->selectedReport === 'overview') { + $this->loadServiceTrends(); + } + + if ($this->selectedReport === 'performance_metrics' || $this->selectedReport === 'overview') { + $this->loadPerformanceMetrics(); + } + } + + public function loadOverviewStats() + { + $this->overviewStats = [ + 'total_revenue' => 125000.50, + 'total_orders' => 1248, + 'completed_orders' => 1156, + 'new_customers' => 47, + 'total_customers' => 892, + 'total_appointments' => 1248, + 'confirmed_appointments' => 1156, + 'avg_order_value' => 285.50, + 'customer_satisfaction' => 4.3 + ]; + } + + public function loadRevenueData() + { + $this->revenueData = Report::getRevenueData($this->startDate, $this->endDate); + } + + public function loadCustomerAnalytics() + { + $this->customerAnalytics = Report::getCustomerAnalytics($this->startDate, $this->endDate); + } + + public function loadServiceTrends() + { + $this->serviceTrends = Report::getServiceTrends($this->startDate, $this->endDate); + } + + public function loadPerformanceMetrics() + { + $this->performanceMetrics = Report::getPerformanceMetrics($this->startDate, $this->endDate); + } + + private function getGroupByFromDateRange() + { + return in_array($this->dateRange, ['this_month', 'last_month', 'this_quarter', 'this_year', 'last_year']) + ? 'month' + : 'day'; + } + + public function exportReport($type = 'pdf') + { + // TODO: Implement export functionality + $this->dispatch('notify', [ + 'type' => 'info', + 'message' => 'Export functionality will be implemented soon.' + ]); + } + + public function getDateRangeOptions() + { + return [ + 'today' => 'Today', + 'yesterday' => 'Yesterday', + 'last_7_days' => 'Last 7 Days', + 'last_30_days' => 'Last 30 Days', + 'this_month' => 'This Month', + 'last_month' => 'Last Month', + 'this_quarter' => 'This Quarter', + 'this_year' => 'This Year', + 'last_year' => 'Last Year' + ]; + } + + public function getReportOptions() + { + return [ + 'overview' => 'Overview', + 'revenue' => 'Revenue Analysis', + 'customer_analytics' => 'Customer Analytics', + 'service_trends' => 'Service Trends', + 'performance_metrics' => 'Performance Metrics' + ]; + } + + public function render() + { + return view('livewire.reports.dashboard')->layout('components.layouts.app', [ + 'title' => 'Reports & Analytics' + ]); + } +} diff --git a/app/Livewire/ServiceItems/Manage.php b/app/Livewire/ServiceItems/Manage.php new file mode 100644 index 0000000..81bc24f --- /dev/null +++ b/app/Livewire/ServiceItems/Manage.php @@ -0,0 +1,169 @@ + 'Diagnosis', + 'Engine' => 'Engine', + 'Brake' => 'Brake', + 'Suspension' => 'Suspension', + 'Electrical' => 'Electrical', + 'Transmission' => 'Transmission', + 'Maintenance' => 'Maintenance', + 'HVAC' => 'HVAC', + 'Cooling' => 'Cooling', + 'Exterior' => 'Exterior', + 'Interior' => 'Interior', + 'Other' => 'Other', + ]; + + protected $rules = [ + 'service_name' => 'required|string|max:255', + 'description' => 'nullable|string', + 'category' => 'required|string|max:100', + 'labor_rate' => 'required|numeric|min:0|max:500', + 'estimated_hours' => 'required|numeric|min:0.25|max:40', + 'status' => 'required|in:active,inactive', + 'technician_notes' => 'nullable|string', + ]; + + protected $messages = [ + 'service_name.required' => 'Service name is required.', + 'category.required' => 'Category is required.', + 'labor_rate.required' => 'Labor rate is required.', + 'labor_rate.min' => 'Labor rate must be at least $0.', + 'labor_rate.max' => 'Labor rate cannot exceed $500/hour.', + 'estimated_hours.required' => 'Estimated hours is required.', + 'estimated_hours.min' => 'Estimated hours must be at least 0.25 hours.', + 'estimated_hours.max' => 'Estimated hours cannot exceed 40 hours.', + ]; + + public function updatingSearchTerm() + { + $this->resetPage(); + } + + public function updatingCategoryFilter() + { + $this->resetPage(); + } + + public function toggleForm() + { + $this->showForm = !$this->showForm; + if (!$this->showForm) { + $this->resetForm(); + } + } + + public function resetForm() + { + $this->reset([ + 'service_name', 'description', 'category', 'labor_rate', + 'estimated_hours', 'status', 'technician_notes', 'editingId' + ]); + $this->labor_rate = 85.00; + $this->estimated_hours = 1.0; + $this->status = 'active'; + } + + public function save() + { + $this->validate(); + + if ($this->editingId) { + // Update existing service item + ServiceItem::findOrFail($this->editingId)->update([ + 'service_name' => $this->service_name, + 'description' => $this->description, + 'category' => $this->category, + 'labor_rate' => $this->labor_rate, + 'estimated_hours' => $this->estimated_hours, + 'status' => $this->status, + 'technician_notes' => $this->technician_notes, + ]); + + session()->flash('message', 'Service item updated successfully!'); + } else { + // Create new service item + ServiceItem::create([ + 'service_name' => $this->service_name, + 'description' => $this->description, + 'category' => $this->category, + 'labor_rate' => $this->labor_rate, + 'estimated_hours' => $this->estimated_hours, + 'status' => $this->status, + 'technician_notes' => $this->technician_notes, + ]); + + session()->flash('message', 'Service item created successfully!'); + } + + $this->resetForm(); + $this->showForm = false; + } + + public function edit($id) + { + $serviceItem = ServiceItem::findOrFail($id); + + $this->editingId = $id; + $this->service_name = $serviceItem->service_name; + $this->description = $serviceItem->description; + $this->category = $serviceItem->category; + $this->labor_rate = $serviceItem->labor_rate; + $this->estimated_hours = $serviceItem->estimated_hours; + $this->status = $serviceItem->status; + $this->technician_notes = $serviceItem->technician_notes; + $this->showForm = true; + } + + public function delete($id) + { + ServiceItem::findOrFail($id)->delete(); + session()->flash('message', 'Service item deleted successfully!'); + } + + public function render() + { + $query = ServiceItem::query(); + + if ($this->searchTerm) { + $query->where(function ($q) { + $q->where('service_name', 'like', '%' . $this->searchTerm . '%') + ->orWhere('description', 'like', '%' . $this->searchTerm . '%'); + }); + } + + if ($this->categoryFilter) { + $query->where('category', $this->categoryFilter); + } + + $serviceItems = $query->orderBy('service_name')->paginate(15); + + return view('livewire.service-items.manage', [ + 'serviceItems' => $serviceItems + ]); + } +} diff --git a/app/Livewire/ServiceOrders/Create.php b/app/Livewire/ServiceOrders/Create.php new file mode 100644 index 0000000..a7e262e --- /dev/null +++ b/app/Livewire/ServiceOrders/Create.php @@ -0,0 +1,312 @@ + '', + 'description' => '', + 'category' => '', + 'labor_rate' => 75.00, + 'estimated_hours' => 1.0, + 'status' => 'pending', + 'technician_notes' => '', + ]; + + // Parts + public $selectedParts = []; + public $newPart = [ + 'part_id' => '', + 'quantity_used' => 1, + 'unit_price' => 0, + 'notes' => '', + ]; + + // Available data + public $customers = []; + public $vehicles = []; + public $technicians = []; + public $availableParts = []; + + public function mount() + { + $this->loadCustomers(); + $this->loadTechnicians(); + $this->loadParts(); + + // Pre-select vehicle if passed in query string + if (request()->has('vehicle')) { + $this->vehicle_id = request('vehicle'); + $this->updatedVehicleId(); + } + } + + public function loadCustomers() + { + $this->customers = Customer::where('status', 'active') + ->orderBy('first_name') + ->get(); + } + + public function loadTechnicians() + { + $this->technicians = Technician::where('status', 'active') + ->orderBy('first_name') + ->get(); + } + + public function loadParts() + { + $this->availableParts = Part::where('status', 'active') + ->where('quantity_on_hand', '>', 0) + ->orderBy('name') + ->get(); + } + + public function updatedCustomerId() + { + if ($this->customer_id) { + $this->vehicles = Vehicle::where('customer_id', $this->customer_id) + ->where('status', 'active') + ->orderBy('year') + ->orderBy('make') + ->orderBy('model') + ->get(); + } else { + $this->vehicles = []; + $this->vehicle_id = ''; + } + } + + public function updatedVehicleId() + { + if ($this->vehicle_id) { + $vehicle = Vehicle::find($this->vehicle_id); + if ($vehicle) { + $this->customer_id = $vehicle->customer_id; + $this->updatedCustomerId(); + } + } + } + + public function addServiceItem() + { + $this->validate([ + 'newServiceItem.service_name' => 'required|string|max:255', + 'newServiceItem.description' => 'nullable|string|max:500', + 'newServiceItem.category' => 'required|string|max:100', + 'newServiceItem.labor_rate' => 'required|numeric|min:0', + 'newServiceItem.estimated_hours' => 'required|numeric|min:0.1', + ]); + + $this->serviceItems[] = [ + 'service_name' => $this->newServiceItem['service_name'], + 'description' => $this->newServiceItem['description'], + 'category' => $this->newServiceItem['category'], + 'labor_rate' => $this->newServiceItem['labor_rate'], + 'estimated_hours' => $this->newServiceItem['estimated_hours'], + 'labor_cost' => $this->newServiceItem['labor_rate'] * $this->newServiceItem['estimated_hours'], + 'status' => $this->newServiceItem['status'], + 'technician_notes' => $this->newServiceItem['technician_notes'], + ]; + + // Reset form + $this->newServiceItem = [ + 'service_name' => '', + 'description' => '', + 'category' => '', + 'labor_rate' => 75.00, + 'estimated_hours' => 1.0, + 'status' => 'pending', + 'technician_notes' => '', + ]; + } + + public function removeServiceItem($index) + { + unset($this->serviceItems[$index]); + $this->serviceItems = array_values($this->serviceItems); + } + + public function addPart() + { + $this->validate([ + 'newPart.part_id' => 'required|exists:parts,id', + 'newPart.quantity_used' => 'required|integer|min:1', + 'newPart.unit_price' => 'required|numeric|min:0', + ]); + + $part = Part::find($this->newPart['part_id']); + + if ($part) { + $this->selectedParts[] = [ + 'part_id' => $part->id, + 'part_name' => $part->name, + 'part_number' => $part->part_number, + 'quantity_used' => $this->newPart['quantity_used'], + 'unit_cost' => $part->cost_price, + 'unit_price' => $this->newPart['unit_price'], + 'total_cost' => $part->cost_price * $this->newPart['quantity_used'], + 'total_price' => $this->newPart['unit_price'] * $this->newPart['quantity_used'], + 'status' => 'requested', + 'notes' => $this->newPart['notes'], + ]; + + // Reset form + $this->newPart = [ + 'part_id' => '', + 'quantity_used' => 1, + 'unit_price' => 0, + 'notes' => '', + ]; + } + } + + public function removePart($index) + { + unset($this->selectedParts[$index]); + $this->selectedParts = array_values($this->selectedParts); + } + + public function updatedNewPartPartId() + { + if ($this->newPart['part_id']) { + $part = Part::find($this->newPart['part_id']); + if ($part) { + $this->newPart['unit_price'] = $part->sell_price; + } + } + } + + public function getTotalLaborCost() + { + return collect($this->serviceItems)->sum('labor_cost'); + } + + public function getTotalPartsCost() + { + return collect($this->selectedParts)->sum('total_price'); + } + + public function getSubtotal() + { + return $this->getTotalLaborCost() + $this->getTotalPartsCost(); + } + + public function getTaxAmount() + { + return $this->getSubtotal() * 0.08; // 8% tax + } + + public function getTotalAmount() + { + return $this->getSubtotal() + $this->getTaxAmount(); + } + + public function createServiceOrder() + { + $this->validate(); + + // Create the service order + $serviceOrder = ServiceOrder::create([ + 'customer_id' => $this->customer_id, + 'vehicle_id' => $this->vehicle_id, + 'assigned_technician_id' => $this->assigned_technician_id ?: null, + 'customer_complaint' => $this->customer_complaint, + 'recommended_services' => $this->recommended_services, + 'priority' => $this->priority, + 'status' => $this->status, + 'scheduled_date' => $this->scheduled_date ?: null, + 'estimated_hours' => $this->estimated_hours, + 'internal_notes' => $this->internal_notes, + 'customer_notes' => $this->customer_notes, + 'labor_cost' => $this->getTotalLaborCost(), + 'parts_cost' => $this->getTotalPartsCost(), + 'tax_amount' => $this->getTaxAmount(), + 'discount_amount' => 0, + 'total_amount' => $this->getTotalAmount(), + ]); + + // Create service items + foreach ($this->serviceItems as $item) { + ServiceItem::create([ + 'service_order_id' => $serviceOrder->id, + 'service_name' => $item['service_name'], + 'description' => $item['description'], + 'category' => $item['category'], + 'labor_rate' => $item['labor_rate'], + 'estimated_hours' => $item['estimated_hours'], + 'labor_cost' => $item['labor_cost'], + 'status' => $item['status'], + 'technician_notes' => $item['technician_notes'], + ]); + } + + // Attach parts + foreach ($this->selectedParts as $part) { + $serviceOrder->parts()->attach($part['part_id'], [ + 'quantity_used' => $part['quantity_used'], + 'unit_cost' => $part['unit_cost'], + 'unit_price' => $part['unit_price'], + 'total_cost' => $part['total_cost'], + 'total_price' => $part['total_price'], + 'status' => $part['status'], + 'notes' => $part['notes'], + ]); + } + + session()->flash('success', 'Service order created successfully!'); + + return $this->redirect('/service-orders/' . $serviceOrder->id, navigate: true); + } + + public function render() + { + return view('livewire.service-orders.create'); + } +} diff --git a/app/Livewire/ServiceOrders/Edit.php b/app/Livewire/ServiceOrders/Edit.php new file mode 100644 index 0000000..a53abdc --- /dev/null +++ b/app/Livewire/ServiceOrders/Edit.php @@ -0,0 +1,244 @@ +serviceOrder = $serviceOrder; + $this->customer_id = $serviceOrder->customer_id; + $this->vehicle_id = $serviceOrder->vehicle_id; + $this->technician_id = $serviceOrder->technician_id; + $this->customer_complaint = $serviceOrder->customer_complaint; + $this->diagnosis = $serviceOrder->diagnosis; + $this->customer_notes = $serviceOrder->customer_notes; + $this->discount_amount = $serviceOrder->discount_amount; + $this->status = $serviceOrder->status; + + // Load existing service items + $this->serviceItems = $serviceOrder->serviceItems->map(function ($item) { + return [ + 'id' => $item->id, + 'service_name' => $item->service_name, + 'description' => $item->description, + 'labor_rate' => $item->labor_rate, + 'estimated_hours' => $item->estimated_hours, + 'labor_cost' => $item->labor_cost, + ]; + })->toArray(); + + // Load existing parts + $this->selectedParts = $serviceOrder->parts->map(function ($part) { + return [ + 'part_id' => $part->id, + 'quantity_used' => $part->pivot->quantity_used, + 'unit_price' => $part->pivot->unit_price, + 'total_price' => $part->pivot->total_price, + ]; + })->toArray(); + + $this->loadData(); + } + + public function loadData() + { + $this->customers = Customer::orderBy('first_name')->get(); + $this->vehicles = $this->customer_id ? + Vehicle::where('customer_id', $this->customer_id)->get() : + collect(); + $this->technicians = Technician::where('status', 'active')->orderBy('first_name')->get(); + $this->availableParts = Part::where('quantity_on_hand', '>', 0)->orderBy('name')->get(); + } + + public function updatedCustomerId() + { + $this->vehicle_id = null; + $this->vehicles = $this->customer_id ? + Vehicle::where('customer_id', $this->customer_id)->get() : + collect(); + } + + public function addServiceItem() + { + $this->serviceItems[] = [ + 'id' => null, + 'service_name' => '', + 'description' => '', + 'labor_rate' => 75.00, + 'estimated_hours' => 1, + 'labor_cost' => 75.00, + ]; + } + + public function removeServiceItem($index) + { + unset($this->serviceItems[$index]); + $this->serviceItems = array_values($this->serviceItems); + } + + public function updateServiceItemCost($index) + { + if (isset($this->serviceItems[$index])) { + $item = &$this->serviceItems[$index]; + $item['labor_cost'] = $item['labor_rate'] * $item['estimated_hours']; + } + } + + public function addPart() + { + $this->selectedParts[] = [ + 'part_id' => '', + 'quantity_used' => 1, + 'unit_price' => 0, + 'total_price' => 0, + ]; + } + + public function removePart($index) + { + unset($this->selectedParts[$index]); + $this->selectedParts = array_values($this->selectedParts); + } + + public function updatePartPrice($index) + { + if (isset($this->selectedParts[$index])) { + $part = &$this->selectedParts[$index]; + if ($part['part_id']) { + $partModel = Part::find($part['part_id']); + if ($partModel) { + $part['unit_price'] = $partModel->cost_price * 1.3; // 30% markup + } + } + $part['total_price'] = $part['quantity_used'] * $part['unit_price']; + } + } + + public function updatePartTotal($index) + { + if (isset($this->selectedParts[$index])) { + $part = &$this->selectedParts[$index]; + $part['total_price'] = $part['quantity_used'] * $part['unit_price']; + } + } + + public function getTotalLaborCost() + { + return collect($this->serviceItems)->sum('labor_cost'); + } + + public function getTotalPartsCost() + { + return collect($this->selectedParts)->sum('total_price'); + } + + public function getSubtotal() + { + return $this->getTotalLaborCost() + $this->getTotalPartsCost() - $this->discount_amount; + } + + public function getTaxAmount() + { + return $this->getSubtotal() * 0.08; // 8% tax + } + + public function getGrandTotal() + { + return $this->getSubtotal() + $this->getTaxAmount(); + } + + public function update() + { + $this->validate(); + + // Update the service order + $this->serviceOrder->update([ + 'customer_id' => $this->customer_id, + 'vehicle_id' => $this->vehicle_id, + 'technician_id' => $this->technician_id, + 'customer_complaint' => $this->customer_complaint, + 'diagnosis' => $this->diagnosis, + 'customer_notes' => $this->customer_notes, + 'discount_amount' => $this->discount_amount, + 'status' => $this->status, + 'labor_cost' => $this->getTotalLaborCost(), + 'parts_cost' => $this->getTotalPartsCost(), + 'tax_amount' => $this->getTaxAmount(), + 'total_amount' => $this->getGrandTotal(), + ]); + + // Update service items + $this->serviceOrder->serviceItems()->delete(); + foreach ($this->serviceItems as $item) { + $this->serviceOrder->serviceItems()->create([ + 'service_name' => $item['service_name'], + 'description' => $item['description'], + 'labor_rate' => $item['labor_rate'], + 'estimated_hours' => $item['estimated_hours'], + 'labor_cost' => $item['labor_cost'], + ]); + } + + // Update parts + $this->serviceOrder->parts()->detach(); + foreach ($this->selectedParts as $part) { + if ($part['part_id']) { + $this->serviceOrder->parts()->attach($part['part_id'], [ + 'quantity_used' => $part['quantity_used'], + 'unit_price' => $part['unit_price'], + 'total_price' => $part['total_price'], + ]); + } + } + + session()->flash('success', 'Service order updated successfully!'); + return redirect()->route('service-orders.show', $this->serviceOrder); + } + + public function render() + { + return view('livewire.service-orders.edit'); + } +} diff --git a/app/Livewire/ServiceOrders/Index.php b/app/Livewire/ServiceOrders/Index.php new file mode 100644 index 0000000..843c2f3 --- /dev/null +++ b/app/Livewire/ServiceOrders/Index.php @@ -0,0 +1,163 @@ + ['except' => ''], + 'status' => ['except' => ''], + 'priority' => ['except' => ''], + 'technician_id' => ['except' => ''], + 'date_from' => ['except' => ''], + 'date_to' => ['except' => ''], + 'sortBy' => ['except' => 'created_at'], + 'sortDirection' => ['except' => 'desc'], + ]; + + public function updatingSearch() + { + $this->resetPage(); + } + + public function updatingStatus() + { + $this->resetPage(); + } + + public function updatingPriority() + { + $this->resetPage(); + } + + public function updatingTechnicianId() + { + $this->resetPage(); + } + + public function sortBy($field) + { + if ($this->sortBy === $field) { + $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc'; + } else { + $this->sortBy = $field; + $this->sortDirection = 'asc'; + } + $this->resetPage(); + } + + public function deleteServiceOrder($id) + { + $serviceOrder = ServiceOrder::find($id); + + if ($serviceOrder) { + $serviceOrder->delete(); + session()->flash('success', 'Service order deleted successfully!'); + } else { + session()->flash('error', 'Service order not found.'); + } + } + + public function updateStatus($id, $status) + { + $serviceOrder = ServiceOrder::find($id); + + if ($serviceOrder) { + $serviceOrder->status = $status; + + if ($status === 'in_progress' && !$serviceOrder->started_at) { + $serviceOrder->started_at = now(); + } elseif ($status === 'completed' && !$serviceOrder->completed_at) { + $serviceOrder->completed_at = now(); + } + + $serviceOrder->save(); + session()->flash('success', 'Service order status updated successfully!'); + } + } + + public function render() + { + $query = ServiceOrder::query() + ->with(['customer', 'vehicle', 'assignedTechnician', 'serviceItems', 'parts']); + + // Apply search filter + if ($this->search) { + $query->where(function($q) { + $q->where('order_number', 'like', '%' . $this->search . '%') + ->orWhere('customer_complaint', 'like', '%' . $this->search . '%') + ->orWhereHas('customer', function($q) { + $q->where('first_name', 'like', '%' . $this->search . '%') + ->orWhere('last_name', 'like', '%' . $this->search . '%'); + }) + ->orWhereHas('vehicle', function($q) { + $q->where('make', 'like', '%' . $this->search . '%') + ->orWhere('model', 'like', '%' . $this->search . '%') + ->orWhere('license_plate', 'like', '%' . $this->search . '%'); + }); + }); + } + + // Apply filters + if ($this->status) { + $query->where('status', $this->status); + } + + if ($this->priority) { + $query->where('priority', $this->priority); + } + + if ($this->technician_id) { + $query->where('assigned_technician_id', $this->technician_id); + } + + if ($this->date_from) { + $query->whereDate('created_at', '>=', $this->date_from); + } + + if ($this->date_to) { + $query->whereDate('created_at', '<=', $this->date_to); + } + + // Apply sorting + $query->orderBy($this->sortBy, $this->sortDirection); + + $serviceOrders = $query->paginate(15); + + // Load additional data for filters + $technicians = Technician::where('status', 'active')->orderBy('first_name')->get(); + + // Quick stats + $stats = [ + 'total' => ServiceOrder::count(), + 'pending' => ServiceOrder::where('status', 'pending')->count(), + 'in_progress' => ServiceOrder::where('status', 'in_progress')->count(), + 'completed_today' => ServiceOrder::where('status', 'completed') + ->whereDate('completed_at', today())->count(), + ]; + + return view('livewire.service-orders.index', [ + 'serviceOrders' => $serviceOrders, + 'technicians' => $technicians, + 'stats' => $stats, + ]); + } +} diff --git a/app/Livewire/ServiceOrders/Invoice.php b/app/Livewire/ServiceOrders/Invoice.php new file mode 100644 index 0000000..b2bf3bd --- /dev/null +++ b/app/Livewire/ServiceOrders/Invoice.php @@ -0,0 +1,27 @@ +serviceOrder = $serviceOrder->load([ + 'customer', + 'vehicle', + 'assignedTechnician', + 'serviceItems', + 'parts' + ]); + } + + public function render() + { + return view('livewire.service-orders.invoice'); + } +} diff --git a/app/Livewire/ServiceOrders/Show.php b/app/Livewire/ServiceOrders/Show.php new file mode 100644 index 0000000..69e0eaf --- /dev/null +++ b/app/Livewire/ServiceOrders/Show.php @@ -0,0 +1,46 @@ +serviceOrder = $serviceOrder->load([ + 'customer', + 'vehicle', + 'assignedTechnician', + 'serviceItems', + 'parts', + 'inspections', + 'appointments' + ]); + } + + public function updateStatus($status) + { + $this->serviceOrder->status = $status; + + if ($status === 'in_progress' && !$this->serviceOrder->started_at) { + $this->serviceOrder->started_at = now(); + } elseif ($status === 'completed' && !$this->serviceOrder->completed_at) { + $this->serviceOrder->completed_at = now(); + } + + $this->serviceOrder->save(); + session()->flash('success', 'Service order status updated successfully!'); + + // Refresh the model + $this->mount($this->serviceOrder); + } + + public function render() + { + return view('livewire.service-orders.show'); + } +} diff --git a/app/Livewire/TechnicianManagement/Index.php b/app/Livewire/TechnicianManagement/Index.php new file mode 100644 index 0000000..90098ac --- /dev/null +++ b/app/Livewire/TechnicianManagement/Index.php @@ -0,0 +1,108 @@ + ['except' => ''], + 'statusFilter' => ['except' => ''], + 'skillFilter' => ['except' => ''], + 'sortBy' => ['except' => 'first_name'], + 'sortDirection' => ['except' => 'asc'] + ]; + + public function updatingSearch() + { + $this->resetPage(); + } + + public function updatingStatusFilter() + { + $this->resetPage(); + } + + public function updatingSkillFilter() + { + $this->resetPage(); + } + + public function sortBy($field) + { + if ($this->sortBy === $field) { + $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc'; + } else { + $this->sortBy = $field; + $this->sortDirection = 'asc'; + } + $this->resetPage(); + } + + public function showDetails($technicianId) + { + $this->selectedTechnician = Technician::with(['skills', 'performances', 'workloads'])->find($technicianId); + $this->showingDetails = true; + } + + public function closeDetails() + { + $this->selectedTechnician = null; + $this->showingDetails = false; + } + + public function getTechniciansProperty() + { + return Technician::query() + ->when($this->search, function ($query) { + $query->where(function ($q) { + $q->where('first_name', 'like', '%' . $this->search . '%') + ->orWhere('last_name', 'like', '%' . $this->search . '%') + ->orWhere('email', 'like', '%' . $this->search . '%') + ->orWhere('employee_id', 'like', '%' . $this->search . '%'); + }); + }) + ->when($this->statusFilter, function ($query) { + $query->where('status', $this->statusFilter); + }) + ->when($this->skillFilter, function ($query) { + $query->whereHas('skills', function ($q) { + $q->where('skill_name', $this->skillFilter); + }); + }) + ->with(['skills' => function($query) { + $query->orderBy('is_primary_skill', 'desc'); + }, 'performances', 'workloads']) + ->orderBy($this->sortBy, $this->sortDirection) + ->paginate(10); + } + + public function getAvailableSkillsProperty() + { + return \App\Models\TechnicianSkill::distinct('skill_name') + ->pluck('skill_name') + ->sort() + ->values(); + } + + public function render() + { + return view('livewire.technician-management.index', [ + 'technicians' => $this->technicians, + 'availableSkills' => $this->availableSkills + ]); + } +} diff --git a/app/Livewire/TechnicianManagement/PerformanceTracking.php b/app/Livewire/TechnicianManagement/PerformanceTracking.php new file mode 100644 index 0000000..68c43f1 --- /dev/null +++ b/app/Livewire/TechnicianManagement/PerformanceTracking.php @@ -0,0 +1,251 @@ + 'required|string|max:255', + 'metric_value' => 'required|numeric', + 'performance_date' => 'required|date', + 'period_type' => 'required|in:daily,weekly,monthly,quarterly,yearly', + 'notes' => 'nullable|string|max:1000' + ]; + + public function mount() + { + $this->startDate = now()->startOfMonth()->format('Y-m-d'); + $this->endDate = now()->format('Y-m-d'); + $this->performance_date = now()->format('Y-m-d'); + } + + #[On('track-performance')] + public function trackPerformance($technicianId) + { + $this->technicianId = $technicianId; + $this->technician = Technician::with('performances')->findOrFail($technicianId); + $this->showModal = true; + $this->resetForm(); + $this->loadChartData(); + } + + public function updatedPeriodFilter() + { + $this->setDateRange(); + $this->loadChartData(); + } + + public function updatedSelectedMetric() + { + $this->loadChartData(); + } + + public function updatedStartDate() + { + $this->loadChartData(); + } + + public function updatedEndDate() + { + $this->loadChartData(); + } + + public function setDateRange() + { + switch ($this->periodFilter) { + case 'current_week': + $this->startDate = now()->startOfWeek()->format('Y-m-d'); + $this->endDate = now()->endOfWeek()->format('Y-m-d'); + break; + case 'current_month': + $this->startDate = now()->startOfMonth()->format('Y-m-d'); + $this->endDate = now()->endOfMonth()->format('Y-m-d'); + break; + case 'current_quarter': + $this->startDate = now()->startOfQuarter()->format('Y-m-d'); + $this->endDate = now()->endOfQuarter()->format('Y-m-d'); + break; + case 'current_year': + $this->startDate = now()->startOfYear()->format('Y-m-d'); + $this->endDate = now()->endOfYear()->format('Y-m-d'); + break; + case 'last_30_days': + $this->startDate = now()->subDays(30)->format('Y-m-d'); + $this->endDate = now()->format('Y-m-d'); + break; + } + } + + public function loadChartData() + { + if (!$this->technician) return; + + $performances = $this->technician->performances() + ->where('metric_type', $this->selectedMetric) + ->whereBetween('performance_date', [$this->startDate, $this->endDate]) + ->orderBy('performance_date') + ->get(); + + $this->chartData = $performances->map(function ($performance) { + return [ + 'date' => $performance->performance_date->format('Y-m-d'), + 'value' => $performance->metric_value, + 'formatted_value' => $performance->formatted_value + ]; + })->toArray(); + } + + public function addPerformanceRecord() + { + $this->resetForm(); + $this->editing = false; + } + + public function editPerformance($performanceId) + { + $performance = TechnicianPerformance::findOrFail($performanceId); + + $this->performanceId = $performance->id; + $this->metric_type = $performance->metric_type; + $this->metric_value = $performance->metric_value; + $this->performance_date = $performance->performance_date->format('Y-m-d'); + $this->period_type = $performance->period_type; + $this->notes = $performance->notes; + + $this->editing = true; + } + + public function savePerformance() + { + $this->validate(); + + $data = [ + 'technician_id' => $this->technicianId, + 'metric_type' => $this->metric_type, + 'metric_value' => $this->metric_value, + 'performance_date' => $this->performance_date, + 'period_type' => $this->period_type, + 'notes' => $this->notes, + ]; + + if ($this->editing) { + $performance = TechnicianPerformance::findOrFail($this->performanceId); + $performance->update($data); + session()->flash('message', 'Performance record updated successfully!'); + } else { + TechnicianPerformance::create($data); + session()->flash('message', 'Performance record added successfully!'); + } + + $this->technician->refresh(); + $this->loadChartData(); + $this->resetForm(); + } + + public function deletePerformance($performanceId) + { + TechnicianPerformance::findOrFail($performanceId)->delete(); + $this->technician->refresh(); + $this->loadChartData(); + session()->flash('message', 'Performance record deleted successfully!'); + } + + public function closeModal() + { + $this->showModal = false; + $this->resetForm(); + } + + public function resetForm() + { + $this->performanceId = null; + $this->metric_type = ''; + $this->metric_value = ''; + $this->performance_date = now()->format('Y-m-d'); + $this->period_type = 'daily'; + $this->notes = ''; + $this->editing = false; + $this->resetErrorBag(); + } + + public function getMetricTypesProperty() + { + return TechnicianPerformance::getMetricTypes(); + } + + public function getPeriodTypesProperty() + { + return TechnicianPerformance::getPeriodTypes(); + } + + public function getFilteredPerformancesProperty() + { + if (!$this->technician) return collect(); + + return $this->technician->performances() + ->whereBetween('performance_date', [$this->startDate, $this->endDate]) + ->orderBy('performance_date', 'desc') + ->get(); + } + + public function getPerformanceStatsProperty() + { + if (!$this->technician) return []; + + $performances = $this->filteredPerformances; + + $stats = []; + foreach ($this->metricTypes as $type => $label) { + $typePerformances = $performances->where('metric_type', $type); + if ($typePerformances->count() > 0) { + $stats[$type] = [ + 'label' => $label, + 'current' => $typePerformances->first()->metric_value ?? 0, + 'average' => round($typePerformances->avg('metric_value'), 2), + 'total' => round($typePerformances->sum('metric_value'), 2), + 'count' => $typePerformances->count() + ]; + } + } + + return $stats; + } + + public function render() + { + return view('livewire.technician-management.performance-tracking', [ + 'metricTypes' => $this->metricTypes, + 'periodTypes' => $this->periodTypes, + 'filteredPerformances' => $this->filteredPerformances, + 'performanceStats' => $this->performanceStats + ]); + } +} diff --git a/app/Livewire/TechnicianManagement/SkillsManagement.php b/app/Livewire/TechnicianManagement/SkillsManagement.php new file mode 100644 index 0000000..56e704e --- /dev/null +++ b/app/Livewire/TechnicianManagement/SkillsManagement.php @@ -0,0 +1,146 @@ + 'required|string|max:255', + 'category' => 'required|string|max:255', + 'proficiency_level' => 'required|integer|min:1|max:5', + 'certification_body' => 'nullable|string|max:255', + 'certification_expires' => 'nullable|date', + 'is_primary_skill' => 'boolean', + 'notes' => 'nullable|string|max:1000' + ]; + + #[On('manage-skills')] + public function manageSkills($technicianId) + { + $this->technicianId = $technicianId; + $this->technician = Technician::with('skills')->findOrFail($technicianId); + $this->showModal = true; + $this->resetForm(); + } + + public function addSkill() + { + $this->resetForm(); + $this->editing = false; + } + + public function editSkill($skillId) + { + $skill = TechnicianSkill::findOrFail($skillId); + + $this->skillId = $skill->id; + $this->skill_name = $skill->skill_name; + $this->category = $skill->category; + $this->proficiency_level = $skill->proficiency_level; + $this->certification_body = $skill->certification_body; + $this->certification_expires = $skill->certification_expires ? $skill->certification_expires->format('Y-m-d') : ''; + $this->is_primary_skill = $skill->is_primary_skill; + $this->notes = $skill->notes; + + $this->editing = true; + } + + public function saveSkill() + { + $this->validate(); + + $data = [ + 'technician_id' => $this->technicianId, + 'skill_name' => $this->skill_name, + 'category' => $this->category, + 'proficiency_level' => $this->proficiency_level, + 'certification_body' => $this->certification_body, + 'certification_expires' => $this->certification_expires ?: null, + 'is_primary_skill' => $this->is_primary_skill, + 'notes' => $this->notes, + ]; + + if ($this->editing) { + $skill = TechnicianSkill::findOrFail($this->skillId); + $skill->update($data); + session()->flash('message', 'Skill updated successfully!'); + } else { + TechnicianSkill::create($data); + session()->flash('message', 'Skill added successfully!'); + } + + $this->technician->refresh(); + $this->resetForm(); + } + + public function deleteSkill($skillId) + { + TechnicianSkill::findOrFail($skillId)->delete(); + $this->technician->refresh(); + session()->flash('message', 'Skill removed successfully!'); + } + + public function closeModal() + { + $this->showModal = false; + $this->resetForm(); + } + + public function resetForm() + { + $this->skillId = null; + $this->skill_name = ''; + $this->category = ''; + $this->proficiency_level = 1; + $this->certification_body = ''; + $this->certification_expires = ''; + $this->is_primary_skill = false; + $this->notes = ''; + $this->editing = false; + $this->resetErrorBag(); + } + + public function getSkillCategoriesProperty() + { + return TechnicianSkill::getSkillCategories(); + } + + public function getCommonSkillsProperty() + { + return TechnicianSkill::getCommonSkills(); + } + + public function getProficiencyLevelsProperty() + { + return TechnicianSkill::getProficiencyLevels(); + } + + public function render() + { + return view('livewire.technician-management.skills-management', [ + 'skillCategories' => $this->skillCategories, + 'commonSkills' => $this->commonSkills, + 'proficiencyLevels' => $this->proficiencyLevels + ]); + } +} diff --git a/app/Livewire/TechnicianManagement/TechnicianForm.php b/app/Livewire/TechnicianManagement/TechnicianForm.php new file mode 100644 index 0000000..bbdfb05 --- /dev/null +++ b/app/Livewire/TechnicianManagement/TechnicianForm.php @@ -0,0 +1,183 @@ + 'Active', + 'inactive' => 'Inactive', + 'on_leave' => 'On Leave' + ]; + + public $skillLevelOptions = [ + 'apprentice' => 'Apprentice', + 'junior' => 'Junior', + 'journeyman' => 'Journeyman', + 'intermediate' => 'Intermediate', + 'senior' => 'Senior', + 'master' => 'Master', + 'expert' => 'Expert' + ]; + + public $specializationOptions = [ + 'engine' => 'Engine', + 'engine_repair' => 'Engine Repair', + 'transmission' => 'Transmission', + 'electrical_systems' => 'Electrical Systems', + 'computer_systems' => 'Computer Systems', + 'brake_systems' => 'Brake Systems', + 'brakes' => 'Brakes', + 'suspension' => 'Suspension', + 'air_conditioning' => 'Air Conditioning', + 'diagnostics' => 'Diagnostics', + 'diagnostic' => 'Diagnostic', + 'bodywork' => 'Bodywork', + 'painting' => 'Painting', + 'paint' => 'Paint', + 'general_maintenance' => 'General Maintenance' + ]; + + protected $rules = [ + 'first_name' => 'required|string|max:255', + 'last_name' => 'required|string|max:255', + 'email' => 'required|email|max:255', + 'phone' => 'required|string|max:20', + 'employee_id' => 'required|string|max:50', + 'hourly_rate' => 'required|numeric|min:0', + 'status' => 'required|in:active,inactive,on_leave', + 'skill_level' => 'required|in:apprentice,junior,journeyman,intermediate,senior,master,expert', + 'shift_start' => 'required|date_format:H:i', + 'shift_end' => 'required|date_format:H:i|after:shift_start', + 'specializations' => 'array' + ]; + + protected $messages = [ + 'shift_end.after' => 'Shift end time must be after shift start time.', + 'employee_id.unique' => 'This employee ID is already taken.', + 'email.unique' => 'This email address is already taken.' + ]; + + #[On('create-technician')] + public function create() + { + $this->resetForm(); + $this->editing = false; + $this->showModal = true; + } + + #[On('edit-technician')] + public function edit($technicianId) + { + $technician = Technician::findOrFail($technicianId); + + $this->technicianId = $technician->id; + $this->first_name = $technician->first_name; + $this->last_name = $technician->last_name; + $this->email = $technician->email; + $this->phone = $technician->phone; + $this->employee_id = $technician->employee_id; + $this->hourly_rate = $technician->hourly_rate; + $this->status = $technician->status; + $this->skill_level = $technician->skill_level ?? 'junior'; + $this->shift_start = $technician->shift_start ? $technician->shift_start->format('H:i') : '08:00'; + $this->shift_end = $technician->shift_end ? $technician->shift_end->format('H:i') : '17:00'; + $this->specializations = is_array($technician->specializations) ? $technician->specializations : []; + + $this->editing = true; + $this->showModal = true; + } + + public function save() + { + // Add unique validation rules + $rules = $this->rules; + if ($this->editing) { + $rules['employee_id'] .= ',employee_id,' . $this->technicianId; + $rules['email'] .= ',email,' . $this->technicianId; + } else { + $rules['employee_id'] .= '|unique:technicians,employee_id'; + $rules['email'] .= '|unique:technicians,email'; + } + + $this->validate($rules); + + $data = [ + 'first_name' => $this->first_name, + 'last_name' => $this->last_name, + 'email' => $this->email, + 'phone' => $this->phone, + 'employee_id' => $this->employee_id, + 'hourly_rate' => $this->hourly_rate, + 'status' => $this->status, + 'skill_level' => $this->skill_level, + 'shift_start' => $this->shift_start, + 'shift_end' => $this->shift_end, + 'specializations' => $this->specializations, + ]; + + if ($this->editing) { + $technician = Technician::findOrFail($this->technicianId); + $technician->update($data); + session()->flash('message', 'Technician updated successfully!'); + } else { + Technician::create($data); + session()->flash('message', 'Technician created successfully!'); + } + + $this->resetForm(); + $this->showModal = false; + $this->dispatch('technician-saved'); + } + + public function closeModal() + { + $this->resetForm(); + $this->showModal = false; + } + + public function resetForm() + { + $this->technicianId = null; + $this->first_name = ''; + $this->last_name = ''; + $this->email = ''; + $this->phone = ''; + $this->employee_id = ''; + $this->hourly_rate = ''; + $this->status = 'active'; + $this->skill_level = 'junior'; + $this->shift_start = '08:00'; + $this->shift_end = '17:00'; + $this->specializations = []; + $this->editing = false; + $this->resetErrorBag(); + } + + public function render() + { + return view('livewire.technician-management.technician-form'); + } +} diff --git a/app/Livewire/TechnicianManagement/WorkloadManagement.php b/app/Livewire/TechnicianManagement/WorkloadManagement.php new file mode 100644 index 0000000..102f40c --- /dev/null +++ b/app/Livewire/TechnicianManagement/WorkloadManagement.php @@ -0,0 +1,256 @@ + 'required|date|unique:technician_workloads,workload_date,NULL,id,technician_id,' . null, + 'scheduled_hours' => 'required|numeric|min:0|max:24', + 'actual_hours' => 'required|numeric|min:0|max:24', + 'overtime_hours' => 'nullable|numeric|min:0|max:12', + 'jobs_assigned' => 'required|integer|min:0', + 'jobs_completed' => 'required|integer|min:0', + 'notes' => 'nullable|string|max:1000' + ]; + + public function mount() + { + $this->setWeekView(); + $this->workload_date = now()->format('Y-m-d'); + } + + #[On('manage-workload')] + public function manageWorkload($technicianId) + { + $this->technicianId = $technicianId; + $this->technician = Technician::with('workloads')->findOrFail($technicianId); + $this->showModal = true; + $this->resetForm(); + } + + public function updatedViewMode() + { + switch ($this->viewMode) { + case 'week': + $this->setWeekView(); + break; + case 'month': + $this->setMonthView(); + break; + // custom stays as is + } + } + + public function setWeekView() + { + $this->startDate = now()->startOfWeek()->format('Y-m-d'); + $this->endDate = now()->endOfWeek()->format('Y-m-d'); + } + + public function setMonthView() + { + $this->startDate = now()->startOfMonth()->format('Y-m-d'); + $this->endDate = now()->endOfMonth()->format('Y-m-d'); + } + + public function previousPeriod() + { + if ($this->viewMode === 'week') { + $start = Carbon::parse($this->startDate)->subWeek(); + $this->startDate = $start->format('Y-m-d'); + $this->endDate = $start->endOfWeek()->format('Y-m-d'); + } elseif ($this->viewMode === 'month') { + $start = Carbon::parse($this->startDate)->subMonth()->startOfMonth(); + $this->startDate = $start->format('Y-m-d'); + $this->endDate = $start->endOfMonth()->format('Y-m-d'); + } + } + + public function nextPeriod() + { + if ($this->viewMode === 'week') { + $start = Carbon::parse($this->startDate)->addWeek(); + $this->startDate = $start->format('Y-m-d'); + $this->endDate = $start->endOfWeek()->format('Y-m-d'); + } elseif ($this->viewMode === 'month') { + $start = Carbon::parse($this->startDate)->addMonth()->startOfMonth(); + $this->startDate = $start->format('Y-m-d'); + $this->endDate = $start->endOfMonth()->format('Y-m-d'); + } + } + + public function addWorkloadRecord() + { + $this->resetForm(); + $this->editing = false; + } + + public function editWorkload($workloadId) + { + $workload = TechnicianWorkload::findOrFail($workloadId); + + $this->workloadId = $workload->id; + $this->workload_date = $workload->workload_date->format('Y-m-d'); + $this->scheduled_hours = $workload->scheduled_hours; + $this->actual_hours = $workload->actual_hours; + $this->overtime_hours = $workload->overtime_hours; + $this->jobs_assigned = $workload->jobs_assigned; + $this->jobs_completed = $workload->jobs_completed; + $this->notes = $workload->notes; + + $this->editing = true; + } + + public function saveWorkload() + { + // Update unique rule for editing + if ($this->editing) { + $this->rules['workload_date'] = 'required|date|unique:technician_workloads,workload_date,' . $this->workloadId . ',id,technician_id,' . $this->technicianId; + } else { + $this->rules['workload_date'] = 'required|date|unique:technician_workloads,workload_date,NULL,id,technician_id,' . $this->technicianId; + } + + $this->validate(); + + $data = [ + 'technician_id' => $this->technicianId, + 'workload_date' => $this->workload_date, + 'scheduled_hours' => $this->scheduled_hours, + 'actual_hours' => $this->actual_hours, + 'overtime_hours' => $this->overtime_hours ?? 0, + 'jobs_assigned' => $this->jobs_assigned, + 'jobs_completed' => $this->jobs_completed, + 'notes' => $this->notes, + ]; + + if ($this->editing) { + $workload = TechnicianWorkload::findOrFail($this->workloadId); + $workload->update($data); + + // Recalculate rates + $workload->utilization_rate = $workload->calculateUtilizationRate(); + $workload->efficiency_rate = $workload->calculateEfficiencyRate(); + $workload->save(); + + session()->flash('message', 'Workload record updated successfully!'); + } else { + $workload = TechnicianWorkload::create($data); + + // Calculate rates + $workload->utilization_rate = $workload->calculateUtilizationRate(); + $workload->efficiency_rate = $workload->calculateEfficiencyRate(); + $workload->save(); + + session()->flash('message', 'Workload record added successfully!'); + } + + $this->technician->refresh(); + $this->resetForm(); + } + + public function deleteWorkload($workloadId) + { + TechnicianWorkload::findOrFail($workloadId)->delete(); + $this->technician->refresh(); + session()->flash('message', 'Workload record deleted successfully!'); + } + + public function closeModal() + { + $this->showModal = false; + $this->resetForm(); + } + + public function resetForm() + { + $this->workloadId = null; + $this->workload_date = now()->format('Y-m-d'); + $this->scheduled_hours = 8.0; + $this->actual_hours = 0.0; + $this->overtime_hours = 0.0; + $this->jobs_assigned = 0; + $this->jobs_completed = 0; + $this->notes = ''; + $this->editing = false; + $this->resetErrorBag(); + } + + public function getFilteredWorkloadsProperty() + { + if (!$this->technician) return collect(); + + return $this->technician->workloads() + ->whereBetween('workload_date', [$this->startDate, $this->endDate]) + ->orderBy('workload_date') + ->get(); + } + + public function getWorkloadStatsProperty() + { + $workloads = $this->filteredWorkloads; + + if ($workloads->isEmpty()) { + return [ + 'total_scheduled' => 0, + 'total_actual' => 0, + 'total_overtime' => 0, + 'avg_utilization' => 0, + 'avg_efficiency' => 0, + 'total_jobs_assigned' => 0, + 'total_jobs_completed' => 0, + 'completion_rate' => 0 + ]; + } + + $totalJobsAssigned = $workloads->sum('jobs_assigned'); + $totalJobsCompleted = $workloads->sum('jobs_completed'); + + return [ + 'total_scheduled' => $workloads->sum('scheduled_hours'), + 'total_actual' => $workloads->sum('actual_hours'), + 'total_overtime' => $workloads->sum('overtime_hours'), + 'avg_utilization' => round($workloads->avg('utilization_rate'), 1), + 'avg_efficiency' => round($workloads->avg('efficiency_rate'), 1), + 'total_jobs_assigned' => $totalJobsAssigned, + 'total_jobs_completed' => $totalJobsCompleted, + 'completion_rate' => $totalJobsAssigned > 0 ? round(($totalJobsCompleted / $totalJobsAssigned) * 100, 1) : 0 + ]; + } + + public function render() + { + return view('livewire.technician-management.workload-management', [ + 'filteredWorkloads' => $this->filteredWorkloads, + 'workloadStats' => $this->workloadStats + ]); + } +} diff --git a/app/Livewire/Timesheets/Create.php b/app/Livewire/Timesheets/Create.php new file mode 100644 index 0000000..6a16136 --- /dev/null +++ b/app/Livewire/Timesheets/Create.php @@ -0,0 +1,108 @@ + 'required|exists:job_cards,id', + 'task_type' => 'required|in:diagnosis,repair,maintenance,inspection,other', + 'task_description' => 'required|string|max:500', + 'start_time' => 'required|date', + 'end_time' => 'nullable|date|after:start_time', + 'duration_hours' => 'nullable|numeric|min:0', + 'completion_percentage' => 'required|integer|min:0|max:100', + 'status' => 'required|in:scheduled,in_progress,completed,paused', + ]; + + public function mount() + { + $this->start_time = now()->format('Y-m-d\TH:i'); + } + + public function updatedEndTime() + { + if ($this->start_time && $this->end_time) { + $start = \Carbon\Carbon::parse($this->start_time); + $end = \Carbon\Carbon::parse($this->end_time); + $this->duration_hours = $end->diffInHours($start, true); + } + } + + public function updatedJobCardId() + { + // Reset work order task when job card changes + $this->work_order_task_id = ''; + } + + public function save() + { + $this->validate(); + + Timesheet::create([ + 'job_card_id' => $this->job_card_id, + 'work_order_task_id' => $this->work_order_task_id ?: null, + 'technician_id' => auth()->id(), + 'task_type' => $this->task_type, + 'task_description' => $this->task_description, + 'start_time' => $this->start_time, + 'end_time' => $this->end_time, + 'duration_hours' => $this->duration_hours, + 'notes' => $this->notes, + 'tools_used' => $this->tools_used, + 'materials_used' => $this->materials_used, + 'completion_percentage' => $this->completion_percentage, + 'status' => $this->status, + ]); + + session()->flash('message', 'Timesheet entry created successfully!'); + return redirect()->route('timesheets.index'); + } + + public function getJobCardsProperty() + { + return JobCard::with(['customer', 'vehicle']) + ->whereNotIn('status', ['completed', 'cancelled']) + ->orderBy('created_at', 'desc') + ->get(); + } + + public function getWorkOrderTasksProperty() + { + if (!$this->job_card_id) { + return collect(); + } + + return WorkOrderTask::whereHas('workOrder', function ($query) { + $query->where('job_card_id', $this->job_card_id); + }) + ->where('status', '!=', 'completed') + ->get(); + } + + public function render() + { + return view('livewire.timesheets.create', [ + 'jobCards' => $this->jobCards, + 'workOrderTasks' => $this->workOrderTasks, + ]); + } +} diff --git a/app/Livewire/Timesheets/Edit.php b/app/Livewire/Timesheets/Edit.php new file mode 100644 index 0000000..f0774e9 --- /dev/null +++ b/app/Livewire/Timesheets/Edit.php @@ -0,0 +1,13 @@ +resetPage(); + } + + public function render() + { + $timesheets = Timesheet::with([ + 'jobCard.customer', + 'jobCard.vehicle', + 'technician', + 'workOrderTask' + ]) + ->when($this->search, function ($query) { + $query->whereHas('jobCard', function ($jobQuery) { + $jobQuery->where('job_number', 'like', '%' . $this->search . '%') + ->orWhereHas('customer', function ($customerQuery) { + $customerQuery->where('name', 'like', '%' . $this->search . '%'); + }); + }) + ->orWhereHas('technician', function ($techQuery) { + $techQuery->where('name', 'like', '%' . $this->search . '%'); + }); + }) + ->when($this->typeFilter, function ($query) { + $query->where('task_type', $this->typeFilter); + }) + ->when($this->statusFilter, function ($query) { + $query->where('status', $this->statusFilter); + }) + ->when($this->dateFilter, function ($query) { + $query->whereDate('start_time', $this->dateFilter); + }) + ->latest() + ->paginate(15); + + return view('livewire.timesheets.index', compact('timesheets')); + } +} diff --git a/app/Livewire/Timesheets/Show.php b/app/Livewire/Timesheets/Show.php new file mode 100644 index 0000000..a9e988a --- /dev/null +++ b/app/Livewire/Timesheets/Show.php @@ -0,0 +1,13 @@ + 'required|string|max:255|min:2', + 'email' => 'required|email|unique:users,email|max:255', + 'password' => ['required', 'confirmed', Password::min(8)->letters()->mixedCase()->numbers()->symbols()], + 'employee_id' => 'nullable|string|max:50|unique:users,employee_id|regex:/^[A-Z0-9-]+$/', + 'phone' => 'nullable|string|max:20|regex:/^[\+]?[0-9\s\-\(\)]+$/', + 'department' => 'nullable|string|max:100', + 'position' => 'nullable|string|max:100', + 'branch_code' => 'required|string|max:10|exists:branches,code', + 'hire_date' => 'nullable|date|before_or_equal:today', + 'salary' => 'nullable|numeric|min:0|max:999999.99', + 'status' => 'required|in:active,inactive,suspended', + 'emergency_contact_name' => 'nullable|string|max:255', + 'emergency_contact_phone' => 'nullable|string|max:20|regex:/^[\+]?[0-9\s\-\(\)]+$/', + 'address' => 'nullable|string|max:500', + 'date_of_birth' => 'nullable|date|before:-18 years', + 'national_id' => 'nullable|string|max:50|unique:users,national_id', + 'selectedRoles' => 'array|min:1', + 'selectedRoles.*' => 'exists:roles,id', + 'selectedPermissions' => 'array', + 'selectedPermissions.*' => 'exists:permissions,id', + ]; + } + + protected $messages = [ + 'name.min' => 'Name must be at least 2 characters long.', + 'branch_code.required' => 'Branch code is required.', + 'branch_code.exists' => 'Selected branch code does not exist.', + 'email.unique' => 'This email address is already registered.', + 'employee_id.unique' => 'This employee ID is already in use.', + 'employee_id.regex' => 'Employee ID can only contain letters, numbers, and hyphens.', + 'phone.regex' => 'Please enter a valid phone number.', + 'emergency_contact_phone.regex' => 'Please enter a valid emergency contact phone number.', + 'date_of_birth.before' => 'Employee must be at least 18 years old.', + 'hire_date.before_or_equal' => 'Hire date cannot be in the future.', + 'national_id.unique' => 'This national ID is already registered.', + 'selectedRoles.min' => 'Please assign at least one role to the user.', + 'salary.max' => 'Salary cannot exceed 999,999.99.', + ]; + + public function mount() + { + $this->hire_date = now()->format('Y-m-d'); + $this->branch_code = auth()->user()->branch_code ?? ''; + } + + public function render() + { + $roles = Role::where('is_active', true) + ->orderBy('display_name') + ->get(); + + $permissions = Permission::where('is_active', true) + ->orderBy('module') + ->orderBy('name') + ->get() + ->groupBy('module'); + + $departments = User::select('department') + ->distinct() + ->whereNotNull('department') + ->where('department', '!=', '') + ->orderBy('department') + ->pluck('department'); + + $branches = \DB::table('branches') + ->where('is_active', true) + ->orderBy('name') + ->get(['code', 'name']); + + $positions = $this->getPositionsForDepartment($this->department); + + return view('livewire.users.create', [ + 'roles' => $roles, + 'permissions' => $permissions, + 'departments' => $departments, + 'branches' => $branches, + 'positions' => $positions, + ]); + } + + public function save() + { + $this->saving = true; + $this->validate(); + + DB::beginTransaction(); + try { + // Create the user + $user = User::create([ + 'name' => trim($this->name), + 'email' => strtolower(trim($this->email)), + 'password' => Hash::make($this->password), + 'employee_id' => $this->employee_id ? strtoupper(trim($this->employee_id)) : null, + 'phone' => $this->phone ? preg_replace('/[^0-9+\-\s\(\)]/', '', $this->phone) : null, + 'department' => $this->department ?: null, + 'position' => $this->position ?: null, + 'branch_code' => $this->branch_code, + 'hire_date' => $this->hire_date ?: null, + 'salary' => $this->salary ?: null, + 'status' => $this->status, + 'emergency_contact_name' => trim($this->emergency_contact_name) ?: null, + 'emergency_contact_phone' => $this->emergency_contact_phone ? preg_replace('/[^0-9+\-\s\(\)]/', '', $this->emergency_contact_phone) : null, + 'address' => trim($this->address) ?: null, + 'date_of_birth' => $this->date_of_birth ?: null, + 'national_id' => $this->national_id ? trim($this->national_id) : null, + 'email_verified_at' => now(), + 'created_by' => auth()->id(), + ]); + + // Assign roles + if (!empty($this->selectedRoles)) { + foreach ($this->selectedRoles as $roleId) { + $role = Role::find($roleId); + if ($role) { + $user->assignRole($role, $this->branch_code); + } + } + } + + // Assign direct permissions + if (!empty($this->selectedPermissions)) { + foreach ($this->selectedPermissions as $permissionId) { + $permission = Permission::find($permissionId); + if ($permission) { + $user->givePermission($permission, $this->branch_code); + } + } + } + + // Log the creation + activity() + ->performedOn($user) + ->causedBy(auth()->user()) + ->withProperties([ + 'user_data' => [ + 'name' => $user->name, + 'email' => $user->email, + 'employee_id' => $user->employee_id, + 'department' => $user->department, + 'branch_code' => $user->branch_code, + 'status' => $user->status, + ], + 'roles_assigned' => $this->selectedRoles, + 'permissions_assigned' => $this->selectedPermissions, + ]) + ->log('User created'); + + // Send welcome email if requested + if ($this->sendWelcomeEmail) { + try { + // TODO: Implement welcome email notification + // $user->notify(new WelcomeNotification($this->password)); + } catch (\Exception $e) { + // Log email failure but don't fail the user creation + \Log::warning('Failed to send welcome email to user: ' . $user->email, ['error' => $e->getMessage()]); + } + } + + DB::commit(); + + session()->flash('success', "User '{$user->name}' created successfully!"); + $this->saving = false; + + return redirect()->route('users.show', $user); + + } catch (\Exception $e) { + DB::rollBack(); + $this->saving = false; + + \Log::error('Failed to create user', [ + 'error' => $e->getMessage(), + 'user_data' => [ + 'name' => $this->name, + 'email' => $this->email, + 'employee_id' => $this->employee_id, + ] + ]); + + session()->flash('error', 'Failed to create user: ' . $e->getMessage()); + } + } + + public function cancel() + { + return redirect()->route('users.index'); + } + + public function generatePassword() + { + $this->generatedPassword = $this->generateSecurePassword(); + $this->password = $this->generatedPassword; + $this->password_confirmation = $this->generatedPassword; + $this->showPasswordGenerator = true; + } + + public function generateSecurePassword($length = 12) + { + // Generate a secure password with mixed case, numbers, and symbols + $uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $lowercase = 'abcdefghijklmnopqrstuvwxyz'; + $numbers = '0123456789'; + $symbols = '!@#$%^&*()_+-=[]{}|;:,.<>?'; + + $password = ''; + $password .= $uppercase[random_int(0, strlen($uppercase) - 1)]; + $password .= $lowercase[random_int(0, strlen($lowercase) - 1)]; + $password .= $numbers[random_int(0, strlen($numbers) - 1)]; + $password .= $symbols[random_int(0, strlen($symbols) - 1)]; + + $allChars = $uppercase . $lowercase . $numbers . $symbols; + for ($i = 4; $i < $length; $i++) { + $password .= $allChars[random_int(0, strlen($allChars) - 1)]; + } + + return str_shuffle($password); + } + + public function nextStep() + { + if ($this->currentStep < $this->totalSteps) { + $this->currentStep++; + } + } + + public function previousStep() + { + if ($this->currentStep > 1) { + $this->currentStep--; + } + } + + public function updatedDepartment() + { + // Clear position when department changes + $this->position = ''; + } + + public function getPositionsForDepartment($department) + { + $positions = [ + 'Service' => ['Service Advisor', 'Service Manager', 'Service Coordinator', 'Service Writer'], + 'Technician' => ['Lead Technician', 'Senior Technician', 'Junior Technician', 'Apprentice Technician'], + 'Parts' => ['Parts Manager', 'Parts Associate', 'Inventory Specialist', 'Parts Counter Person'], + 'Administration' => ['Administrator', 'Office Manager', 'Receptionist', 'Data Entry Clerk'], + 'Management' => ['General Manager', 'Assistant Manager', 'Supervisor', 'Team Lead'], + 'Sales' => ['Sales Manager', 'Sales Associate', 'Sales Coordinator'], + 'Finance' => ['Finance Manager', 'Accountant', 'Cashier', 'Billing Specialist'], + ]; + + return $positions[$department] ?? []; + } + + public function validateStep1() + { + $this->validateOnly([ + 'name', + 'email', + 'password', + 'password_confirmation', + 'employee_id', + 'phone', + ]); + } + + public function validateStep2() + { + $this->validateOnly([ + 'department', + 'position', + 'branch_code', + 'hire_date', + 'salary', + 'status', + ]); + } + + public function copyPasswordToClipboard() + { + // This will be handled by Alpine.js on the frontend + $this->dispatch('password-copied'); + } + + public function getRolePermissionCount($roleId) + { + $role = Role::find($roleId); + return $role ? $role->permissions()->count() : 0; + } + + public function getSelectedRolesPermissions() + { + if (empty($this->selectedRoles)) { + return collect(); + } + + return Permission::whereHas('roles', function($query) { + $query->whereIn('roles.id', $this->selectedRoles); + })->get(); + } + + public function hasValidationErrors() + { + return $this->getErrorBag()->isNotEmpty(); + } + + public function getProgressPercentage() + { + return ($this->currentStep / $this->totalSteps) * 100; + } +} diff --git a/app/Livewire/Users/Edit.php b/app/Livewire/Users/Edit.php new file mode 100644 index 0000000..bed0efc --- /dev/null +++ b/app/Livewire/Users/Edit.php @@ -0,0 +1,467 @@ + 'required|string|max:255|min:2', + 'email' => 'required|email|unique:users,email,' . $this->user->id . '|max:255', + 'employee_id' => 'nullable|string|max:50|unique:users,employee_id,' . $this->user->id . '|regex:/^[A-Z0-9-]+$/', + 'phone' => 'nullable|string|max:20|regex:/^[\+]?[0-9\s\-\(\)]+$/', + 'department' => 'nullable|string|max:100', + 'position' => 'nullable|string|max:100', + 'branch_code' => 'required|string|max:10|exists:branches,code', + 'hire_date' => 'nullable|date|before_or_equal:today', + 'salary' => 'nullable|numeric|min:0|max:999999.99', + 'status' => 'required|in:active,inactive,suspended', + 'emergency_contact_name' => 'nullable|string|max:255', + 'emergency_contact_phone' => 'nullable|string|max:20|regex:/^[\+]?[0-9\s\-\(\)]+$/', + 'address' => 'nullable|string|max:500', + 'date_of_birth' => 'nullable|date|before:-18 years', + 'national_id' => 'nullable|string|max:50|unique:users,national_id,' . $this->user->id, + 'selectedRoles' => 'array', + 'selectedRoles.*' => 'exists:roles,id', + 'selectedPermissions' => 'array', + 'selectedPermissions.*' => 'exists:permissions,id', + ]; + + if ($this->changePassword) { + $rules['password'] = ['required', 'confirmed', Password::min(8)->letters()->mixedCase()->numbers()->symbols()]; + } + + return $rules; + } + + protected $messages = [ + 'name.min' => 'Name must be at least 2 characters long.', + 'branch_code.required' => 'Branch code is required.', + 'branch_code.exists' => 'Selected branch code does not exist.', + 'email.unique' => 'This email address is already registered.', + 'employee_id.unique' => 'This employee ID is already in use.', + 'employee_id.regex' => 'Employee ID can only contain letters, numbers, and hyphens.', + 'phone.regex' => 'Please enter a valid phone number.', + 'emergency_contact_phone.regex' => 'Please enter a valid emergency contact phone number.', + 'date_of_birth.before' => 'Employee must be at least 18 years old.', + 'hire_date.before_or_equal' => 'Hire date cannot be in the future.', + 'national_id.unique' => 'This national ID is already registered.', + 'salary.max' => 'Salary cannot exceed 999,999.99.', + ]; + + public function mount(User $user) + { + $this->user = $user; + + // Store original data for change tracking + $this->originalData = [ + 'name' => $user->name, + 'email' => $user->email, + 'employee_id' => $user->employee_id, + 'phone' => $user->phone, + 'department' => $user->department, + 'position' => $user->position, + 'branch_code' => $user->branch_code, + 'status' => $user->status, + ]; + + // Load user data + $this->name = $user->name; + $this->email = $user->email; + $this->employee_id = $user->employee_id; + $this->phone = $user->phone; + $this->department = $user->department; + $this->position = $user->position; + $this->branch_code = $user->branch_code; + $this->hire_date = $user->hire_date ? $user->hire_date->format('Y-m-d') : ''; + $this->salary = $user->salary; + $this->status = $user->status; + $this->emergency_contact_name = $user->emergency_contact_name; + $this->emergency_contact_phone = $user->emergency_contact_phone; + $this->address = $user->address; + $this->date_of_birth = $user->date_of_birth ? $user->date_of_birth->format('Y-m-d') : ''; + $this->national_id = $user->national_id; + + // Load current roles + $this->selectedRoles = $user->roles() + ->where('user_roles.is_active', true) + ->where(function ($q) { + $q->whereNull('user_roles.expires_at') + ->orWhere('user_roles.expires_at', '>', now()); + }) + ->pluck('roles.id') + ->toArray(); + + // Load current direct permissions + $this->selectedPermissions = $user->permissions() + ->where('user_permissions.granted', true) + ->where(function ($q) { + $q->whereNull('user_permissions.expires_at') + ->orWhere('user_permissions.expires_at', '>', now()); + }) + ->pluck('permissions.id') + ->toArray(); + } + + public function render() + { + $roles = Role::where('is_active', true) + ->orderBy('display_name') + ->get(); + + $permissions = Permission::where('is_active', true) + ->orderBy('module') + ->orderBy('name') + ->get() + ->groupBy('module'); + + $departments = User::select('department') + ->distinct() + ->whereNotNull('department') + ->where('department', '!=', '') + ->orderBy('department') + ->pluck('department'); + + $branches = \DB::table('branches') + ->where('is_active', true) + ->orderBy('name') + ->get(['code', 'name']); + + $positions = $this->getPositionsForDepartment($this->department); + + // Get user activity logs + $causedByUser = \Spatie\Activitylog\Models\Activity::where('causer_id', $this->user->id) + ->where('causer_type', \App\Models\User::class) + ->latest() + ->limit(5) + ->get(); + + $performedOnUser = \Spatie\Activitylog\Models\Activity::where('subject_id', $this->user->id) + ->where('subject_type', \App\Models\User::class) + ->latest() + ->limit(5) + ->get(); + + $recentActivity = $causedByUser->merge($performedOnUser) + ->sortByDesc('created_at') + ->take(10); + + return view('livewire.users.edit', [ + 'availableRoles' => $roles, + 'permissions' => $permissions, + 'departments' => $departments, + 'branches' => $branches, + 'positions' => $positions, + 'recentActivity' => $recentActivity, + ]); + } + + public function save() + { + $this->saving = true; + $this->validate(); + + DB::beginTransaction(); + try { + // Prepare update data + $userData = [ + 'name' => trim($this->name), + 'email' => strtolower(trim($this->email)), + 'employee_id' => $this->employee_id ? strtoupper(trim($this->employee_id)) : null, + 'phone' => $this->phone ? preg_replace('/[^0-9+\-\s\(\)]/', '', $this->phone) : null, + 'department' => $this->department ?: null, + 'position' => $this->position ?: null, + 'branch_code' => $this->branch_code, + 'hire_date' => $this->hire_date ?: null, + 'salary' => $this->salary ?: null, + 'status' => $this->status, + 'emergency_contact_name' => trim($this->emergency_contact_name) ?: null, + 'emergency_contact_phone' => $this->emergency_contact_phone ? preg_replace('/[^0-9+\-\s\(\)]/', '', $this->emergency_contact_phone) : null, + 'address' => trim($this->address) ?: null, + 'date_of_birth' => $this->date_of_birth ?: null, + 'national_id' => $this->national_id ? trim($this->national_id) : null, + 'updated_by' => auth()->id(), + ]; + + // Update password if requested + if ($this->changePassword && $this->password) { + $userData['password'] = Hash::make($this->password); + $userData['password_changed_at'] = now(); + } + + // Track changes for activity log + $changes = $this->getChangedData($userData); + + $this->user->update($userData); + + // Sync roles with branch code + $roleData = []; + foreach ($this->selectedRoles as $roleId) { + $roleData[$roleId] = [ + 'branch_code' => $this->branch_code, + 'is_active' => true, + 'assigned_at' => now(), + 'expires_at' => null, + ]; + } + $this->user->roles()->sync($roleData); + + // Sync direct permissions + $permissionData = []; + foreach ($this->selectedPermissions as $permissionId) { + $permissionData[$permissionId] = [ + 'granted' => true, + 'branch_code' => $this->branch_code, + 'assigned_at' => now(), + 'expires_at' => null, + ]; + } + $this->user->permissions()->sync($permissionData); + + // Log the update with changes + if (!empty($changes) || $this->changePassword) { + $logProperties = ['changes' => $changes]; + if ($this->changePassword) { + $logProperties['password_changed'] = true; + } + + activity() + ->performedOn($this->user) + ->causedBy(auth()->user()) + ->withProperties($logProperties) + ->log('User updated'); + } + + DB::commit(); + + session()->flash('success', "User '{$this->user->name}' updated successfully!"); + $this->saving = false; + + return redirect()->route('users.show', $this->user); + + } catch (\Exception $e) { + DB::rollBack(); + $this->saving = false; + + \Log::error('Failed to update user', [ + 'user_id' => $this->user->id, + 'error' => $e->getMessage(), + ]); + + session()->flash('error', 'Failed to update user: ' . $e->getMessage()); + } + } + + public function cancel() + { + return redirect()->route('users.show', $this->user); + } + + public function generatePassword() + { + $this->generatedPassword = $this->generateSecurePassword(); + $this->password = $this->generatedPassword; + $this->password_confirmation = $this->generatedPassword; + $this->changePassword = true; + $this->showPasswordGenerator = true; + } + + public function generateSecurePassword($length = 12) + { + // Generate a secure password with mixed case, numbers, and symbols + $uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $lowercase = 'abcdefghijklmnopqrstuvwxyz'; + $numbers = '0123456789'; + $symbols = '!@#$%^&*()_+-=[]{}|;:,.<>?'; + + $password = ''; + $password .= $uppercase[random_int(0, strlen($uppercase) - 1)]; + $password .= $lowercase[random_int(0, strlen($lowercase) - 1)]; + $password .= $numbers[random_int(0, strlen($numbers) - 1)]; + $password .= $symbols[random_int(0, strlen($symbols) - 1)]; + + $allChars = $uppercase . $lowercase . $numbers . $symbols; + for ($i = 4; $i < $length; $i++) { + $password .= $allChars[random_int(0, strlen($allChars) - 1)]; + } + + return str_shuffle($password); + } + + public function resetPassword() + { + + try { + $newPassword = \Str::random(12); + $this->user->update([ + 'password' => Hash::make($newPassword) + ]); + + // TODO: Send password reset email + // $this->user->notify(new PasswordResetNotification($newPassword)); + + session()->flash('success', 'Password reset successfully. New password: ' . $newPassword); + } catch (\Exception $e) { + session()->flash('error', 'Failed to reset password: ' . $e->getMessage()); + } + } + + public function impersonateUser() + { + + if ($this->user->id === auth()->id()) { + session()->flash('error', 'You cannot impersonate yourself.'); + return; + } + + // Store original user ID for returning later + session(['impersonate_original_user' => auth()->id()]); + auth()->loginUsingId($this->user->id); + + session()->flash('success', 'Now impersonating ' . $this->user->name); + return redirect()->route('dashboard'); + } + + public function getAvailableRolesProperty() + { + return Role::orderBy('name')->get(); + } + + public function confirmDelete() + { + $this->showDeleteModal = true; + } + + public function deleteUser() + { + + // Prevent self-deletion + if ($this->user->id === auth()->id()) { + session()->flash('error', 'You cannot delete your own account.'); + return; + } + + try { + $this->user->delete(); + session()->flash('success', 'User deleted successfully.'); + return redirect()->route('users.index'); + } catch (\Exception $e) { + session()->flash('error', 'Failed to delete user: ' . $e->getMessage()); + } + + $this->showDeleteModal = false; + } + + public function setActiveTab($tab) + { + $this->currentTab = $tab; + } + + public function getChangedData($newData) + { + $changes = []; + foreach ($newData as $key => $value) { + if (isset($this->originalData[$key]) && $this->originalData[$key] != $value) { + $changes[$key] = [ + 'old' => $this->originalData[$key], + 'new' => $value + ]; + } + } + return $changes; + } + + public function getPositionsForDepartment($department) + { + $positions = [ + 'Service' => ['Service Advisor', 'Service Manager', 'Service Coordinator', 'Service Writer'], + 'Technician' => ['Lead Technician', 'Senior Technician', 'Junior Technician', 'Apprentice Technician'], + 'Parts' => ['Parts Manager', 'Parts Associate', 'Inventory Specialist', 'Parts Counter Person'], + 'Administration' => ['Administrator', 'Office Manager', 'Receptionist', 'Data Entry Clerk'], + 'Management' => ['General Manager', 'Assistant Manager', 'Supervisor', 'Team Lead'], + 'Sales' => ['Sales Manager', 'Sales Associate', 'Sales Coordinator'], + 'Finance' => ['Finance Manager', 'Accountant', 'Cashier', 'Billing Specialist'], + ]; + + return $positions[$department] ?? []; + } + + public function updatedDepartment() + { + // Clear position when department changes unless it's valid for new department + $validPositions = $this->getPositionsForDepartment($this->department); + if (!in_array($this->position, $validPositions)) { + $this->position = ''; + } + } + + public function hasUnsavedChanges() + { + $currentData = [ + 'name' => $this->name, + 'email' => $this->email, + 'employee_id' => $this->employee_id, + 'phone' => $this->phone, + 'department' => $this->department, + 'position' => $this->position, + 'branch_code' => $this->branch_code, + 'status' => $this->status, + ]; + + return !empty($this->getChangedData($currentData)) || $this->changePassword; + } + + public function copyPasswordToClipboard() + { + // This will be handled by Alpine.js on the frontend + $this->dispatch('password-copied'); + } + + public function togglePasswordVisibility() + { + $this->showPasswordGenerator = !$this->showPasswordGenerator; + } +} diff --git a/app/Livewire/Users/Index.php b/app/Livewire/Users/Index.php new file mode 100644 index 0000000..2db549f --- /dev/null +++ b/app/Livewire/Users/Index.php @@ -0,0 +1,376 @@ + ['except' => ''], + 'roleFilter' => ['except' => ''], + 'statusFilter' => ['except' => ''], + 'departmentFilter' => ['except' => ''], + 'branchFilter' => ['except' => ''], + 'sortField' => ['except' => 'name'], + 'sortDirection' => ['except' => 'asc'], + 'perPage' => ['except' => 25], + 'showInactive' => ['except' => false], + ]; + + public function updatingSearch() + { + $this->resetPage(); + } + + public function updatingRoleFilter() + { + $this->resetPage(); + } + + public function updatingStatusFilter() + { + $this->resetPage(); + } + + public function updatingDepartmentFilter() + { + $this->resetPage(); + } + + public function updatingBranchFilter() + { + $this->resetPage(); + } + + public function updatingPerPage() + { + $this->resetPage(); + } + + public function render() + { + $query = User::query() + ->with(['roles' => function($query) { + $query->where('user_roles.is_active', true) + ->where(function ($q) { + $q->whereNull('user_roles.expires_at') + ->orWhere('user_roles.expires_at', '>', now()); + }); + }]) + ->withCount(['roles as active_roles_count' => function($query) { + $query->where('user_roles.is_active', true) + ->where(function ($q) { + $q->whereNull('user_roles.expires_at') + ->orWhere('user_roles.expires_at', '>', now()); + }); + }]) + ->when($this->search, function ($q) { + $q->where(function ($query) { + $query->where('name', 'like', '%' . $this->search . '%') + ->orWhere('email', 'like', '%' . $this->search . '%') + ->orWhere('employee_id', 'like', '%' . $this->search . '%') + ->orWhere('phone', 'like', '%' . $this->search . '%') + ->orWhere('national_id', 'like', '%' . $this->search . '%'); + }); + }) + ->when($this->roleFilter, function ($q) { + $q->whereHas('roles', function ($query) { + $query->where('roles.name', $this->roleFilter) + ->where('user_roles.is_active', true) + ->where(function ($subQ) { + $subQ->whereNull('user_roles.expires_at') + ->orWhere('user_roles.expires_at', '>', now()); + }); + }); + }) + ->when($this->statusFilter, function ($q) { + $q->where('status', $this->statusFilter); + }) + ->when($this->departmentFilter, function ($q) { + $q->where('department', $this->departmentFilter); + }) + ->when($this->branchFilter, function ($q) { + $q->where('branch_code', $this->branchFilter); + }) + ->when(!$this->showInactive, function ($q) { + $q->where('status', '!=', 'inactive'); + }) + ->orderBy($this->sortField, $this->sortDirection); + + $users = $query->paginate($this->perPage); + + $roles = Role::where('is_active', true)->orderBy('display_name')->get(); + $departments = User::select('department') + ->distinct() + ->whereNotNull('department') + ->where('department', '!=', '') + ->orderBy('department') + ->pluck('department'); + $branches = User::select('branch_code') + ->distinct() + ->whereNotNull('branch_code') + ->where('branch_code', '!=', '') + ->orderBy('branch_code') + ->pluck('branch_code'); + + // Get summary statistics + $stats = [ + 'total' => User::count(), + 'active' => User::where('status', 'active')->count(), + 'inactive' => User::where('status', 'inactive')->count(), + 'suspended' => User::where('status', 'suspended')->count(), + ]; + + return view('livewire.users.index', compact('users', 'roles', 'departments', 'branches', 'stats')); + } + + public function sortBy($field) + { + if ($this->sortField === $field) { + $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc'; + } else { + $this->sortField = $field; + $this->sortDirection = 'asc'; + } + } + + public function clearFilters() + { + $this->search = ''; + $this->roleFilter = ''; + $this->statusFilter = ''; + $this->departmentFilter = ''; + $this->branchFilter = ''; + $this->sortField = 'name'; + $this->sortDirection = 'asc'; + $this->showInactive = false; + $this->resetPage(); + } + + public function toggleShowInactive() + { + $this->showInactive = !$this->showInactive; + $this->resetPage(); + } + + public function selectAllUsers() + { + if ($this->selectAll) { + $this->selectedUsers = []; + $this->selectAll = false; + } else { + $this->selectedUsers = User::pluck('id')->toArray(); + $this->selectAll = true; + } + } + + public function bulkActivate() + { + if (empty($this->selectedUsers)) { + session()->flash('error', 'No users selected.'); + return; + } + + $count = User::whereIn('id', $this->selectedUsers) + ->where('id', '!=', auth()->id()) + ->update(['status' => 'active']); + + $this->selectedUsers = []; + $this->selectAll = false; + + session()->flash('success', "Activated {$count} users successfully."); + } + + public function bulkDeactivate() + { + if (empty($this->selectedUsers)) { + session()->flash('error', 'No users selected.'); + return; + } + + $count = User::whereIn('id', $this->selectedUsers) + ->where('id', '!=', auth()->id()) + ->update(['status' => 'inactive']); + + $this->selectedUsers = []; + $this->selectAll = false; + + session()->flash('success', "Deactivated {$count} users successfully."); + } + + public function deactivateUser($userId) + { + $user = User::findOrFail($userId); + + if ($user->id === auth()->id()) { + session()->flash('error', 'You cannot deactivate your own account.'); + return; + } + + $user->update(['status' => 'inactive']); + + // Log the action + activity() + ->performedOn($user) + ->causedBy(auth()->user()) + ->log('User deactivated'); + + session()->flash('success', "User '{$user->name}' deactivated successfully."); + } + + public function activateUser($userId) + { + $user = User::findOrFail($userId); + $user->update(['status' => 'active']); + + // Log the action + activity() + ->performedOn($user) + ->causedBy(auth()->user()) + ->log('User activated'); + + session()->flash('success', "User '{$user->name}' activated successfully."); + } + + public function suspendUser($userId) + { + $user = User::findOrFail($userId); + + if ($user->id === auth()->id()) { + session()->flash('error', 'You cannot suspend your own account.'); + return; + } + + $user->update(['status' => 'suspended']); + + // Log the action + activity() + ->performedOn($user) + ->causedBy(auth()->user()) + ->log('User suspended'); + + session()->flash('success', "User '{$user->name}' suspended successfully."); + } + + public function deleteUser($userId) + { + $user = User::findOrFail($userId); + + if ($user->id === auth()->id()) { + session()->flash('error', 'You cannot delete your own account.'); + return; + } + + try { + // Log before deletion + activity() + ->performedOn($user) + ->causedBy(auth()->user()) + ->log('User deleted'); + + $userName = $user->name; + $user->delete(); + + session()->flash('success', "User '{$userName}' deleted successfully."); + } catch (\Exception $e) { + session()->flash('error', 'Failed to delete user: ' . $e->getMessage()); + } + } + + public function getUserRoles($user) + { + return $user->roles() + ->where('user_roles.is_active', true) + ->where(function ($q) { + $q->whereNull('user_roles.expires_at') + ->orWhere('user_roles.expires_at', '>', now()); + }) + ->pluck('display_name') + ->join(', '); + } + + public function getUserPermissionCount($user) + { + return $user->getAllPermissions()->count(); + } + + public function hasActiveFilters() + { + return !empty($this->search) || + !empty($this->roleFilter) || + !empty($this->statusFilter) || + !empty($this->departmentFilter) || + !empty($this->branchFilter) || + $this->showInactive; + } + + public function getSelectedCount() + { + return count($this->selectedUsers); + } + + public function exportUsers() + { + // This would typically export to CSV or Excel + $users = User::with(['roles']) + ->when($this->search, function ($q) { + $q->where(function ($query) { + $query->where('name', 'like', '%' . $this->search . '%') + ->orWhere('email', 'like', '%' . $this->search . '%') + ->orWhere('employee_id', 'like', '%' . $this->search . '%'); + }); + }) + ->get(); + + session()->flash('success', 'Export initiated for ' . $users->count() . ' users.'); + } + + public function resetFilters() + { + $this->clearFilters(); + } + + public function getRoleBadgeClass($roleName) + { + return match($roleName) { + 'super_admin' => 'bg-gradient-to-r from-purple-500 to-pink-500 text-white', + 'administrator' => 'bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200', + 'manager' => 'bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200', + 'technician' => 'bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200', + 'receptionist' => 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200', + 'parts_clerk' => 'bg-amber-100 dark:bg-amber-900 text-amber-800 dark:text-amber-200', + 'service_advisor' => 'bg-indigo-100 dark:bg-indigo-900 text-indigo-800 dark:text-indigo-200', + 'cashier' => 'bg-pink-100 dark:bg-pink-900 text-pink-800 dark:text-pink-200', + 'customer' => 'bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200', + default => 'bg-zinc-100 dark:bg-zinc-700 text-zinc-800 dark:text-zinc-200', + }; + } + + public function getStatusBadgeClass($status) + { + return match($status) { + 'active' => 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200', + 'inactive' => 'bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200', + 'suspended' => 'bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200', + default => 'bg-zinc-100 dark:bg-zinc-700 text-zinc-800 dark:text-zinc-200', + }; + } +} diff --git a/app/Livewire/Users/ManageRolesPermissions.php b/app/Livewire/Users/ManageRolesPermissions.php new file mode 100644 index 0000000..ea1bd2a --- /dev/null +++ b/app/Livewire/Users/ManageRolesPermissions.php @@ -0,0 +1,393 @@ + 'array', + 'selectedPermissions' => 'array', + 'branchCode' => 'required|string|max:10', + 'expiresAt' => 'nullable|date|after:today', + 'notes' => 'nullable|string|max:500', + ]; + + public function mount(User $user) + { + + $this->user = $user; + $this->branchCode = $user->branch_code ?? auth()->user()->branch_code ?? ''; + + // Load current roles + $this->selectedRoles = $user->roles() + ->where('user_roles.is_active', true) + ->where(function ($q) { + $q->whereNull('user_roles.expires_at') + ->orWhere('user_roles.expires_at', '>', now()); + }) + ->pluck('roles.id') + ->toArray(); + + // Load current direct permissions + $this->selectedPermissions = $user->permissions() + ->where('user_permissions.granted', true) + ->where(function ($q) { + $q->whereNull('user_permissions.expires_at') + ->orWhere('user_permissions.expires_at', '>', now()); + }) + ->pluck('permissions.id') + ->toArray(); + } + + public function render() + { + $roles = Role::where('is_active', true) + ->with('permissions') + ->get(); + + $permissions = Permission::where('is_active', true) + ->orderBy('module') + ->orderBy('name') + ->get(); + + $permissionsByModule = $permissions->groupBy('module'); + + // Get user's current roles with details + $currentRoles = $this->user->roles() + ->where('user_roles.is_active', true) + ->withPivot(['branch_code', 'assigned_at', 'expires_at']) + ->get(); + + // Get user's current direct permissions with details + $currentPermissions = $this->user->permissions() + ->where('user_permissions.granted', true) + ->withPivot(['branch_code', 'assigned_at', 'expires_at']) + ->get(); + + // Get all effective permissions + $allPermissions = $this->user->getAllPermissions($this->branchCode); + $effectivePermissionsByModule = $allPermissions->groupBy('module'); + + return view('livewire.users.manage-roles-permissions', [ + 'availableRoles' => $roles, + 'permissions' => $permissions, + 'groupedPermissions' => $permissionsByModule, + 'currentRoles' => $currentRoles, + 'currentPermissions' => $currentPermissions, + 'effectivePermissionsByModule' => $effectivePermissionsByModule, + ]); + } + + public function setActiveTab($tab) + { + $this->activeTab = $tab; + } + + public function updateRoles() + { + $this->validate(); + + try { + // Sync roles with additional data + $roleData = []; + foreach ($this->selectedRoles as $roleId) { + $roleData[$roleId] = [ + 'branch_code' => $this->branchCode, + 'is_active' => true, + 'assigned_at' => now(), + 'expires_at' => $this->expiresAt ? $this->expiresAt : null, + ]; + } + + $this->user->roles()->sync($roleData); + + session()->flash('success', 'User roles updated successfully!'); + $this->user->refresh(); + + } catch (\Exception $e) { + session()->flash('error', 'Failed to update roles: ' . $e->getMessage()); + } + } + + public function updatePermissions() + { + $this->validate(); + + try { + // Sync direct permissions + $permissionData = []; + foreach ($this->selectedPermissions as $permissionId) { + $permissionData[$permissionId] = [ + 'granted' => true, + 'branch_code' => $this->branchCode, + 'assigned_at' => now(), + 'expires_at' => $this->expiresAt ? $this->expiresAt : null, + ]; + } + + $this->user->permissions()->sync($permissionData); + + session()->flash('success', 'User permissions updated successfully!'); + $this->user->refresh(); + + } catch (\Exception $e) { + session()->flash('error', 'Failed to update permissions: ' . $e->getMessage()); + } + } + + public function addRole($roleId) + { + + if (!in_array($roleId, $this->selectedRoles)) { + $this->selectedRoles[] = $roleId; + } + } + + public function removeRole($roleId) + { + + $this->selectedRoles = array_filter($this->selectedRoles, fn($id) => $id != $roleId); + } + + public function addPermission($permissionId) + { + + if (!in_array($permissionId, $this->selectedPermissions)) { + $this->selectedPermissions[] = $permissionId; + } + } + + public function removePermission($permissionId) + { + + $this->selectedPermissions = array_filter($this->selectedPermissions, fn($id) => $id != $permissionId); + } + + public function selectAllPermissionsInModule($module) + { + $modulePermissions = Permission::where('module', $module) + ->where('is_active', true) + ->pluck('id') + ->toArray(); + + $this->selectedPermissions = array_unique(array_merge($this->selectedPermissions, $modulePermissions)); + } + + public function deselectAllPermissionsInModule($module) + { + $modulePermissions = Permission::where('module', $module) + ->pluck('id') + ->toArray(); + + $this->selectedPermissions = array_diff($this->selectedPermissions, $modulePermissions); + } + + public function copyRolesFromUser($sourceUserId) + { + + try { + $sourceUser = User::findOrFail($sourceUserId); + $sourceRoles = $sourceUser->roles() + ->where('user_roles.is_active', true) + ->pluck('roles.id') + ->toArray(); + + $this->selectedRoles = $sourceRoles; + session()->flash('success', 'Roles copied from ' . $sourceUser->name); + + } catch (\Exception $e) { + session()->flash('error', 'Failed to copy roles: ' . $e->getMessage()); + } + } + + public function presetForRole($roleType) + { + $presets = [ + 'admin' => Role::where('name', 'admin')->pluck('id')->toArray(), + 'manager' => Role::whereIn('name', ['manager', 'service_supervisor'])->pluck('id')->toArray(), + 'technician' => Role::where('name', 'technician')->pluck('id')->toArray(), + 'advisor' => Role::where('name', 'service_advisor')->pluck('id')->toArray(), + ]; + + if (isset($presets[$roleType])) { + $this->selectedRoles = $presets[$roleType]; + } + } + + public function bulkExecute() + { + + try { + switch ($this->bulkAction) { + case 'add_roles': + foreach ($this->bulkRoleIds as $roleId) { + if (!in_array($roleId, $this->selectedRoles)) { + $this->selectedRoles[] = $roleId; + } + } + break; + + case 'remove_roles': + $this->selectedRoles = array_diff($this->selectedRoles, $this->bulkRoleIds); + break; + + case 'add_permissions': + foreach ($this->bulkPermissionIds as $permissionId) { + if (!in_array($permissionId, $this->selectedPermissions)) { + $this->selectedPermissions[] = $permissionId; + } + } + break; + + case 'remove_permissions': + $this->selectedPermissions = array_diff($this->selectedPermissions, $this->bulkPermissionIds); + break; + } + + session()->flash('success', 'Bulk operation completed successfully!'); + + } catch (\Exception $e) { + session()->flash('error', 'Bulk operation failed: ' . $e->getMessage()); + } + } + + public function resetToDefault() + { + // Reset to basic role based on user's department/position + $defaultRoles = []; + + switch ($this->user->department) { + case 'Service': + $defaultRoles = Role::whereIn('name', ['service_advisor'])->pluck('id')->toArray(); + break; + case 'Technician': + $defaultRoles = Role::where('name', 'technician')->pluck('id')->toArray(); + break; + case 'Parts': + $defaultRoles = Role::where('name', 'parts_manager')->pluck('id')->toArray(); + break; + case 'Management': + $defaultRoles = Role::where('name', 'manager')->pluck('id')->toArray(); + break; + } + + $this->selectedRoles = $defaultRoles; + $this->selectedPermissions = []; + } + + public function applyRolePreset($roleType) + { + + // Define role presets + $presets = [ + 'manager' => [ + 'roles' => ['manager', 'senior_technician'], + 'permissions' => [] // Manager gets permissions through role + ], + 'technician' => [ + 'roles' => ['technician'], + 'permissions' => [] // Technician gets permissions through role + ], + 'receptionist' => [ + 'roles' => ['receptionist'], + 'permissions' => [] // Receptionist gets permissions through role + ], + 'parts_clerk' => [ + 'roles' => ['parts_manager'], + 'permissions' => [] // Parts clerk gets permissions through role + ] + ]; + + if (!isset($presets[$roleType])) { + session()->flash('error', 'Invalid role preset.'); + return; + } + + $preset = $presets[$roleType]; + + // Get role IDs + $roleIds = Role::whereIn('name', $preset['roles'])->pluck('id')->toArray(); + $this->selectedRoles = $roleIds; + + // Get permission IDs if any + if (!empty($preset['permissions'])) { + $permissionIds = Permission::whereIn('name', $preset['permissions'])->pluck('id')->toArray(); + $this->selectedPermissions = $permissionIds; + } else { + $this->selectedPermissions = []; + } + + session()->flash('success', 'Applied ' . ucfirst($roleType) . ' preset successfully.'); + } + + public function selectAllPermissions() + { + $this->selectedPermissions = Permission::where('is_active', true)->pluck('id')->toArray(); + } + + public function deselectAllPermissions() + { + $this->selectedPermissions = []; + } + + public function selectModulePermissions($module) + { + $modulePermissions = Permission::where('is_active', true) + ->where('module', $module) + ->pluck('id') + ->toArray(); + + $this->selectedPermissions = array_unique(array_merge($this->selectedPermissions, $modulePermissions)); + } + + public function deselectModulePermissions($module) + { + $modulePermissions = Permission::where('is_active', true) + ->where('module', $module) + ->pluck('id') + ->toArray(); + + $this->selectedPermissions = array_diff($this->selectedPermissions, $modulePermissions); + } + + public function removeAllRoles() + { + + try { + $this->user->roles()->detach(); + session()->flash('success', 'All roles removed successfully.'); + } catch (\Exception $e) { + session()->flash('error', 'Failed to remove roles: ' . $e->getMessage()); + } + } + + public function removeAllPermissions() + { + + try { + $this->user->permissions()->detach(); + session()->flash('success', 'All direct permissions removed successfully.'); + } catch (\Exception $e) { + session()->flash('error', 'Failed to remove permissions: ' . $e->getMessage()); + } + } +} diff --git a/app/Livewire/Users/Show.php b/app/Livewire/Users/Show.php new file mode 100644 index 0000000..deeb6a1 --- /dev/null +++ b/app/Livewire/Users/Show.php @@ -0,0 +1,506 @@ +user = $user->load(['roles.permissions', 'permissions']); + } + + public function render() + { + $userRoles = $this->user->roles() + ->where('user_roles.is_active', true) + ->where(function ($q) { + $q->whereNull('user_roles.expires_at') + ->orWhere('user_roles.expires_at', '>', now()); + }) + ->withPivot(['branch_code', 'assigned_at', 'expires_at']) + ->get(); + + $userDirectPermissions = $this->user->permissions() + ->where('user_permissions.granted', true) + ->where(function ($q) { + $q->whereNull('user_permissions.expires_at') + ->orWhere('user_permissions.expires_at', '>', now()); + }) + ->withPivot(['branch_code', 'assigned_at', 'expires_at']) + ->get(); + + $allPermissions = $this->user->getAllPermissions(); + $permissionsByModule = $allPermissions->groupBy('module'); + + // Get role-based permissions + $rolePermissions = collect(); + foreach ($userRoles as $role) { + $rolePermissions = $rolePermissions->merge($role->permissions); + } + $rolePermissions = $rolePermissions->unique('id'); + + // Get recent activity + $causedByUser = Activity::where('causer_id', $this->user->id) + ->where('causer_type', User::class) + ->latest() + ->limit(10) + ->get(); + + $performedOnUser = Activity::where('subject_id', $this->user->id) + ->where('subject_type', User::class) + ->latest() + ->limit(10) + ->get(); + + $recentActivity = $causedByUser->merge($performedOnUser) + ->sortByDesc('created_at') + ->take(20); + + // Get user metrics + $metrics = $this->getUserMetrics(); + + // Get user's work orders, service orders, etc. (if applicable) + $workStats = $this->getUserWorkStats(); + + return view('livewire.users.show', [ + 'userRoles' => $userRoles, + 'userDirectPermissions' => $userDirectPermissions, + 'allPermissions' => $allPermissions, + 'permissionsByModule' => $permissionsByModule, + 'rolePermissions' => $rolePermissions, + 'recentActivity' => $recentActivity, + 'metrics' => $metrics, + 'workStats' => $workStats, + ]); + } + + public function setActiveTab($tab) + { + $this->activeTab = $tab; + } + + public function showRoleDetails($roleId) + { + $this->selectedRole = Role::with('permissions')->find($roleId); + $this->showRoleModal = true; + } + + public function showPermissionDetails($permissionId) + { + $this->selectedPermission = Permission::find($permissionId); + $this->showPermissionModal = true; + } + + public function closeModals() + { + $this->showRoleModal = false; + $this->showPermissionModal = false; + $this->showDeleteModal = false; + $this->showImpersonateModal = false; + $this->showActivityModal = false; + $this->selectedRole = null; + $this->selectedPermission = null; + } + + public function removeRole($roleId) + { + try { + $this->user->roles()->detach($roleId); + $this->user->refresh(); + + // Log the action + activity() + ->performedOn($this->user) + ->causedBy(auth()->user()) + ->withProperties(['role_id' => $roleId]) + ->log('Role removed from user'); + + session()->flash('success', 'Role removed successfully.'); + } catch (\Exception $e) { + session()->flash('error', 'Failed to remove role: ' . $e->getMessage()); + } + } + + public function removePermission($permissionId) + { + try { + $this->user->permissions()->detach($permissionId); + $this->user->refresh(); + + // Log the action + activity() + ->performedOn($this->user) + ->causedBy(auth()->user()) + ->withProperties(['permission_id' => $permissionId]) + ->log('Permission removed from user'); + + session()->flash('success', 'Permission removed successfully.'); + } catch (\Exception $e) { + session()->flash('error', 'Failed to remove permission: ' . $e->getMessage()); + } + } + + public function toggleUserStatus() + { + if ($this->user->id === auth()->id()) { + session()->flash('error', 'You cannot change your own status.'); + return; + } + + try { + $newStatus = $this->user->status === 'active' ? 'inactive' : 'active'; + $oldStatus = $this->user->status; + + $this->user->update(['status' => $newStatus]); + + // Log the action + activity() + ->performedOn($this->user) + ->causedBy(auth()->user()) + ->withProperties([ + 'old_status' => $oldStatus, + 'new_status' => $newStatus, + ]) + ->log('User status changed'); + + $statusText = $newStatus === 'active' ? 'activated' : 'deactivated'; + session()->flash('success', "User {$statusText} successfully."); + } catch (\Exception $e) { + session()->flash('error', 'Failed to update status: ' . $e->getMessage()); + } + } + + public function suspendUser() + { + if ($this->user->id === auth()->id()) { + session()->flash('error', 'You cannot suspend your own account.'); + return; + } + + try { + $oldStatus = $this->user->status; + $this->user->update(['status' => 'suspended']); + + // Log the action + activity() + ->performedOn($this->user) + ->causedBy(auth()->user()) + ->withProperties([ + 'old_status' => $oldStatus, + 'new_status' => 'suspended', + ]) + ->log('User suspended'); + + session()->flash('success', 'User suspended successfully.'); + } catch (\Exception $e) { + session()->flash('error', 'Failed to suspend user: ' . $e->getMessage()); + } + } + + public function impersonateUser() + { + if ($this->user->id === auth()->id()) { + session()->flash('error', 'You cannot impersonate yourself.'); + return; + } + + if ($this->user->status !== 'active') { + session()->flash('error', 'Cannot impersonate inactive user.'); + return; + } + + try { + // Log the impersonation start + activity() + ->performedOn($this->user) + ->causedBy(auth()->user()) + ->log('Impersonation started'); + + // Store original user ID for returning later + session(['impersonate_original_user' => auth()->id()]); + auth()->loginUsingId($this->user->id); + + session()->flash('success', 'Now impersonating ' . $this->user->name); + return redirect()->route('dashboard'); + } catch (\Exception $e) { + session()->flash('error', 'Failed to impersonate user: ' . $e->getMessage()); + } + } + + public function sendPasswordReset() + { + try { + // Generate a secure temporary password + $tempPassword = $this->generateSecurePassword(); + + $this->user->update([ + 'password' => Hash::make($tempPassword), + 'password_changed_at' => now(), + ]); + + // Log the action + activity() + ->performedOn($this->user) + ->causedBy(auth()->user()) + ->log('Password reset by admin'); + + // TODO: Send password reset email with new temporary password + // $this->user->notify(new PasswordResetByAdminNotification($tempPassword)); + + session()->flash('success', "Password reset successfully. New temporary password: {$tempPassword}"); + } catch (\Exception $e) { + session()->flash('error', 'Failed to reset password: ' . $e->getMessage()); + } + } + + public function generateSecurePassword($length = 12) + { + $uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $lowercase = 'abcdefghijklmnopqrstuvwxyz'; + $numbers = '0123456789'; + $symbols = '!@#$%^&*()_+-=[]{}|;:,.<>?'; + + $password = ''; + $password .= $uppercase[random_int(0, strlen($uppercase) - 1)]; + $password .= $lowercase[random_int(0, strlen($lowercase) - 1)]; + $password .= $numbers[random_int(0, strlen($numbers) - 1)]; + $password .= $symbols[random_int(0, strlen($symbols) - 1)]; + + $allChars = $uppercase . $lowercase . $numbers . $symbols; + for ($i = 4; $i < $length; $i++) { + $password .= $allChars[random_int(0, strlen($allChars) - 1)]; + } + + return str_shuffle($password); + } + + public function exportUserData() + { + try { + // Log the export request + activity() + ->performedOn($this->user) + ->causedBy(auth()->user()) + ->log('User data export requested'); + + // TODO: Implement user data export (GDPR compliance) + // This should include all user data, activity logs, etc. + + session()->flash('success', 'User data export initiated. You will receive an email when ready.'); + } catch (\Exception $e) { + session()->flash('error', 'Failed to export user data: ' . $e->getMessage()); + } + } + + public function deleteUser() + { + if ($this->user->id === auth()->id()) { + session()->flash('error', 'You cannot delete your own account.'); + return; + } + + try { + // Log before deletion + activity() + ->performedOn($this->user) + ->causedBy(auth()->user()) + ->withProperties([ + 'deleted_user_data' => [ + 'name' => $this->user->name, + 'email' => $this->user->email, + 'employee_id' => $this->user->employee_id, + ] + ]) + ->log('User deleted by admin'); + + $userName = $this->user->name; + $this->user->delete(); + + session()->flash('success', "User '{$userName}' deleted successfully."); + return redirect()->route('users.index'); + } catch (\Exception $e) { + session()->flash('error', 'Failed to delete user: ' . $e->getMessage()); + } + + $this->showDeleteModal = false; + } + + public function confirmDelete() + { + $this->showDeleteModal = true; + } + + public function confirmImpersonate() + { + $this->showImpersonateModal = true; + } + + public function getUserMetrics() + { + $totalPermissions = $this->user->getAllPermissions()->count(); + $directPermissions = $this->user->permissions() + ->where('user_permissions.granted', true) + ->where(function ($q) { + $q->whereNull('user_permissions.expires_at') + ->orWhere('user_permissions.expires_at', '>', now()); + }) + ->count(); + + $activeRoles = $this->user->roles() + ->where('user_roles.is_active', true) + ->where(function ($q) { + $q->whereNull('user_roles.expires_at') + ->orWhere('user_roles.expires_at', '>', now()); + }) + ->count(); + + return [ + 'total_permissions' => $totalPermissions, + 'direct_permissions' => $directPermissions, + 'role_permissions' => $totalPermissions - $directPermissions, + 'active_roles' => $activeRoles, + 'days_since_created' => $this->user->created_at->diffInDays(now()), + 'last_login' => $this->user->last_login_at ? $this->user->last_login_at->diffForHumans() : 'Never', + 'password_age' => $this->user->password_changed_at ? $this->user->password_changed_at->diffInDays(now()) : null, + ]; + } + + public function getUserWorkStats() + { + // Get work-related statistics for the user + $stats = [ + 'work_orders_assigned' => 0, + 'work_orders_completed' => 0, + 'service_orders_created' => 0, + 'total_revenue_generated' => 0, + ]; + + try { + // Work orders assigned to user (if technician) + if (\Schema::hasTable('work_orders')) { + $stats['work_orders_assigned'] = \DB::table('work_orders') + ->where('assigned_technician_id', $this->user->id) + ->count(); + + $stats['work_orders_completed'] = \DB::table('work_orders') + ->where('assigned_technician_id', $this->user->id) + ->where('status', 'completed') + ->count(); + } + + // Service orders created by user (if service advisor) + if (\Schema::hasTable('service_orders')) { + $stats['service_orders_created'] = \DB::table('service_orders') + ->where('created_by', $this->user->id) + ->count(); + + $stats['total_revenue_generated'] = \DB::table('service_orders') + ->where('created_by', $this->user->id) + ->where('status', 'completed') + ->sum('total_amount') ?? 0; + } + } catch (\Exception $e) { + // Tables might not exist, return default stats + } + + return $stats; + } + + public function getStatusBadgeClass($status) + { + return match($status) { + 'active' => 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200', + 'inactive' => 'bg-zinc-100 dark:bg-zinc-700 text-zinc-800 dark:text-zinc-200', + 'suspended' => 'bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200', + default => 'bg-zinc-100 dark:bg-zinc-700 text-zinc-800 dark:text-zinc-200' + }; + } + + public function getRoleBadgeClass($roleName) + { + return match($roleName) { + 'super_admin' => 'bg-gradient-to-r from-purple-500 to-pink-500 text-white', + 'administrator' => 'bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200', + 'manager' => 'bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200', + 'service_manager' => 'bg-indigo-100 dark:bg-indigo-900 text-indigo-800 dark:text-indigo-200', + 'technician' => 'bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200', + 'senior_technician' => 'bg-emerald-100 dark:bg-emerald-900 text-emerald-800 dark:text-emerald-200', + 'parts_clerk' => 'bg-amber-100 dark:bg-amber-900 text-amber-800 dark:text-amber-200', + 'service_advisor' => 'bg-teal-100 dark:bg-teal-900 text-teal-800 dark:text-teal-200', + 'receptionist' => 'bg-pink-100 dark:bg-pink-900 text-pink-800 dark:text-pink-200', + 'cashier' => 'bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200', + 'customer' => 'bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200', + 'viewer' => 'bg-zinc-100 dark:bg-zinc-700 text-zinc-800 dark:text-zinc-200', + default => 'bg-zinc-100 dark:bg-zinc-700 text-zinc-800 dark:text-zinc-200' + }; + } + + public function canPerformAction($action) + { + // Check if current user can perform certain actions on this user + $currentUser = auth()->user(); + + // Super admin can do anything + if ($currentUser->hasRole('super_admin')) { + return true; + } + + // Can't perform actions on yourself (except view) + if ($currentUser->id === $this->user->id && $action !== 'view') { + return false; + } + + // Check specific permissions based on action + return match($action) { + 'edit' => $currentUser->can('users.edit'), + 'delete' => $currentUser->can('users.delete'), + 'impersonate' => $currentUser->can('users.impersonate'), + 'reset_password' => $currentUser->can('users.reset-password'), + 'manage_roles' => $currentUser->can('users.manage-roles'), + 'view_activity' => $currentUser->can('users.view-activity'), + default => false, + }; + } + + public function getLastActivityDate() + { + $lastActivity = Activity::where('causer_id', $this->user->id) + ->where('causer_type', User::class) + ->latest() + ->first(); + + return $lastActivity ? $lastActivity->created_at->diffForHumans() : 'No activity recorded'; + } + + public function getTotalLoginCount() + { + // This would require a login tracking system + return Activity::where('causer_id', $this->user->id) + ->where('causer_type', User::class) + ->where('description', 'like', '%login%') + ->count(); + } +} diff --git a/app/Livewire/Vehicles/Create.php b/app/Livewire/Vehicles/Create.php new file mode 100644 index 0000000..6af2db1 --- /dev/null +++ b/app/Livewire/Vehicles/Create.php @@ -0,0 +1,181 @@ +vinDecoderService = $vinDecoderService; + } + + public function mount() + { + // If customer ID is passed via query parameter, pre-select it + if (request()->has('customer')) { + $this->customer_id = request()->get('customer'); + } + } + + public function decodeVin() + { + // Clear previous messages + $this->vinDecodeError = ''; + $this->vinDecodeSuccess = ''; + + // Validate VIN format first + if (strlen(trim($this->vin)) !== 17) { + $this->vinDecodeError = 'VIN must be exactly 17 characters long.'; + return; + } + + $this->isDecodingVin = true; + + try { + $result = $this->vinDecoderService->decodeVin($this->vin); + + if ($result['success']) { + $data = $result['data']; + + // Fill form fields with decoded data + if (!empty($data['make'])) { + $this->make = $data['make']; + } + if (!empty($data['model'])) { + $this->model = $data['model']; + } + if (!empty($data['year'])) { + $this->year = $data['year']; + } + if (!empty($data['engine_type'])) { + $this->engine_type = $data['engine_type']; + } + if (!empty($data['transmission'])) { + $this->transmission = $data['transmission']; + } + + // Show success message + $filledFields = array_filter([ + $data['make'] ? 'Make' : null, + $data['model'] ? 'Model' : null, + $data['year'] ? 'Year' : null, + $data['engine_type'] ? 'Engine' : null, + $data['transmission'] ? 'Transmission' : null, + ]); + + if (!empty($filledFields)) { + $this->vinDecodeSuccess = 'VIN decoded successfully! Auto-filled: ' . implode(', ', $filledFields); + } else { + $this->vinDecodeError = 'VIN was decoded but no vehicle information was found.'; + } + + // Show error codes if any + if (!empty($data['error_codes'])) { + $this->vinDecodeError = 'VIN decoded with warnings: ' . implode(', ', array_slice($data['error_codes'], 0, 2)); + } + + } else { + $this->vinDecodeError = $result['error']; + } + + } catch (\Exception $e) { + $this->vinDecodeError = 'An unexpected error occurred while decoding the VIN.'; + } finally { + $this->isDecodingVin = false; + } + } + + public function createVehicle() + { + $this->validate(); + + // Handle vehicle image upload + $vehicleImagePath = null; + if ($this->vehicle_image) { + $vehicleImagePath = $this->vehicle_image->store('vehicles', 'public'); + } + + $vehicle = Vehicle::create([ + 'customer_id' => $this->customer_id, + 'vin' => strtoupper($this->vin), + 'make' => $this->make, + 'model' => $this->model, + 'year' => $this->year, + 'color' => $this->color, + 'license_plate' => strtoupper($this->license_plate), + 'engine_type' => $this->engine_type, + 'transmission' => $this->transmission, + 'mileage' => $this->mileage, + 'notes' => $this->notes, + 'status' => $this->status, + 'vehicle_image' => $vehicleImagePath, + ]); + + session()->flash('success', 'Vehicle added successfully!'); + + return $this->redirect('/vehicles/' . $vehicle->id, navigate: true); + } + + public function render() + { + $customers = Customer::where('status', 'active')->orderBy('first_name')->get(); + + return view('livewire.vehicles.create', [ + 'customers' => $customers, + ]); + } +} diff --git a/app/Livewire/Vehicles/Edit.php b/app/Livewire/Vehicles/Edit.php new file mode 100644 index 0000000..3ec0736 --- /dev/null +++ b/app/Livewire/Vehicles/Edit.php @@ -0,0 +1,211 @@ +vinDecoderService = $vinDecoderService; + } + + public function mount(Vehicle $vehicle) + { + $this->vehicle = $vehicle; + $this->customer_id = $vehicle->customer_id; + $this->vin = $vehicle->vin; + $this->make = $vehicle->make; + $this->model = $vehicle->model; + $this->year = $vehicle->year; + $this->color = $vehicle->color; + $this->license_plate = $vehicle->license_plate; + $this->engine_type = $vehicle->engine_type; + $this->transmission = $vehicle->transmission; + $this->mileage = $vehicle->mileage; + $this->notes = $vehicle->notes; + $this->status = $vehicle->status; + } + + public function decodeVin() + { + // Clear previous messages + $this->vinDecodeError = ''; + $this->vinDecodeSuccess = ''; + + // Validate VIN format first + if (strlen(trim($this->vin)) !== 17) { + $this->vinDecodeError = 'VIN must be exactly 17 characters long.'; + return; + } + + $this->isDecodingVin = true; + + try { + $result = $this->vinDecoderService->decodeVin($this->vin); + + if ($result['success']) { + $data = $result['data']; + + // Ask user before overwriting existing data + $updatedFields = []; + + if (!empty($data['make']) && $data['make'] !== $this->make) { + $this->make = $data['make']; + $updatedFields[] = 'Make'; + } + if (!empty($data['model']) && $data['model'] !== $this->model) { + $this->model = $data['model']; + $updatedFields[] = 'Model'; + } + if (!empty($data['year']) && $data['year'] != $this->year) { + $this->year = $data['year']; + $updatedFields[] = 'Year'; + } + if (!empty($data['engine_type']) && $data['engine_type'] !== $this->engine_type) { + $this->engine_type = $data['engine_type']; + $updatedFields[] = 'Engine'; + } + if (!empty($data['transmission']) && $data['transmission'] !== $this->transmission) { + $this->transmission = $data['transmission']; + $updatedFields[] = 'Transmission'; + } + + // Show success message + if (!empty($updatedFields)) { + $this->vinDecodeSuccess = 'VIN decoded successfully! Updated: ' . implode(', ', $updatedFields); + } else { + $this->vinDecodeSuccess = 'VIN decoded successfully! No changes needed - current data matches VIN.'; + } + + // Show error codes if any + if (!empty($data['error_codes'])) { + $this->vinDecodeError = 'VIN decoded with warnings: ' . implode(', ', array_slice($data['error_codes'], 0, 2)); + } + + } else { + $this->vinDecodeError = $result['error']; + } + + } catch (\Exception $e) { + $this->vinDecodeError = 'An unexpected error occurred while decoding the VIN.'; + } finally { + $this->isDecodingVin = false; + } + } + + public function updateVehicle() + { + // Update validation rules to exclude current vehicle's unique fields + $this->validate([ + 'customer_id' => 'required|exists:customers,id', + 'vin' => 'required|string|size:17|unique:vehicles,vin,' . $this->vehicle->id, + 'make' => 'required|string|max:255', + 'model' => 'required|string|max:255', + 'year' => 'required|integer|min:1900|max:2030', + 'color' => 'required|string|max:255', + 'license_plate' => 'required|string|max:20|unique:vehicles,license_plate,' . $this->vehicle->id, + 'engine_type' => 'nullable|string|max:255', + 'transmission' => 'nullable|string|max:255', + 'mileage' => 'required|integer|min:0|max:999999', + 'notes' => 'nullable|string|max:1000', + 'status' => 'required|in:active,inactive,sold', + 'vehicle_image' => 'nullable|image|max:2048', + ]); + + // Handle vehicle image upload + $updateData = [ + 'customer_id' => $this->customer_id, + 'vin' => strtoupper($this->vin), + 'make' => $this->make, + 'model' => $this->model, + 'year' => $this->year, + 'color' => $this->color, + 'license_plate' => strtoupper($this->license_plate), + 'engine_type' => $this->engine_type, + 'transmission' => $this->transmission, + 'mileage' => $this->mileage, + 'notes' => $this->notes, + 'status' => $this->status, + ]; + + if ($this->vehicle_image) { + // Delete old image if exists + if ($this->vehicle->vehicle_image) { + \Storage::disk('public')->delete($this->vehicle->vehicle_image); + } + // Store new image + $updateData['vehicle_image'] = $this->vehicle_image->store('vehicles', 'public'); + } + + $this->vehicle->update($updateData); + + session()->flash('success', 'Vehicle updated successfully!'); + + return $this->redirect('/vehicles/' . $this->vehicle->id, navigate: true); + } + + public function render() + { + $customers = Customer::where('status', 'active')->orderBy('first_name')->get(); + + return view('livewire.vehicles.edit', [ + 'customers' => $customers, + ]); + } +} diff --git a/app/Livewire/Vehicles/Index.php b/app/Livewire/Vehicles/Index.php new file mode 100644 index 0000000..0acc648 --- /dev/null +++ b/app/Livewire/Vehicles/Index.php @@ -0,0 +1,114 @@ + ['except' => ''], + 'customer_id' => ['except' => ''], + 'make' => ['except' => ''], + 'status' => ['except' => ''], + 'sortBy' => ['except' => 'created_at'], + 'sortDirection' => ['except' => 'desc'], + ]; + + public function updatingSearch() + { + $this->resetPage(); + } + + public function updatingCustomerId() + { + $this->resetPage(); + } + + public function updatingMake() + { + $this->resetPage(); + } + + public function updatingStatus() + { + $this->resetPage(); + } + + public function sortBy($field) + { + if ($this->sortBy === $field) { + $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc'; + } else { + $this->sortBy = $field; + $this->sortDirection = 'asc'; + } + } + + public function deleteVehicle($vehicleId) + { + $vehicle = Vehicle::findOrFail($vehicleId); + + // Check if vehicle has any service orders + if ($vehicle->serviceOrders()->count() > 0) { + session()->flash('error', 'Cannot delete vehicle with existing service orders. Please complete or cancel them first.'); + return; + } + + $vehicleName = $vehicle->display_name; + $vehicle->delete(); + + session()->flash('success', "Vehicle {$vehicleName} has been deleted successfully."); + } + + public function render() + { + $vehicles = Vehicle::query() + ->with(['customer']) + ->when($this->search, function ($query) { + $query->where(function ($q) { + $q->where('make', 'like', '%' . $this->search . '%') + ->orWhere('model', 'like', '%' . $this->search . '%') + ->orWhere('year', 'like', '%' . $this->search . '%') + ->orWhere('vin', 'like', '%' . $this->search . '%') + ->orWhere('license_plate', 'like', '%' . $this->search . '%') + ->orWhereHas('customer', function ($customerQuery) { + $customerQuery->where('first_name', 'like', '%' . $this->search . '%') + ->orWhere('last_name', 'like', '%' . $this->search . '%'); + }); + }); + }) + ->when($this->customer_id, function ($query) { + $query->where('customer_id', $this->customer_id); + }) + ->when($this->make, function ($query) { + $query->where('make', $this->make); + }) + ->when($this->status, function ($query) { + $query->where('status', $this->status); + }) + ->orderBy($this->sortBy, $this->sortDirection) + ->paginate(15); + + $customers = Customer::orderBy('first_name')->get(); + $makes = Vehicle::distinct()->orderBy('make')->pluck('make')->filter(); + + return view('livewire.vehicles.index', [ + 'vehicles' => $vehicles, + 'customers' => $customers, + 'makes' => $makes, + ]); + } +} diff --git a/app/Livewire/Vehicles/Show.php b/app/Livewire/Vehicles/Show.php new file mode 100644 index 0000000..1be9d3d --- /dev/null +++ b/app/Livewire/Vehicles/Show.php @@ -0,0 +1,26 @@ +vehicle = $vehicle->load([ + 'customer', + 'serviceOrders.assignedTechnician', + 'appointments', + 'inspections' + ]); + } + + public function render() + { + return view('livewire.vehicles.show'); + } +} diff --git a/app/Livewire/WorkOrders/Create.php b/app/Livewire/WorkOrders/Create.php new file mode 100644 index 0000000..fee5061 --- /dev/null +++ b/app/Livewire/WorkOrders/Create.php @@ -0,0 +1,150 @@ + 'required|string', + 'estimated_start_time' => 'required|date|after:now', + 'estimated_completion_time' => 'required|date|after:estimated_start_time', + 'assigned_technician_id' => 'required|exists:users,id', + 'tasks.*.task_name' => 'required|string', + 'tasks.*.estimated_duration' => 'required|numeric|min:0.5', + 'tasks.*.required_skills' => 'nullable|string', + ]; + + public function mount(Estimate $estimate) + { + $this->estimate = $estimate->load([ + 'jobCard.customer', + 'jobCard.vehicle', + 'diagnosis', + 'lineItems' + ]); + + $this->work_description = "Perform repairs as per approved estimate #{$estimate->estimate_number}"; + $this->estimated_start_time = now()->addDay()->format('Y-m-d\TH:i'); + $this->estimated_completion_time = now()->addDays(3)->format('Y-m-d\TH:i'); + + // Initialize tasks from estimate line items + $this->initializeTasks(); + } + + public function initializeTasks() + { + foreach ($this->estimate->lineItems as $item) { + if ($item->type === 'labor') { + $this->tasks[] = [ + 'task_name' => $item->description, + 'task_description' => $item->description, + 'estimated_duration' => $item->labor_hours ?? 1, + 'required_skills' => '', + 'safety_requirements' => '', + 'status' => 'pending', + ]; + } + } + } + + public function addTask() + { + $this->tasks[] = [ + 'task_name' => '', + 'task_description' => '', + 'estimated_duration' => 1, + 'required_skills' => '', + 'safety_requirements' => '', + 'status' => 'pending', + ]; + } + + public function removeTask($index) + { + unset($this->tasks[$index]); + $this->tasks = array_values($this->tasks); + } + + public function save() + { + $this->validate(); + + // Generate work order number + $branchCode = $this->estimate->jobCard->branch_code; + $lastWONumber = WorkOrder::where('work_order_number', 'like', $branchCode . '/WO%') + ->whereYear('created_at', now()->year) + ->count(); + $workOrderNumber = $branchCode . '/WO' . str_pad($lastWONumber + 1, 4, '0', STR_PAD_LEFT); + + $workOrder = WorkOrder::create([ + 'work_order_number' => $workOrderNumber, + 'job_card_id' => $this->estimate->job_card_id, + 'estimate_id' => $this->estimate->id, + 'service_coordinator_id' => auth()->id(), + 'assigned_technician_id' => $this->assigned_technician_id, + 'priority' => $this->estimate->jobCard->priority, + 'status' => 'scheduled', + 'work_description' => $this->work_description, + 'special_instructions' => $this->special_instructions, + 'safety_requirements' => $this->safety_requirements, + 'estimated_start_time' => $this->estimated_start_time, + 'estimated_completion_time' => $this->estimated_completion_time, + 'quality_check_required' => $this->quality_check_required, + 'customer_notification_required' => $this->customer_notification_required, + 'notes' => $this->notes, + ]); + + // Create tasks + foreach ($this->tasks as $task) { + WorkOrderTask::create([ + 'work_order_id' => $workOrder->id, + 'task_name' => $task['task_name'], + 'task_description' => $task['task_description'], + 'estimated_duration' => $task['estimated_duration'], + 'required_skills' => $task['required_skills'], + 'safety_requirements' => $task['safety_requirements'], + 'status' => 'pending', + ]); + } + + // Update job card status + $this->estimate->jobCard->update(['status' => 'work_order_created']); + + session()->flash('message', 'Work Order created successfully!'); + return redirect()->route('work-orders.show', $workOrder); + } + + public function getTechniciansProperty() + { + return User::whereIn('role', ['technician', 'service_coordinator']) + ->where('status', 'active') + ->orderBy('name') + ->get(); + } + + public function render() + { + return view('livewire.work-orders.create', [ + 'technicians' => $this->technicians + ]); + } +} diff --git a/app/Livewire/WorkOrders/Edit.php b/app/Livewire/WorkOrders/Edit.php new file mode 100644 index 0000000..f85dce9 --- /dev/null +++ b/app/Livewire/WorkOrders/Edit.php @@ -0,0 +1,13 @@ +resetPage(); + } + + public function render() + { + $workOrders = WorkOrder::with([ + 'jobCard.customer', + 'jobCard.vehicle', + 'serviceCoordinator', + 'assignedTechnician', + 'estimate' + ]) + ->when($this->search, function ($query) { + $query->where(function ($q) { + $q->where('work_order_number', 'like', '%' . $this->search . '%') + ->orWhereHas('jobCard', function ($jobQuery) { + $jobQuery->where('job_number', 'like', '%' . $this->search . '%') + ->orWhereHas('customer', function ($customerQuery) { + $customerQuery->where('first_name', 'like', '%' . $this->search . '%') + ->orWhere('last_name', 'like', '%' . $this->search . '%'); + }); + }); + }); + }) + ->when($this->statusFilter, function ($query) { + $query->where('status', $this->statusFilter); + }) + ->when($this->priorityFilter, function ($query) { + $query->where('priority', $this->priorityFilter); + }) + ->latest() + ->paginate(15); + + return view('livewire.work-orders.index', compact('workOrders')); + } +} diff --git a/app/Livewire/WorkOrders/Show.php b/app/Livewire/WorkOrders/Show.php new file mode 100644 index 0000000..351481d --- /dev/null +++ b/app/Livewire/WorkOrders/Show.php @@ -0,0 +1,13 @@ + */ + use HasFactory; + + protected $fillable = [ + 'customer_id', + 'vehicle_id', + 'assigned_technician_id', + 'scheduled_datetime', + 'estimated_duration_minutes', + 'appointment_type', + 'service_requested', + 'customer_notes', + 'internal_notes', + 'status', + 'checked_in_at', + 'completed_at', + 'service_order_id', + ]; + + protected $casts = [ + 'scheduled_datetime' => 'datetime', + 'checked_in_at' => 'datetime', + 'completed_at' => 'datetime', + 'estimated_duration_minutes' => 'integer', + ]; + + // Relationships + public function customer(): BelongsTo + { + return $this->belongsTo(Customer::class); + } + + public function vehicle(): BelongsTo + { + return $this->belongsTo(Vehicle::class); + } + + public function assignedTechnician(): BelongsTo + { + return $this->belongsTo(Technician::class, 'assigned_technician_id'); + } + + public function serviceOrder(): BelongsTo + { + return $this->belongsTo(ServiceOrder::class); + } + + // Scopes + public function scopeToday($query) + { + return $query->whereDate('scheduled_datetime', today()); + } + + public function scopeUpcoming($query) + { + return $query->where('scheduled_datetime', '>=', now()); + } + + public function scopeForTechnician($query, $technicianId) + { + return $query->where('assigned_technician_id', $technicianId); + } + + public function scopeByStatus($query, $status) + { + return $query->where('status', $status); + } + + public function scopeInDateRange($query, $startDate, $endDate) + { + return $query->whereBetween('scheduled_datetime', [$startDate, $endDate]); + } + + // Accessors & Mutators + public function getFormattedDateTimeAttribute(): string + { + return $this->scheduled_datetime->format('M j, Y g:i A'); + } + + public function getFormattedDateAttribute(): string + { + return $this->scheduled_datetime->format('M j, Y'); + } + + public function getFormattedTimeAttribute(): string + { + return $this->scheduled_datetime->format('g:i A'); + } + + public function getEndTimeAttribute(): Carbon + { + return $this->scheduled_datetime->addMinutes($this->estimated_duration_minutes); + } + + public function getFormattedEndTimeAttribute(): string + { + return $this->end_time->format('g:i A'); + } + + public function getStatusColorAttribute(): string + { + return match($this->status) { + 'scheduled' => 'blue', + 'confirmed' => 'green', + 'in_progress' => 'yellow', + 'completed' => 'emerald', + 'cancelled' => 'red', + 'no_show' => 'gray', + default => 'blue' + }; + } + + public function getTypeColorAttribute(): string + { + return match($this->appointment_type) { + 'maintenance' => 'blue', + 'repair' => 'red', + 'inspection' => 'yellow', + 'estimate' => 'purple', + 'pickup' => 'green', + 'delivery' => 'indigo', + default => 'blue' + }; + } + + // Helper Methods + public function canBeModified(): bool + { + return in_array($this->status, ['scheduled', 'confirmed']) && + $this->scheduled_datetime->isFuture(); + } + + public function canBeCheckedIn(): bool + { + return $this->status === 'confirmed' && + $this->scheduled_datetime->isPast() && + !$this->checked_in_at; + } + + public function canBeCompleted(): bool + { + return in_array($this->status, ['confirmed', 'in_progress']) && + $this->checked_in_at; + } + + public function isOverdue(): bool + { + return $this->status === 'scheduled' && + $this->scheduled_datetime->isPast(); + } + + public function getDurationInHours(): float + { + return round($this->estimated_duration_minutes / 60, 1); + } + + // Status Management + public function confirm(): bool + { + if ($this->status === 'scheduled') { + return $this->update(['status' => 'confirmed']); + } + return false; + } + + public function checkIn(): bool + { + if ($this->canBeCheckedIn()) { + return $this->update([ + 'status' => 'in_progress', + 'checked_in_at' => now() + ]); + } + return false; + } + + public function complete(): bool + { + if ($this->canBeCompleted()) { + return $this->update([ + 'status' => 'completed', + 'completed_at' => now() + ]); + } + return false; + } + + public function cancel(): bool + { + if ($this->canBeModified()) { + return $this->update(['status' => 'cancelled']); + } + return false; + } + + public function markNoShow(): bool + { + if ($this->isOverdue()) { + return $this->update(['status' => 'no_show']); + } + return false; + } +} diff --git a/app/Models/Branch.php b/app/Models/Branch.php new file mode 100644 index 0000000..a2da4d7 --- /dev/null +++ b/app/Models/Branch.php @@ -0,0 +1,44 @@ + 'boolean', + ]; + + /** + * Get users belonging to this branch + */ + public function users() + { + return $this->hasMany(User::class, 'branch_code', 'code'); + } + + /** + * Scope to get only active branches + */ + public function scopeActive($query) + { + return $query->where('is_active', true); + } +} diff --git a/app/Models/Customer.php b/app/Models/Customer.php new file mode 100644 index 0000000..51bc322 --- /dev/null +++ b/app/Models/Customer.php @@ -0,0 +1,57 @@ + */ + use HasFactory; + + protected $fillable = [ + 'first_name', + 'last_name', + 'email', + 'phone', + 'secondary_phone', + 'address', + 'city', + 'state', + 'zip_code', + 'notes', + 'status', + 'last_service_date', + ]; + + protected $casts = [ + 'last_service_date' => 'datetime', + ]; + + public function vehicles(): HasMany + { + return $this->hasMany(Vehicle::class); + } + + public function serviceOrders(): HasMany + { + return $this->hasMany(ServiceOrder::class); + } + + public function appointments(): HasMany + { + return $this->hasMany(Appointment::class); + } + + public function getFullNameAttribute(): string + { + return "{$this->first_name} {$this->last_name}"; + } + + public function getFormattedAddressAttribute(): string + { + return "{$this->address}, {$this->city}, {$this->state} {$this->zip_code}"; + } +} diff --git a/app/Models/Diagnosis.php b/app/Models/Diagnosis.php new file mode 100644 index 0000000..3912f68 --- /dev/null +++ b/app/Models/Diagnosis.php @@ -0,0 +1,68 @@ + 'datetime', + 'photos' => 'array', + 'diagnostic_codes' => 'array', + 'test_results' => 'array', + 'parts_required' => 'array', + 'labor_operations' => 'array', + 'special_tools_required' => 'array', + 'customer_authorization_required' => 'boolean', + ]; + + public function jobCard(): BelongsTo + { + return $this->belongsTo(JobCard::class); + } + + public function serviceCoordinator(): BelongsTo + { + return $this->belongsTo(User::class, 'service_coordinator_id'); + } + + public function estimate(): HasOne + { + return $this->hasOne(Estimate::class); + } + + public function timesheets(): HasMany + { + return $this->hasMany(Timesheet::class); + } +} diff --git a/app/Models/Estimate.php b/app/Models/Estimate.php new file mode 100644 index 0000000..e5650c3 --- /dev/null +++ b/app/Models/Estimate.php @@ -0,0 +1,115 @@ + 'decimal:2', + 'parts_cost' => 'decimal:2', + 'miscellaneous_cost' => 'decimal:2', + 'subtotal' => 'decimal:2', + 'tax_rate' => 'decimal:4', + 'tax_amount' => 'decimal:2', + 'discount_amount' => 'decimal:2', + 'total_amount' => 'decimal:2', + 'customer_approved_at' => 'datetime', + 'sent_to_customer_at' => 'datetime', + 'sms_sent_at' => 'datetime', + 'email_sent_at' => 'datetime', + ]; + + protected static function boot() + { + parent::boot(); + + static::creating(function ($estimate) { + if (empty($estimate->estimate_number)) { + $estimate->estimate_number = 'EST-' . date('Y') . '-' . str_pad( + static::whereYear('created_at', now()->year)->count() + 1, + 6, + '0', + STR_PAD_LEFT + ); + } + }); + } + + public function jobCard(): BelongsTo + { + return $this->belongsTo(JobCard::class); + } + + public function diagnosis(): BelongsTo + { + return $this->belongsTo(Diagnosis::class); + } + + public function preparedBy(): BelongsTo + { + return $this->belongsTo(User::class, 'prepared_by_id'); + } + + public function lineItems(): HasMany + { + return $this->hasMany(EstimateLineItem::class); + } + + public function originalEstimate(): BelongsTo + { + return $this->belongsTo(Estimate::class, 'original_estimate_id'); + } + + public function revisions(): HasMany + { + return $this->hasMany(Estimate::class, 'original_estimate_id'); + } + + public function calculateTotals(): void + { + $this->labor_cost = $this->lineItems()->where('type', 'labor')->sum('total_amount'); + $this->parts_cost = $this->lineItems()->where('type', 'parts')->sum('total_amount'); + $this->miscellaneous_cost = $this->lineItems()->where('type', 'miscellaneous')->sum('total_amount'); + + $this->subtotal = $this->labor_cost + $this->parts_cost + $this->miscellaneous_cost - $this->discount_amount; + $this->tax_amount = $this->subtotal * ($this->tax_rate / 100); + $this->total_amount = $this->subtotal + $this->tax_amount; + + $this->save(); + } +} diff --git a/app/Models/EstimateLineItem.php b/app/Models/EstimateLineItem.php new file mode 100644 index 0000000..926dd4b --- /dev/null +++ b/app/Models/EstimateLineItem.php @@ -0,0 +1,48 @@ + 'decimal:2', + 'unit_price' => 'decimal:2', + 'total_amount' => 'decimal:2', + 'labor_hours' => 'decimal:2', + 'labor_rate' => 'decimal:2', + 'markup_percentage' => 'decimal:2', + 'required' => 'boolean', + ]; + + public function estimate(): BelongsTo + { + return $this->belongsTo(Estimate::class); + } + + public function part(): BelongsTo + { + return $this->belongsTo(Part::class); + } +} diff --git a/app/Models/JobCard.php b/app/Models/JobCard.php new file mode 100644 index 0000000..a6dd903 --- /dev/null +++ b/app/Models/JobCard.php @@ -0,0 +1,118 @@ + 'datetime', + 'expected_completion_date' => 'datetime', + 'completion_datetime' => 'datetime', + 'personal_items_removed' => 'boolean', + 'photos_taken' => 'boolean', + ]; + + protected static function boot() + { + parent::boot(); + + static::creating(function ($jobCard) { + if (empty($jobCard->job_card_number)) { + $branchCode = $jobCard->branch_code ?? config('app.default_branch_code', 'ACC'); + $nextNumber = static::where('branch_code', $branchCode) + ->whereYear('created_at', now()->year) + ->count() + 1; + + $jobCard->job_card_number = $branchCode . '/' . str_pad($nextNumber, 5, '0', STR_PAD_LEFT); + } + }); + } + + public function customer(): BelongsTo + { + return $this->belongsTo(Customer::class); + } + + public function vehicle(): BelongsTo + { + return $this->belongsTo(Vehicle::class); + } + + public function serviceAdvisor(): BelongsTo + { + return $this->belongsTo(User::class, 'service_advisor_id'); + } + + public function incomingInspection(): HasOne + { + return $this->hasOne(VehicleInspection::class)->where('inspection_type', 'incoming'); + } + + public function outgoingInspection(): HasOne + { + return $this->hasOne(VehicleInspection::class)->where('inspection_type', 'outgoing'); + } + + public function diagnosis(): HasOne + { + return $this->hasOne(Diagnosis::class); + } + + public function workOrders(): HasMany + { + return $this->hasMany(WorkOrder::class); + } + + public function timesheets(): HasMany + { + return $this->hasMany(Timesheet::class); + } + + public function estimates(): HasMany + { + return $this->hasMany(Estimate::class); + } + + public function scopeByBranch($query, $branchCode) + { + return $query->where('branch_code', $branchCode); + } + + public function scopeByStatus($query, $status) + { + return $query->where('status', $status); + } +} diff --git a/app/Models/Part.php b/app/Models/Part.php new file mode 100644 index 0000000..ea1b35f --- /dev/null +++ b/app/Models/Part.php @@ -0,0 +1,142 @@ + */ + use HasFactory, LogsPartHistory; + + protected $fillable = [ + 'part_number', + 'name', + 'description', + 'manufacturer', + 'category', + 'cost_price', + 'sell_price', + 'quantity_on_hand', + 'minimum_stock_level', + 'maximum_stock_level', + 'location', + 'supplier_id', + 'supplier_part_number', + 'lead_time_days', + 'status', + 'barcode', + 'weight', + 'dimensions', + 'warranty_period', + 'image', + ]; + + protected $casts = [ + 'cost_price' => 'decimal:2', + 'sell_price' => 'decimal:2', + 'quantity_on_hand' => 'integer', + 'minimum_stock_level' => 'integer', + 'maximum_stock_level' => 'integer', + 'lead_time_days' => 'integer', + 'weight' => 'decimal:2', + ]; + + public function serviceOrders(): BelongsToMany + { + return $this->belongsToMany(ServiceOrder::class, 'service_items') + ->withPivot(['quantity', 'price', 'total']) + ->withTimestamps(); + } + + public function supplier(): BelongsTo + { + return $this->belongsTo(Supplier::class); + } + + public function stockMovements(): HasMany + { + return $this->hasMany(StockMovement::class); + } + + public function purchaseOrderItems(): HasMany + { + return $this->hasMany(PurchaseOrderItem::class); + } + + public function histories(): HasMany + { + return $this->hasMany(PartHistory::class); + } + + public function isLowStock(): bool + { + return $this->quantity_on_hand <= $this->minimum_stock_level; + } + + public function getMarkupPercentageAttribute() + { + if ($this->cost_price > 0) { + return round((($this->sell_price - $this->cost_price) / $this->cost_price) * 100, 2); + } + return 0; + } + + public function getStockStatusAttribute(): string + { + if ($this->quantity_on_hand <= 0) { + return 'out_of_stock'; + } elseif ($this->quantity_on_hand <= $this->minimum_stock_level) { + return 'low_stock'; + } elseif ($this->quantity_on_hand >= $this->maximum_stock_level) { + return 'overstock'; + } + return 'in_stock'; + } + + public function getStockStatusColorAttribute(): string + { + return match($this->stock_status) { + 'out_of_stock' => 'text-red-600 dark:text-red-400', + 'low_stock' => 'text-orange-600 dark:text-orange-400', + 'overstock' => 'text-purple-600 dark:text-purple-400', + 'in_stock' => 'text-green-600 dark:text-green-400', + default => 'text-gray-600 dark:text-gray-400', + }; + } + + public function getStockValueAttribute(): float + { + return $this->quantity_on_hand * $this->cost_price; + } + + public function needsReorder(): bool + { + return $this->quantity_on_hand <= $this->minimum_stock_level; + } + + public function scopeLowStock($query) + { + return $query->whereColumn('quantity_on_hand', '<=', 'minimum_stock_level'); + } + + public function scopeOutOfStock($query) + { + return $query->where('quantity_on_hand', '<=', 0); + } + + public function scopeByCategory($query, $category) + { + return $query->where('category', $category); + } + + public function scopeActive($query) + { + return $query->where('status', 'active'); + } +} diff --git a/app/Models/PartHistory.php b/app/Models/PartHistory.php new file mode 100644 index 0000000..0ec52ec --- /dev/null +++ b/app/Models/PartHistory.php @@ -0,0 +1,127 @@ + 'array', + 'new_values' => 'array', + 'quantity_change' => 'integer', + 'quantity_before' => 'integer', + 'quantity_after' => 'integer', + 'cost_before' => 'decimal:2', + 'cost_after' => 'decimal:2', + ]; + + // Event types + const EVENT_CREATED = 'created'; + const EVENT_UPDATED = 'updated'; + const EVENT_STOCK_IN = 'stock_in'; + const EVENT_STOCK_OUT = 'stock_out'; + const EVENT_ADJUSTMENT = 'adjustment'; + const EVENT_PRICE_CHANGE = 'price_change'; + const EVENT_SUPPLIER_CHANGE = 'supplier_change'; + const EVENT_DELETED = 'deleted'; + const EVENT_RESTORED = 'restored'; + + public function part(): BelongsTo + { + return $this->belongsTo(Part::class); + } + + public function createdBy(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by'); + } + + public function getEventColorAttribute(): string + { + return match($this->event_type) { + self::EVENT_CREATED => 'green', + self::EVENT_UPDATED => 'blue', + self::EVENT_STOCK_IN => 'green', + self::EVENT_STOCK_OUT => 'red', + self::EVENT_ADJUSTMENT => 'yellow', + self::EVENT_PRICE_CHANGE => 'purple', + self::EVENT_SUPPLIER_CHANGE => 'orange', + self::EVENT_DELETED => 'red', + self::EVENT_RESTORED => 'green', + default => 'gray', + }; + } + + public function getEventIconAttribute(): string + { + return match($this->event_type) { + self::EVENT_CREATED => 'plus-circle', + self::EVENT_UPDATED => 'pencil', + self::EVENT_STOCK_IN => 'arrow-down', + self::EVENT_STOCK_OUT => 'arrow-up', + self::EVENT_ADJUSTMENT => 'cog-6-tooth', + self::EVENT_PRICE_CHANGE => 'currency-dollar', + self::EVENT_SUPPLIER_CHANGE => 'building-office', + self::EVENT_DELETED => 'trash', + self::EVENT_RESTORED => 'arrow-path', + default => 'information-circle', + }; + } + + public function getFormattedQuantityChangeAttribute(): string + { + if ($this->quantity_change === null) { + return ''; + } + + $prefix = $this->quantity_change > 0 ? '+' : ''; + return $prefix . number_format($this->quantity_change); + } + + public static function logEvent( + int $partId, + string $eventType, + array $options = [] + ): self { + return self::create([ + 'part_id' => $partId, + 'event_type' => $eventType, + 'old_values' => $options['old_values'] ?? null, + 'new_values' => $options['new_values'] ?? null, + 'quantity_change' => $options['quantity_change'] ?? null, + 'quantity_before' => $options['quantity_before'] ?? null, + 'quantity_after' => $options['quantity_after'] ?? null, + 'cost_before' => $options['cost_before'] ?? null, + 'cost_after' => $options['cost_after'] ?? null, + 'reference_type' => $options['reference_type'] ?? null, + 'reference_id' => $options['reference_id'] ?? null, + 'notes' => $options['notes'] ?? null, + 'ip_address' => request()->ip(), + 'user_agent' => request()->userAgent(), + 'created_by' => auth()->id() ?? 1, + ]); + } +} diff --git a/app/Models/Permission.php b/app/Models/Permission.php new file mode 100644 index 0000000..63325ea --- /dev/null +++ b/app/Models/Permission.php @@ -0,0 +1,59 @@ + 'boolean', + ]; + + /** + * Get the roles that have this permission + */ + public function roles(): BelongsToMany + { + return $this->belongsToMany(Role::class, 'role_permissions') + ->withTimestamps(); + } + + /** + * Get users who have this permission directly + */ + public function users(): BelongsToMany + { + return $this->belongsToMany(User::class, 'user_permissions') + ->withPivot(['granted', 'branch_code', 'assigned_at', 'expires_at']) + ->withTimestamps(); + } + + /** + * Scope to filter by module + */ + public function scopeByModule($query, string $module) + { + return $query->where('module', $module); + } + + /** + * Scope to filter active permissions + */ + public function scopeActive($query) + { + return $query->where('is_active', true); + } +} diff --git a/app/Models/PurchaseOrder.php b/app/Models/PurchaseOrder.php new file mode 100644 index 0000000..b710d63 --- /dev/null +++ b/app/Models/PurchaseOrder.php @@ -0,0 +1,94 @@ + 'date', + 'expected_date' => 'date', + 'received_date' => 'date', + 'subtotal' => 'decimal:2', + 'tax' => 'decimal:2', + 'shipping' => 'decimal:2', + 'total' => 'decimal:2', + ]; + + const STATUS_DRAFT = 'draft'; + const STATUS_PENDING = 'pending'; + const STATUS_ORDERED = 'ordered'; + const STATUS_PARTIAL = 'partial'; + const STATUS_RECEIVED = 'received'; + const STATUS_CANCELLED = 'cancelled'; + + public function supplier(): BelongsTo + { + return $this->belongsTo(Supplier::class); + } + + public function items(): HasMany + { + return $this->hasMany(PurchaseOrderItem::class); + } + + public function stockMovements(): HasMany + { + return $this->hasMany(StockMovement::class); + } + + public function getStatusBadgeClassAttribute(): string + { + return match($this->status) { + self::STATUS_DRAFT => 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300', + self::STATUS_PENDING => 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300', + self::STATUS_ORDERED => 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300', + self::STATUS_PARTIAL => 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-300', + self::STATUS_RECEIVED => 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300', + self::STATUS_CANCELLED => 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300', + default => 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300', + }; + } + + public function isEditable(): bool + { + return in_array($this->status, [self::STATUS_DRAFT, self::STATUS_PENDING]); + } + + public function canBeReceived(): bool + { + return in_array($this->status, [self::STATUS_ORDERED, self::STATUS_PARTIAL]); + } + + public function getItemsCountAttribute(): int + { + return $this->items()->count(); + } + + public function getTotalAmountAttribute(): float + { + return $this->items()->sum(\DB::raw('quantity_ordered * unit_cost')); + } +} diff --git a/app/Models/PurchaseOrderItem.php b/app/Models/PurchaseOrderItem.php new file mode 100644 index 0000000..6aaa00d --- /dev/null +++ b/app/Models/PurchaseOrderItem.php @@ -0,0 +1,54 @@ + 'integer', + 'quantity_received' => 'integer', + 'unit_cost' => 'decimal:2', + 'total_cost' => 'decimal:2', + ]; + + public function purchaseOrder(): BelongsTo + { + return $this->belongsTo(PurchaseOrder::class); + } + + public function part(): BelongsTo + { + return $this->belongsTo(Part::class); + } + + public function getQuantityPendingAttribute(): int + { + return $this->quantity_ordered - $this->quantity_received; + } + + public function getIsCompleteAttribute(): bool + { + return $this->quantity_received >= $this->quantity_ordered; + } + + public function getIsPartialAttribute(): bool + { + return $this->quantity_received > 0 && $this->quantity_received < $this->quantity_ordered; + } +} diff --git a/app/Models/Report.php b/app/Models/Report.php new file mode 100644 index 0000000..ecb4878 --- /dev/null +++ b/app/Models/Report.php @@ -0,0 +1,284 @@ + 'array', + 'filters' => 'array', + 'generated_at' => 'datetime', + ]; + + public function generatedBy(): BelongsTo + { + return $this->belongsTo(User::class, 'generated_by'); + } + + public static function getRevenueData($dateFrom = null, $dateTo = null): array + { + $dateFrom = $dateFrom ? Carbon::parse($dateFrom) : Carbon::now()->subMonths(6); + $dateTo = $dateTo ? Carbon::parse($dateTo) : Carbon::now(); + + // Return sample data since we don't have actual service_orders table structure + return [ + 'total_revenue' => 125000.50, + 'monthly_revenue' => [ + '2024-12' => 18500, + '2025-01' => 15000, + '2025-02' => 18000, + '2025-03' => 22000, + '2025-04' => 19000, + '2025-05' => 25000, + '2025-06' => 26500, + '2025-07' => 28000, + ], + 'service_revenue' => [ + 'Oil Change' => 8500, + 'Brake Repair' => 15000, + 'Engine Repair' => 35000, + 'Transmission' => 12000, + 'Tire Service' => 9500, + 'Diagnostics' => 11200, + ], + 'avg_order_value' => 285.50, + 'period' => [ + 'from' => $dateFrom->format('Y-m-d'), + 'to' => $dateTo->format('Y-m-d'), + ] + ]; + } + + public static function getCustomerAnalytics($dateFrom = null, $dateTo = null): array + { + $dateFrom = $dateFrom ? Carbon::parse($dateFrom) : Carbon::now()->subMonths(6); + $dateTo = $dateTo ? Carbon::parse($dateTo) : Carbon::now(); + + return [ + 'total_customers' => 542, + 'new_customers' => 47, + 'retention_rate' => 78.5, + 'customer_segments' => [ + 'new' => 47, + 'regular' => 385, + 'vip' => 110, + ], + 'avg_satisfaction' => 4.3, + 'customer_lifetime_value' => 1250.75, + 'repeat_customers' => 385, + 'top_customers' => collect([ + (object)[ + 'id' => 1, + 'first_name' => 'John', + 'last_name' => 'Smith', + 'full_name' => 'John Smith', + 'service_orders_count' => 12, + 'total_spent' => 3500.00, + ], + (object)[ + 'id' => 2, + 'first_name' => 'Sarah', + 'last_name' => 'Johnson', + 'full_name' => 'Sarah Johnson', + 'service_orders_count' => 8, + 'total_spent' => 2850.00, + ], + (object)[ + 'id' => 3, + 'first_name' => 'Mike', + 'last_name' => 'Davis', + 'full_name' => 'Mike Davis', + 'service_orders_count' => 6, + 'total_spent' => 2200.00, + ], + (object)[ + 'id' => 4, + 'first_name' => 'Emily', + 'last_name' => 'Wilson', + 'full_name' => 'Emily Wilson', + 'service_orders_count' => 5, + 'total_spent' => 1875.00, + ], + (object)[ + 'id' => 5, + 'first_name' => 'David', + 'last_name' => 'Brown', + 'full_name' => 'David Brown', + 'service_orders_count' => 4, + 'total_spent' => 1650.00, + ], + (object)[ + 'id' => 6, + 'first_name' => 'Lisa', + 'last_name' => 'Garcia', + 'full_name' => 'Lisa Garcia', + 'service_orders_count' => 3, + 'total_spent' => 1200.00, + ], + ]), + 'period' => [ + 'from' => $dateFrom->format('Y-m-d'), + 'to' => $dateTo->format('Y-m-d'), + ] + ]; + } + + public static function getServiceTrends($dateFrom = null, $dateTo = null): array + { + $dateFrom = $dateFrom ? Carbon::parse($dateFrom) : Carbon::now()->subMonths(6); + $dateTo = $dateTo ? Carbon::parse($dateTo) : Carbon::now(); + + return [ + 'total_services' => 1248, + 'service_distribution' => [ + 'Maintenance' => 45, + 'Repair' => 35, + 'Inspection' => 15, + 'Emergency' => 5, + ], + 'popular_services' => [ + 'Oil Change' => 156, + 'Brake Service' => 89, + 'Tire Rotation' => 73, + 'Engine Diagnostics' => 65, + 'Transmission Service' => 41, + ], + 'monthly_trends' => [ + '2024-12' => 185, + '2025-01' => 185, + '2025-02' => 198, + '2025-03' => 225, + '2025-04' => 201, + '2025-05' => 216, + '2025-06' => 223, + '2025-07' => 240, + ], + 'service_trends' => collect([ + (object)[ + 'service_type' => 'oil_change', + 'count' => 156, + 'avg_amount' => 45.50, + 'total_revenue' => 7098.00, + ], + (object)[ + 'service_type' => 'brake_service', + 'count' => 89, + 'avg_amount' => 168.50, + 'total_revenue' => 14996.50, + ], + (object)[ + 'service_type' => 'tire_rotation', + 'count' => 73, + 'avg_amount' => 25.00, + 'total_revenue' => 1825.00, + ], + (object)[ + 'service_type' => 'engine_diagnostics', + 'count' => 65, + 'avg_amount' => 125.00, + 'total_revenue' => 8125.00, + ], + (object)[ + 'service_type' => 'transmission_service', + 'count' => 41, + 'avg_amount' => 292.75, + 'total_revenue' => 12002.75, + ], + (object)[ + 'service_type' => 'air_conditioning', + 'count' => 38, + 'avg_amount' => 85.00, + 'total_revenue' => 3230.00, + ], + ]), + 'avg_service_time' => 2.5, // hours + 'period' => [ + 'from' => $dateFrom->format('Y-m-d'), + 'to' => $dateTo->format('Y-m-d'), + ] + ]; + } + + public static function getPerformanceMetrics($dateFrom = null, $dateTo = null): array + { + $dateFrom = $dateFrom ? Carbon::parse($dateFrom) : Carbon::now()->subMonths(6); + $dateTo = $dateTo ? Carbon::parse($dateTo) : Carbon::now(); + + return [ + 'total_technicians' => 8, + 'average_efficiency' => 87.5, + 'average_quality' => 92.3, + 'customer_satisfaction' => 4.3, + 'technician_performance' => [ + 'John Smith' => [ + 'efficiency' => 94.2, + 'quality' => 96.8, + 'jobs_completed' => 145, + 'customer_rating' => 4.8, + ], + 'Mike Johnson' => [ + 'efficiency' => 89.1, + 'quality' => 91.5, + 'jobs_completed' => 132, + 'customer_rating' => 4.2, + ], + 'Sarah Davis' => [ + 'efficiency' => 92.4, + 'quality' => 94.2, + 'jobs_completed' => 128, + 'customer_rating' => 4.6, + ], + 'Tom Wilson' => [ + 'efficiency' => 85.7, + 'quality' => 88.9, + 'jobs_completed' => 118, + 'customer_rating' => 4.1, + ], + 'Lisa Brown' => [ + 'efficiency' => 88.3, + 'quality' => 93.1, + 'jobs_completed' => 125, + 'customer_rating' => 4.4, + ], + 'David Garcia' => [ + 'efficiency' => 91.7, + 'quality' => 89.6, + 'jobs_completed' => 138, + 'customer_rating' => 4.3, + ], + 'Amanda Chen' => [ + 'efficiency' => 86.9, + 'quality' => 95.2, + 'jobs_completed' => 142, + 'customer_rating' => 4.7, + ], + 'Robert Martinez' => [ + 'efficiency' => 93.1, + 'quality' => 90.4, + 'jobs_completed' => 156, + 'customer_rating' => 4.5, + ], + ], + 'period' => [ + 'from' => $dateFrom->format('Y-m-d'), + 'to' => $dateTo->format('Y-m-d'), + ] + ]; + } +} diff --git a/app/Models/Role.php b/app/Models/Role.php new file mode 100644 index 0000000..ceee9fc --- /dev/null +++ b/app/Models/Role.php @@ -0,0 +1,99 @@ + 'boolean', + ]; + + /** + * Get the permissions assigned to this role + */ + public function permissions(): BelongsToMany + { + return $this->belongsToMany(Permission::class, 'role_permissions') + ->withTimestamps(); + } + + /** + * Get users assigned to this role + */ + public function users(): BelongsToMany + { + return $this->belongsToMany(User::class, 'user_roles') + ->withPivot(['branch_code', 'is_active', 'assigned_at', 'expires_at']) + ->withTimestamps(); + } + + /** + * Check if role has a specific permission + */ + public function hasPermission(string $permission): bool + { + return $this->permissions()->where('name', $permission)->exists(); + } + + /** + * Assign permission to role + */ + public function givePermission(Permission|string $permission): self + { + if (is_string($permission)) { + $permission = Permission::where('name', $permission)->first(); + } + + if ($permission && !$this->hasPermission($permission->name)) { + $this->permissions()->attach($permission->id); + } + + return $this; + } + + /** + * Remove permission from role + */ + public function revokePermission(Permission|string $permission): self + { + if (is_string($permission)) { + $permission = Permission::where('name', $permission)->first(); + } + + if ($permission) { + $this->permissions()->detach($permission->id); + } + + return $this; + } + + /** + * Sync permissions for role + */ + public function syncPermissions(array $permissions): self + { + $permissionIds = collect($permissions)->map(function ($permission) { + if (is_string($permission)) { + return Permission::where('name', $permission)->first()?->id; + } + return $permission instanceof Permission ? $permission->id : $permission; + })->filter()->toArray(); + + $this->permissions()->sync($permissionIds); + + return $this; + } +} diff --git a/app/Models/ServiceItem.php b/app/Models/ServiceItem.php new file mode 100644 index 0000000..495c60f --- /dev/null +++ b/app/Models/ServiceItem.php @@ -0,0 +1,38 @@ + */ + use HasFactory; + + protected $fillable = [ + 'service_order_id', + 'service_name', + 'description', + 'category', + 'labor_rate', + 'estimated_hours', + 'actual_hours', + 'labor_cost', + 'status', + 'technician_notes', + ]; + + protected $casts = [ + 'labor_rate' => 'decimal:2', + 'estimated_hours' => 'decimal:2', + 'actual_hours' => 'decimal:2', + 'labor_cost' => 'decimal:2', + ]; + + public function serviceOrder(): BelongsTo + { + return $this->belongsTo(ServiceOrder::class); + } +} diff --git a/app/Models/ServiceOrder.php b/app/Models/ServiceOrder.php new file mode 100644 index 0000000..db5fce6 --- /dev/null +++ b/app/Models/ServiceOrder.php @@ -0,0 +1,114 @@ + */ + use HasFactory; + + protected $fillable = [ + 'order_number', + 'customer_id', + 'vehicle_id', + 'assigned_technician_id', + 'customer_complaint', + 'recommended_services', + 'priority', + 'status', + 'labor_cost', + 'parts_cost', + 'tax_amount', + 'discount_amount', + 'total_amount', + 'scheduled_date', + 'started_at', + 'completed_at', + 'estimated_hours', + 'actual_hours', + 'internal_notes', + 'customer_notes', + ]; + + protected $casts = [ + 'scheduled_date' => 'datetime', + 'started_at' => 'datetime', + 'completed_at' => 'datetime', + 'labor_cost' => 'decimal:2', + 'parts_cost' => 'decimal:2', + 'tax_amount' => 'decimal:2', + 'discount_amount' => 'decimal:2', + 'total_amount' => 'decimal:2', + ]; + + protected static function boot() + { + parent::boot(); + + static::creating(function ($serviceOrder) { + if (empty($serviceOrder->order_number)) { + $serviceOrder->order_number = 'SO-' . str_pad( + static::count() + 1, + 6, + '0', + STR_PAD_LEFT + ); + } + }); + } + + public function customer(): BelongsTo + { + return $this->belongsTo(Customer::class); + } + + public function vehicle(): BelongsTo + { + return $this->belongsTo(Vehicle::class); + } + + public function assignedTechnician(): BelongsTo + { + return $this->belongsTo(Technician::class, 'assigned_technician_id'); + } + + public function serviceItems(): HasMany + { + return $this->hasMany(ServiceItem::class); + } + + public function parts(): BelongsToMany + { + return $this->belongsToMany(Part::class, 'service_order_parts') + ->withPivot(['quantity_used', 'unit_cost', 'unit_price', 'total_cost', 'total_price', 'status', 'notes']) + ->withTimestamps(); + } + + public function inspections(): HasMany + { + return $this->hasMany(VehicleInspection::class); + } + + public function appointments(): HasMany + { + return $this->hasMany(Appointment::class); + } + + public function calculateTotals(): void + { + $this->labor_cost = $this->serviceItems->sum('labor_cost'); + $this->parts_cost = $this->parts->sum('pivot.total_price'); + + $subtotal = $this->labor_cost + $this->parts_cost - $this->discount_amount; + $this->tax_amount = $subtotal * 0.08; // 8% tax rate - should be configurable + $this->total_amount = $subtotal + $this->tax_amount; + + $this->save(); + } +} diff --git a/app/Models/ServiceOrderPart.php b/app/Models/ServiceOrderPart.php new file mode 100644 index 0000000..31e79ce --- /dev/null +++ b/app/Models/ServiceOrderPart.php @@ -0,0 +1,12 @@ + */ + use HasFactory; +} diff --git a/app/Models/StockMovement.php b/app/Models/StockMovement.php new file mode 100644 index 0000000..8221578 --- /dev/null +++ b/app/Models/StockMovement.php @@ -0,0 +1,129 @@ + 'integer', + 'unit_cost' => 'decimal:2', + 'total_cost' => 'decimal:2', + ]; + + protected static function boot() + { + parent::boot(); + + static::created(function ($stockMovement) { + $part = $stockMovement->part; + + // Get the quantity before this movement + $quantityBefore = $part->quantity_on_hand; + + // Calculate quantity after based on movement type + $quantityChange = $stockMovement->movement_type === 'in' ? + $stockMovement->quantity : -$stockMovement->quantity; + $quantityAfter = $quantityBefore + $quantityChange; + + // Log the stock movement in part history + PartHistory::logEvent( + $stockMovement->part_id, + $stockMovement->movement_type === 'in' ? PartHistory::EVENT_STOCK_IN : + ($stockMovement->movement_type === 'out' ? PartHistory::EVENT_STOCK_OUT : PartHistory::EVENT_ADJUSTMENT), + [ + 'quantity_change' => $quantityChange, + 'quantity_before' => $quantityBefore, + 'quantity_after' => $quantityAfter, + 'reference_type' => $stockMovement->reference_type, + 'reference_id' => $stockMovement->reference_id, + 'notes' => $stockMovement->notes ?? "Stock {$stockMovement->movement_type}", + ] + ); + }); + } + + // Handle both user_id and created_by for compatibility + public function setUserIdAttribute($value) + { + $this->attributes['created_by'] = $value; + } + + const TYPE_IN = 'in'; + const TYPE_OUT = 'out'; + const TYPE_ADJUSTMENT = 'adjustment'; + const TYPE_TRANSFER = 'transfer'; + const TYPE_RETURN = 'return'; + + const REFERENCE_PURCHASE = 'purchase'; + const REFERENCE_SALE = 'sale'; + const REFERENCE_ADJUSTMENT = 'adjustment'; + const REFERENCE_TRANSFER = 'transfer'; + const REFERENCE_RETURN = 'return'; + + public function part(): BelongsTo + { + return $this->belongsTo(Part::class); + } + + public function supplier(): BelongsTo + { + return $this->belongsTo(Supplier::class); + } + + public function purchaseOrder(): BelongsTo + { + return $this->belongsTo(PurchaseOrder::class); + } + + public function serviceOrder(): BelongsTo + { + return $this->belongsTo(ServiceOrder::class); + } + + public function createdBy(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by'); + } + + public function getMovementTypeColorAttribute(): string + { + return match($this->movement_type) { + self::TYPE_IN => 'text-green-600 dark:text-green-400', + self::TYPE_OUT => 'text-red-600 dark:text-red-400', + self::TYPE_ADJUSTMENT => 'text-blue-600 dark:text-blue-400', + self::TYPE_TRANSFER => 'text-purple-600 dark:text-purple-400', + self::TYPE_RETURN => 'text-orange-600 dark:text-orange-400', + default => 'text-gray-600 dark:text-gray-400', + }; + } + + public function getFormattedQuantityAttribute(): string + { + $prefix = $this->movement_type === self::TYPE_IN ? '+' : '-'; + return $prefix . number_format($this->quantity); + } +} diff --git a/app/Models/Supplier.php b/app/Models/Supplier.php new file mode 100644 index 0000000..f9f5449 --- /dev/null +++ b/app/Models/Supplier.php @@ -0,0 +1,62 @@ + 'boolean', + 'rating' => 'decimal:1', + ]; + + public function parts(): HasMany + { + return $this->hasMany(Part::class); + } + + public function purchaseOrders(): HasMany + { + return $this->hasMany(PurchaseOrder::class); + } + + public function stockMovements(): HasMany + { + return $this->hasMany(StockMovement::class); + } + + public function getFullNameAttribute(): string + { + return $this->company_name ?: $this->name; + } + + public function scopeActive($query) + { + return $query->where('is_active', true); + } +} diff --git a/app/Models/Technician.php b/app/Models/Technician.php new file mode 100644 index 0000000..71ee0c3 --- /dev/null +++ b/app/Models/Technician.php @@ -0,0 +1,144 @@ + */ + use HasFactory; + + protected $fillable = [ + 'first_name', + 'last_name', + 'email', + 'phone', + 'employee_id', + 'hourly_rate', + 'specializations', + 'skill_level', + 'certifications', + 'status', + 'shift_start', + 'shift_end', + ]; + + protected $casts = [ + 'hourly_rate' => 'decimal:2', + 'specializations' => 'array', + 'shift_start' => 'datetime:H:i', + 'shift_end' => 'datetime:H:i', + ]; + + protected $attributes = [ + 'specializations' => '[]', + 'status' => 'active', + 'skill_level' => 'apprentice', + ]; + + // Existing relationships + public function serviceOrders(): HasMany + { + return $this->hasMany(ServiceOrder::class, 'assigned_technician_id'); + } + + public function appointments(): HasMany + { + return $this->hasMany(Appointment::class, 'assigned_technician_id'); + } + + public function inspections(): HasMany + { + return $this->hasMany(VehicleInspection::class, 'technician_id'); + } + + // New relationships for technician management + public function skills(): HasMany + { + return $this->hasMany(TechnicianSkill::class); + } + + public function performances(): HasMany + { + return $this->hasMany(TechnicianPerformance::class); + } + + public function workloads(): HasMany + { + return $this->hasMany(TechnicianWorkload::class); + } + + public function primarySkills(): HasMany + { + return $this->skills()->where('is_primary_skill', true); + } + + public function currentWorkload() + { + return $this->workloads()->whereDate('workload_date', now()->toDateString())->first(); + } + + public function getFullNameAttribute(): string + { + return "{$this->first_name} {$this->last_name}"; + } + + public function isAvailable(): bool + { + return $this->status === 'active'; + } + + // New helper methods for technician management + public function getAverageRating(): float + { + return $this->performances() + ->where('metric_type', 'customer_rating') + ->avg('metric_value') ?? 0; + } + + public function getTotalJobsCompleted(): int + { + return $this->performances() + ->where('metric_type', 'jobs_completed') + ->sum('metric_value') ?? 0; + } + + public function getCurrentUtilizationRate(): float + { + $workload = $this->currentWorkload(); + return $workload ? $workload->utilization_rate : 0; + } + + public function getSkillLevel(string $skillName): int + { + $skill = $this->skills()->where('skill_name', $skillName)->first(); + return $skill ? $skill->proficiency_level : 0; + } + + public function hasSkill(string $skillName, int $minimumLevel = 1): bool + { + return $this->getSkillLevel($skillName) >= $minimumLevel; + } + + public function getWorkloadForPeriod($startDate, $endDate) + { + return $this->workloads() + ->whereBetween('workload_date', [$startDate, $endDate]) + ->orderBy('workload_date') + ->get(); + } + + public function getPerformanceMetric(string $metricType, $startDate = null, $endDate = null) + { + $query = $this->performances()->where('metric_type', $metricType); + + if ($startDate && $endDate) { + $query->whereBetween('performance_date', [$startDate, $endDate]); + } + + return $query->avg('metric_value') ?? 0; + } +} diff --git a/app/Models/TechnicianPerformance.php b/app/Models/TechnicianPerformance.php new file mode 100644 index 0000000..508a640 --- /dev/null +++ b/app/Models/TechnicianPerformance.php @@ -0,0 +1,71 @@ + 'date', + 'metric_value' => 'decimal:2', + 'additional_data' => 'array', + ]; + + public function technician(): BelongsTo + { + return $this->belongsTo(Technician::class); + } + + public static function getMetricTypes(): array + { + return [ + 'jobs_completed' => 'Jobs Completed', + 'hours_worked' => 'Hours Worked', + 'billable_hours' => 'Billable Hours', + 'revenue_generated' => 'Revenue Generated', + 'customer_rating' => 'Customer Rating', + 'efficiency_rate' => 'Efficiency Rate (%)', + 'rework_rate' => 'Rework Rate (%)', + 'safety_incidents' => 'Safety Incidents', + 'training_hours' => 'Training Hours', + 'overtime_hours' => 'Overtime Hours', + ]; + } + + public static function getPeriodTypes(): array + { + return [ + 'daily' => 'Daily', + 'weekly' => 'Weekly', + 'monthly' => 'Monthly', + 'quarterly' => 'Quarterly', + 'yearly' => 'Yearly', + ]; + } + + public function getFormattedValueAttribute(): string + { + return match($this->metric_type) { + 'revenue_generated' => '$' . number_format($this->metric_value, 2), + 'customer_rating' => number_format($this->metric_value, 1) . '/5', + 'efficiency_rate', 'rework_rate' => number_format($this->metric_value, 1) . '%', + 'hours_worked', 'billable_hours', 'training_hours', 'overtime_hours' => number_format($this->metric_value, 1) . ' hrs', + default => number_format($this->metric_value, 0), + }; + } +} diff --git a/app/Models/TechnicianSkill.php b/app/Models/TechnicianSkill.php new file mode 100644 index 0000000..34d21d6 --- /dev/null +++ b/app/Models/TechnicianSkill.php @@ -0,0 +1,131 @@ + 'date', + 'certification_expires' => 'date', + 'is_primary_skill' => 'boolean', + ]; + + public function technician(): BelongsTo + { + return $this->belongsTo(Technician::class); + } + + public function getProficiencyLabelAttribute(): string + { + return match($this->proficiency_level) { + 1 => 'Beginner', + 2 => 'Basic', + 3 => 'Intermediate', + 4 => 'Advanced', + 5 => 'Expert', + default => 'Unknown', + }; + } + + public function isCertificationExpiring(int $days = 30): bool + { + if (!$this->certification_expires) { + return false; + } + + return $this->certification_expires->diffInDays(now()) <= $days; + } + + public function isCertificationExpired(): bool + { + if (!$this->certification_expires) { + return false; + } + + return $this->certification_expires->isPast(); + } + + public static function getSkillCategories(): array + { + return [ + 'engine' => 'Engine & Powertrain', + 'electrical' => 'Electrical Systems', + 'transmission' => 'Transmission', + 'brakes' => 'Brakes & Suspension', + 'hvac' => 'HVAC & Climate', + 'diagnostics' => 'Diagnostics', + 'bodywork' => 'Body & Paint', + 'electronics' => 'Electronics & Infotainment', + 'hybrid' => 'Hybrid & Electric', + 'general' => 'General Maintenance', + ]; + } + + public static function getProficiencyLevels(): array + { + return [ + 1 => 'Beginner', + 2 => 'Basic', + 3 => 'Intermediate', + 4 => 'Advanced', + 5 => 'Expert', + ]; + } + + public static function getCommonSkills(): array + { + return [ + 'engine' => [ + 'Engine Diagnostics', + 'Engine Rebuild', + 'Timing Belt Replacement', + 'Oil Change', + 'Cooling System Repair', + ], + 'electrical' => [ + 'Wiring Repair', + 'Battery Replacement', + 'Alternator Repair', + 'Starter Motor Repair', + 'Lighting Systems', + ], + 'transmission' => [ + 'Manual Transmission Repair', + 'Automatic Transmission Service', + 'Clutch Replacement', + 'Transmission Flush', + ], + 'brakes' => [ + 'Brake Pad Replacement', + 'Brake Fluid Flush', + 'Rotor Replacement', + 'Suspension Repair', + 'Shock Absorber Replacement', + ], + 'diagnostics' => [ + 'OBD-II Diagnostics', + 'Computer Scan', + 'Emission Testing', + 'Performance Testing', + ], + ]; + } +} diff --git a/app/Models/TechnicianWorkload.php b/app/Models/TechnicianWorkload.php new file mode 100644 index 0000000..5a778a2 --- /dev/null +++ b/app/Models/TechnicianWorkload.php @@ -0,0 +1,83 @@ + 'date', + 'scheduled_hours' => 'decimal:2', + 'actual_hours' => 'decimal:2', + 'billable_hours' => 'decimal:2', + 'utilization_rate' => 'decimal:2', + 'efficiency_rate' => 'decimal:2', + ]; + + public function technician(): BelongsTo + { + return $this->belongsTo(Technician::class); + } + + public function calculateUtilizationRate(): float + { + if ($this->scheduled_hours <= 0) { + return 0; + } + + return ($this->actual_hours / $this->scheduled_hours) * 100; + } + + public function calculateEfficiencyRate(): float + { + if ($this->actual_hours <= 0) { + return 0; + } + + return ($this->billable_hours / $this->actual_hours) * 100; + } + + public function updateCalculatedFields(): void + { + $this->utilization_rate = $this->calculateUtilizationRate(); + $this->efficiency_rate = $this->calculateEfficiencyRate(); + } + + public function getJobCompletionRateAttribute(): float + { + if ($this->jobs_assigned <= 0) { + return 0; + } + + return ($this->jobs_completed / $this->jobs_assigned) * 100; + } + + public function isOverUtilized(float $threshold = 100): bool + { + return $this->utilization_rate > $threshold; + } + + public function isUnderUtilized(float $threshold = 80): bool + { + return $this->utilization_rate < $threshold; + } +} diff --git a/app/Models/Timesheet.php b/app/Models/Timesheet.php new file mode 100644 index 0000000..99631e3 --- /dev/null +++ b/app/Models/Timesheet.php @@ -0,0 +1,96 @@ + 'date', + 'start_time' => 'datetime', + 'end_time' => 'datetime', + 'hours_worked' => 'decimal:2', + 'break_hours' => 'decimal:2', + 'billable_hours' => 'decimal:2', + 'hourly_rate' => 'decimal:2', + 'total_amount' => 'decimal:2', + 'approved_at' => 'datetime', + 'is_overtime' => 'boolean', + 'overtime_multiplier' => 'decimal:2', + ]; + + public function jobCard(): BelongsTo + { + return $this->belongsTo(JobCard::class); + } + + public function workOrder(): BelongsTo + { + return $this->belongsTo(WorkOrder::class); + } + + public function diagnosis(): BelongsTo + { + return $this->belongsTo(Diagnosis::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function technician(): BelongsTo + { + return $this->belongsTo(User::class, 'user_id'); + } + + public function supervisorApprovedBy(): BelongsTo + { + return $this->belongsTo(User::class, 'supervisor_approved_by_id'); + } + + public function calculateHours(): void + { + if ($this->start_time && $this->end_time) { + $totalMinutes = $this->end_time->diffInMinutes($this->start_time) - ($this->break_hours ?? 0) * 60; + $totalHours = $totalMinutes / 60; + + // For diagnosis entries, all hours are typically billable + $this->hours_worked = $totalHours; + $this->billable_hours = $totalHours; + + $this->total_amount = $this->billable_hours * ($this->hourly_rate ?? 0); + } + } +} diff --git a/app/Models/User.php b/app/Models/User.php new file mode 100644 index 0000000..a908246 --- /dev/null +++ b/app/Models/User.php @@ -0,0 +1,135 @@ + */ + use HasFactory, Notifiable, HasRolesAndPermissions, LogsActivity; + + /** + * The attributes that are mass assignable. + * + * @var list + */ + protected $fillable = [ + 'name', + 'email', + 'password', + 'password_changed_at', + 'employee_id', + 'phone', + 'department', + 'position', + 'branch_code', + 'hire_date', + 'salary', + 'status', + 'emergency_contact_name', + 'emergency_contact_phone', + 'address', + 'date_of_birth', + 'national_id', + 'last_login_at', + ]; + + /** + * The attributes that should be hidden for serialization. + * + * @var list + */ + protected $hidden = [ + 'password', + 'remember_token', + ]; + + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts(): array + { + return [ + 'email_verified_at' => 'datetime', + 'password' => 'hashed', + 'password_changed_at' => 'datetime', + 'hire_date' => 'date', + 'date_of_birth' => 'date', + 'last_login_at' => 'datetime', + 'salary' => 'decimal:2', + ]; + } + + /** + * Get the user's initials + */ + public function initials(): string + { + return Str::of($this->name) + ->explode(' ') + ->take(2) + ->map(fn ($word) => Str::substr($word, 0, 1)) + ->implode(''); + } + + /** + * Get the branch that the user belongs to + */ + public function branch() + { + return $this->belongsTo(Branch::class, 'branch_code', 'code'); + } + + /** + * Check if user is a service supervisor + */ + public function isServiceSupervisor(): bool + { + return $this->hasRole('service_supervisor'); + } + + /** + * Check if user is a service coordinator + */ + public function isServiceCoordinator(): bool + { + return $this->hasRole('service_coordinator'); + } + + /** + * Check if user is parts manager + */ + public function isPartsManager(): bool + { + return $this->hasRole('parts_manager'); + } + + /** + * Check if user is service advisor + */ + public function isServiceAdvisor(): bool + { + return $this->hasRole('service_advisor'); + } + + /** + * Get the options for logging activities. + */ + public function getActivitylogOptions(): LogOptions + { + return LogOptions::defaults() + ->logOnly(['name', 'email', 'employee_id', 'phone', 'department', 'position', 'branch_code', 'status']) + ->logOnlyDirty() + ->dontSubmitEmptyLogs(); + } +} diff --git a/app/Models/Vehicle.php b/app/Models/Vehicle.php new file mode 100644 index 0000000..23377be --- /dev/null +++ b/app/Models/Vehicle.php @@ -0,0 +1,65 @@ + */ + use HasFactory; + + protected $fillable = [ + 'customer_id', + 'vin', + 'make', + 'model', + 'year', + 'color', + 'license_plate', + 'engine_type', + 'transmission', + 'mileage', + 'notes', + 'status', + 'last_service_date', + 'vehicle_image', + ]; + + protected $casts = [ + 'last_service_date' => 'datetime', + ]; + + public function customer(): BelongsTo + { + return $this->belongsTo(Customer::class); + } + + public function serviceOrders(): HasMany + { + return $this->hasMany(ServiceOrder::class); + } + + public function appointments(): HasMany + { + return $this->hasMany(Appointment::class); + } + + public function inspections(): HasMany + { + return $this->hasMany(VehicleInspection::class); + } + + public function getDisplayNameAttribute(): string + { + return "{$this->year} {$this->make} {$this->model}"; + } + + public function getVinDisplayAttribute(): string + { + return strtoupper(substr($this->vin, -8)); + } +} diff --git a/app/Models/VehicleInspection.php b/app/Models/VehicleInspection.php new file mode 100644 index 0000000..a36a9a8 --- /dev/null +++ b/app/Models/VehicleInspection.php @@ -0,0 +1,93 @@ + */ + use HasFactory; + + protected $fillable = [ + 'job_card_id', + 'service_order_id', + 'vehicle_id', + 'inspector_id', + 'inspection_type', // 'incoming', 'outgoing' + 'current_mileage', + 'fuel_level', + 'inspection_checklist', + 'photos', + 'videos', + 'overall_condition', + 'recommendations', + 'discrepancies_found', + 'damage_notes', + 'cleanliness_rating', + 'inspection_date', + 'signature_inspector', + 'signature_customer', + 'notes', + 'follow_up_required', + 'quality_rating', + ]; + + protected $casts = [ + 'inspection_checklist' => 'array', + 'photos' => 'array', + 'videos' => 'array', + 'recommendations' => 'array', + 'discrepancies_found' => 'array', + 'inspection_date' => 'datetime', + 'follow_up_required' => 'boolean', + ]; + + public function jobCard(): BelongsTo + { + return $this->belongsTo(JobCard::class); + } + + public function serviceOrder(): BelongsTo + { + return $this->belongsTo(ServiceOrder::class); + } + + public function vehicle(): BelongsTo + { + return $this->belongsTo(Vehicle::class); + } + + public function inspector(): BelongsTo + { + return $this->belongsTo(User::class, 'inspector_id'); + } + + public function scopeIncoming($query) + { + return $query->where('inspection_type', 'incoming'); + } + + public function scopeOutgoing($query) + { + return $query->where('inspection_type', 'outgoing'); + } + + public function compareWithOtherInspection(VehicleInspection $otherInspection): array + { + $differences = []; + + if ($this->overall_condition !== $otherInspection->overall_condition) { + $differences['overall_condition'] = [ + 'before' => $otherInspection->overall_condition, + 'after' => $this->overall_condition + ]; + } + + // Add more comparison logic as needed + + return $differences; + } +} diff --git a/app/Models/WorkOrder.php b/app/Models/WorkOrder.php new file mode 100644 index 0000000..d92709a --- /dev/null +++ b/app/Models/WorkOrder.php @@ -0,0 +1,107 @@ + 'datetime', + 'estimated_completion_time' => 'datetime', + 'actual_start_time' => 'datetime', + 'actual_completion_time' => 'datetime', + 'quality_check_date' => 'datetime', + 'quality_check_required' => 'boolean', + 'customer_notification_required' => 'boolean', + 'completion_percentage' => 'decimal:2', + ]; + + protected static function boot() + { + parent::boot(); + + static::creating(function ($workOrder) { + if (empty($workOrder->work_order_number)) { + $workOrder->work_order_number = 'WO-' . date('Y') . '-' . str_pad( + static::whereYear('created_at', now()->year)->count() + 1, + 6, + '0', + STR_PAD_LEFT + ); + } + }); + } + + public function jobCard(): BelongsTo + { + return $this->belongsTo(JobCard::class); + } + + public function estimate(): BelongsTo + { + return $this->belongsTo(Estimate::class); + } + + public function serviceCoordinator(): BelongsTo + { + return $this->belongsTo(User::class, 'service_coordinator_id'); + } + + public function assignedTechnician(): BelongsTo + { + return $this->belongsTo(Technician::class, 'assigned_technician_id'); + } + + public function qualityCheckedBy(): BelongsTo + { + return $this->belongsTo(User::class, 'quality_checked_by_id'); + } + + public function timesheets(): HasMany + { + return $this->hasMany(Timesheet::class); + } + + public function tasks(): HasMany + { + return $this->hasMany(WorkOrderTask::class); + } + + public function parts(): BelongsToMany + { + return $this->belongsToMany(Part::class, 'work_order_parts') + ->withPivot(['quantity_used', 'unit_cost', 'status', 'allocated_at', 'used_at']) + ->withTimestamps(); + } +} diff --git a/app/Models/WorkOrderTask.php b/app/Models/WorkOrderTask.php new file mode 100644 index 0000000..8e54c11 --- /dev/null +++ b/app/Models/WorkOrderTask.php @@ -0,0 +1,47 @@ + 'decimal:2', + 'actual_hours' => 'decimal:2', + 'completion_percentage' => 'decimal:2', + 'start_time' => 'datetime', + 'completion_time' => 'datetime', + 'tools_required' => 'array', + ]; + + public function workOrder(): BelongsTo + { + return $this->belongsTo(WorkOrder::class); + } + + public function assignedTechnician(): BelongsTo + { + return $this->belongsTo(Technician::class, 'assigned_technician_id'); + } +} diff --git a/app/Notifications/EstimateNotification.php b/app/Notifications/EstimateNotification.php new file mode 100644 index 0000000..867138e --- /dev/null +++ b/app/Notifications/EstimateNotification.php @@ -0,0 +1,55 @@ +estimate->jobCard; + $portalUrl = route('customer-portal.estimate', [ + 'jobCard' => $jobCard->id, + 'estimate' => $this->estimate->id + ]); + + return (new MailMessage) + ->subject("Repair Estimate Ready - Job #{$jobCard->job_number}") + ->greeting("Hello {$notifiable->name},") + ->line("Your vehicle repair estimate is ready for review.") + ->line("**Vehicle:** {$jobCard->vehicle->year} {$jobCard->vehicle->make} {$jobCard->vehicle->model}") + ->line("**Job Number:** {$jobCard->job_number}") + ->line("**Estimate Total:** $" . number_format($this->estimate->total_amount, 2)) + ->action('View Estimate', $portalUrl) + ->line('Please review and approve the estimate to proceed with repairs.') + ->line('If you have any questions, please contact us at your earliest convenience.') + ->salutation('Best regards,') + ->salutation('Your Service Team'); + } + + public function toArray(object $notifiable): array + { + return [ + 'estimate_id' => $this->estimate->id, + 'job_card_id' => $this->estimate->job_card_id, + 'estimate_number' => $this->estimate->estimate_number, + 'total_amount' => $this->estimate->total_amount, + ]; + } +} \ No newline at end of file diff --git a/app/Notifications/WorkflowStatusNotification.php b/app/Notifications/WorkflowStatusNotification.php new file mode 100644 index 0000000..020b45e --- /dev/null +++ b/app/Notifications/WorkflowStatusNotification.php @@ -0,0 +1,103 @@ +getEventMessages(); + + return (new MailMessage) + ->subject($messages['subject']) + ->greeting("Hello {$notifiable->name},") + ->line($messages['message']) + ->line("**Job Number:** {$this->jobCard->job_number}") + ->line("**Vehicle:** {$this->jobCard->vehicle->year} {$this->jobCard->vehicle->make} {$this->jobCard->vehicle->model}") + ->line("**Customer:** {$this->jobCard->customer->name}") + ->when($messages['action_url'], function ($mail) use ($messages) { + return $mail->action($messages['action_text'], $messages['action_url']); + }) + ->line('Thank you for your attention to this matter.'); + } + + public function toArray(object $notifiable): array + { + $messages = $this->getEventMessages(); + + return [ + 'job_card_id' => $this->jobCard->id, + 'job_number' => $this->jobCard->job_number, + 'event' => $this->event, + 'message' => $messages['message'], + 'customer_name' => $this->jobCard->customer->name, + 'vehicle_info' => "{$this->jobCard->vehicle->year} {$this->jobCard->vehicle->make} {$this->jobCard->vehicle->model}", + ]; + } + + private function getEventMessages(): array + { + return match($this->event) { + 'job_assigned' => [ + 'subject' => "Job Assignment - {$this->jobCard->job_number}", + 'message' => "A new job has been assigned to you for diagnosis and service coordination.", + 'action_text' => 'View Job Card', + 'action_url' => route('job-cards.show', $this->jobCard), + ], + 'estimate_approved' => [ + 'subject' => "Estimate Approved - {$this->jobCard->job_number}", + 'message' => "The customer has approved the repair estimate. You can now proceed with the work order creation.", + 'action_text' => 'View Estimate', + 'action_url' => route('estimates.index'), + ], + 'parts_procurement_needed' => [ + 'subject' => "Parts Procurement Required - {$this->jobCard->job_number}", + 'message' => "Parts procurement is required for this approved job. Please review the estimate and arrange for parts ordering.", + 'action_text' => 'View Parts Requirements', + 'action_url' => route('inventory.parts.index'), + ], + 'work_order_created' => [ + 'subject' => "Work Order Created - {$this->jobCard->job_number}", + 'message' => "A work order has been created and is ready for assignment to technicians.", + 'action_text' => 'View Work Order', + 'action_url' => route('work-orders.index'), + ], + 'quality_inspection_required' => [ + 'subject' => "Quality Inspection Required - {$this->jobCard->job_number}", + 'message' => "Work has been completed and the vehicle is ready for quality inspection.", + 'action_text' => 'Perform Inspection', + 'action_url' => route('inspections.index'), + ], + 'vehicle_ready' => [ + 'subject' => "Vehicle Ready for Pickup - {$this->jobCard->job_number}", + 'message' => "The vehicle has passed quality inspection and is ready for customer pickup.", + 'action_text' => 'View Job Details', + 'action_url' => route('job-cards.show', $this->jobCard), + ], + default => [ + 'subject' => "Job Update - {$this->jobCard->job_number}", + 'message' => "There has been an update to this job card.", + 'action_text' => 'View Job Card', + 'action_url' => route('job-cards.show', $this->jobCard), + ], + }; + } +} \ No newline at end of file diff --git a/app/Policies/JobCardPolicy.php b/app/Policies/JobCardPolicy.php new file mode 100644 index 0000000..bd346a4 --- /dev/null +++ b/app/Policies/JobCardPolicy.php @@ -0,0 +1,160 @@ +hasRole('super_admin')) { + return true; + } + + return $user->hasAnyPermission([ + 'job-cards.view', + 'job-cards.view-all' + ], $user->branch_code); + } + + /** + * Determine whether the user can view the job card. + */ + public function view(User $user, JobCard $jobCard): bool + { + // Super admin can view all without branch restrictions + if ($user->hasRole('super_admin')) { + return true; + } + + // Admin or users with view-all permission can see any job card + if ($user->hasPermission('job-cards.view-all', $user->branch_code)) { + return true; + } + + // Users can view job cards in their branch + if ($user->hasPermission('job-cards.view', $user->branch_code) && + $jobCard->branch_code === $user->branch_code) { + return true; + } + + // Service advisors can view their own job cards + if ($user->hasPermission('job-cards.view-own') && + $jobCard->service_advisor_id === $user->id) { + return true; + } + + return false; + } + + /** + * Determine whether the user can create job cards. + */ + public function create(User $user): bool + { + // Super admin can create without branch restrictions + if ($user->hasRole('super_admin')) { + return true; + } + + return $user->hasPermission('job-cards.create', $user->branch_code); + } + + /** + * Determine whether the user can update the job card. + */ + public function update(User $user, JobCard $jobCard): bool + { + // Super admin can update all without branch restrictions + if ($user->hasRole('super_admin')) { + return true; + } + + // Admin or users with update-all permission can update any job card + if ($user->hasPermission('job-cards.update-all', $user->branch_code)) { + return true; + } + + // Users can update job cards in their branch + if ($user->hasPermission('job-cards.update', $user->branch_code) && + $jobCard->branch_code === $user->branch_code) { + return true; + } + + // Service advisors can update their own job cards + if ($user->hasPermission('job-cards.update-own') && + $jobCard->service_advisor_id === $user->id) { + return true; + } + + return false; + } + + /** + * Determine whether the user can delete the job card. + */ + public function delete(User $user, JobCard $jobCard): bool + { + // Super admin can delete all without branch restrictions + if ($user->hasRole('super_admin')) { + return true; + } + + // Only admin or users with delete permission can delete + if ($user->hasPermission('job-cards.delete', $user->branch_code)) { + return $jobCard->branch_code === $user->branch_code; + } + + return false; + } + + /** + * Determine whether the user can restore the job card. + */ + public function restore(User $user, JobCard $jobCard): bool + { + // Super admin can restore all without branch restrictions + if ($user->hasRole('super_admin')) { + return true; + } + + return $user->hasPermission('job-cards.restore', $user->branch_code); + } + + /** + * Determine whether the user can permanently delete the job card. + */ + public function forceDelete(User $user, JobCard $jobCard): bool + { + // Super admin can force delete all without branch restrictions + if ($user->hasRole('super_admin')) { + return true; + } + + return $user->hasPermission('job-cards.force-delete', $user->branch_code); + } + + /** + * Determine whether the user can approve job cards. + */ + public function approve(User $user, JobCard $jobCard): bool + { + return $user->hasPermission('job-cards.approve', $user->branch_code) && + $jobCard->branch_code === $user->branch_code; + } + + /** + * Determine whether the user can assign technicians. + */ + public function assignTechnician(User $user, JobCard $jobCard): bool + { + return $user->hasPermission('job-cards.assign-technician', $user->branch_code) && + $jobCard->branch_code === $user->branch_code; + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php new file mode 100644 index 0000000..452e6b6 --- /dev/null +++ b/app/Providers/AppServiceProvider.php @@ -0,0 +1,24 @@ + + */ + protected $policies = [ + JobCard::class => JobCardPolicy::class, + ]; + + /** + * Register any authentication / authorization services. + */ + public function boot(): void + { + $this->registerPolicies(); + + // Define gates for common permission checks + Gate::define('access-admin-panel', function (User $user) { + return $user->hasAnyRole(['admin', 'manager'], $user->branch_code); + }); + + Gate::define('manage-users', function (User $user) { + return $user->hasPermission('users.create', $user->branch_code) || + $user->hasPermission('users.update', $user->branch_code) || + $user->hasPermission('users.delete', $user->branch_code); + }); + + Gate::define('view-reports', function (User $user) { + return $user->hasAnyPermission([ + 'reports.view', + 'reports.financial', + 'reports.operational' + ], $user->branch_code); + }); + + Gate::define('manage-inventory', function (User $user) { + return $user->hasAnyPermission([ + 'inventory.create', + 'inventory.update', + 'inventory.delete', + 'inventory.stock-movements', + 'inventory.purchase-orders' + ], $user->branch_code); + }); + + Gate::define('supervise-service', function (User $user) { + return $user->hasAnyRole([ + 'service_supervisor', + 'service_coordinator', + 'manager' + ], $user->branch_code); + }); + + // Super admin gate (bypass all restrictions) + Gate::before(function (User $user, string $ability) { + if ($user->hasRole('admin')) { + return true; + } + }); + } +} diff --git a/app/Providers/BladeServiceProvider.php b/app/Providers/BladeServiceProvider.php new file mode 100644 index 0000000..9902964 --- /dev/null +++ b/app/Providers/BladeServiceProvider.php @@ -0,0 +1,60 @@ +check() && auth()->user()->hasRole($role, $branchCode); + }); + + // Blade directive for permission checking + Blade::if('hasPermission', function ($permission, $branchCode = null) { + return auth()->check() && auth()->user()->hasPermission($permission, $branchCode); + }); + + // Blade directive for checking any permission + Blade::if('hasAnyPermission', function ($permissions, $branchCode = null) { + if (!auth()->check()) return false; + + if (is_string($permissions)) { + $permissions = explode('|', $permissions); + } + + return auth()->user()->hasAnyPermission($permissions, $branchCode); + }); + + // Blade directive for checking any role + Blade::if('hasAnyRole', function ($roles, $branchCode = null) { + if (!auth()->check()) return false; + + if (is_string($roles)) { + $roles = explode('|', $roles); + } + + return auth()->user()->hasAnyRole($roles, $branchCode); + }); + + // Blade directive for admin check + Blade::if('isAdmin', function () { + return auth()->check() && auth()->user()->hasRole('admin'); + }); + } +} diff --git a/app/Providers/VoltServiceProvider.php b/app/Providers/VoltServiceProvider.php new file mode 100644 index 0000000..e61d984 --- /dev/null +++ b/app/Providers/VoltServiceProvider.php @@ -0,0 +1,28 @@ +load(['customer', 'vehicle', 'assignedTechnician']); + + // Send email notification + $this->sendConfirmationEmail($appointment); + + // Log successful notification + Log::info('Appointment confirmation notification sent', [ + 'appointment_id' => $appointment->id, + 'customer_id' => $appointment->customer_id, + 'scheduled_datetime' => $appointment->scheduled_datetime, + ]); + + return true; + } catch (\Exception $e) { + Log::error('Failed to send appointment confirmation notification', [ + 'appointment_id' => $appointment->id, + 'error' => $e->getMessage(), + ]); + + return false; + } + } + + /** + * Send appointment reminder notification + */ + public function sendReminderNotification(Appointment $appointment): bool + { + try { + $appointment->load(['customer', 'vehicle', 'assignedTechnician']); + + // Send email reminder + $this->sendReminderEmail($appointment); + + Log::info('Appointment reminder notification sent', [ + 'appointment_id' => $appointment->id, + 'customer_id' => $appointment->customer_id, + 'scheduled_datetime' => $appointment->scheduled_datetime, + ]); + + return true; + } catch (\Exception $e) { + Log::error('Failed to send appointment reminder notification', [ + 'appointment_id' => $appointment->id, + 'error' => $e->getMessage(), + ]); + + return false; + } + } + + /** + * Send appointment cancellation notification + */ + public function sendCancellationNotification(Appointment $appointment, string $reason = ''): bool + { + try { + $appointment->load(['customer', 'vehicle', 'assignedTechnician']); + + // Send email notification + $this->sendCancellationEmail($appointment, $reason); + + Log::info('Appointment cancellation notification sent', [ + 'appointment_id' => $appointment->id, + 'customer_id' => $appointment->customer_id, + 'reason' => $reason, + ]); + + return true; + } catch (\Exception $e) { + Log::error('Failed to send appointment cancellation notification', [ + 'appointment_id' => $appointment->id, + 'error' => $e->getMessage(), + ]); + + return false; + } + } + + /** + * Send appointment reschedule notification + */ + public function sendRescheduleNotification(Appointment $appointment, Carbon $oldDateTime): bool + { + try { + $appointment->load(['customer', 'vehicle', 'assignedTechnician']); + + // Send email notification + $this->sendRescheduleEmail($appointment, $oldDateTime); + + Log::info('Appointment reschedule notification sent', [ + 'appointment_id' => $appointment->id, + 'customer_id' => $appointment->customer_id, + 'old_datetime' => $oldDateTime, + 'new_datetime' => $appointment->scheduled_datetime, + ]); + + return true; + } catch (\Exception $e) { + Log::error('Failed to send appointment reschedule notification', [ + 'appointment_id' => $appointment->id, + 'error' => $e->getMessage(), + ]); + + return false; + } + } + + /** + * Send completed service notification + */ + public function sendCompletionNotification(Appointment $appointment): bool + { + try { + $appointment->load(['customer', 'vehicle', 'assignedTechnician', 'serviceOrder']); + + // Send email notification + $this->sendCompletionEmail($appointment); + + Log::info('Service completion notification sent', [ + 'appointment_id' => $appointment->id, + 'customer_id' => $appointment->customer_id, + 'completed_at' => $appointment->completed_at, + ]); + + return true; + } catch (\Exception $e) { + Log::error('Failed to send service completion notification', [ + 'appointment_id' => $appointment->id, + 'error' => $e->getMessage(), + ]); + + return false; + } + } + + /** + * Send daily appointment reminders + */ + public function sendDailyReminders(): int + { + $tomorrow = now()->addDay(); + $remindersSent = 0; + + // Get appointments scheduled for tomorrow that are confirmed + $appointments = Appointment::with(['customer', 'vehicle', 'assignedTechnician']) + ->whereDate('scheduled_datetime', $tomorrow->toDateString()) + ->where('status', 'confirmed') + ->get(); + + foreach ($appointments as $appointment) { + if ($this->sendReminderNotification($appointment)) { + $remindersSent++; + } + } + + Log::info('Daily appointment reminders sent', [ + 'date' => $tomorrow->toDateString(), + 'reminders_sent' => $remindersSent, + 'total_appointments' => $appointments->count(), + ]); + + return $remindersSent; + } + + /** + * Send overdue appointment notifications + */ + public function sendOverdueNotifications(): int + { + $notificationsSent = 0; + + // Get appointments that are overdue (scheduled but past their time) + $overdueAppointments = Appointment::with(['customer', 'vehicle', 'assignedTechnician']) + ->where('status', 'scheduled') + ->where('scheduled_datetime', '<', now()->subMinutes(30)) // 30 minutes past + ->get(); + + foreach ($overdueAppointments as $appointment) { + // Mark as no-show and send notification + if ($appointment->markNoShow()) { + $this->sendNoShowNotification($appointment); + $notificationsSent++; + } + } + + Log::info('Overdue appointment notifications sent', [ + 'notifications_sent' => $notificationsSent, + 'overdue_appointments' => $overdueAppointments->count(), + ]); + + return $notificationsSent; + } + + /** + * Send no-show notification + */ + private function sendNoShowNotification(Appointment $appointment): bool + { + try { + // This would typically send an internal notification to staff + // and possibly a follow-up email to the customer + + Log::info('Customer marked as no-show', [ + 'appointment_id' => $appointment->id, + 'customer_id' => $appointment->customer_id, + 'scheduled_datetime' => $appointment->scheduled_datetime, + ]); + + return true; + } catch (\Exception $e) { + Log::error('Failed to send no-show notification', [ + 'appointment_id' => $appointment->id, + 'error' => $e->getMessage(), + ]); + + return false; + } + } + + /** + * Send confirmation email + */ + private function sendConfirmationEmail(Appointment $appointment): void + { + $data = [ + 'appointment' => $appointment, + 'customer' => $appointment->customer, + 'vehicle' => $appointment->vehicle, + 'technician' => $appointment->assignedTechnician, + 'shopName' => config('app.name', 'Auto Repair Shop'), + ]; + + // In a real application, you would create a Mailable class + // For now, we'll simulate the email sending + $this->simulateEmail($appointment->customer->email, 'Appointment Confirmation', $data); + } + + /** + * Send reminder email + */ + private function sendReminderEmail(Appointment $appointment): void + { + $data = [ + 'appointment' => $appointment, + 'customer' => $appointment->customer, + 'vehicle' => $appointment->vehicle, + 'technician' => $appointment->assignedTechnician, + 'shopName' => config('app.name', 'Auto Repair Shop'), + ]; + + $this->simulateEmail($appointment->customer->email, 'Appointment Reminder', $data); + } + + /** + * Send cancellation email + */ + private function sendCancellationEmail(Appointment $appointment, string $reason): void + { + $data = [ + 'appointment' => $appointment, + 'customer' => $appointment->customer, + 'vehicle' => $appointment->vehicle, + 'reason' => $reason, + 'shopName' => config('app.name', 'Auto Repair Shop'), + ]; + + $this->simulateEmail($appointment->customer->email, 'Appointment Cancelled', $data); + } + + /** + * Send reschedule email + */ + private function sendRescheduleEmail(Appointment $appointment, Carbon $oldDateTime): void + { + $data = [ + 'appointment' => $appointment, + 'customer' => $appointment->customer, + 'vehicle' => $appointment->vehicle, + 'technician' => $appointment->assignedTechnician, + 'oldDateTime' => $oldDateTime, + 'shopName' => config('app.name', 'Auto Repair Shop'), + ]; + + $this->simulateEmail($appointment->customer->email, 'Appointment Rescheduled', $data); + } + + /** + * Send completion email + */ + private function sendCompletionEmail(Appointment $appointment): void + { + $data = [ + 'appointment' => $appointment, + 'customer' => $appointment->customer, + 'vehicle' => $appointment->vehicle, + 'technician' => $appointment->assignedTechnician, + 'serviceOrder' => $appointment->serviceOrder, + 'shopName' => config('app.name', 'Auto Repair Shop'), + ]; + + $this->simulateEmail($appointment->customer->email, 'Service Completed', $data); + } + + /** + * Simulate email sending (replace with actual Mail::send in production) + */ + private function simulateEmail(string $email, string $subject, array $data): void + { + // In production, replace this with: + // Mail::send('emails.appointment.template', $data, function ($message) use ($email, $subject) { + // $message->to($email)->subject($subject); + // }); + + Log::info('Email notification simulated', [ + 'to' => $email, + 'subject' => $subject, + 'appointment_id' => $data['appointment']->id ?? null, + ]); + } + + /** + * Get notification preferences for a customer + */ + public function getCustomerNotificationPreferences($customerId): array + { + // In a real application, this would fetch from customer preferences + // For now, return default preferences + return [ + 'email_confirmations' => true, + 'email_reminders' => true, + 'email_cancellations' => true, + 'sms_reminders' => false, // Could be implemented later + 'reminder_hours_before' => 24, + ]; + } + + /** + * Check if notification should be sent based on customer preferences + */ + public function shouldSendNotification($customerId, string $notificationType): bool + { + $preferences = $this->getCustomerNotificationPreferences($customerId); + + return match($notificationType) { + 'confirmation' => $preferences['email_confirmations'] ?? true, + 'reminder' => $preferences['email_reminders'] ?? true, + 'cancellation' => $preferences['email_cancellations'] ?? true, + default => true, + }; + } +} diff --git a/app/Services/NhtsaVehicleService.php b/app/Services/NhtsaVehicleService.php new file mode 100644 index 0000000..e69de29 diff --git a/app/Services/NotificationService.php b/app/Services/NotificationService.php new file mode 100644 index 0000000..a3d7bb8 --- /dev/null +++ b/app/Services/NotificationService.php @@ -0,0 +1,160 @@ +jobCard; + $customer = $jobCard->customer; + + // Send email + if ($customer->email) { + Mail::to($customer->email)->send(new \App\Mail\EstimateNotification($estimate)); + $estimate->update(['email_sent_at' => now()]); + } + + // Send SMS + if ($customer->phone) { + $this->sendSMS( + $customer->phone, + "Your vehicle estimate #{$estimate->estimate_number} is ready. Please check your email or visit our customer portal to review and approve." + ); + $estimate->update(['sms_sent_at' => now()]); + } + + $estimate->update([ + 'sent_to_customer_at' => now(), + 'status' => 'sent' + ]); + + } catch (\Exception $e) { + Log::error('Failed to send estimate notification: ' . $e->getMessage()); + throw $e; + } + } + + /** + * Notify team when estimate is approved + */ + public function notifyEstimateApproved(Estimate $estimate): void + { + $jobCard = $estimate->jobCard; + + // Notify Service Supervisor + $serviceSupervisors = User::where('role', 'service_supervisor') + ->where('branch_code', $jobCard->branch_code) + ->get(); + + foreach ($serviceSupervisors as $supervisor) { + $this->sendInternalNotification( + $supervisor, + 'Estimate Approved', + "Estimate #{$estimate->estimate_number} for job card {$jobCard->job_card_number} has been approved by the customer." + ); + } + + // Notify Service Coordinator + if ($estimate->diagnosis && $estimate->diagnosis->serviceCoordinator) { + $this->sendInternalNotification( + $estimate->diagnosis->serviceCoordinator, + 'Estimate Approved', + "Your estimate #{$estimate->estimate_number} has been approved. You can now proceed with work order creation." + ); + } + + // Notify Parts Manager + $partsManagers = User::where('role', 'parts_manager') + ->where('branch_code', $jobCard->branch_code) + ->get(); + + foreach ($partsManagers as $manager) { + $this->sendInternalNotification( + $manager, + 'Parts Required', + "Estimate #{$estimate->estimate_number} approved. Please prepare parts for job card {$jobCard->job_card_number}." + ); + } + } + + /** + * Notify when vehicle is ready for pickup + */ + public function notifyVehicleReady(JobCard $jobCard): void + { + $customer = $jobCard->customer; + + try { + // Send email + if ($customer->email) { + Mail::to($customer->email)->send(new \App\Mail\VehicleReadyNotification($jobCard)); + } + + // Send SMS + if ($customer->phone) { + $this->sendSMS( + $customer->phone, + "Good news! Your {$jobCard->vehicle->display_name} is ready for pickup. Job Card: {$jobCard->job_card_number}" + ); + } + + } catch (\Exception $e) { + Log::error('Failed to send vehicle ready notification: ' . $e->getMessage()); + } + } + + /** + * Send quality check alert if inspections don't match + */ + public function sendQualityAlert(JobCard $jobCard, array $discrepancies): void + { + $serviceSupervisors = User::where('role', 'service_supervisor') + ->where('branch_code', $jobCard->branch_code) + ->get(); + + $message = "QUALITY ALERT: Incoming and outgoing inspections for job card {$jobCard->job_card_number} do not match. Immediate review required."; + + foreach ($serviceSupervisors as $supervisor) { + $this->sendInternalNotification($supervisor, 'Quality Alert', $message); + } + } + + /** + * Send internal notification to user + */ + private function sendInternalNotification(User $user, string $subject, string $message): void + { + // This can be enhanced with real-time notifications, push notifications, etc. + try { + if ($user->email) { + Mail::to($user->email)->send(new \App\Mail\InternalNotification($subject, $message)); + } + } catch (\Exception $e) { + Log::error('Failed to send internal notification: ' . $e->getMessage()); + } + } + + /** + * Send SMS (placeholder - integrate with your SMS provider) + */ + private function sendSMS(string $phoneNumber, string $message): void + { + // Integrate with SMS service provider (Twilio, AWS SNS, etc.) + Log::info("SMS to {$phoneNumber}: {$message}"); + + // Example with a generic SMS service + // SMSService::send($phoneNumber, $message); + } +} diff --git a/app/Services/VinDecoderService.php b/app/Services/VinDecoderService.php new file mode 100644 index 0000000..c941a23 --- /dev/null +++ b/app/Services/VinDecoderService.php @@ -0,0 +1,216 @@ + false, + 'error' => 'VIN must be exactly 17 characters long.', + 'data' => null + ]; + } + + // Make API request to NHTSA + $response = Http::timeout(10)->get(self::NHTSA_API_BASE . "/vehicles/DecodeVin/{$vin}", [ + 'format' => 'json' + ]); + + if (!$response->successful()) { + return [ + 'success' => false, + 'error' => 'Failed to connect to VIN decoder service.', + 'data' => null + ]; + } + + $data = $response->json(); + + if (empty($data['Results'])) { + return [ + 'success' => false, + 'error' => 'No vehicle data found for this VIN.', + 'data' => null + ]; + } + + // Parse the results + $vehicleData = $this->parseVinResults($data['Results']); + + return [ + 'success' => true, + 'error' => null, + 'data' => $vehicleData + ]; + + } catch (\Exception $e) { + Log::error('VIN Decoder Error: ' . $e->getMessage()); + + return [ + 'success' => false, + 'error' => 'An error occurred while decoding the VIN. Please try again.', + 'data' => null + ]; + } + } + + /** + * Parse NHTSA API results into usable vehicle data + */ + private function parseVinResults(array $results): array + { + $vehicleData = [ + 'make' => null, + 'model' => null, + 'year' => null, + 'engine_type' => null, + 'transmission' => null, + 'body_class' => null, + 'fuel_type' => null, + 'drive_type' => null, + 'error_codes' => [] + ]; + + // Initialize engine-related variables + $cylinders = null; + $displacement = null; + $engineConfig = null; + + foreach ($results as $result) { + $variable = $result['Variable'] ?? ''; + $value = $result['Value'] ?? ''; + $errorCode = $result['ErrorCode'] ?? ''; + + // Skip empty values + if (empty($value) || $value === 'Not Applicable' || $value === 'N/A') { + continue; + } + + // Collect error codes + if (!empty($errorCode) && $errorCode !== '0') { + $vehicleData['error_codes'][] = $result['ErrorText'] ?? 'Unknown error'; + continue; + } + + // Map NHTSA fields to our vehicle data + switch ($variable) { + case 'Make': + $vehicleData['make'] = $value; + break; + case 'Model': + $vehicleData['model'] = $value; + break; + case 'Model Year': + $vehicleData['year'] = (int) $value; + break; + case 'Engine Number of Cylinders': + $cylinders = $value; + break; + case 'Displacement (L)': + $displacement = $value; + break; + case 'Engine Configuration': + $engineConfig = $value; + break; + case 'Fuel Type - Primary': + $vehicleData['fuel_type'] = $value; + break; + case 'Transmission Style': + $vehicleData['transmission'] = $value; + break; + case 'Body Class': + $vehicleData['body_class'] = $value; + break; + case 'Drive Type': + $vehicleData['drive_type'] = $value; + break; + } + } + + // Build engine description from available data + $engineParts = array_filter([ + $displacement ? $displacement . 'L' : null, + $cylinders ? $cylinders . '-cyl' : null, + $engineConfig ?? null, + $vehicleData['fuel_type'] ?? null + ]); + + if (!empty($engineParts)) { + $vehicleData['engine_type'] = implode(' ', $engineParts); + } + + return $vehicleData; + } + + /** + * Get vehicle makes for dropdown + */ + public function getVehicleMakes(): array + { + try { + $response = Http::timeout(10)->get(self::NHTSA_API_BASE . '/vehicles/GetMakesForVehicleType/car', [ + 'format' => 'json' + ]); + + if ($response->successful()) { + $data = $response->json(); + $makes = collect($data['Results'] ?? []) + ->pluck('MakeName') + ->filter() + ->sort() + ->values() + ->all(); + + return $makes; + } + } catch (\Exception $e) { + Log::error('Error fetching vehicle makes: ' . $e->getMessage()); + } + + return []; + } + + /** + * Get vehicle models for a specific make and year + */ + public function getModelsForMakeAndYear(string $make, int $year): array + { + try { + $response = Http::timeout(10)->get(self::NHTSA_API_BASE . "/vehicles/GetModelsForMakeYear/make/{$make}/modelyear/{$year}", [ + 'format' => 'json' + ]); + + if ($response->successful()) { + $data = $response->json(); + $models = collect($data['Results'] ?? []) + ->pluck('Model_Name') + ->filter() + ->unique() + ->sort() + ->values() + ->all(); + + return $models; + } + } catch (\Exception $e) { + Log::error('Error fetching vehicle models: ' . $e->getMessage()); + } + + return []; + } +} diff --git a/app/Services/WorkflowService.php b/app/Services/WorkflowService.php new file mode 100644 index 0000000..c71fccc --- /dev/null +++ b/app/Services/WorkflowService.php @@ -0,0 +1,298 @@ + $data['customer_id'], + 'vehicle_id' => $data['vehicle_id'], + 'service_advisor_id' => $data['service_advisor_id'], + 'branch_code' => $data['branch_code'] ?? config('app.default_branch_code', 'ACC'), + 'arrival_datetime' => $data['arrival_datetime'], + 'mileage_in' => $data['mileage_in'], + 'fuel_level_in' => $data['fuel_level_in'], + 'customer_reported_issues' => $data['customer_reported_issues'], + 'vehicle_condition_notes' => $data['vehicle_condition_notes'], + 'keys_location' => $data['keys_location'], + 'personal_items_removed' => $data['personal_items_removed'] ?? false, + 'photos_taken' => $data['photos_taken'] ?? false, + 'expected_completion_date' => $data['expected_completion_date'] ?? null, + 'priority' => $data['priority'] ?? 'medium', + 'notes' => $data['notes'] ?? null, + ]); + + // Create incoming inspection checklist + if (isset($data['inspection_checklist'])) { + VehicleInspection::create([ + 'job_card_id' => $jobCard->id, + 'vehicle_id' => $jobCard->vehicle_id, + 'inspector_id' => $data['inspector_id'], + 'inspection_type' => 'incoming', + 'current_mileage' => $data['mileage_in'], + 'fuel_level' => $data['fuel_level_in'], + 'inspection_checklist' => $data['inspection_checklist'], + 'photos' => $data['inspection_photos'] ?? [], + 'overall_condition' => $data['overall_condition'], + 'inspection_date' => now(), + 'notes' => $data['inspection_notes'] ?? null, + ]); + } + + return $jobCard; + }); + } + + /** + * Assign job card to service coordinator and start diagnosis + */ + public function assignToServiceCoordinator(JobCard $jobCard, int $serviceCoordinatorId): Diagnosis + { + $diagnosis = Diagnosis::create([ + 'job_card_id' => $jobCard->id, + 'service_coordinator_id' => $serviceCoordinatorId, + 'customer_reported_issues' => $jobCard->customer_reported_issues, + 'diagnosis_status' => 'in_progress', + 'diagnosis_date' => now(), + ]); + + $jobCard->update(['status' => 'in_diagnosis']); + + return $diagnosis; + } + + /** + * Complete diagnosis and create estimate + */ + public function completeDiagnosis(Diagnosis $diagnosis, array $diagnosisData, array $estimateItems): Estimate + { + return DB::transaction(function () use ($diagnosis, $diagnosisData, $estimateItems) { + // Update diagnosis + $diagnosis->update([ + 'diagnostic_findings' => $diagnosisData['diagnostic_findings'], + 'root_cause_analysis' => $diagnosisData['root_cause_analysis'], + 'recommended_repairs' => $diagnosisData['recommended_repairs'], + 'additional_issues_found' => $diagnosisData['additional_issues_found'] ?? null, + 'priority_level' => $diagnosisData['priority_level'] ?? 'medium', + 'estimated_repair_time' => $diagnosisData['estimated_repair_time'], + 'parts_required' => $diagnosisData['parts_required'] ?? [], + 'labor_operations' => $diagnosisData['labor_operations'] ?? [], + 'special_tools_required' => $diagnosisData['special_tools_required'] ?? [], + 'safety_concerns' => $diagnosisData['safety_concerns'] ?? null, + 'customer_authorization_required' => $diagnosisData['customer_authorization_required'] ?? false, + 'diagnosis_status' => 'completed', + 'notes' => $diagnosisData['notes'] ?? null, + ]); + + // Create estimate + $estimate = Estimate::create([ + 'job_card_id' => $diagnosis->job_card_id, + 'diagnosis_id' => $diagnosis->id, + 'prepared_by_id' => $diagnosis->service_coordinator_id, + 'tax_rate' => config('app.default_tax_rate', 8.25), + 'validity_period_days' => 30, + 'terms_and_conditions' => config('app.default_estimate_terms'), + 'status' => 'draft', + ]); + + // Add estimate line items + foreach ($estimateItems as $item) { + $estimate->lineItems()->create($item); + } + + // Calculate totals + $estimate->calculateTotals(); + + // Update job card status + $diagnosis->jobCard->update(['status' => 'estimate_sent']); + + return $estimate; + }); + } + + /** + * Send estimate to customer + */ + public function sendEstimateToCustomer(Estimate $estimate): void + { + $this->notificationService->sendEstimateToCustomer($estimate); + } + + /** + * Approve estimate and create work order + */ + public function approveEstimate(Estimate $estimate, string $approvalMethod = 'portal'): WorkOrder + { + return DB::transaction(function () use ($estimate, $approvalMethod) { + // Update estimate + $estimate->update([ + 'customer_approval_status' => 'approved', + 'customer_approved_at' => now(), + 'customer_approval_method' => $approvalMethod, + 'status' => 'approved', + ]); + + // Update job card + $estimate->jobCard->update(['status' => 'approved']); + + // Send notifications + $this->notificationService->notifyEstimateApproved($estimate); + + // Create work order + $workOrder = WorkOrder::create([ + 'job_card_id' => $estimate->job_card_id, + 'estimate_id' => $estimate->id, + 'service_coordinator_id' => $estimate->diagnosis->service_coordinator_id, + 'priority' => $estimate->jobCard->priority, + 'work_description' => $estimate->diagnosis->recommended_repairs, + 'special_instructions' => $estimate->diagnosis->notes, + 'quality_check_required' => true, + 'status' => 'pending', + ]); + + return $workOrder; + }); + } + + /** + * Assign work order to technician and start work + */ + public function assignWorkOrder(WorkOrder $workOrder, int $technicianId): void + { + $workOrder->update([ + 'assigned_technician_id' => $technicianId, + 'status' => 'assigned', + 'actual_start_time' => now(), + ]); + + $workOrder->jobCard->update(['status' => 'in_progress']); + } + + /** + * Complete work order and perform quality check + */ + public function completeWorkOrder(WorkOrder $workOrder, int $qualityCheckerId): void + { + $workOrder->update([ + 'status' => 'quality_check', + 'actual_completion_time' => now(), + 'quality_checked_by_id' => $qualityCheckerId, + 'quality_check_date' => now(), + 'completion_percentage' => 100, + ]); + } + + /** + * Perform outgoing inspection and final quality check + */ + public function performOutgoingInspection(JobCard $jobCard, array $inspectionData, int $inspectorId): void + { + // Create outgoing inspection + $outgoingInspection = VehicleInspection::create([ + 'job_card_id' => $jobCard->id, + 'vehicle_id' => $jobCard->vehicle_id, + 'inspector_id' => $inspectorId, + 'inspection_type' => 'outgoing', + 'current_mileage' => $inspectionData['mileage_out'], + 'fuel_level' => $inspectionData['fuel_level_out'], + 'inspection_checklist' => $inspectionData['inspection_checklist'], + 'photos' => $inspectionData['photos'] ?? [], + 'overall_condition' => $inspectionData['overall_condition'], + 'inspection_date' => now(), + 'notes' => $inspectionData['notes'] ?? null, + ]); + + // Compare with incoming inspection + $incomingInspection = $jobCard->incomingInspection; + if ($incomingInspection) { + $discrepancies = $outgoingInspection->compareWithOtherInspection($incomingInspection); + + if (!empty($discrepancies)) { + // Alert service supervisor about discrepancies + $this->notificationService->sendQualityAlert($jobCard, $discrepancies); + + $outgoingInspection->update([ + 'discrepancies_found' => $discrepancies, + 'follow_up_required' => true, + ]); + } else { + // No discrepancies, mark as completed + $jobCard->update([ + 'status' => 'completed', + 'mileage_out' => $inspectionData['mileage_out'], + 'fuel_level_out' => $inspectionData['fuel_level_out'], + 'completion_datetime' => now(), + ]); + + // Notify customer + $this->notificationService->notifyVehicleReady($jobCard); + } + } + } + + /** + * Close job card after delivery + */ + public function closeJobCard(JobCard $jobCard, array $deliveryData): void + { + $jobCard->update([ + 'status' => 'delivered', + 'delivery_method' => $deliveryData['delivery_method'], + 'customer_satisfaction_rating' => $deliveryData['satisfaction_rating'] ?? null, + 'completion_datetime' => now(), + ]); + } + + /** + * Get workflow status for a job card + */ + public function getWorkflowStatus(JobCard $jobCard): array + { + return [ + 'job_card' => $jobCard, + 'incoming_inspection' => $jobCard->incomingInspection, + 'diagnosis' => $jobCard->diagnosis, + 'estimate' => $jobCard->estimates()->latest()->first(), + 'work_orders' => $jobCard->workOrders, + 'outgoing_inspection' => $jobCard->outgoingInspection, + 'completion_percentage' => $this->calculateCompletionPercentage($jobCard), + ]; + } + + /** + * Calculate completion percentage for a job card + */ + private function calculateCompletionPercentage(JobCard $jobCard): int + { + $steps = [ + 'received' => 10, + 'in_diagnosis' => 25, + 'estimate_sent' => 40, + 'approved' => 50, + 'in_progress' => 75, + 'quality_check' => 90, + 'completed' => 95, + 'delivered' => 100, + ]; + + return $steps[$jobCard->status] ?? 0; + } +} diff --git a/app/Settings/GeneralSettings.php b/app/Settings/GeneralSettings.php new file mode 100644 index 0000000..e29434e --- /dev/null +++ b/app/Settings/GeneralSettings.php @@ -0,0 +1,42 @@ +belongsToMany(Role::class, 'user_roles') + ->withPivot(['branch_code', 'is_active', 'assigned_at', 'expires_at']) + ->withTimestamps(); + } + + /** + * Get user's direct permissions + */ + public function permissions(): BelongsToMany + { + return $this->belongsToMany(Permission::class, 'user_permissions') + ->withPivot(['granted', 'branch_code', 'assigned_at', 'expires_at']) + ->withTimestamps(); + } + + /** + * Check if user has a specific role + */ + public function hasRole(string|array $roles, ?string $branchCode = null): bool + { + if (is_array($roles)) { + return collect($roles)->some(fn($role) => $this->hasRole($role, $branchCode)); + } + + $query = $this->roles()->where('roles.name', $roles); + + if ($branchCode) { + $query->where('user_roles.branch_code', $branchCode); + } + + return $query->where('user_roles.is_active', true) + ->where(function ($q) { + $q->whereNull('user_roles.expires_at') + ->orWhere('user_roles.expires_at', '>', now()); + }) + ->exists(); + } + + /** + * Check if user has any of the given roles + */ + public function hasAnyRole(array $roles, ?string $branchCode = null): bool + { + return collect($roles)->some(fn($role) => $this->hasRole($role, $branchCode)); + } + + /** + * Check if user has all of the given roles + */ + public function hasAllRoles(array $roles, ?string $branchCode = null): bool + { + return collect($roles)->every(fn($role) => $this->hasRole($role, $branchCode)); + } + + /** + * Check if user has a specific permission + */ + public function hasPermission(string $permission, ?string $branchCode = null): bool + { + // Check direct permissions first + $directPermission = $this->permissions() + ->where('permissions.name', $permission) + ->when($branchCode, fn($q) => $q->where('user_permissions.branch_code', $branchCode)) + ->where('user_permissions.granted', true) + ->where(function ($q) { + $q->whereNull('user_permissions.expires_at') + ->orWhere('user_permissions.expires_at', '>', now()); + }) + ->exists(); + + if ($directPermission) { + return true; + } + + // Check permissions through roles + $rolePermissions = $this->roles() + ->when($branchCode, fn($q) => $q->where('user_roles.branch_code', $branchCode)) + ->where('user_roles.is_active', true) + ->where(function ($q) { + $q->whereNull('user_roles.expires_at') + ->orWhere('user_roles.expires_at', '>', now()); + }) + ->whereHas('permissions', function ($q) use ($permission) { + $q->where('permissions.name', $permission)->where('permissions.is_active', true); + }) + ->exists(); + + return $rolePermissions; + } + + /** + * Check if user has any of the given permissions + */ + public function hasAnyPermission(array $permissions, ?string $branchCode = null): bool + { + return collect($permissions)->some(fn($permission) => $this->hasPermission($permission, $branchCode)); + } + + /** + * Check if user has all of the given permissions + */ + public function hasAllPermissions(array $permissions, ?string $branchCode = null): bool + { + return collect($permissions)->every(fn($permission) => $this->hasPermission($permission, $branchCode)); + } + + /** + * Assign a role to user + */ + public function assignRole(string|Role $role, ?string $branchCode = null, ?\DateTime $expiresAt = null): self + { + if (is_string($role)) { + $role = Role::where('name', $role)->first(); + } + + if ($role && !$this->hasRole($role->name, $branchCode)) { + $this->roles()->attach($role->id, [ + 'branch_code' => $branchCode, + 'is_active' => true, + 'assigned_at' => now(), + 'expires_at' => $expiresAt, + ]); + } + + return $this; + } + + /** + * Remove a role from user + */ + public function removeRole(string|Role $role, ?string $branchCode = null): self + { + if (is_string($role)) { + $role = Role::where('name', $role)->first(); + } + + if ($role) { + $query = $this->roles()->where('role_id', $role->id); + + if ($branchCode) { + $query->where('user_roles.branch_code', $branchCode); + } + + $query->detach(); + } + + return $this; + } + + /** + * Give permission directly to user + */ + public function givePermission(string|Permission $permission, ?string $branchCode = null, ?\DateTime $expiresAt = null): self + { + if (is_string($permission)) { + $permission = Permission::where('name', $permission)->first(); + } + + if ($permission) { + $this->permissions()->syncWithoutDetaching([ + $permission->id => [ + 'granted' => true, + 'branch_code' => $branchCode, + 'assigned_at' => now(), + 'expires_at' => $expiresAt, + ] + ]); + } + + return $this; + } + + /** + * Revoke permission from user + */ + public function revokePermission(string|Permission $permission, ?string $branchCode = null): self + { + if (is_string($permission)) { + $permission = Permission::where('name', $permission)->first(); + } + + if ($permission) { + $query = $this->permissions()->where('permission_id', $permission->id); + + if ($branchCode) { + $query->where('user_permissions.branch_code', $branchCode); + } + + $query->detach(); + } + + return $this; + } + + /** + * Get all user permissions (from roles and direct) + */ + public function getAllPermissions(?string $branchCode = null): Collection + { + // Get direct permissions + $directPermissions = $this->permissions() + ->when($branchCode, fn($q) => $q->where('user_permissions.branch_code', $branchCode)) + ->where('user_permissions.granted', true) + ->where(function ($q) { + $q->whereNull('user_permissions.expires_at') + ->orWhere('user_permissions.expires_at', '>', now()); + }) + ->get(); + + // Get permissions through roles + $rolePermissions = Permission::whereHas('roles.users', function ($q) use ($branchCode) { + $q->where('user_id', $this->id) + ->when($branchCode, fn($query) => $query->where('user_roles.branch_code', $branchCode)) + ->where('user_roles.is_active', true) + ->where(function ($query) { + $query->whereNull('user_roles.expires_at') + ->orWhere('user_roles.expires_at', '>', now()); + }); + })->where('is_active', true)->get(); + + return $directPermissions->merge($rolePermissions)->unique('id'); + } + + /** + * Check if user can access a specific module + */ + public function canAccessModule(string $module, ?string $branchCode = null): bool + { + return $this->getAllPermissions($branchCode) + ->where('module', $module) + ->isNotEmpty(); + } +} diff --git a/app/Traits/LogsPartHistory.php b/app/Traits/LogsPartHistory.php new file mode 100644 index 0000000..0f9aaac --- /dev/null +++ b/app/Traits/LogsPartHistory.php @@ -0,0 +1,110 @@ +id, + PartHistory::EVENT_CREATED, + [ + 'new_values' => $part->toArray(), + 'notes' => 'Part created', + ] + ); + }); + + static::updated(function ($part) { + $changes = $part->getChanges(); + $original = $part->getOriginal(); + + if (empty($changes)) { + return; + } + + // Track quantity changes separately + if (isset($changes['quantity_on_hand'])) { + $oldQuantity = $original['quantity_on_hand'] ?? 0; + $newQuantity = $changes['quantity_on_hand']; + $quantityChange = $newQuantity - $oldQuantity; + + PartHistory::logEvent( + $part->id, + $quantityChange > 0 ? PartHistory::EVENT_STOCK_IN : PartHistory::EVENT_STOCK_OUT, + [ + 'quantity_change' => $quantityChange, + 'quantity_before' => $oldQuantity, + 'quantity_after' => $newQuantity, + 'notes' => 'Stock quantity adjusted', + 'reference_type' => 'manual_adjustment', + ] + ); + } + + // Track price changes + if (isset($changes['cost_price']) || isset($changes['sell_price'])) { + PartHistory::logEvent( + $part->id, + PartHistory::EVENT_PRICE_CHANGE, + [ + 'old_values' => array_intersect_key($original, $changes), + 'new_values' => $changes, + 'cost_before' => $original['cost_price'] ?? null, + 'cost_after' => $changes['cost_price'] ?? $part->cost_price, + 'notes' => 'Price updated', + ] + ); + } + + // Track supplier changes + if (isset($changes['supplier_id'])) { + PartHistory::logEvent( + $part->id, + PartHistory::EVENT_SUPPLIER_CHANGE, + [ + 'old_values' => ['supplier_id' => $original['supplier_id']], + 'new_values' => ['supplier_id' => $changes['supplier_id']], + 'notes' => 'Supplier changed', + ] + ); + } + + // Track other general updates + $generalChanges = array_diff_key($changes, [ + 'quantity_on_hand' => null, + 'cost_price' => null, + 'sell_price' => null, + 'supplier_id' => null, + 'updated_at' => null, + ]); + + if (!empty($generalChanges)) { + PartHistory::logEvent( + $part->id, + PartHistory::EVENT_UPDATED, + [ + 'old_values' => array_intersect_key($original, $generalChanges), + 'new_values' => $generalChanges, + 'notes' => 'Part information updated', + ] + ); + } + }); + + static::deleted(function ($part) { + PartHistory::logEvent( + $part->id, + PartHistory::EVENT_DELETED, + [ + 'old_values' => $part->toArray(), + 'notes' => 'Part deleted', + ] + ); + }); + } +} diff --git a/app/View/Components/Flux/Icon/CalendarPlus.php b/app/View/Components/Flux/Icon/CalendarPlus.php new file mode 100644 index 0000000..9d7353d --- /dev/null +++ b/app/View/Components/Flux/Icon/CalendarPlus.php @@ -0,0 +1,26 @@ +permission = $permission; + $this->role = $role; + $this->branchCode = $branchCode ?? auth()->user()?->branch_code; + } + + /** + * Determine if the component should be rendered. + */ + public function shouldRender(): bool + { + if (!auth()->check()) { + return false; + } + + $user = auth()->user(); + + // Check permission if provided + if ($this->permission) { + if (is_array($this->permission)) { + return $user->hasAnyPermission($this->permission, $this->branchCode); + } + return $user->hasPermission($this->permission, $this->branchCode); + } + + // Check role if provided + if ($this->role) { + if (is_array($this->role)) { + return $user->hasAnyRole($this->role, $this->branchCode); + } + return $user->hasRole($this->role, $this->branchCode); + } + + return true; + } + + /** + * Get the view / contents that represent the component. + */ + public function render() + { + return view('components.permission-check'); + } +} diff --git a/artisan b/artisan new file mode 100755 index 0000000..c35e31d --- /dev/null +++ b/artisan @@ -0,0 +1,18 @@ +#!/usr/bin/env php +handleCommand(new ArgvInput); + +exit($status); diff --git a/bootstrap/app.php b/bootstrap/app.php new file mode 100644 index 0000000..5481729 --- /dev/null +++ b/bootstrap/app.php @@ -0,0 +1,21 @@ +withRouting( + web: __DIR__.'/../routes/web.php', + commands: __DIR__.'/../routes/console.php', + health: '/up', + ) + ->withMiddleware(function (Middleware $middleware) { + $middleware->alias([ + 'role' => \App\Http\Middleware\RoleMiddleware::class, + 'permission' => \App\Http\Middleware\PermissionMiddleware::class, + ]); + }) + ->withExceptions(function (Exceptions $exceptions) { + // + })->create(); diff --git a/bootstrap/cache/.gitignore b/bootstrap/cache/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/bootstrap/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/bootstrap/providers.php b/bootstrap/providers.php new file mode 100644 index 0000000..aafd151 --- /dev/null +++ b/bootstrap/providers.php @@ -0,0 +1,8 @@ +=5.0.0" + }, + "require-dev": { + "doctrine/dbal": "^4.0.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2024-02-09T16:56:22+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" + }, + "time": "2024-07-08T12:26:09+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=13" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^13", + "phpstan/phpstan": "1.4.10 || 2.1.11", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.5" + }, + "time": "2025-04-07T20:06:18+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.0.10", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^11.0", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^8.5 || ^9.5", + "vimeo/psalm": "^4.25 || ^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.0.10" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2024-02-18T20:23:39+00:00" + }, + { + "name": "doctrine/lexer", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.21" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/3.0.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2024-02-05T11:56:58+00:00" + }, + { + "name": "dragonmantank/cron-expression", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/dragonmantank/cron-expression.git", + "reference": "8c784d071debd117328803d86b2097615b457500" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/8c784d071debd117328803d86b2097615b457500", + "reference": "8c784d071debd117328803d86b2097615b457500", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "webmozart/assert": "^1.0" + }, + "replace": { + "mtdowling/cron-expression": "^1.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.0", + "phpunit/phpunit": "^7.0|^8.0|^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Cron\\": "src/Cron/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Tankersley", + "email": "chris@ctankersley.com", + "homepage": "https://github.com/dragonmantank" + } + ], + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "keywords": [ + "cron", + "schedule" + ], + "support": { + "issues": "https://github.com/dragonmantank/cron-expression/issues", + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.4.0" + }, + "funding": [ + { + "url": "https://github.com/dragonmantank", + "type": "github" + } + ], + "time": "2024-10-09T13:47:03+00:00" + }, + { + "name": "egulias/email-validator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2.0 || ^3.0", + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.26" + }, + "require-dev": { + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "time": "2025-03-06T22:45:56+00:00" + }, + { + "name": "fruitcake/php-cors", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/fruitcake/php-cors.git", + "reference": "3d158f36e7875e2f040f37bc0573956240a5a38b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/3d158f36e7875e2f040f37bc0573956240a5a38b", + "reference": "3d158f36e7875e2f040f37bc0573956240a5a38b", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0", + "symfony/http-foundation": "^4.4|^5.4|^6|^7" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "Fruitcake\\Cors\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fruitcake", + "homepage": "https://fruitcake.nl" + }, + { + "name": "Barryvdh", + "email": "barryvdh@gmail.com" + } + ], + "description": "Cross-origin resource sharing library for the Symfony HttpFoundation", + "homepage": "https://github.com/fruitcake/php-cors", + "keywords": [ + "cors", + "laravel", + "symfony" + ], + "support": { + "issues": "https://github.com/fruitcake/php-cors/issues", + "source": "https://github.com/fruitcake/php-cors/tree/v1.3.0" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2023-10-12T05:21:21+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.1.3", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.3" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2024-07-20T21:45:45+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.9.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", + "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.9.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2025-03-27T13:37:11+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c", + "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.2.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2025-03-27T13:27:01+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.7.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.7.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2025-03-27T12:30:47+00:00" + }, + { + "name": "guzzlehttp/uri-template", + "version": "v1.0.4", + "source": { + "type": "git", + "url": "https://github.com/guzzle/uri-template.git", + "reference": "30e286560c137526eccd4ce21b2de477ab0676d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/30e286560c137526eccd4ce21b2de477ab0676d2", + "reference": "30e286560c137526eccd4ce21b2de477ab0676d2", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-php80": "^1.24" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "uri-template/tests": "1.0.0" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\UriTemplate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + } + ], + "description": "A polyfill class for uri_template of PHP", + "keywords": [ + "guzzlehttp", + "uri-template" + ], + "support": { + "issues": "https://github.com/guzzle/uri-template/issues", + "source": "https://github.com/guzzle/uri-template/tree/v1.0.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/uri-template", + "type": "tidelift" + } + ], + "time": "2025-02-03T10:55:03+00:00" + }, + { + "name": "laravel/framework", + "version": "v12.20.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "1b9a00f8caf5503c92aa436279172beae1a484ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/1b9a00f8caf5503c92aa436279172beae1a484ff", + "reference": "1b9a00f8caf5503c92aa436279172beae1a484ff", + "shasum": "" + }, + "require": { + "brick/math": "^0.11|^0.12|^0.13", + "composer-runtime-api": "^2.2", + "doctrine/inflector": "^2.0.5", + "dragonmantank/cron-expression": "^3.4", + "egulias/email-validator": "^3.2.1|^4.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-session": "*", + "ext-tokenizer": "*", + "fruitcake/php-cors": "^1.3", + "guzzlehttp/guzzle": "^7.8.2", + "guzzlehttp/uri-template": "^1.0", + "laravel/prompts": "^0.3.0", + "laravel/serializable-closure": "^1.3|^2.0", + "league/commonmark": "^2.7", + "league/flysystem": "^3.25.1", + "league/flysystem-local": "^3.25.1", + "league/uri": "^7.5.1", + "monolog/monolog": "^3.0", + "nesbot/carbon": "^3.8.4", + "nunomaduro/termwind": "^2.0", + "php": "^8.2", + "psr/container": "^1.1.1|^2.0.1", + "psr/log": "^1.0|^2.0|^3.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "ramsey/uuid": "^4.7", + "symfony/console": "^7.2.0", + "symfony/error-handler": "^7.2.0", + "symfony/finder": "^7.2.0", + "symfony/http-foundation": "^7.2.0", + "symfony/http-kernel": "^7.2.0", + "symfony/mailer": "^7.2.0", + "symfony/mime": "^7.2.0", + "symfony/polyfill-php83": "^1.31", + "symfony/process": "^7.2.0", + "symfony/routing": "^7.2.0", + "symfony/uid": "^7.2.0", + "symfony/var-dumper": "^7.2.0", + "tijsverkoyen/css-to-inline-styles": "^2.2.5", + "vlucas/phpdotenv": "^5.6.1", + "voku/portable-ascii": "^2.0.2" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "psr/log-implementation": "1.0|2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/broadcasting": "self.version", + "illuminate/bus": "self.version", + "illuminate/cache": "self.version", + "illuminate/collections": "self.version", + "illuminate/concurrency": "self.version", + "illuminate/conditionable": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/contracts": "self.version", + "illuminate/cookie": "self.version", + "illuminate/database": "self.version", + "illuminate/encryption": "self.version", + "illuminate/events": "self.version", + "illuminate/filesystem": "self.version", + "illuminate/hashing": "self.version", + "illuminate/http": "self.version", + "illuminate/log": "self.version", + "illuminate/macroable": "self.version", + "illuminate/mail": "self.version", + "illuminate/notifications": "self.version", + "illuminate/pagination": "self.version", + "illuminate/pipeline": "self.version", + "illuminate/process": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/testing": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version", + "spatie/once": "*" + }, + "require-dev": { + "ably/ably-php": "^1.0", + "aws/aws-sdk-php": "^3.322.9", + "ext-gmp": "*", + "fakerphp/faker": "^1.24", + "guzzlehttp/promises": "^2.0.3", + "guzzlehttp/psr7": "^2.4", + "laravel/pint": "^1.18", + "league/flysystem-aws-s3-v3": "^3.25.1", + "league/flysystem-ftp": "^3.25.1", + "league/flysystem-path-prefixing": "^3.25.1", + "league/flysystem-read-only": "^3.25.1", + "league/flysystem-sftp-v3": "^3.25.1", + "mockery/mockery": "^1.6.10", + "orchestra/testbench-core": "^10.0.0", + "pda/pheanstalk": "^5.0.6|^7.0.0", + "php-http/discovery": "^1.15", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^10.5.35|^11.5.3|^12.0.1", + "predis/predis": "^2.3|^3.0", + "resend/resend-php": "^0.10.0", + "symfony/cache": "^7.2.0", + "symfony/http-client": "^7.2.0", + "symfony/psr-http-message-bridge": "^7.2.0", + "symfony/translation": "^7.2.0" + }, + "suggest": { + "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.322.9).", + "brianium/paratest": "Required to run tests in parallel (^7.0|^8.0).", + "ext-apcu": "Required to use the APC cache driver.", + "ext-fileinfo": "Required to use the Filesystem class.", + "ext-ftp": "Required to use the Flysystem FTP driver.", + "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", + "ext-memcached": "Required to use the memcache cache driver.", + "ext-pcntl": "Required to use all features of the queue worker and console signal trapping.", + "ext-pdo": "Required to use all database features.", + "ext-posix": "Required to use all features of the queue worker.", + "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0|^6.0).", + "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", + "filp/whoops": "Required for friendly error pages in development (^2.14.3).", + "laravel/tinker": "Required to use the tinker console command (^2.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", + "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).", + "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.25.1).", + "league/flysystem-read-only": "Required to use read-only disks (^3.25.1)", + "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).", + "mockery/mockery": "Required to use mocking (^1.6).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).", + "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).", + "phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.5.3|^12.0.1).", + "predis/predis": "Required to use the predis connector (^2.3|^3.0).", + "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", + "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^7.2).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.2).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.2).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.2).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.2).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.2)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "files": [ + "src/Illuminate/Collections/functions.php", + "src/Illuminate/Collections/helpers.php", + "src/Illuminate/Events/functions.php", + "src/Illuminate/Filesystem/functions.php", + "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Log/functions.php", + "src/Illuminate/Support/functions.php", + "src/Illuminate/Support/helpers.php" + ], + "psr-4": { + "Illuminate\\": "src/Illuminate/", + "Illuminate\\Support\\": [ + "src/Illuminate/Macroable/", + "src/Illuminate/Collections/", + "src/Illuminate/Conditionable/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Laravel Framework.", + "homepage": "https://laravel.com", + "keywords": [ + "framework", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-07-08T15:02:21+00:00" + }, + { + "name": "laravel/prompts", + "version": "v0.3.6", + "source": { + "type": "git", + "url": "https://github.com/laravel/prompts.git", + "reference": "86a8b692e8661d0fb308cec64f3d176821323077" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/prompts/zipball/86a8b692e8661d0fb308cec64f3d176821323077", + "reference": "86a8b692e8661d0fb308cec64f3d176821323077", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.2", + "ext-mbstring": "*", + "php": "^8.1", + "symfony/console": "^6.2|^7.0" + }, + "conflict": { + "illuminate/console": ">=10.17.0 <10.25.0", + "laravel/framework": ">=10.17.0 <10.25.0" + }, + "require-dev": { + "illuminate/collections": "^10.0|^11.0|^12.0", + "mockery/mockery": "^1.5", + "pestphp/pest": "^2.3|^3.4", + "phpstan/phpstan": "^1.11", + "phpstan/phpstan-mockery": "^1.1" + }, + "suggest": { + "ext-pcntl": "Required for the spinner to be animated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.3.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Laravel\\Prompts\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Add beautiful and user-friendly forms to your command-line applications.", + "support": { + "issues": "https://github.com/laravel/prompts/issues", + "source": "https://github.com/laravel/prompts/tree/v0.3.6" + }, + "time": "2025-07-07T14:17:42+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v2.0.4", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/b352cf0534aa1ae6b4d825d1e762e35d43f8a841", + "reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "illuminate/support": "^10.0|^11.0|^12.0", + "nesbot/carbon": "^2.67|^3.0", + "pestphp/pest": "^2.36|^3.0", + "phpstan/phpstan": "^2.0", + "symfony/var-dumper": "^6.2.0|^7.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2025-03-19T13:51:03+00:00" + }, + { + "name": "laravel/tinker", + "version": "v2.10.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/tinker.git", + "reference": "22177cc71807d38f2810c6204d8f7183d88a57d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/tinker/zipball/22177cc71807d38f2810c6204d8f7183d88a57d3", + "reference": "22177cc71807d38f2810c6204d8f7183d88a57d3", + "shasum": "" + }, + "require": { + "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "php": "^7.2.5|^8.0", + "psy/psysh": "^0.11.1|^0.12.0", + "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0" + }, + "require-dev": { + "mockery/mockery": "~1.3.3|^1.4.2", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8.5.8|^9.3.3|^10.0" + }, + "suggest": { + "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0)." + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Tinker\\TinkerServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Tinker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Powerful REPL for the Laravel framework.", + "keywords": [ + "REPL", + "Tinker", + "laravel", + "psysh" + ], + "support": { + "issues": "https://github.com/laravel/tinker/issues", + "source": "https://github.com/laravel/tinker/tree/v2.10.1" + }, + "time": "2025-01-27T14:24:01+00:00" + }, + { + "name": "league/commonmark", + "version": "2.7.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "10732241927d3971d28e7ea7b5712721fa2296ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/10732241927d3971d28e7ea7b5712721fa2296ca", + "reference": "10732241927d3971d28e7ea7b5712721fa2296ca", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.31.1", + "commonmark/commonmark.js": "0.31.1", + "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4 || ^2.0", + "nyholm/psr7": "^1.5", + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3 | ^6.0 | ^7.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", + "unleashedtech/php-coding-standard": "^3.1.1", + "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2025-07-20T12:47:49+00:00" + }, + { + "name": "league/config", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2022-12-11T20:36:23+00:00" + }, + { + "name": "league/flysystem", + "version": "3.30.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "2203e3151755d874bb2943649dae1eb8533ac93e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/2203e3151755d874bb2943649dae1eb8533ac93e", + "reference": "2203e3151755d874bb2943649dae1eb8533ac93e", + "shasum": "" + }, + "require": { + "league/flysystem-local": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1", + "phpseclib/phpseclib": "3.0.15", + "symfony/http-client": "<5.2" + }, + "require-dev": { + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", + "aws/aws-sdk-php": "^3.295.10", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-mongodb": "^1.3|^2", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "guzzlehttp/psr7": "^2.6", + "microsoft/azure-storage-blob": "^1.1", + "mongodb/mongodb": "^1.2|^2", + "phpseclib/phpseclib": "^3.0.36", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.11|^10.0", + "sabre/dav": "^4.6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "File storage abstraction for PHP", + "keywords": [ + "WebDAV", + "aws", + "cloud", + "file", + "files", + "filesystem", + "filesystems", + "ftp", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/3.30.0" + }, + "time": "2025-06-25T13:29:59+00:00" + }, + { + "name": "league/flysystem-local", + "version": "3.30.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "6691915f77c7fb69adfb87dcd550052dc184ee10" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/6691915f77c7fb69adfb87dcd550052dc184ee10", + "reference": "6691915f77c7fb69adfb87dcd550052dc184ee10", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Local\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Local filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "local" + ], + "support": { + "source": "https://github.com/thephpleague/flysystem-local/tree/3.30.0" + }, + "time": "2025-05-21T10:34:19+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.16.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2024-09-21T08:32:55+00:00" + }, + { + "name": "league/uri", + "version": "7.5.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri.git", + "reference": "81fb5145d2644324614cc532b28efd0215bda430" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/81fb5145d2644324614cc532b28efd0215bda430", + "reference": "81fb5145d2644324614cc532b28efd0215bda430", + "shasum": "" + }, + "require": { + "league/uri-interfaces": "^7.5", + "php": "^8.1" + }, + "conflict": { + "league/uri-schemes": "^1.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-fileinfo": "to create Data URI from file contennts", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", + "league/uri-components": "Needed to easily manipulate URI objects components", + "php-64bit": "to improve IPV4 host parsing", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "URI manipulation library", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "middleware", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "uri-template", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri/tree/7.5.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2024-12-08T08:40:02+00:00" + }, + { + "name": "league/uri-interfaces", + "version": "7.5.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-interfaces.git", + "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^8.1", + "psr/http-factory": "^1", + "psr/http-message": "^1.1 || ^2.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "php-64bit": "to improve IPV4 host parsing", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "Common interfaces and classes for URI representation and interaction", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.5.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2024-12-08T08:18:47+00:00" + }, + { + "name": "livewire/flux", + "version": "v2.2.3", + "source": { + "type": "git", + "url": "https://github.com/livewire/flux.git", + "reference": "0fb4c0b78eac393ad3a19a387af193573c310371" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/livewire/flux/zipball/0fb4c0b78eac393ad3a19a387af193573c310371", + "reference": "0fb4c0b78eac393ad3a19a387af193573c310371", + "shasum": "" + }, + "require": { + "illuminate/console": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0", + "illuminate/view": "^10.0|^11.0|^12.0", + "laravel/prompts": "^0.1|^0.2|^0.3", + "livewire/livewire": "^3.5.19", + "php": "^8.1", + "symfony/console": "^6.0|^7.0" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Flux": "Flux\\Flux" + }, + "providers": [ + "Flux\\FluxServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Flux\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "proprietary" + ], + "authors": [ + { + "name": "Caleb Porzio", + "email": "calebporzio@gmail.com" + } + ], + "description": "The official UI component library for Livewire.", + "keywords": [ + "components", + "flux", + "laravel", + "livewire", + "ui" + ], + "support": { + "issues": "https://github.com/livewire/flux/issues", + "source": "https://github.com/livewire/flux/tree/v2.2.3" + }, + "time": "2025-07-11T00:25:51+00:00" + }, + { + "name": "livewire/livewire", + "version": "v3.6.4", + "source": { + "type": "git", + "url": "https://github.com/livewire/livewire.git", + "reference": "ef04be759da41b14d2d129e670533180a44987dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/livewire/livewire/zipball/ef04be759da41b14d2d129e670533180a44987dc", + "reference": "ef04be759da41b14d2d129e670533180a44987dc", + "shasum": "" + }, + "require": { + "illuminate/database": "^10.0|^11.0|^12.0", + "illuminate/routing": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0", + "illuminate/validation": "^10.0|^11.0|^12.0", + "laravel/prompts": "^0.1.24|^0.2|^0.3", + "league/mime-type-detection": "^1.9", + "php": "^8.1", + "symfony/console": "^6.0|^7.0", + "symfony/http-kernel": "^6.2|^7.0" + }, + "require-dev": { + "calebporzio/sushi": "^2.1", + "laravel/framework": "^10.15.0|^11.0|^12.0", + "mockery/mockery": "^1.3.1", + "orchestra/testbench": "^8.21.0|^9.0|^10.0", + "orchestra/testbench-dusk": "^8.24|^9.1|^10.0", + "phpunit/phpunit": "^10.4|^11.5", + "psy/psysh": "^0.11.22|^0.12" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Livewire": "Livewire\\Livewire" + }, + "providers": [ + "Livewire\\LivewireServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Livewire\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Caleb Porzio", + "email": "calebporzio@gmail.com" + } + ], + "description": "A front-end framework for Laravel.", + "support": { + "issues": "https://github.com/livewire/livewire/issues", + "source": "https://github.com/livewire/livewire/tree/v3.6.4" + }, + "funding": [ + { + "url": "https://github.com/livewire", + "type": "github" + } + ], + "time": "2025-07-17T05:12:15+00:00" + }, + { + "name": "livewire/volt", + "version": "v1.7.1", + "source": { + "type": "git", + "url": "https://github.com/livewire/volt.git", + "reference": "ba3e609fd4c71f8b5783f024baf51715e48e93a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/livewire/volt/zipball/ba3e609fd4c71f8b5783f024baf51715e48e93a6", + "reference": "ba3e609fd4c71f8b5783f024baf51715e48e93a6", + "shasum": "" + }, + "require": { + "laravel/framework": "^10.38.2|^11.0|^12.0", + "livewire/livewire": "^3.6.1", + "php": "^8.1" + }, + "require-dev": { + "laravel/folio": "^1.1", + "mockery/mockery": "^1.6", + "orchestra/testbench": "^8.15.0|^9.0|^10.0", + "pestphp/pest": "^2.9.5|^3.0", + "phpstan/phpstan": "^1.10" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Livewire\\Volt\\VoltServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Livewire\\Volt\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "An elegantly crafted functional API for Laravel Livewire.", + "homepage": "https://github.com/livewire/volt", + "keywords": [ + "laravel", + "livewire", + "volt" + ], + "support": { + "issues": "https://github.com/livewire/volt/issues", + "source": "https://github.com/livewire/volt" + }, + "time": "2025-04-08T15:13:36+00:00" + }, + { + "name": "monolog/monolog", + "version": "3.9.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6", + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "predis/predis": "^1.1 || ^2", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.9.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2025-03-24T10:02:05+00:00" + }, + { + "name": "nesbot/carbon", + "version": "3.10.1", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "1fd1935b2d90aef2f093c5e35f7ae1257c448d00" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/1fd1935b2d90aef2f093c5e35f7ae1257c448d00", + "reference": "1fd1935b2d90aef2f093c5e35f7ae1257c448d00", + "shasum": "" + }, + "require": { + "carbonphp/carbon-doctrine-types": "<100.0", + "ext-json": "*", + "php": "^8.1", + "psr/clock": "^1.0", + "symfony/clock": "^6.3.12 || ^7.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "doctrine/dbal": "^3.6.3 || ^4.0", + "doctrine/orm": "^2.15.2 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.75.0", + "kylekatarnls/multi-tester": "^2.5.3", + "phpmd/phpmd": "^2.15.0", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.17", + "phpunit/phpunit": "^10.5.46", + "squizlabs/php_codesniffer": "^3.13.0" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbon.nesbot.com/docs", + "issues": "https://github.com/CarbonPHP/carbon/issues", + "source": "https://github.com/CarbonPHP/carbon" + }, + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "time": "2025-06-21T15:19:35+00:00" + }, + { + "name": "nette/schema", + "version": "v1.3.2", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "da801d52f0354f70a638673c4a0f04e16529431d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d", + "reference": "da801d52f0354f70a638673c4a0f04e16529431d", + "shasum": "" + }, + "require": { + "nette/utils": "^4.0", + "php": "8.1 - 8.4" + }, + "require-dev": { + "nette/tester": "^2.5.2", + "phpstan/phpstan-nette": "^1.0", + "tracy/tracy": "^2.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.3.2" + }, + "time": "2024-10-06T23:10:23+00:00" + }, + { + "name": "nette/utils", + "version": "v4.0.7", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/e67c4061eb40b9c113b218214e42cb5a0dda28f2", + "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2", + "shasum": "" + }, + "require": { + "php": "8.0 - 8.4" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "dev-master", + "nette/tester": "^2.5", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.0.7" + }, + "time": "2025-06-03T04:55:08+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.5.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" + }, + "time": "2025-05-31T08:24:38+00:00" + }, + { + "name": "nunomaduro/termwind", + "version": "v2.3.1", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "dfa08f390e509967a15c22493dc0bac5733d9123" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/dfa08f390e509967a15c22493dc0bac5733d9123", + "reference": "dfa08f390e509967a15c22493dc0bac5733d9123", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^8.2", + "symfony/console": "^7.2.6" + }, + "require-dev": { + "illuminate/console": "^11.44.7", + "laravel/pint": "^1.22.0", + "mockery/mockery": "^1.6.12", + "pestphp/pest": "^2.36.0 || ^3.8.2", + "phpstan/phpstan": "^1.12.25", + "phpstan/phpstan-strict-rules": "^1.6.2", + "symfony/var-dumper": "^7.2.6", + "thecodingmachine/phpstan-strict-rules": "^1.0.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Termwind\\Laravel\\TermwindServiceProvider" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Functions.php" + ], + "psr-4": { + "Termwind\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Its like Tailwind CSS, but for the console.", + "keywords": [ + "cli", + "console", + "css", + "package", + "php", + "style" + ], + "support": { + "issues": "https://github.com/nunomaduro/termwind/issues", + "source": "https://github.com/nunomaduro/termwind/tree/v2.3.1" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://github.com/xiCO2k", + "type": "github" + } + ], + "time": "2025-05-08T08:14:37+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.18|^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" + }, + "time": "2024-11-09T15:12:26+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.3", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2024-07-20T21:41:07+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/b9e61a61e39e02dd90944e9115241c7f7e76bfd8", + "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.2.0" + }, + "time": "2025-07-13T07:04:09+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, + { + "name": "psy/psysh", + "version": "v0.12.9", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "1b801844becfe648985372cb4b12ad6840245ace" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/1b801844becfe648985372cb4b12ad6840245ace", + "reference": "1b801844becfe648985372cb4b12ad6840245ace", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "nikic/php-parser": "^5.0 || ^4.0", + "php": "^8.0 || ^7.4", + "symfony/console": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" + }, + "conflict": { + "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.2" + }, + "suggest": { + "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", + "ext-pdo-sqlite": "The doc command requires SQLite to work.", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well." + }, + "bin": [ + "bin/psysh" + ], + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": false, + "forward-command": false + }, + "branch-alias": { + "dev-main": "0.12.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Psy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "An interactive shell for modern PHP.", + "homepage": "http://psysh.org", + "keywords": [ + "REPL", + "console", + "interactive", + "shell" + ], + "support": { + "issues": "https://github.com/bobthecow/psysh/issues", + "source": "https://github.com/bobthecow/psysh/tree/v0.12.9" + }, + "time": "2025-06-23T02:35:06+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "ramsey/collection", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.1.1" + }, + "time": "2025-03-22T05:38:12+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.9.0", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/4e0e23cc785f0724a0e838279a9eb03f28b092a0", + "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0", + "shasum": "" + }, + "require": { + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.25", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.9.0" + }, + "time": "2025-06-25T14:20:11+00:00" + }, + { + "name": "spatie/laravel-activitylog", + "version": "4.10.2", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-activitylog.git", + "reference": "bb879775d487438ed9a99e64f09086b608990c10" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-activitylog/zipball/bb879775d487438ed9a99e64f09086b608990c10", + "reference": "bb879775d487438ed9a99e64f09086b608990c10", + "shasum": "" + }, + "require": { + "illuminate/config": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0", + "illuminate/database": "^8.69 || ^9.27 || ^10.0 || ^11.0 || ^12.0", + "illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0", + "php": "^8.1", + "spatie/laravel-package-tools": "^1.6.3" + }, + "require-dev": { + "ext-json": "*", + "orchestra/testbench": "^6.23 || ^7.0 || ^8.0 || ^9.0 || ^10.0", + "pestphp/pest": "^1.20 || ^2.0 || ^3.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Activitylog\\ActivitylogServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Activitylog\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + }, + { + "name": "Sebastian De Deyne", + "email": "sebastian@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + }, + { + "name": "Tom Witkowski", + "email": "dev.gummibeer@gmail.com", + "homepage": "https://gummibeer.de", + "role": "Developer" + } + ], + "description": "A very simple activity logger to monitor the users of your website or application", + "homepage": "https://github.com/spatie/activitylog", + "keywords": [ + "activity", + "laravel", + "log", + "spatie", + "user" + ], + "support": { + "issues": "https://github.com/spatie/laravel-activitylog/issues", + "source": "https://github.com/spatie/laravel-activitylog/tree/4.10.2" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-06-15T06:59:49+00:00" + }, + { + "name": "spatie/laravel-package-tools", + "version": "1.92.7", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-package-tools.git", + "reference": "f09a799850b1ed765103a4f0b4355006360c49a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/f09a799850b1ed765103a4f0b4355006360c49a5", + "reference": "f09a799850b1ed765103a4f0b4355006360c49a5", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^9.28|^10.0|^11.0|^12.0", + "php": "^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.5", + "orchestra/testbench": "^7.7|^8.0|^9.0|^10.0", + "pestphp/pest": "^1.23|^2.1|^3.1", + "phpunit/php-code-coverage": "^9.0|^10.0|^11.0", + "phpunit/phpunit": "^9.5.24|^10.5|^11.5", + "spatie/pest-plugin-test-time": "^1.1|^2.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\LaravelPackageTools\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "role": "Developer" + } + ], + "description": "Tools for creating Laravel packages", + "homepage": "https://github.com/spatie/laravel-package-tools", + "keywords": [ + "laravel-package-tools", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-package-tools/issues", + "source": "https://github.com/spatie/laravel-package-tools/tree/1.92.7" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-07-17T15:46:43+00:00" + }, + { + "name": "spatie/laravel-settings", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-settings.git", + "reference": "fd0eb5a832131b56cd98834b93be3425ee28e333" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-settings/zipball/fd0eb5a832131b56cd98834b93be3425ee28e333", + "reference": "fd0eb5a832131b56cd98834b93be3425ee28e333", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/database": "^11.0|^12.0", + "php": "^8.2", + "phpdocumentor/type-resolver": "^1.5", + "spatie/temporary-directory": "^1.3|^2.0" + }, + "require-dev": { + "ext-redis": "*", + "mockery/mockery": "^1.4", + "orchestra/testbench": "^9.0|^10.0", + "pestphp/pest": "^2.0|^3.0", + "pestphp/pest-plugin-laravel": "^2.0|^3.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "spatie/laravel-data": "^2.0.0|^4.0.0", + "spatie/pest-plugin-snapshots": "^2.0", + "spatie/phpunit-snapshot-assertions": "^4.2|^5.0", + "spatie/ray": "^1.36" + }, + "suggest": { + "spatie/data-transfer-object": "Allows for DTO casting to settings. (deprecated)" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\LaravelSettings\\LaravelSettingsServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\LaravelSettings\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ruben Van Assche", + "email": "ruben@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Store your application settings", + "homepage": "https://github.com/spatie/laravel-settings", + "keywords": [ + "laravel-settings", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-settings/issues", + "source": "https://github.com/spatie/laravel-settings/tree/3.4.4" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-04-11T11:35:56+00:00" + }, + { + "name": "spatie/temporary-directory", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/temporary-directory.git", + "reference": "580eddfe9a0a41a902cac6eeb8f066b42e65a32b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/580eddfe9a0a41a902cac6eeb8f066b42e65a32b", + "reference": "580eddfe9a0a41a902cac6eeb8f066b42e65a32b", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\TemporaryDirectory\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Vanderbist", + "email": "alex@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Easily create, use and destroy temporary directories", + "homepage": "https://github.com/spatie/temporary-directory", + "keywords": [ + "php", + "spatie", + "temporary-directory" + ], + "support": { + "issues": "https://github.com/spatie/temporary-directory/issues", + "source": "https://github.com/spatie/temporary-directory/tree/2.3.0" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-01-13T13:04:43+00:00" + }, + { + "name": "symfony/clock", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/clock.git", + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/clock/zipball/b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/console", + "version": "v7.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/9e27aecde8f506ba0fd1d9989620c04a87697101", + "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.3.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-27T19:55:54+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2", + "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v7.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "35b55b166f6752d6aaf21aa042fc5ed280fce235" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/35b55b166f6752d6aaf21aa042fc5ed280fce235", + "reference": "35b55b166f6752d6aaf21aa042fc5ed280fce235", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^6.4|^7.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/webpack-encore-bundle": "^1.0|^2.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v7.3.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-13T07:48:40+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "497f73ac996a598c92409b44ac43b6690c4f666d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/497f73ac996a598c92409b44ac43b6690c4f666d", + "reference": "497f73ac996a598c92409b44ac43b6690c4f666d", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-22T09:11:45+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/ec2344cf77a48253bbca6939aa3d2477773ea63d", + "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-30T19:00:26+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v7.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "23dd60256610c86a3414575b70c596e5deff6ed9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/23dd60256610c86a3414575b70c596e5deff6ed9", + "reference": "23dd60256610c86a3414575b70c596e5deff6ed9", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" + }, + "require-dev": { + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4.12|^7.1.5", + "symfony/clock": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v7.3.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-23T15:07:14+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v7.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "1644879a66e4aa29c36fe33dfa6c54b450ce1831" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/1644879a66e4aa29c36fe33dfa6c54b450ce1831", + "reference": "1644879a66e4aa29c36fe33dfa6c54b450ce1831", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^7.3", + "symfony/http-foundation": "^7.3", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/browser-kit": "<6.4", + "symfony/cache": "<6.4", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/translation": "<6.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.4", + "twig/twig": "<3.12" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^7.1", + "symfony/routing": "^6.4|^7.0", + "symfony/serializer": "^7.1", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0", + "twig/twig": "^3.12" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v7.3.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-28T08:24:55+00:00" + }, + { + "name": "symfony/mailer", + "version": "v7.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/mailer.git", + "reference": "b5db5105b290bdbea5ab27b89c69effcf1cb3368" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b5db5105b290bdbea5ab27b89c69effcf1cb3368", + "reference": "b5db5105b290bdbea5ab27b89c69effcf1cb3368", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.1.10|^3|^4", + "php": ">=8.2", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/mime": "^7.2", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/messenger": "<6.4", + "symfony/mime": "<6.4", + "symfony/twig-bridge": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps sending emails", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/mailer/tree/v7.3.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-27T19:55:54+00:00" + }, + { + "name": "symfony/mime", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9", + "reference": "0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<6.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-02-19T08:51:26+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-10T14:38:51+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-02T08:10:11+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/process", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-17T09:11:12+00:00" + }, + { + "name": "symfony/routing", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "8e213820c5fea844ecea29203d2a308019007c15" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/8e213820c5fea844ecea29203d2a308019007c15", + "reference": "8e213820c5fea844ecea29203d2a308019007c15", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-05-24T20:43:28+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-25T09:37:31+00:00" + }, + { + "name": "symfony/string", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125", + "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-20T20:19:01+00:00" + }, + { + "name": "symfony/translation", + "version": "v7.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "241d5ac4910d256660238a7ecf250deba4c73063" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/241d5ac4910d256660238a7ecf250deba4c73063", + "reference": "241d5ac4910d256660238a7ecf250deba4c73063", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5|^3.0" + }, + "conflict": { + "nikic/php-parser": "<5.0", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v7.3.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-27T19:55:54+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-27T08:32:26+00:00" + }, + { + "name": "symfony/uid", + "version": "v7.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "a69f69f3159b852651a6bf45a9fdd149520525bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/a69f69f3159b852651a6bf45a9fdd149520525bb", + "reference": "a69f69f3159b852651a6bf45a9fdd149520525bb", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/uid/tree/v7.3.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-27T19:55:54+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v7.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "6e209fbe5f5a7b6043baba46fe5735a4b85d0d42" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6e209fbe5f5a7b6043baba46fe5735a4b85d0d42", + "reference": "6e209fbe5f5a7b6043baba46fe5735a4b85d0d42", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", + "twig/twig": "^3.12" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v7.3.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-27T19:55:54+00:00" + }, + { + "name": "tijsverkoyen/css-to-inline-styles", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", + "reference": "0d72ac1c00084279c1816675284073c5a337c20d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/0d72ac1c00084279c1816675284073c5a337c20d", + "reference": "0d72ac1c00084279c1816675284073c5a337c20d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "php": "^7.4 || ^8.0", + "symfony/css-selector": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^8.5.21 || ^9.5.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "TijsVerkoyen\\CssToInlineStyles\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Tijs Verkoyen", + "email": "css_to_inline_styles@verkoyen.eu", + "role": "Developer" + } + ], + "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", + "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", + "support": { + "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.3.0" + }, + "time": "2024-12-21T16:25:41+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.6.2", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af", + "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.3", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.3", + "symfony/polyfill-ctype": "^1.24", + "symfony/polyfill-mbstring": "^1.24", + "symfony/polyfill-php80": "^1.24" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "5.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2025-04-30T23:37:27+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "https://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/2.0.3" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2024-11-21T01:49:47+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" + } + ], + "packages-dev": [ + { + "name": "fakerphp/faker", + "version": "v1.24.1", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" + }, + "time": "2024-11-21T13:46:39+00:00" + }, + { + "name": "filp/whoops", + "version": "2.18.3", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "59a123a3d459c5a23055802237cb317f609867e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/59a123a3d459c5a23055802237cb317f609867e5", + "reference": "59a123a3d459c5a23055802237cb317f609867e5", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.18.3" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2025-06-16T00:02:10+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" + }, + "time": "2025-04-30T06:54:44+00:00" + }, + { + "name": "laravel/pail", + "version": "v1.2.3", + "source": { + "type": "git", + "url": "https://github.com/laravel/pail.git", + "reference": "8cc3d575c1f0e57eeb923f366a37528c50d2385a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/pail/zipball/8cc3d575c1f0e57eeb923f366a37528c50d2385a", + "reference": "8cc3d575c1f0e57eeb923f366a37528c50d2385a", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "illuminate/console": "^10.24|^11.0|^12.0", + "illuminate/contracts": "^10.24|^11.0|^12.0", + "illuminate/log": "^10.24|^11.0|^12.0", + "illuminate/process": "^10.24|^11.0|^12.0", + "illuminate/support": "^10.24|^11.0|^12.0", + "nunomaduro/termwind": "^1.15|^2.0", + "php": "^8.2", + "symfony/console": "^6.0|^7.0" + }, + "require-dev": { + "laravel/framework": "^10.24|^11.0|^12.0", + "laravel/pint": "^1.13", + "orchestra/testbench-core": "^8.13|^9.0|^10.0", + "pestphp/pest": "^2.20|^3.0", + "pestphp/pest-plugin-type-coverage": "^2.3|^3.0", + "phpstan/phpstan": "^1.12.27", + "symfony/var-dumper": "^6.3|^7.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Pail\\PailServiceProvider" + ] + }, + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Pail\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Easily delve into your Laravel application's log files directly from the command line.", + "homepage": "https://github.com/laravel/pail", + "keywords": [ + "dev", + "laravel", + "logs", + "php", + "tail" + ], + "support": { + "issues": "https://github.com/laravel/pail/issues", + "source": "https://github.com/laravel/pail" + }, + "time": "2025-06-05T13:55:57+00:00" + }, + { + "name": "laravel/pint", + "version": "v1.24.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/pint.git", + "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/pint/zipball/0345f3b05f136801af8c339f9d16ef29e6b4df8a", + "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "ext-tokenizer": "*", + "ext-xml": "*", + "php": "^8.2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.82.2", + "illuminate/view": "^11.45.1", + "larastan/larastan": "^3.5.0", + "laravel-zero/framework": "^11.45.0", + "mockery/mockery": "^1.6.12", + "nunomaduro/termwind": "^2.3.1", + "pestphp/pest": "^2.36.0" + }, + "bin": [ + "builds/pint" + ], + "type": "project", + "autoload": { + "files": [ + "overrides/Runner/Parallel/ProcessFactory.php" + ], + "psr-4": { + "App\\": "app/", + "Database\\Seeders\\": "database/seeders/", + "Database\\Factories\\": "database/factories/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "An opinionated code formatter for PHP.", + "homepage": "https://laravel.com", + "keywords": [ + "format", + "formatter", + "lint", + "linter", + "php" + ], + "support": { + "issues": "https://github.com/laravel/pint/issues", + "source": "https://github.com/laravel/pint" + }, + "time": "2025-07-10T18:09:32+00:00" + }, + { + "name": "laravel/sail", + "version": "v1.43.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/sail.git", + "reference": "3e7d899232a8c5e3ea4fc6dee7525ad583887e72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sail/zipball/3e7d899232a8c5e3ea4fc6dee7525ad583887e72", + "reference": "3e7d899232a8c5e3ea4fc6dee7525ad583887e72", + "shasum": "" + }, + "require": { + "illuminate/console": "^9.52.16|^10.0|^11.0|^12.0", + "illuminate/contracts": "^9.52.16|^10.0|^11.0|^12.0", + "illuminate/support": "^9.52.16|^10.0|^11.0|^12.0", + "php": "^8.0", + "symfony/console": "^6.0|^7.0", + "symfony/yaml": "^6.0|^7.0" + }, + "require-dev": { + "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0", + "phpstan/phpstan": "^1.10" + }, + "bin": [ + "bin/sail" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Sail\\SailServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sail\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Docker files for running a basic Laravel application.", + "keywords": [ + "docker", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/sail/issues", + "source": "https://github.com/laravel/sail" + }, + "time": "2025-05-19T13:19:21+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.6.12", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": ">=7.3" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" + }, + "type": "library", + "autoload": { + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "https://github.com/padraic", + "role": "Author" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "docs": "https://docs.mockery.io/", + "issues": "https://github.com/mockery/mockery/issues", + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" + }, + "time": "2024-05-16T03:13:13+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.3", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "faed855a7b5f4d4637717c2b3863e277116beb36" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36", + "reference": "faed855a7b5f4d4637717c2b3863e277116beb36", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.3" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-07-05T12:25:42+00:00" + }, + { + "name": "nunomaduro/collision", + "version": "v8.8.2", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/collision.git", + "reference": "60207965f9b7b7a4ce15a0f75d57f9dadb105bdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/60207965f9b7b7a4ce15a0f75d57f9dadb105bdb", + "reference": "60207965f9b7b7a4ce15a0f75d57f9dadb105bdb", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.18.1", + "nunomaduro/termwind": "^2.3.1", + "php": "^8.2.0", + "symfony/console": "^7.3.0" + }, + "conflict": { + "laravel/framework": "<11.44.2 || >=13.0.0", + "phpunit/phpunit": "<11.5.15 || >=13.0.0" + }, + "require-dev": { + "brianium/paratest": "^7.8.3", + "larastan/larastan": "^3.4.2", + "laravel/framework": "^11.44.2 || ^12.18", + "laravel/pint": "^1.22.1", + "laravel/sail": "^1.43.1", + "laravel/sanctum": "^4.1.1", + "laravel/tinker": "^2.10.1", + "orchestra/testbench-core": "^9.12.0 || ^10.4", + "pestphp/pest": "^3.8.2", + "sebastian/environment": "^7.2.1 || ^8.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" + ] + }, + "branch-alias": { + "dev-8.x": "8.x-dev" + } + }, + "autoload": { + "files": [ + "./src/Adapters/Phpunit/Autoload.php" + ], + "psr-4": { + "NunoMaduro\\Collision\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Cli error handling for console/command-line PHP applications.", + "keywords": [ + "artisan", + "cli", + "command-line", + "console", + "dev", + "error", + "handling", + "laravel", + "laravel-zero", + "php", + "symfony" + ], + "support": { + "issues": "https://github.com/nunomaduro/collision/issues", + "source": "https://github.com/nunomaduro/collision" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2025-06-25T02:12:12+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "11.0.10", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "1a800a7446add2d79cc6b3c01c45381810367d76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/1a800a7446add2d79cc6b3c01c45381810367d76", + "reference": "1a800a7446add2d79cc6b3c01c45381810367d76", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.4.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.0", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^11.5.2" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/show" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" + } + ], + "time": "2025-06-18T08:56:18+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-27T05:02:59+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:07:44+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:08:43+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:09:35+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "11.5.27", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "446d43867314781df7e9adf79c3ec7464956fd8f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/446d43867314781df7e9adf79c3ec7464956fd8f", + "reference": "446d43867314781df7e9adf79c3ec7464956fd8f", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.3", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.10", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.3", + "sebastian/comparator": "^6.3.1", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.1", + "sebastian/exporter": "^6.3.0", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/type": "^5.1.2", + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.27" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2025-07-11T04:10:06+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:41:36+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-03-19T07:56:08+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:45:54+00:00" + }, + { + "name": "sebastian/comparator", + "version": "6.3.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/24b8fbc2c8e201bb1308e7b05148d6ab393b6959", + "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.4" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-03-07T06:57:01+00:00" + }, + { + "name": "sebastian/complexity", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:49:50+00:00" + }, + { + "name": "sebastian/diff", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:53:05+00:00" + }, + { + "name": "sebastian/environment", + "version": "7.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" + } + ], + "time": "2025-05-21T11:55:47+00:00" + }, + { + "name": "sebastian/exporter", + "version": "6.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/3473f61172093b2da7de1fb5782e1f24cc036dc3", + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-12-05T09:17:50+00:00" + }, + { + "name": "sebastian/global-state", + "version": "7.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:57:36+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:58:38+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:00:13+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:01:32+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16", + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:10:34+00:00" + }, + { + "name": "sebastian/type", + "version": "5.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", + "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.1.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-03-18T13:35:50+00:00" + }, + { + "name": "sebastian/version", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-09T05:16:32+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, + { + "name": "symfony/yaml", + "version": "v7.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/0c3555045a46ab3cd4cc5a69d161225195230edb", + "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v7.3.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-03T06:57:57+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": "^8.2" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/config/app.php b/config/app.php new file mode 100644 index 0000000..324b513 --- /dev/null +++ b/config/app.php @@ -0,0 +1,126 @@ + env('APP_NAME', 'Laravel'), + + /* + |-------------------------------------------------------------------------- + | Application Environment + |-------------------------------------------------------------------------- + | + | This value determines the "environment" your application is currently + | running in. This may determine how you prefer to configure various + | services the application utilizes. Set this in your ".env" file. + | + */ + + 'env' => env('APP_ENV', 'production'), + + /* + |-------------------------------------------------------------------------- + | Application Debug Mode + |-------------------------------------------------------------------------- + | + | When your application is in debug mode, detailed error messages with + | stack traces will be shown on every error that occurs within your + | application. If disabled, a simple generic error page is shown. + | + */ + + 'debug' => (bool) env('APP_DEBUG', false), + + /* + |-------------------------------------------------------------------------- + | Application URL + |-------------------------------------------------------------------------- + | + | This URL is used by the console to properly generate URLs when using + | the Artisan command line tool. You should set this to the root of + | the application so that it's available within Artisan commands. + | + */ + + 'url' => env('APP_URL', 'http://localhost'), + + /* + |-------------------------------------------------------------------------- + | Application Timezone + |-------------------------------------------------------------------------- + | + | Here you may specify the default timezone for your application, which + | will be used by the PHP date and date-time functions. The timezone + | is set to "UTC" by default as it is suitable for most use cases. + | + */ + + 'timezone' => 'UTC', + + /* + |-------------------------------------------------------------------------- + | Application Locale Configuration + |-------------------------------------------------------------------------- + | + | The application locale determines the default locale that will be used + | by Laravel's translation / localization methods. This option can be + | set to any locale for which you plan to have translation strings. + | + */ + + 'locale' => env('APP_LOCALE', 'en'), + + 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), + + 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), + + /* + |-------------------------------------------------------------------------- + | Encryption Key + |-------------------------------------------------------------------------- + | + | This key is utilized by Laravel's encryption services and should be set + | to a random, 32 character string to ensure that all encrypted values + | are secure. You should do this prior to deploying the application. + | + */ + + 'cipher' => 'AES-256-CBC', + + 'key' => env('APP_KEY'), + + 'previous_keys' => [ + ...array_filter( + explode(',', env('APP_PREVIOUS_KEYS', '')) + ), + ], + + /* + |-------------------------------------------------------------------------- + | Maintenance Mode Driver + |-------------------------------------------------------------------------- + | + | These configuration options determine the driver used to determine and + | manage Laravel's "maintenance mode" status. The "cache" driver will + | allow maintenance mode to be controlled across multiple machines. + | + | Supported drivers: "file", "cache" + | + */ + + 'maintenance' => [ + 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), + 'store' => env('APP_MAINTENANCE_STORE', 'database'), + ], + +]; diff --git a/config/auth.php b/config/auth.php new file mode 100644 index 0000000..0ba5d5d --- /dev/null +++ b/config/auth.php @@ -0,0 +1,115 @@ + [ + 'guard' => env('AUTH_GUARD', 'web'), + 'passwords' => env('AUTH_PASSWORD_BROKER', 'users'), + ], + + /* + |-------------------------------------------------------------------------- + | Authentication Guards + |-------------------------------------------------------------------------- + | + | Next, you may define every authentication guard for your application. + | Of course, a great default configuration has been defined for you + | which utilizes session storage plus the Eloquent user provider. + | + | All authentication guards have a user provider, which defines how the + | users are actually retrieved out of your database or other storage + | system used by the application. Typically, Eloquent is utilized. + | + | Supported: "session" + | + */ + + 'guards' => [ + 'web' => [ + 'driver' => 'session', + 'provider' => 'users', + ], + ], + + /* + |-------------------------------------------------------------------------- + | User Providers + |-------------------------------------------------------------------------- + | + | All authentication guards have a user provider, which defines how the + | users are actually retrieved out of your database or other storage + | system used by the application. Typically, Eloquent is utilized. + | + | If you have multiple user tables or models you may configure multiple + | providers to represent the model / table. These providers may then + | be assigned to any extra authentication guards you have defined. + | + | Supported: "database", "eloquent" + | + */ + + 'providers' => [ + 'users' => [ + 'driver' => 'eloquent', + 'model' => env('AUTH_MODEL', App\Models\User::class), + ], + + // 'users' => [ + // 'driver' => 'database', + // 'table' => 'users', + // ], + ], + + /* + |-------------------------------------------------------------------------- + | Resetting Passwords + |-------------------------------------------------------------------------- + | + | These configuration options specify the behavior of Laravel's password + | reset functionality, including the table utilized for token storage + | and the user provider that is invoked to actually retrieve users. + | + | The expiry time is the number of minutes that each reset token will be + | considered valid. This security feature keeps tokens short-lived so + | they have less time to be guessed. You may change this as needed. + | + | The throttle setting is the number of seconds a user must wait before + | generating more password reset tokens. This prevents the user from + | quickly generating a very large amount of password reset tokens. + | + */ + + 'passwords' => [ + 'users' => [ + 'provider' => 'users', + 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'), + 'expire' => 60, + 'throttle' => 60, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Password Confirmation Timeout + |-------------------------------------------------------------------------- + | + | Here you may define the amount of seconds before a password confirmation + | window expires and users are asked to re-enter their password via the + | confirmation screen. By default, the timeout lasts for three hours. + | + */ + + 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800), + +]; diff --git a/config/cache.php b/config/cache.php new file mode 100644 index 0000000..925f7d2 --- /dev/null +++ b/config/cache.php @@ -0,0 +1,108 @@ + env('CACHE_STORE', 'database'), + + /* + |-------------------------------------------------------------------------- + | Cache Stores + |-------------------------------------------------------------------------- + | + | Here you may define all of the cache "stores" for your application as + | well as their drivers. You may even define multiple stores for the + | same cache driver to group types of items stored in your caches. + | + | Supported drivers: "array", "database", "file", "memcached", + | "redis", "dynamodb", "octane", "null" + | + */ + + 'stores' => [ + + 'array' => [ + 'driver' => 'array', + 'serialize' => false, + ], + + 'database' => [ + 'driver' => 'database', + 'connection' => env('DB_CACHE_CONNECTION'), + 'table' => env('DB_CACHE_TABLE', 'cache'), + 'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'), + 'lock_table' => env('DB_CACHE_LOCK_TABLE'), + ], + + 'file' => [ + 'driver' => 'file', + 'path' => storage_path('framework/cache/data'), + 'lock_path' => storage_path('framework/cache/data'), + ], + + 'memcached' => [ + 'driver' => 'memcached', + 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), + 'sasl' => [ + env('MEMCACHED_USERNAME'), + env('MEMCACHED_PASSWORD'), + ], + 'options' => [ + // Memcached::OPT_CONNECT_TIMEOUT => 2000, + ], + 'servers' => [ + [ + 'host' => env('MEMCACHED_HOST', '127.0.0.1'), + 'port' => env('MEMCACHED_PORT', 11211), + 'weight' => 100, + ], + ], + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'), + 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'), + ], + + 'dynamodb' => [ + 'driver' => 'dynamodb', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), + 'endpoint' => env('DYNAMODB_ENDPOINT'), + ], + + 'octane' => [ + 'driver' => 'octane', + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Cache Key Prefix + |-------------------------------------------------------------------------- + | + | When utilizing the APC, database, memcached, Redis, and DynamoDB cache + | stores, there might be other applications using the same cache. For + | that reason, you may prefix every cache key to avoid collisions. + | + */ + + 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'), + +]; diff --git a/config/database.php b/config/database.php new file mode 100644 index 0000000..8910562 --- /dev/null +++ b/config/database.php @@ -0,0 +1,174 @@ + env('DB_CONNECTION', 'sqlite'), + + /* + |-------------------------------------------------------------------------- + | Database Connections + |-------------------------------------------------------------------------- + | + | Below are all of the database connections defined for your application. + | An example configuration is provided for each database system which + | is supported by Laravel. You're free to add / remove connections. + | + */ + + 'connections' => [ + + 'sqlite' => [ + 'driver' => 'sqlite', + 'url' => env('DB_URL'), + 'database' => env('DB_DATABASE', database_path('database.sqlite')), + 'prefix' => '', + 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), + 'busy_timeout' => null, + 'journal_mode' => null, + 'synchronous' => null, + ], + + 'mysql' => [ + 'driver' => 'mysql', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + + 'mariadb' => [ + 'driver' => 'mariadb', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + + 'pgsql' => [ + 'driver' => 'pgsql', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => env('DB_CHARSET', 'utf8'), + 'prefix' => '', + 'prefix_indexes' => true, + 'search_path' => 'public', + 'sslmode' => 'prefer', + ], + + 'sqlsrv' => [ + 'driver' => 'sqlsrv', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', '1433'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => env('DB_CHARSET', 'utf8'), + 'prefix' => '', + 'prefix_indexes' => true, + // 'encrypt' => env('DB_ENCRYPT', 'yes'), + // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'), + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Migration Repository Table + |-------------------------------------------------------------------------- + | + | This table keeps track of all the migrations that have already run for + | your application. Using this information, we can determine which of + | the migrations on disk haven't actually been run on the database. + | + */ + + 'migrations' => [ + 'table' => 'migrations', + 'update_date_on_publish' => true, + ], + + /* + |-------------------------------------------------------------------------- + | Redis Databases + |-------------------------------------------------------------------------- + | + | Redis is an open source, fast, and advanced key-value store that also + | provides a richer body of commands than a typical key-value system + | such as Memcached. You may define your connection settings here. + | + */ + + 'redis' => [ + + 'client' => env('REDIS_CLIENT', 'phpredis'), + + 'options' => [ + 'cluster' => env('REDIS_CLUSTER', 'redis'), + 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), + 'persistent' => env('REDIS_PERSISTENT', false), + ], + + 'default' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_DB', '0'), + ], + + 'cache' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_CACHE_DB', '1'), + ], + + ], + +]; diff --git a/config/filesystems.php b/config/filesystems.php new file mode 100644 index 0000000..3d671bd --- /dev/null +++ b/config/filesystems.php @@ -0,0 +1,80 @@ + env('FILESYSTEM_DISK', 'local'), + + /* + |-------------------------------------------------------------------------- + | Filesystem Disks + |-------------------------------------------------------------------------- + | + | Below you may configure as many filesystem disks as necessary, and you + | may even configure multiple disks for the same driver. Examples for + | most supported storage drivers are configured here for reference. + | + | Supported drivers: "local", "ftp", "sftp", "s3" + | + */ + + 'disks' => [ + + 'local' => [ + 'driver' => 'local', + 'root' => storage_path('app/private'), + 'serve' => true, + 'throw' => false, + 'report' => false, + ], + + 'public' => [ + 'driver' => 'local', + 'root' => storage_path('app/public'), + 'url' => env('APP_URL').'/storage', + 'visibility' => 'public', + 'throw' => false, + 'report' => false, + ], + + 's3' => [ + 'driver' => 's3', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION'), + 'bucket' => env('AWS_BUCKET'), + 'url' => env('AWS_URL'), + 'endpoint' => env('AWS_ENDPOINT'), + 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), + 'throw' => false, + 'report' => false, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Symbolic Links + |-------------------------------------------------------------------------- + | + | Here you may configure the symbolic links that will be created when the + | `storage:link` Artisan command is executed. The array keys should be + | the locations of the links and the values should be their targets. + | + */ + + 'links' => [ + public_path('storage') => storage_path('app/public'), + ], + +]; diff --git a/config/logging.php b/config/logging.php new file mode 100644 index 0000000..8d94292 --- /dev/null +++ b/config/logging.php @@ -0,0 +1,132 @@ + env('LOG_CHANNEL', 'stack'), + + /* + |-------------------------------------------------------------------------- + | Deprecations Log Channel + |-------------------------------------------------------------------------- + | + | This option controls the log channel that should be used to log warnings + | regarding deprecated PHP and library features. This allows you to get + | your application ready for upcoming major versions of dependencies. + | + */ + + 'deprecations' => [ + 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), + 'trace' => env('LOG_DEPRECATIONS_TRACE', false), + ], + + /* + |-------------------------------------------------------------------------- + | Log Channels + |-------------------------------------------------------------------------- + | + | Here you may configure the log channels for your application. Laravel + | utilizes the Monolog PHP logging library, which includes a variety + | of powerful log handlers and formatters that you're free to use. + | + | Available drivers: "single", "daily", "slack", "syslog", + | "errorlog", "monolog", "custom", "stack" + | + */ + + 'channels' => [ + + 'stack' => [ + 'driver' => 'stack', + 'channels' => explode(',', env('LOG_STACK', 'single')), + 'ignore_exceptions' => false, + ], + + 'single' => [ + 'driver' => 'single', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'replace_placeholders' => true, + ], + + 'daily' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'days' => env('LOG_DAILY_DAYS', 14), + 'replace_placeholders' => true, + ], + + 'slack' => [ + 'driver' => 'slack', + 'url' => env('LOG_SLACK_WEBHOOK_URL'), + 'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'), + 'emoji' => env('LOG_SLACK_EMOJI', ':boom:'), + 'level' => env('LOG_LEVEL', 'critical'), + 'replace_placeholders' => true, + ], + + 'papertrail' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class), + 'handler_with' => [ + 'host' => env('PAPERTRAIL_URL'), + 'port' => env('PAPERTRAIL_PORT'), + 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'), + ], + 'processors' => [PsrLogMessageProcessor::class], + ], + + 'stderr' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => StreamHandler::class, + 'formatter' => env('LOG_STDERR_FORMATTER'), + 'with' => [ + 'stream' => 'php://stderr', + ], + 'processors' => [PsrLogMessageProcessor::class], + ], + + 'syslog' => [ + 'driver' => 'syslog', + 'level' => env('LOG_LEVEL', 'debug'), + 'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER), + 'replace_placeholders' => true, + ], + + 'errorlog' => [ + 'driver' => 'errorlog', + 'level' => env('LOG_LEVEL', 'debug'), + 'replace_placeholders' => true, + ], + + 'null' => [ + 'driver' => 'monolog', + 'handler' => NullHandler::class, + ], + + 'emergency' => [ + 'path' => storage_path('logs/laravel.log'), + ], + + ], + +]; diff --git a/config/mail.php b/config/mail.php new file mode 100644 index 0000000..756305b --- /dev/null +++ b/config/mail.php @@ -0,0 +1,116 @@ + env('MAIL_MAILER', 'log'), + + /* + |-------------------------------------------------------------------------- + | Mailer Configurations + |-------------------------------------------------------------------------- + | + | Here you may configure all of the mailers used by your application plus + | their respective settings. Several examples have been configured for + | you and you are free to add your own as your application requires. + | + | Laravel supports a variety of mail "transport" drivers that can be used + | when delivering an email. You may specify which one you're using for + | your mailers below. You may also add additional mailers if needed. + | + | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2", + | "postmark", "resend", "log", "array", + | "failover", "roundrobin" + | + */ + + 'mailers' => [ + + 'smtp' => [ + 'transport' => 'smtp', + 'scheme' => env('MAIL_SCHEME'), + 'url' => env('MAIL_URL'), + 'host' => env('MAIL_HOST', '127.0.0.1'), + 'port' => env('MAIL_PORT', 2525), + 'username' => env('MAIL_USERNAME'), + 'password' => env('MAIL_PASSWORD'), + 'timeout' => null, + 'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url(env('APP_URL', 'http://localhost'), PHP_URL_HOST)), + ], + + 'ses' => [ + 'transport' => 'ses', + ], + + 'postmark' => [ + 'transport' => 'postmark', + // 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'), + // 'client' => [ + // 'timeout' => 5, + // ], + ], + + 'resend' => [ + 'transport' => 'resend', + ], + + 'sendmail' => [ + 'transport' => 'sendmail', + 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'), + ], + + 'log' => [ + 'transport' => 'log', + 'channel' => env('MAIL_LOG_CHANNEL'), + ], + + 'array' => [ + 'transport' => 'array', + ], + + 'failover' => [ + 'transport' => 'failover', + 'mailers' => [ + 'smtp', + 'log', + ], + ], + + 'roundrobin' => [ + 'transport' => 'roundrobin', + 'mailers' => [ + 'ses', + 'postmark', + ], + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Global "From" Address + |-------------------------------------------------------------------------- + | + | You may wish for all emails sent by your application to be sent from + | the same address. Here you may specify a name and address that is + | used globally for all emails that are sent by your application. + | + */ + + 'from' => [ + 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), + 'name' => env('MAIL_FROM_NAME', 'Example'), + ], + +]; diff --git a/config/queue.php b/config/queue.php new file mode 100644 index 0000000..116bd8d --- /dev/null +++ b/config/queue.php @@ -0,0 +1,112 @@ + env('QUEUE_CONNECTION', 'database'), + + /* + |-------------------------------------------------------------------------- + | Queue Connections + |-------------------------------------------------------------------------- + | + | Here you may configure the connection options for every queue backend + | used by your application. An example configuration is provided for + | each backend supported by Laravel. You're also free to add more. + | + | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" + | + */ + + 'connections' => [ + + 'sync' => [ + 'driver' => 'sync', + ], + + 'database' => [ + 'driver' => 'database', + 'connection' => env('DB_QUEUE_CONNECTION'), + 'table' => env('DB_QUEUE_TABLE', 'jobs'), + 'queue' => env('DB_QUEUE', 'default'), + 'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90), + 'after_commit' => false, + ], + + 'beanstalkd' => [ + 'driver' => 'beanstalkd', + 'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'), + 'queue' => env('BEANSTALKD_QUEUE', 'default'), + 'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90), + 'block_for' => 0, + 'after_commit' => false, + ], + + 'sqs' => [ + 'driver' => 'sqs', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), + 'queue' => env('SQS_QUEUE', 'default'), + 'suffix' => env('SQS_SUFFIX'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'after_commit' => false, + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), + 'queue' => env('REDIS_QUEUE', 'default'), + 'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90), + 'block_for' => null, + 'after_commit' => false, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Job Batching + |-------------------------------------------------------------------------- + | + | The following options configure the database and table that store job + | batching information. These options can be updated to any database + | connection and table which has been defined by your application. + | + */ + + 'batching' => [ + 'database' => env('DB_CONNECTION', 'sqlite'), + 'table' => 'job_batches', + ], + + /* + |-------------------------------------------------------------------------- + | Failed Queue Jobs + |-------------------------------------------------------------------------- + | + | These options configure the behavior of failed queue job logging so you + | can control how and where failed jobs are stored. Laravel ships with + | support for storing failed jobs in a simple file or in a database. + | + | Supported drivers: "database-uuids", "dynamodb", "file", "null" + | + */ + + 'failed' => [ + 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), + 'database' => env('DB_CONNECTION', 'sqlite'), + 'table' => 'failed_jobs', + ], + +]; diff --git a/config/services.php b/config/services.php new file mode 100644 index 0000000..27a3617 --- /dev/null +++ b/config/services.php @@ -0,0 +1,38 @@ + [ + 'token' => env('POSTMARK_TOKEN'), + ], + + 'ses' => [ + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + ], + + 'resend' => [ + 'key' => env('RESEND_KEY'), + ], + + 'slack' => [ + 'notifications' => [ + 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), + 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), + ], + ], + +]; diff --git a/config/session.php b/config/session.php new file mode 100644 index 0000000..ba0aa60 --- /dev/null +++ b/config/session.php @@ -0,0 +1,217 @@ + env('SESSION_DRIVER', 'database'), + + /* + |-------------------------------------------------------------------------- + | Session Lifetime + |-------------------------------------------------------------------------- + | + | Here you may specify the number of minutes that you wish the session + | to be allowed to remain idle before it expires. If you want them + | to expire immediately when the browser is closed then you may + | indicate that via the expire_on_close configuration option. + | + */ + + 'lifetime' => (int) env('SESSION_LIFETIME', 120), + + 'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false), + + /* + |-------------------------------------------------------------------------- + | Session Encryption + |-------------------------------------------------------------------------- + | + | This option allows you to easily specify that all of your session data + | should be encrypted before it's stored. All encryption is performed + | automatically by Laravel and you may use the session like normal. + | + */ + + 'encrypt' => env('SESSION_ENCRYPT', false), + + /* + |-------------------------------------------------------------------------- + | Session File Location + |-------------------------------------------------------------------------- + | + | When utilizing the "file" session driver, the session files are placed + | on disk. The default storage location is defined here; however, you + | are free to provide another location where they should be stored. + | + */ + + 'files' => storage_path('framework/sessions'), + + /* + |-------------------------------------------------------------------------- + | Session Database Connection + |-------------------------------------------------------------------------- + | + | When using the "database" or "redis" session drivers, you may specify a + | connection that should be used to manage these sessions. This should + | correspond to a connection in your database configuration options. + | + */ + + 'connection' => env('SESSION_CONNECTION'), + + /* + |-------------------------------------------------------------------------- + | Session Database Table + |-------------------------------------------------------------------------- + | + | When using the "database" session driver, you may specify the table to + | be used to store sessions. Of course, a sensible default is defined + | for you; however, you're welcome to change this to another table. + | + */ + + 'table' => env('SESSION_TABLE', 'sessions'), + + /* + |-------------------------------------------------------------------------- + | Session Cache Store + |-------------------------------------------------------------------------- + | + | When using one of the framework's cache driven session backends, you may + | define the cache store which should be used to store the session data + | between requests. This must match one of your defined cache stores. + | + | Affects: "apc", "dynamodb", "memcached", "redis" + | + */ + + 'store' => env('SESSION_STORE'), + + /* + |-------------------------------------------------------------------------- + | Session Sweeping Lottery + |-------------------------------------------------------------------------- + | + | Some session drivers must manually sweep their storage location to get + | rid of old sessions from storage. Here are the chances that it will + | happen on a given request. By default, the odds are 2 out of 100. + | + */ + + 'lottery' => [2, 100], + + /* + |-------------------------------------------------------------------------- + | Session Cookie Name + |-------------------------------------------------------------------------- + | + | Here you may change the name of the session cookie that is created by + | the framework. Typically, you should not need to change this value + | since doing so does not grant a meaningful security improvement. + | + */ + + 'cookie' => env( + 'SESSION_COOKIE', + Str::slug(env('APP_NAME', 'laravel'), '_').'_session' + ), + + /* + |-------------------------------------------------------------------------- + | Session Cookie Path + |-------------------------------------------------------------------------- + | + | The session cookie path determines the path for which the cookie will + | be regarded as available. Typically, this will be the root path of + | your application, but you're free to change this when necessary. + | + */ + + 'path' => env('SESSION_PATH', '/'), + + /* + |-------------------------------------------------------------------------- + | Session Cookie Domain + |-------------------------------------------------------------------------- + | + | This value determines the domain and subdomains the session cookie is + | available to. By default, the cookie will be available to the root + | domain and all subdomains. Typically, this shouldn't be changed. + | + */ + + 'domain' => env('SESSION_DOMAIN'), + + /* + |-------------------------------------------------------------------------- + | HTTPS Only Cookies + |-------------------------------------------------------------------------- + | + | By setting this option to true, session cookies will only be sent back + | to the server if the browser has a HTTPS connection. This will keep + | the cookie from being sent to you when it can't be done securely. + | + */ + + 'secure' => env('SESSION_SECURE_COOKIE'), + + /* + |-------------------------------------------------------------------------- + | HTTP Access Only + |-------------------------------------------------------------------------- + | + | Setting this value to true will prevent JavaScript from accessing the + | value of the cookie and the cookie will only be accessible through + | the HTTP protocol. It's unlikely you should disable this option. + | + */ + + 'http_only' => env('SESSION_HTTP_ONLY', true), + + /* + |-------------------------------------------------------------------------- + | Same-Site Cookies + |-------------------------------------------------------------------------- + | + | This option determines how your cookies behave when cross-site requests + | take place, and can be used to mitigate CSRF attacks. By default, we + | will set this value to "lax" to permit secure cross-site requests. + | + | See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value + | + | Supported: "lax", "strict", "none", null + | + */ + + 'same_site' => env('SESSION_SAME_SITE', 'lax'), + + /* + |-------------------------------------------------------------------------- + | Partitioned Cookies + |-------------------------------------------------------------------------- + | + | Setting this value to true will tie the cookie to the top-level site for + | a cross-site context. Partitioned cookies are accepted by the browser + | when flagged "secure" and the Same-Site attribute is set to "none". + | + */ + + 'partitioned' => env('SESSION_PARTITIONED_COOKIE', false), + +]; diff --git a/config/settings.php b/config/settings.php new file mode 100644 index 0000000..b84b6e9 --- /dev/null +++ b/config/settings.php @@ -0,0 +1,98 @@ + [ + App\Settings\GeneralSettings::class, + App\Settings\ServiceSettings::class, + App\Settings\InventorySettings::class, + App\Settings\NotificationSettings::class, + App\Settings\SecuritySettings::class, + ], + + /* + * The path where the settings classes will be created. + */ + 'setting_class_path' => app_path('Settings'), + + /* + * In these directories settings migrations will be stored and ran when migrating. A settings + * migration created via the make:settings-migration command will be stored in the first path or + * a custom defined path when running the command. + */ + 'migrations_paths' => [ + database_path('settings'), + ], + + /* + * When no repository was set for a settings class the following repository + * will be used for loading and saving settings. + */ + 'default_repository' => 'database', + + /* + * Settings will be stored and loaded from these repositories. + */ + 'repositories' => [ + 'database' => [ + 'type' => Spatie\LaravelSettings\SettingsRepositories\DatabaseSettingsRepository::class, + 'model' => null, + 'table' => null, + 'connection' => null, + ], + 'redis' => [ + 'type' => Spatie\LaravelSettings\SettingsRepositories\RedisSettingsRepository::class, + 'connection' => null, + 'prefix' => null, + ], + ], + + /* + * The encoder and decoder will determine how settings are stored and + * retrieved in the database. By default, `json_encode` and `json_decode` + * are used. + */ + 'encoder' => null, + 'decoder' => null, + + /* + * The contents of settings classes can be cached through your application, + * settings will be stored within a provided Laravel store and can have an + * additional prefix. + */ + 'cache' => [ + 'enabled' => env('SETTINGS_CACHE_ENABLED', false), + 'store' => null, + 'prefix' => null, + 'ttl' => null, + ], + + /* + * These global casts will be automatically used whenever a property within + * your settings class isn't a default PHP type. + */ + 'global_casts' => [ + DateTimeInterface::class => Spatie\LaravelSettings\SettingsCasts\DateTimeInterfaceCast::class, + DateTimeZone::class => Spatie\LaravelSettings\SettingsCasts\DateTimeZoneCast::class, +// Spatie\DataTransferObject\DataTransferObject::class => Spatie\LaravelSettings\SettingsCasts\DtoCast::class, + Spatie\LaravelData\Data::class => Spatie\LaravelSettings\SettingsCasts\DataCast::class, + ], + + /* + * The package will look for settings in these paths and automatically + * register them. + */ + 'auto_discover_settings' => [ + app_path('Settings'), + ], + + /* + * Automatically discovered settings classes can be cached, so they don't + * need to be searched each time the application boots up. + */ + 'discovered_settings_cache_path' => base_path('bootstrap/cache'), +]; diff --git a/database/.gitignore b/database/.gitignore new file mode 100644 index 0000000..9b19b93 --- /dev/null +++ b/database/.gitignore @@ -0,0 +1 @@ +*.sqlite* diff --git a/database/factories/AppointmentFactory.php b/database/factories/AppointmentFactory.php new file mode 100644 index 0000000..2fbf2d3 --- /dev/null +++ b/database/factories/AppointmentFactory.php @@ -0,0 +1,23 @@ + + */ +class AppointmentFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/database/factories/CustomerFactory.php b/database/factories/CustomerFactory.php new file mode 100644 index 0000000..b35cb1a --- /dev/null +++ b/database/factories/CustomerFactory.php @@ -0,0 +1,34 @@ + + */ +class CustomerFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'first_name' => $this->faker->firstName(), + 'last_name' => $this->faker->lastName(), + 'email' => $this->faker->unique()->safeEmail(), + 'phone' => $this->faker->phoneNumber(), + 'secondary_phone' => $this->faker->optional(0.3)->phoneNumber(), + 'address' => $this->faker->streetAddress(), + 'city' => $this->faker->city(), + 'state' => $this->faker->stateAbbr(), + 'zip_code' => $this->faker->postcode(), + 'notes' => $this->faker->optional(0.4)->paragraph(), + 'status' => $this->faker->randomElement(['active', 'inactive']), + 'last_service_date' => $this->faker->optional(0.7)->dateTimeBetween('-2 years', 'now'), + ]; + } +} diff --git a/database/factories/PartFactory.php b/database/factories/PartFactory.php new file mode 100644 index 0000000..203f99d --- /dev/null +++ b/database/factories/PartFactory.php @@ -0,0 +1,23 @@ + + */ +class PartFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/database/factories/ReportFactory.php b/database/factories/ReportFactory.php new file mode 100644 index 0000000..5726df8 --- /dev/null +++ b/database/factories/ReportFactory.php @@ -0,0 +1,138 @@ + + */ +class ReportFactory extends Factory +{ + protected $model = \App\Models\Report::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + $types = ['revenue', 'customer', 'service', 'performance']; + $type = $this->faker->randomElement($types); + + return [ + 'name' => $this->faker->words(3, true) . ' Report', + 'type' => $type, + 'data' => $this->generateDataForType($type), + 'filters' => [ + 'date_from' => $this->faker->dateTimeBetween('-6 months', '-1 month')->format('Y-m-d'), + 'date_to' => $this->faker->dateTimeBetween('-1 month', 'now')->format('Y-m-d'), + ], + 'generated_at' => $this->faker->dateTimeBetween('-1 month', 'now'), + 'generated_by' => User::factory(), + ]; + } + + private function generateDataForType(string $type): array + { + switch ($type) { + case 'revenue': + return [ + 'total_revenue' => $this->faker->numberBetween(10000, 100000), + 'monthly_revenue' => collect(range(1, 12))->mapWithKeys(fn($month) => [ + 'month_' . $month => $this->faker->numberBetween(5000, 15000) + ])->toArray(), + 'service_revenue' => [ + 'oil_change' => $this->faker->numberBetween(2000, 8000), + 'brake_repair' => $this->faker->numberBetween(3000, 12000), + 'engine_repair' => $this->faker->numberBetween(5000, 20000), + 'transmission' => $this->faker->numberBetween(2000, 10000), + ], + ]; + + case 'customer': + return [ + 'total_customers' => $this->faker->numberBetween(100, 1000), + 'new_customers' => $this->faker->numberBetween(10, 50), + 'retention_rate' => $this->faker->numberBetween(70, 95), + 'customer_segments' => [ + 'new' => $this->faker->numberBetween(10, 30), + 'regular' => $this->faker->numberBetween(40, 70), + 'vip' => $this->faker->numberBetween(5, 20), + ], + ]; + + case 'service': + return [ + 'total_services' => $this->faker->numberBetween(200, 2000), + 'service_distribution' => [ + 'maintenance' => $this->faker->numberBetween(40, 60), + 'repair' => $this->faker->numberBetween(30, 50), + 'inspection' => $this->faker->numberBetween(10, 20), + 'emergency' => $this->faker->numberBetween(5, 15), + ], + 'popular_services' => [ + 'oil_change' => $this->faker->numberBetween(50, 200), + 'brake_check' => $this->faker->numberBetween(30, 150), + 'tire_rotation' => $this->faker->numberBetween(20, 100), + ], + ]; + + case 'performance': + return [ + 'total_technicians' => $this->faker->numberBetween(5, 20), + 'average_efficiency' => $this->faker->numberBetween(75, 95), + 'customer_satisfaction' => $this->faker->numberBetween(80, 98), + 'technician_performance' => collect(range(1, 5))->mapWithKeys(fn($i) => [ + 'tech_' . $i => [ + 'jobs_completed' => $this->faker->numberBetween(20, 100), + 'efficiency' => $this->faker->numberBetween(70, 100), + 'quality_score' => $this->faker->numberBetween(75, 100), + ] + ])->toArray(), + ]; + + default: + return []; + } + } + + public function revenue(): static + { + return $this->state(fn () => [ + 'type' => 'revenue', + 'name' => 'Revenue Analysis Report', + 'data' => $this->generateDataForType('revenue'), + ]); + } + + public function customer(): static + { + return $this->state(fn () => [ + 'type' => 'customer', + 'name' => 'Customer Analytics Report', + 'data' => $this->generateDataForType('customer'), + ]); + } + + public function service(): static + { + return $this->state(fn () => [ + 'type' => 'service', + 'name' => 'Service Trends Report', + 'data' => $this->generateDataForType('service'), + ]); + } + + public function performance(): static + { + return $this->state(fn () => [ + 'type' => 'performance', + 'name' => 'Performance Metrics Report', + 'data' => $this->generateDataForType('performance'), + ]); + } +} diff --git a/database/factories/ServiceItemFactory.php b/database/factories/ServiceItemFactory.php new file mode 100644 index 0000000..4744959 --- /dev/null +++ b/database/factories/ServiceItemFactory.php @@ -0,0 +1,23 @@ + + */ +class ServiceItemFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/database/factories/ServiceOrderFactory.php b/database/factories/ServiceOrderFactory.php new file mode 100644 index 0000000..a864393 --- /dev/null +++ b/database/factories/ServiceOrderFactory.php @@ -0,0 +1,23 @@ + + */ +class ServiceOrderFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/database/factories/ServiceOrderPartFactory.php b/database/factories/ServiceOrderPartFactory.php new file mode 100644 index 0000000..332069c --- /dev/null +++ b/database/factories/ServiceOrderPartFactory.php @@ -0,0 +1,23 @@ + + */ +class ServiceOrderPartFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/database/factories/TechnicianFactory.php b/database/factories/TechnicianFactory.php new file mode 100644 index 0000000..c733995 --- /dev/null +++ b/database/factories/TechnicianFactory.php @@ -0,0 +1,43 @@ + + */ +class TechnicianFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + $specializations = [ + ['engine', 'transmission'], + ['electrical', 'air_conditioning'], + ['brakes', 'suspension'], + ['diagnostic', 'computer_systems'], + ['bodywork', 'paint'], + ['general_maintenance'], + ]; + + return [ + 'first_name' => $this->faker->firstName(), + 'last_name' => $this->faker->lastName(), + 'email' => $this->faker->unique()->safeEmail(), + 'phone' => $this->faker->phoneNumber(), + 'employee_id' => 'EMP-' . $this->faker->unique()->numberBetween(1000, 9999), + 'hourly_rate' => $this->faker->randomFloat(2, 25, 65), + 'specializations' => $this->faker->randomElement($specializations), + 'skill_level' => $this->faker->randomElement(['apprentice', 'journeyman', 'master']), + 'certifications' => $this->faker->optional(0.7)->text(100), + 'status' => $this->faker->randomElement(['active', 'inactive', 'on_break']), + 'shift_start' => $this->faker->time('H:i', '09:00'), + 'shift_end' => $this->faker->time('H:i', '18:00'), + ]; + } +} diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php new file mode 100644 index 0000000..584104c --- /dev/null +++ b/database/factories/UserFactory.php @@ -0,0 +1,44 @@ + + */ +class UserFactory extends Factory +{ + /** + * The current password being used by the factory. + */ + protected static ?string $password; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'name' => fake()->name(), + 'email' => fake()->unique()->safeEmail(), + 'email_verified_at' => now(), + 'password' => static::$password ??= Hash::make('password'), + 'remember_token' => Str::random(10), + ]; + } + + /** + * Indicate that the model's email address should be unverified. + */ + public function unverified(): static + { + return $this->state(fn (array $attributes) => [ + 'email_verified_at' => null, + ]); + } +} diff --git a/database/factories/VehicleFactory.php b/database/factories/VehicleFactory.php new file mode 100644 index 0000000..8f2f343 --- /dev/null +++ b/database/factories/VehicleFactory.php @@ -0,0 +1,48 @@ + + */ +class VehicleFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + $makes = ['Toyota', 'Honda', 'Ford', 'Chevrolet', 'Nissan', 'BMW', 'Mercedes-Benz', 'Audi', 'Volkswagen', 'Hyundai']; + $models = [ + 'Toyota' => ['Camry', 'Corolla', 'RAV4', 'Highlander', 'Prius'], + 'Honda' => ['Civic', 'Accord', 'CR-V', 'Pilot', 'Odyssey'], + 'Ford' => ['F-150', 'Explorer', 'Focus', 'Mustang', 'Edge'], + 'Chevrolet' => ['Silverado', 'Tahoe', 'Malibu', 'Equinox', 'Corvette'], + 'Nissan' => ['Altima', 'Sentra', 'Rogue', 'Pathfinder', '370Z'], + ]; + + $make = $this->faker->randomElement($makes); + $model = $this->faker->randomElement($models[$make] ?? ['Sedan', 'SUV', 'Coupe']); + + return [ + 'customer_id' => Customer::factory(), + 'vin' => strtoupper($this->faker->bothify('?????????########')), + 'make' => $make, + 'model' => $model, + 'year' => $this->faker->numberBetween(2010, 2024), + 'color' => $this->faker->colorName(), + 'license_plate' => $this->faker->optional(0.8)->bothify('???-####'), + 'engine_type' => $this->faker->randomElement(['4-Cylinder', 'V6', 'V8', 'Hybrid', 'Electric']), + 'transmission' => $this->faker->randomElement(['Manual', 'Automatic', 'CVT']), + 'mileage' => $this->faker->numberBetween(5000, 150000), + 'notes' => $this->faker->optional(0.3)->paragraph(), + 'status' => $this->faker->randomElement(['active', 'inactive']), + 'last_service_date' => $this->faker->optional(0.7)->dateTimeBetween('-1 year', 'now'), + ]; + } +} diff --git a/database/factories/VehicleInspectionFactory.php b/database/factories/VehicleInspectionFactory.php new file mode 100644 index 0000000..cf78194 --- /dev/null +++ b/database/factories/VehicleInspectionFactory.php @@ -0,0 +1,23 @@ + + */ +class VehicleInspectionFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php new file mode 100644 index 0000000..05fb5d9 --- /dev/null +++ b/database/migrations/0001_01_01_000000_create_users_table.php @@ -0,0 +1,49 @@ +id(); + $table->string('name'); + $table->string('email')->unique(); + $table->timestamp('email_verified_at')->nullable(); + $table->string('password'); + $table->rememberToken(); + $table->timestamps(); + }); + + Schema::create('password_reset_tokens', function (Blueprint $table) { + $table->string('email')->primary(); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + }); + + Schema::create('sessions', function (Blueprint $table) { + $table->string('id')->primary(); + $table->foreignId('user_id')->nullable()->index(); + $table->string('ip_address', 45)->nullable(); + $table->text('user_agent')->nullable(); + $table->longText('payload'); + $table->integer('last_activity')->index(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('users'); + Schema::dropIfExists('password_reset_tokens'); + Schema::dropIfExists('sessions'); + } +}; diff --git a/database/migrations/0001_01_01_000001_create_cache_table.php b/database/migrations/0001_01_01_000001_create_cache_table.php new file mode 100644 index 0000000..b9c106b --- /dev/null +++ b/database/migrations/0001_01_01_000001_create_cache_table.php @@ -0,0 +1,35 @@ +string('key')->primary(); + $table->mediumText('value'); + $table->integer('expiration'); + }); + + Schema::create('cache_locks', function (Blueprint $table) { + $table->string('key')->primary(); + $table->string('owner'); + $table->integer('expiration'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('cache'); + Schema::dropIfExists('cache_locks'); + } +}; diff --git a/database/migrations/0001_01_01_000002_create_jobs_table.php b/database/migrations/0001_01_01_000002_create_jobs_table.php new file mode 100644 index 0000000..425e705 --- /dev/null +++ b/database/migrations/0001_01_01_000002_create_jobs_table.php @@ -0,0 +1,57 @@ +id(); + $table->string('queue')->index(); + $table->longText('payload'); + $table->unsignedTinyInteger('attempts'); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + }); + + Schema::create('job_batches', function (Blueprint $table) { + $table->string('id')->primary(); + $table->string('name'); + $table->integer('total_jobs'); + $table->integer('pending_jobs'); + $table->integer('failed_jobs'); + $table->longText('failed_job_ids'); + $table->mediumText('options')->nullable(); + $table->integer('cancelled_at')->nullable(); + $table->integer('created_at'); + $table->integer('finished_at')->nullable(); + }); + + Schema::create('failed_jobs', function (Blueprint $table) { + $table->id(); + $table->string('uuid')->unique(); + $table->text('connection'); + $table->text('queue'); + $table->longText('payload'); + $table->longText('exception'); + $table->timestamp('failed_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('jobs'); + Schema::dropIfExists('job_batches'); + Schema::dropIfExists('failed_jobs'); + } +}; diff --git a/database/migrations/2022_12_14_083707_create_settings_table.php b/database/migrations/2022_12_14_083707_create_settings_table.php new file mode 100644 index 0000000..9b14b86 --- /dev/null +++ b/database/migrations/2022_12_14_083707_create_settings_table.php @@ -0,0 +1,24 @@ +id(); + + $table->string('group'); + $table->string('name'); + $table->boolean('locked')->default(false); + $table->json('payload'); + + $table->timestamps(); + + $table->unique(['group', 'name']); + }); + } +}; diff --git a/database/migrations/2025_07_21_092201_create_customers_table.php b/database/migrations/2025_07_21_092201_create_customers_table.php new file mode 100644 index 0000000..7c61df1 --- /dev/null +++ b/database/migrations/2025_07_21_092201_create_customers_table.php @@ -0,0 +1,39 @@ +id(); + $table->string('first_name'); + $table->string('last_name'); + $table->string('email')->unique(); + $table->string('phone'); + $table->string('secondary_phone')->nullable(); + $table->text('address'); + $table->string('city'); + $table->string('state'); + $table->string('zip_code'); + $table->text('notes')->nullable(); + $table->enum('status', ['active', 'inactive'])->default('active'); + $table->datetime('last_service_date')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('customers'); + } +}; diff --git a/database/migrations/2025_07_21_092218_create_vehicles_table.php b/database/migrations/2025_07_21_092218_create_vehicles_table.php new file mode 100644 index 0000000..51f000a --- /dev/null +++ b/database/migrations/2025_07_21_092218_create_vehicles_table.php @@ -0,0 +1,40 @@ +id(); + $table->foreignId('customer_id')->constrained()->onDelete('cascade'); + $table->string('vin')->unique(); + $table->string('make'); + $table->string('model'); + $table->integer('year'); + $table->string('color'); + $table->string('license_plate')->nullable(); + $table->string('engine_type')->nullable(); + $table->string('transmission')->nullable(); + $table->integer('mileage')->default(0); + $table->text('notes')->nullable(); + $table->enum('status', ['active', 'inactive'])->default('active'); + $table->datetime('last_service_date')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('vehicles'); + } +}; diff --git a/database/migrations/2025_07_21_092230_create_technicians_table.php b/database/migrations/2025_07_21_092230_create_technicians_table.php new file mode 100644 index 0000000..3ae9483 --- /dev/null +++ b/database/migrations/2025_07_21_092230_create_technicians_table.php @@ -0,0 +1,39 @@ +id(); + $table->string('first_name'); + $table->string('last_name'); + $table->string('email')->unique(); + $table->string('phone'); + $table->string('employee_id')->unique(); + $table->decimal('hourly_rate', 8, 2); + $table->json('specializations')->nullable(); // ['engine', 'transmission', 'electrical', etc.] + $table->enum('skill_level', ['apprentice', 'journeyman', 'master'])->default('journeyman'); + $table->text('certifications')->nullable(); + $table->enum('status', ['active', 'inactive', 'on_break'])->default('active'); + $table->time('shift_start')->nullable(); + $table->time('shift_end')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('technicians'); + } +}; diff --git a/database/migrations/2025_07_21_092241_create_service_orders_table.php b/database/migrations/2025_07_21_092241_create_service_orders_table.php new file mode 100644 index 0000000..c073f27 --- /dev/null +++ b/database/migrations/2025_07_21_092241_create_service_orders_table.php @@ -0,0 +1,47 @@ +id(); + $table->string('order_number')->unique(); + $table->foreignId('customer_id')->constrained()->onDelete('cascade'); + $table->foreignId('vehicle_id')->constrained()->onDelete('cascade'); + $table->foreignId('assigned_technician_id')->nullable()->constrained('technicians')->onDelete('set null'); + $table->text('customer_complaint')->nullable(); + $table->text('recommended_services')->nullable(); + $table->enum('priority', ['low', 'normal', 'high', 'urgent'])->default('normal'); + $table->enum('status', ['pending', 'in_progress', 'waiting_parts', 'waiting_approval', 'completed', 'cancelled'])->default('pending'); + $table->decimal('labor_cost', 10, 2)->default(0); + $table->decimal('parts_cost', 10, 2)->default(0); + $table->decimal('tax_amount', 10, 2)->default(0); + $table->decimal('discount_amount', 10, 2)->default(0); + $table->decimal('total_amount', 10, 2)->default(0); + $table->datetime('scheduled_date')->nullable(); + $table->datetime('started_at')->nullable(); + $table->datetime('completed_at')->nullable(); + $table->integer('estimated_hours')->nullable(); + $table->integer('actual_hours')->nullable(); + $table->text('internal_notes')->nullable(); + $table->text('customer_notes')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('service_orders'); + } +}; diff --git a/database/migrations/2025_07_21_092255_create_service_items_table.php b/database/migrations/2025_07_21_092255_create_service_items_table.php new file mode 100644 index 0000000..c9ef883 --- /dev/null +++ b/database/migrations/2025_07_21_092255_create_service_items_table.php @@ -0,0 +1,37 @@ +id(); + $table->foreignId('service_order_id')->constrained()->onDelete('cascade'); + $table->string('service_name'); + $table->text('description')->nullable(); + $table->enum('category', ['maintenance', 'repair', 'diagnostic', 'inspection', 'other']); + $table->decimal('labor_rate', 8, 2); + $table->decimal('estimated_hours', 5, 2); + $table->decimal('actual_hours', 5, 2)->nullable(); + $table->decimal('labor_cost', 10, 2); + $table->enum('status', ['pending', 'in_progress', 'completed', 'cancelled'])->default('pending'); + $table->text('technician_notes')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('service_items'); + } +}; diff --git a/database/migrations/2025_07_21_092302_create_parts_table.php b/database/migrations/2025_07_21_092302_create_parts_table.php new file mode 100644 index 0000000..f13aab7 --- /dev/null +++ b/database/migrations/2025_07_21_092302_create_parts_table.php @@ -0,0 +1,42 @@ +id(); + $table->string('part_number')->unique(); + $table->string('name'); + $table->text('description')->nullable(); + $table->string('manufacturer')->nullable(); + $table->string('category'); // engine, brake, electrical, etc. + $table->decimal('cost_price', 10, 2); + $table->decimal('sell_price', 10, 2); + $table->integer('quantity_on_hand')->default(0); + $table->integer('minimum_stock_level')->default(0); + $table->integer('maximum_stock_level')->nullable(); + $table->string('location')->nullable(); // shelf/bin location + $table->string('supplier')->nullable(); + $table->string('supplier_part_number')->nullable(); + $table->integer('lead_time_days')->nullable(); + $table->enum('status', ['active', 'discontinued', 'special_order'])->default('active'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('parts'); + } +}; diff --git a/database/migrations/2025_07_21_092312_create_service_order_parts_table.php b/database/migrations/2025_07_21_092312_create_service_order_parts_table.php new file mode 100644 index 0000000..2aa07f4 --- /dev/null +++ b/database/migrations/2025_07_21_092312_create_service_order_parts_table.php @@ -0,0 +1,36 @@ +id(); + $table->foreignId('service_order_id')->constrained()->onDelete('cascade'); + $table->foreignId('part_id')->constrained()->onDelete('cascade'); + $table->integer('quantity_used'); + $table->decimal('unit_cost', 10, 2); + $table->decimal('unit_price', 10, 2); + $table->decimal('total_cost', 10, 2); + $table->decimal('total_price', 10, 2); + $table->enum('status', ['ordered', 'received', 'installed', 'returned'])->default('ordered'); + $table->text('notes')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('service_order_parts'); + } +}; diff --git a/database/migrations/2025_07_21_092328_create_appointments_table.php b/database/migrations/2025_07_21_092328_create_appointments_table.php new file mode 100644 index 0000000..aae664e --- /dev/null +++ b/database/migrations/2025_07_21_092328_create_appointments_table.php @@ -0,0 +1,40 @@ +id(); + $table->foreignId('customer_id')->constrained()->onDelete('cascade'); + $table->foreignId('vehicle_id')->constrained()->onDelete('cascade'); + $table->foreignId('assigned_technician_id')->nullable()->constrained('technicians')->onDelete('set null'); + $table->datetime('scheduled_datetime'); + $table->integer('estimated_duration_minutes'); + $table->enum('appointment_type', ['maintenance', 'repair', 'inspection', 'estimate', 'pickup', 'delivery']); + $table->text('service_requested'); + $table->text('customer_notes')->nullable(); + $table->text('internal_notes')->nullable(); + $table->enum('status', ['scheduled', 'confirmed', 'in_progress', 'completed', 'cancelled', 'no_show'])->default('scheduled'); + $table->datetime('checked_in_at')->nullable(); + $table->datetime('completed_at')->nullable(); + $table->foreignId('service_order_id')->nullable()->constrained()->onDelete('set null'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('appointments'); + } +}; diff --git a/database/migrations/2025_07_21_092337_create_vehicle_inspections_table.php b/database/migrations/2025_07_21_092337_create_vehicle_inspections_table.php new file mode 100644 index 0000000..f4e0ac3 --- /dev/null +++ b/database/migrations/2025_07_21_092337_create_vehicle_inspections_table.php @@ -0,0 +1,40 @@ +id(); + $table->foreignId('service_order_id')->constrained()->onDelete('cascade'); + $table->foreignId('vehicle_id')->constrained()->onDelete('cascade'); + $table->foreignId('technician_id')->constrained()->onDelete('cascade'); + $table->integer('current_mileage'); + $table->json('inspection_checklist'); // flexible JSON structure for inspection items + $table->json('photos')->nullable(); // array of photo URLs/paths + $table->json('videos')->nullable(); // array of video URLs/paths + $table->text('overall_condition'); + $table->json('recommendations')->nullable(); // array of recommended services + $table->enum('priority_level', ['low', 'medium', 'high', 'urgent'])->default('medium'); + $table->text('customer_communication')->nullable(); + $table->boolean('customer_approved')->default(false); + $table->datetime('inspection_date'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('vehicle_inspections'); + } +}; diff --git a/database/migrations/2025_07_21_113333_add_vehicle_image_to_vehicles_table.php b/database/migrations/2025_07_21_113333_add_vehicle_image_to_vehicles_table.php new file mode 100644 index 0000000..82d004f --- /dev/null +++ b/database/migrations/2025_07_21_113333_add_vehicle_image_to_vehicles_table.php @@ -0,0 +1,28 @@ +string('vehicle_image')->nullable()->after('notes'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('vehicles', function (Blueprint $table) { + $table->dropColumn('vehicle_image'); + }); + } +}; diff --git a/database/migrations/2025_07_21_173209_create_suppliers_table.php b/database/migrations/2025_07_21_173209_create_suppliers_table.php new file mode 100644 index 0000000..1e7922d --- /dev/null +++ b/database/migrations/2025_07_21_173209_create_suppliers_table.php @@ -0,0 +1,47 @@ +id(); + $table->string('name'); + $table->string('company_name')->nullable(); + $table->string('email')->nullable(); + $table->string('phone')->nullable(); + $table->string('website')->nullable(); + $table->text('address')->nullable(); + $table->string('city')->nullable(); + $table->string('state')->nullable(); + $table->string('zip_code')->nullable(); + $table->string('country')->nullable(); + $table->string('contact_person')->nullable(); + $table->string('payment_terms')->nullable(); + $table->string('account_number')->nullable(); + $table->string('tax_id')->nullable(); + $table->text('notes')->nullable(); + $table->boolean('is_active')->default(true); + $table->decimal('rating', 2, 1)->nullable(); + $table->timestamps(); + + $table->index(['name', 'is_active']); + $table->index('company_name'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('suppliers'); + } +}; diff --git a/database/migrations/2025_07_21_173243_create_purchase_orders_table.php b/database/migrations/2025_07_21_173243_create_purchase_orders_table.php new file mode 100644 index 0000000..f5a98da --- /dev/null +++ b/database/migrations/2025_07_21_173243_create_purchase_orders_table.php @@ -0,0 +1,44 @@ +id(); + $table->foreignId('supplier_id')->constrained()->onDelete('cascade'); + $table->string('po_number')->unique(); + $table->date('order_date'); + $table->date('expected_date')->nullable(); + $table->date('received_date')->nullable(); + $table->enum('status', ['draft', 'pending', 'ordered', 'partial', 'received', 'cancelled'])->default('draft'); + $table->decimal('subtotal', 10, 2)->default(0); + $table->decimal('tax', 10, 2)->default(0); + $table->decimal('shipping', 10, 2)->default(0); + $table->decimal('total', 10, 2)->default(0); + $table->text('notes')->nullable(); + $table->string('approved_by')->nullable(); + $table->string('received_by')->nullable(); + $table->timestamps(); + + $table->index(['supplier_id', 'status']); + $table->index('po_number'); + $table->index('order_date'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('purchase_orders'); + } +}; diff --git a/database/migrations/2025_07_21_173253_create_purchase_order_items_table.php b/database/migrations/2025_07_21_173253_create_purchase_order_items_table.php new file mode 100644 index 0000000..3626833 --- /dev/null +++ b/database/migrations/2025_07_21_173253_create_purchase_order_items_table.php @@ -0,0 +1,36 @@ +id(); + $table->foreignId('purchase_order_id')->constrained()->onDelete('cascade'); + $table->foreignId('part_id')->constrained()->onDelete('cascade'); + $table->integer('quantity_ordered'); + $table->integer('quantity_received')->default(0); + $table->decimal('unit_cost', 10, 2); + $table->decimal('total_cost', 10, 2); + $table->text('notes')->nullable(); + $table->timestamps(); + + $table->index(['purchase_order_id', 'part_id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('purchase_order_items'); + } +}; diff --git a/database/migrations/2025_07_21_173315_create_stock_movements_table.php b/database/migrations/2025_07_21_173315_create_stock_movements_table.php new file mode 100644 index 0000000..0b15daa --- /dev/null +++ b/database/migrations/2025_07_21_173315_create_stock_movements_table.php @@ -0,0 +1,42 @@ +id(); + $table->foreignId('part_id')->constrained()->onDelete('cascade'); + $table->foreignId('supplier_id')->nullable()->constrained()->onDelete('set null'); + $table->foreignId('purchase_order_id')->nullable()->constrained()->onDelete('set null'); + $table->foreignId('service_order_id')->nullable()->constrained()->onDelete('set null'); + $table->enum('movement_type', ['in', 'out', 'adjustment', 'transfer', 'return']); + $table->integer('quantity'); + $table->string('reference_type')->nullable(); + $table->string('reference_id')->nullable(); + $table->decimal('unit_cost', 10, 2)->nullable(); + $table->decimal('total_cost', 10, 2)->nullable(); + $table->text('notes')->nullable(); + $table->foreignId('created_by')->constrained('users')->onDelete('cascade'); + $table->timestamps(); + + $table->index(['part_id', 'movement_type']); + $table->index('created_at'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('stock_movements'); + } +}; diff --git a/database/migrations/2025_07_21_173331_update_parts_table_for_inventory.php b/database/migrations/2025_07_21_173331_update_parts_table_for_inventory.php new file mode 100644 index 0000000..603449a --- /dev/null +++ b/database/migrations/2025_07_21_173331_update_parts_table_for_inventory.php @@ -0,0 +1,52 @@ +dropColumn('supplier'); + $table->foreignId('supplier_id')->nullable()->after('location')->constrained()->onDelete('set null'); + + // Add new inventory fields + $table->string('barcode')->nullable()->after('status'); + $table->decimal('weight', 8, 2)->nullable()->after('barcode'); + $table->string('dimensions')->nullable()->after('weight'); + $table->integer('warranty_period')->nullable()->after('dimensions'); // in days + $table->string('image')->nullable()->after('warranty_period'); + + // Add indexes for better performance + $table->index(['category', 'status']); + $table->index('barcode'); + $table->index(['quantity_on_hand', 'minimum_stock_level']); + }); + } + + public function down(): void + { + Schema::table('parts', function (Blueprint $table) { + $table->dropForeign(['supplier_id']); + $table->dropColumn([ + 'supplier_id', + 'barcode', + 'weight', + 'dimensions', + 'warranty_period', + 'image' + ]); + $table->string('supplier')->nullable(); + + $table->dropIndex(['category', 'status']); + $table->dropIndex(['barcode']); + $table->dropIndex(['quantity_on_hand', 'minimum_stock_level']); + }); + } +}; diff --git a/database/migrations/2025_07_21_185721_create_part_histories_table.php b/database/migrations/2025_07_21_185721_create_part_histories_table.php new file mode 100644 index 0000000..c76f856 --- /dev/null +++ b/database/migrations/2025_07_21_185721_create_part_histories_table.php @@ -0,0 +1,47 @@ +id(); + $table->foreignId('part_id')->constrained()->onDelete('cascade'); + $table->string('event_type'); // 'created', 'updated', 'stock_in', 'stock_out', 'adjustment', 'price_change', 'supplier_change' + $table->json('old_values')->nullable(); // Previous values for updates + $table->json('new_values')->nullable(); // New values for updates + $table->integer('quantity_change')->nullable(); // For stock movements + $table->integer('quantity_before')->nullable(); // Stock before change + $table->integer('quantity_after')->nullable(); // Stock after change + $table->decimal('cost_before', 10, 2)->nullable(); // Cost before change + $table->decimal('cost_after', 10, 2)->nullable(); // Cost after change + $table->string('reference_type')->nullable(); // 'purchase_order', 'service_order', 'manual', 'supplier' + $table->unsignedBigInteger('reference_id')->nullable(); // ID of related record + $table->text('notes')->nullable(); + $table->string('ip_address')->nullable(); + $table->text('user_agent')->nullable(); + $table->foreignId('created_by')->constrained('users')->onDelete('cascade'); + $table->timestamps(); + + $table->index(['part_id', 'event_type']); + $table->index(['part_id', 'created_at']); + $table->index(['event_type', 'created_at']); + $table->index(['reference_type', 'reference_id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('part_histories'); + } +}; diff --git a/database/migrations/2025_07_21_200911_create_technician_skills_table.php b/database/migrations/2025_07_21_200911_create_technician_skills_table.php new file mode 100644 index 0000000..9f86a00 --- /dev/null +++ b/database/migrations/2025_07_21_200911_create_technician_skills_table.php @@ -0,0 +1,39 @@ +id(); + $table->foreignId('technician_id')->constrained()->onDelete('cascade'); + $table->string('skill_name'); + $table->string('category')->default('general'); // engine, electrical, transmission, brakes, etc. + $table->integer('proficiency_level')->default(1); // 1-5 scale + $table->date('certified_date')->nullable(); + $table->date('certification_expires')->nullable(); + $table->string('certification_body')->nullable(); + $table->text('notes')->nullable(); + $table->boolean('is_primary_skill')->default(false); + $table->timestamps(); + + $table->index(['technician_id', 'skill_name']); + $table->index(['category', 'proficiency_level']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('technician_skills'); + } +}; diff --git a/database/migrations/2025_07_21_200928_create_technician_performances_table.php b/database/migrations/2025_07_21_200928_create_technician_performances_table.php new file mode 100644 index 0000000..7a0f64f --- /dev/null +++ b/database/migrations/2025_07_21_200928_create_technician_performances_table.php @@ -0,0 +1,38 @@ +id(); + $table->foreignId('technician_id')->constrained()->onDelete('cascade'); + $table->date('performance_date'); + $table->string('metric_type'); // jobs_completed, hours_worked, revenue_generated, customer_rating, etc. + $table->decimal('metric_value', 10, 2); + $table->string('period_type')->default('daily'); // daily, weekly, monthly, quarterly, yearly + $table->json('additional_data')->nullable(); // store complex performance data + $table->text('notes')->nullable(); + $table->timestamps(); + + $table->index(['technician_id', 'performance_date']); + $table->index(['metric_type', 'period_type']); + $table->index('performance_date'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('technician_performances'); + } +}; diff --git a/database/migrations/2025_07_21_201149_create_technician_workloads_table.php b/database/migrations/2025_07_21_201149_create_technician_workloads_table.php new file mode 100644 index 0000000..66da9be --- /dev/null +++ b/database/migrations/2025_07_21_201149_create_technician_workloads_table.php @@ -0,0 +1,42 @@ +id(); + $table->foreignId('technician_id')->constrained()->onDelete('cascade'); + $table->date('workload_date'); + $table->decimal('scheduled_hours', 5, 2)->default(0); + $table->decimal('actual_hours', 5, 2)->default(0); + $table->decimal('billable_hours', 5, 2)->default(0); + $table->integer('jobs_assigned')->default(0); + $table->integer('jobs_completed')->default(0); + $table->integer('jobs_in_progress')->default(0); + $table->decimal('utilization_rate', 5, 2)->default(0); // percentage + $table->decimal('efficiency_rate', 5, 2)->default(0); // percentage + $table->text('notes')->nullable(); + $table->timestamps(); + + $table->unique(['technician_id', 'workload_date']); + $table->index('workload_date'); + $table->index(['technician_id', 'workload_date']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('technician_workloads'); + } +}; diff --git a/database/migrations/2025_07_22_101906_create_reports_table.php b/database/migrations/2025_07_22_101906_create_reports_table.php new file mode 100644 index 0000000..4e8151b --- /dev/null +++ b/database/migrations/2025_07_22_101906_create_reports_table.php @@ -0,0 +1,36 @@ +id(); + $table->string('name'); + $table->string('type'); // revenue, customer, service, performance + $table->json('data'); + $table->json('filters')->nullable(); + $table->timestamp('generated_at'); + $table->unsignedBigInteger('generated_by')->nullable(); + $table->timestamps(); + + $table->foreign('generated_by')->references('id')->on('users')->onDelete('set null'); + $table->index(['type', 'generated_at']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('reports'); + } +}; diff --git a/database/migrations/2025_07_22_114738_create_job_cards_table.php b/database/migrations/2025_07_22_114738_create_job_cards_table.php new file mode 100644 index 0000000..ed65571 --- /dev/null +++ b/database/migrations/2025_07_22_114738_create_job_cards_table.php @@ -0,0 +1,52 @@ +id(); + $table->string('job_card_number')->unique(); + $table->string('branch_code', 10)->default('ACC'); + $table->foreignId('customer_id')->constrained()->onDelete('cascade'); + $table->foreignId('vehicle_id')->constrained()->onDelete('cascade'); + $table->foreignId('service_advisor_id')->nullable()->constrained('users')->onDelete('set null'); + $table->timestamp('arrival_datetime'); + $table->timestamp('expected_completion_date')->nullable(); + $table->integer('mileage_in')->nullable(); + $table->integer('mileage_out')->nullable(); + $table->string('fuel_level_in', 20)->nullable(); + $table->string('fuel_level_out', 20)->nullable(); + $table->enum('status', ['received', 'in_diagnosis', 'estimate_sent', 'approved', 'in_progress', 'quality_check', 'completed', 'delivered', 'cancelled'])->default('received'); + $table->enum('priority', ['low', 'medium', 'high', 'urgent'])->default('medium'); + $table->text('notes')->nullable(); + $table->text('customer_reported_issues')->nullable(); + $table->text('vehicle_condition_notes')->nullable(); + $table->string('keys_location')->nullable(); + $table->boolean('personal_items_removed')->default(false); + $table->boolean('photos_taken')->default(false); + $table->timestamp('completion_datetime')->nullable(); + $table->enum('delivery_method', ['pickup', 'delivery', 'towing'])->nullable(); + $table->integer('customer_satisfaction_rating')->nullable(); + $table->timestamps(); + + $table->index(['branch_code', 'status']); + $table->index(['customer_id', 'vehicle_id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('job_cards'); + } +}; diff --git a/database/migrations/2025_07_22_115022_create_diagnoses_table.php b/database/migrations/2025_07_22_115022_create_diagnoses_table.php new file mode 100644 index 0000000..75cdbd4 --- /dev/null +++ b/database/migrations/2025_07_22_115022_create_diagnoses_table.php @@ -0,0 +1,50 @@ +id(); + $table->foreignId('job_card_id')->constrained()->onDelete('cascade'); + $table->foreignId('service_coordinator_id')->constrained('users')->onDelete('cascade'); + $table->text('customer_reported_issues')->nullable(); + $table->text('diagnostic_findings')->nullable(); + $table->text('root_cause_analysis')->nullable(); + $table->text('recommended_repairs')->nullable(); + $table->text('additional_issues_found')->nullable(); + $table->enum('priority_level', ['low', 'medium', 'high', 'critical'])->default('medium'); + $table->decimal('estimated_repair_time', 8, 2)->nullable(); + $table->enum('diagnosis_status', ['in_progress', 'completed', 'pending_approval', 'approved'])->default('in_progress'); + $table->timestamp('diagnosis_date')->nullable(); + $table->json('photos')->nullable(); + $table->json('diagnostic_codes')->nullable(); + $table->json('test_results')->nullable(); + $table->json('parts_required')->nullable(); + $table->json('labor_operations')->nullable(); + $table->json('special_tools_required')->nullable(); + $table->text('safety_concerns')->nullable(); + $table->text('environmental_impact')->nullable(); + $table->boolean('customer_authorization_required')->default(false); + $table->text('notes')->nullable(); + $table->timestamps(); + + $table->index(['job_card_id', 'diagnosis_status']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('diagnoses'); + } +}; diff --git a/database/migrations/2025_07_22_115125_create_estimates_table.php b/database/migrations/2025_07_22_115125_create_estimates_table.php new file mode 100644 index 0000000..dfb2518 --- /dev/null +++ b/database/migrations/2025_07_22_115125_create_estimates_table.php @@ -0,0 +1,67 @@ +id(); + $table->string('estimate_number')->unique(); + $table->foreignId('job_card_id')->constrained()->onDelete('cascade'); + $table->foreignId('diagnosis_id')->constrained()->onDelete('cascade'); + $table->foreignId('prepared_by_id')->constrained('users')->onDelete('cascade'); + + // Cost breakdown + $table->decimal('labor_cost', 10, 2)->default(0); + $table->decimal('parts_cost', 10, 2)->default(0); + $table->decimal('miscellaneous_cost', 10, 2)->default(0); + $table->decimal('subtotal', 10, 2)->default(0); + $table->decimal('tax_rate', 5, 4)->default(8.25); + $table->decimal('tax_amount', 10, 2)->default(0); + $table->decimal('discount_amount', 10, 2)->default(0); + $table->decimal('total_amount', 10, 2)->default(0); + + // Terms and validity + $table->integer('validity_period_days')->default(30); + $table->text('terms_and_conditions')->nullable(); + + // Status tracking + $table->enum('status', ['draft', 'sent', 'viewed', 'approved', 'rejected', 'expired', 'revised'])->default('draft'); + $table->enum('customer_approval_status', ['pending', 'approved', 'rejected', 'partially_approved'])->default('pending'); + + // Customer interaction tracking + $table->timestamp('customer_approved_at')->nullable(); + $table->enum('customer_approval_method', ['email', 'sms', 'phone', 'in_person', 'portal'])->nullable(); + $table->timestamp('sent_to_customer_at')->nullable(); + $table->timestamp('sms_sent_at')->nullable(); + $table->timestamp('email_sent_at')->nullable(); + + // Notes and revisions + $table->text('notes')->nullable(); + $table->text('internal_notes')->nullable(); + $table->integer('revision_number')->default(1); + $table->foreignId('original_estimate_id')->nullable()->constrained('estimates')->onDelete('set null'); + + $table->timestamps(); + + // Indexes + $table->index(['status', 'customer_approval_status']); + $table->index(['job_card_id', 'diagnosis_id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('estimates'); + } +}; diff --git a/database/migrations/2025_07_22_115126_create_estimate_line_items_table.php b/database/migrations/2025_07_22_115126_create_estimate_line_items_table.php new file mode 100644 index 0000000..123e56f --- /dev/null +++ b/database/migrations/2025_07_22_115126_create_estimate_line_items_table.php @@ -0,0 +1,50 @@ +id(); + $table->foreignId('estimate_id')->constrained()->onDelete('cascade'); + $table->enum('type', ['labor', 'parts', 'miscellaneous']); + $table->foreignId('part_id')->nullable()->constrained()->onDelete('set null'); + + // Item details + $table->text('description'); + $table->decimal('quantity', 8, 2)->default(1); + $table->decimal('unit_price', 10, 2); + $table->decimal('total_amount', 10, 2); + + // Labor specific fields + $table->decimal('labor_hours', 8, 2)->nullable(); + $table->decimal('labor_rate', 10, 2)->nullable(); + + // Pricing details + $table->decimal('markup_percentage', 5, 2)->default(0); + $table->text('notes')->nullable(); + $table->boolean('required')->default(true); + $table->string('category', 100)->nullable(); + + $table->timestamps(); + + // Indexes + $table->index(['estimate_id', 'type']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('estimate_line_items'); + } +}; diff --git a/database/migrations/2025_07_22_115127_create_work_orders_table.php b/database/migrations/2025_07_22_115127_create_work_orders_table.php new file mode 100644 index 0000000..c212887 --- /dev/null +++ b/database/migrations/2025_07_22_115127_create_work_orders_table.php @@ -0,0 +1,64 @@ +id(); + $table->string('work_order_number')->unique(); + $table->foreignId('job_card_id')->constrained()->onDelete('cascade'); + $table->foreignId('estimate_id')->constrained()->onDelete('cascade'); + $table->foreignId('assigned_technician_id')->nullable()->constrained('users')->onDelete('set null'); + $table->foreignId('supervisor_id')->nullable()->constrained('users')->onDelete('set null'); + + // Work order details + $table->enum('status', ['scheduled', 'in_progress', 'awaiting_parts', 'quality_check', 'completed', 'on_hold'])->default('scheduled'); + $table->enum('priority', ['low', 'medium', 'high', 'urgent'])->default('medium'); + $table->timestamp('scheduled_start_date')->nullable(); + $table->timestamp('actual_start_date')->nullable(); + $table->timestamp('estimated_completion_date')->nullable(); + $table->timestamp('actual_completion_date')->nullable(); + + // Progress tracking + $table->decimal('progress_percentage', 5, 2)->default(0); + $table->text('work_description')->nullable(); + $table->text('technician_notes')->nullable(); + $table->text('supervisor_notes')->nullable(); + $table->text('quality_check_notes')->nullable(); + $table->boolean('quality_check_passed')->nullable(); + $table->foreignId('quality_checked_by')->nullable()->constrained('users')->onDelete('set null'); + $table->timestamp('quality_check_date')->nullable(); + + // Time tracking + $table->decimal('estimated_hours', 8, 2)->nullable(); + $table->decimal('actual_hours', 8, 2)->default(0); + + // Costs + $table->decimal('labor_cost', 10, 2)->default(0); + $table->decimal('parts_cost', 10, 2)->default(0); + $table->decimal('total_cost', 10, 2)->default(0); + + $table->timestamps(); + + // Indexes + $table->index(['status', 'assigned_technician_id']); + $table->index(['job_card_id', 'status']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('work_orders'); + } +}; diff --git a/database/migrations/2025_07_22_115128_create_work_order_tasks_table.php b/database/migrations/2025_07_22_115128_create_work_order_tasks_table.php new file mode 100644 index 0000000..5839078 --- /dev/null +++ b/database/migrations/2025_07_22_115128_create_work_order_tasks_table.php @@ -0,0 +1,55 @@ +id(); + $table->foreignId('work_order_id')->constrained()->onDelete('cascade'); + $table->string('task_number'); + $table->string('title'); + $table->text('description')->nullable(); + + // Task assignment and status + $table->foreignId('assigned_to')->nullable()->constrained('users')->onDelete('set null'); + $table->enum('status', ['pending', 'in_progress', 'completed', 'on_hold', 'cancelled'])->default('pending'); + $table->enum('priority', ['low', 'medium', 'high', 'urgent'])->default('medium'); + + // Time tracking + $table->decimal('estimated_hours', 8, 2)->nullable(); + $table->decimal('actual_hours', 8, 2)->default(0); + $table->timestamp('started_at')->nullable(); + $table->timestamp('completed_at')->nullable(); + + // Task details + $table->integer('sort_order')->default(0); + $table->text('notes')->nullable(); + $table->text('completion_notes')->nullable(); + $table->boolean('requires_parts')->default(false); + $table->json('required_skills')->nullable(); + + $table->timestamps(); + + // Indexes + $table->index(['work_order_id', 'status']); + $table->index(['assigned_to', 'status']); + $table->unique(['work_order_id', 'task_number']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('work_order_tasks'); + } +}; diff --git a/database/migrations/2025_07_22_115129_create_timesheets_table.php b/database/migrations/2025_07_22_115129_create_timesheets_table.php new file mode 100644 index 0000000..4e7a153 --- /dev/null +++ b/database/migrations/2025_07_22_115129_create_timesheets_table.php @@ -0,0 +1,64 @@ +id(); + $table->foreignId('user_id')->constrained()->onDelete('cascade'); + $table->foreignId('job_card_id')->nullable()->constrained()->onDelete('cascade'); + $table->foreignId('work_order_id')->nullable()->constrained()->onDelete('cascade'); + $table->foreignId('work_order_task_id')->nullable()->constrained()->onDelete('cascade'); + $table->foreignId('diagnosis_id')->nullable()->constrained()->onDelete('cascade'); + + // Time tracking + $table->date('date'); + $table->time('start_time'); + $table->time('end_time')->nullable(); + $table->decimal('hours_worked', 8, 2)->nullable(); + $table->decimal('break_hours', 8, 2)->default(0); + $table->decimal('billable_hours', 8, 2)->nullable(); + + // Entry details + $table->enum('entry_type', ['clock_in', 'clock_out', 'break_start', 'break_end', 'manual'])->default('manual'); + $table->enum('status', ['draft', 'submitted', 'approved', 'rejected'])->default('draft'); + $table->text('description')->nullable(); + $table->text('work_performed')->nullable(); + $table->text('notes')->nullable(); + + // Approval workflow + $table->foreignId('approved_by')->nullable()->constrained('users')->onDelete('set null'); + $table->timestamp('approved_at')->nullable(); + $table->text('approval_notes')->nullable(); + + // Rate and billing + $table->decimal('hourly_rate', 10, 2)->nullable(); + $table->decimal('total_amount', 10, 2)->nullable(); + $table->boolean('is_overtime')->default(false); + $table->decimal('overtime_multiplier', 3, 2)->default(1.5); + + $table->timestamps(); + + // Indexes + $table->index(['user_id', 'date']); + $table->index(['job_card_id', 'date']); + $table->index(['status', 'date']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('timesheets'); + } +}; diff --git a/database/migrations/2025_07_22_120746_create_work_order_parts_table.php b/database/migrations/2025_07_22_120746_create_work_order_parts_table.php new file mode 100644 index 0000000..5c2c620 --- /dev/null +++ b/database/migrations/2025_07_22_120746_create_work_order_parts_table.php @@ -0,0 +1,37 @@ +id(); + $table->foreignId('work_order_id')->constrained()->onDelete('cascade'); + $table->foreignId('part_id')->constrained()->onDelete('cascade'); + $table->decimal('quantity_used', 8, 2); + $table->decimal('unit_cost', 10, 2); + $table->enum('status', ['allocated', 'used', 'returned', 'damaged'])->default('allocated'); + $table->timestamp('allocated_at')->nullable(); + $table->timestamp('used_at')->nullable(); + $table->timestamps(); + + $table->unique(['work_order_id', 'part_id']); + $table->index(['work_order_id', 'status']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('work_order_parts'); + } +}; diff --git a/database/migrations/2025_07_22_120810_add_workflow_fields_to_users_table.php b/database/migrations/2025_07_22_120810_add_workflow_fields_to_users_table.php new file mode 100644 index 0000000..3880cf3 --- /dev/null +++ b/database/migrations/2025_07_22_120810_add_workflow_fields_to_users_table.php @@ -0,0 +1,54 @@ +enum('role', [ + 'admin', + 'service_supervisor', + 'service_coordinator', + 'service_advisor', + 'parts_manager', + 'technician', + 'quality_inspector', + 'customer_service', + 'manager' + ])->default('technician')->after('email_verified_at'); + $table->string('branch_code', 10)->default('ACC')->after('role'); + $table->string('employee_id', 50)->unique()->nullable()->after('branch_code'); + $table->string('department', 100)->nullable()->after('employee_id'); + $table->string('phone', 20)->nullable()->after('department'); + $table->string('emergency_contact', 255)->nullable()->after('phone'); + $table->date('hire_date')->nullable()->after('emergency_contact'); + $table->enum('status', ['active', 'inactive', 'terminated', 'on_leave'])->default('active')->after('hire_date'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn([ + 'role', + 'branch_code', + 'employee_id', + 'department', + 'phone', + 'emergency_contact', + 'hire_date', + 'status' + ]); + }); + } +}; diff --git a/database/migrations/2025_07_22_120843_add_workflow_fields_to_vehicle_inspections_table.php b/database/migrations/2025_07_22_120843_add_workflow_fields_to_vehicle_inspections_table.php new file mode 100644 index 0000000..bca0330 --- /dev/null +++ b/database/migrations/2025_07_22_120843_add_workflow_fields_to_vehicle_inspections_table.php @@ -0,0 +1,49 @@ +text('signature_inspector')->nullable()->after('inspection_date'); + } + if (!Schema::hasColumn('vehicle_inspections', 'signature_customer')) { + $table->text('signature_customer')->nullable()->after('signature_inspector'); + } + if (!Schema::hasColumn('vehicle_inspections', 'notes')) { + $table->text('notes')->nullable()->after('signature_customer'); + } + if (!Schema::hasColumn('vehicle_inspections', 'follow_up_required')) { + $table->boolean('follow_up_required')->default(false)->after('notes'); + } + if (!Schema::hasColumn('vehicle_inspections', 'quality_rating')) { + $table->integer('quality_rating')->nullable()->after('follow_up_required'); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('vehicle_inspections', function (Blueprint $table) { + $table->dropColumn([ + 'signature_inspector', + 'signature_customer', + 'notes', + 'follow_up_required', + 'quality_rating', + ]); + }); + } +}; diff --git a/database/migrations/2025_07_24_135226_create_roles_and_permissions_tables.php b/database/migrations/2025_07_24_135226_create_roles_and_permissions_tables.php new file mode 100644 index 0000000..1e52e70 --- /dev/null +++ b/database/migrations/2025_07_24_135226_create_roles_and_permissions_tables.php @@ -0,0 +1,85 @@ +id(); + $table->string('name')->unique(); + $table->string('display_name'); + $table->text('description')->nullable(); + $table->boolean('is_active')->default(true); + $table->timestamps(); + }); + + // Permissions table + Schema::create('permissions', function (Blueprint $table) { + $table->id(); + $table->string('name')->unique(); + $table->string('display_name'); + $table->text('description')->nullable(); + $table->string('module'); // e.g., 'job_cards', 'inventory', 'customers' + $table->boolean('is_active')->default(true); + $table->timestamps(); + }); + + // Role permissions pivot table + Schema::create('role_permissions', function (Blueprint $table) { + $table->id(); + $table->foreignId('role_id')->constrained()->onDelete('cascade'); + $table->foreignId('permission_id')->constrained()->onDelete('cascade'); + $table->timestamps(); + + $table->unique(['role_id', 'permission_id']); + }); + + // User roles table (users can have multiple roles) + Schema::create('user_roles', function (Blueprint $table) { + $table->id(); + $table->foreignId('user_id')->constrained()->onDelete('cascade'); + $table->foreignId('role_id')->constrained()->onDelete('cascade'); + $table->string('branch_code')->nullable(); // Role can be branch-specific + $table->boolean('is_active')->default(true); + $table->timestamp('assigned_at')->useCurrent(); + $table->timestamp('expires_at')->nullable(); + $table->timestamps(); + + $table->unique(['user_id', 'role_id', 'branch_code']); + }); + + // User permissions table (direct permissions to users) + Schema::create('user_permissions', function (Blueprint $table) { + $table->id(); + $table->foreignId('user_id')->constrained()->onDelete('cascade'); + $table->foreignId('permission_id')->constrained()->onDelete('cascade'); + $table->boolean('granted')->default(true); // true = grant, false = deny + $table->string('branch_code')->nullable(); // Permission can be branch-specific + $table->timestamp('assigned_at')->useCurrent(); + $table->timestamp('expires_at')->nullable(); + $table->timestamps(); + + $table->unique(['user_id', 'permission_id', 'branch_code']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('user_permissions'); + Schema::dropIfExists('user_roles'); + Schema::dropIfExists('role_permissions'); + Schema::dropIfExists('permissions'); + Schema::dropIfExists('roles'); + } +}; diff --git a/database/migrations/2025_07_25_080805_add_additional_user_fields_to_users_table.php b/database/migrations/2025_07_25_080805_add_additional_user_fields_to_users_table.php new file mode 100644 index 0000000..47547d9 --- /dev/null +++ b/database/migrations/2025_07_25_080805_add_additional_user_fields_to_users_table.php @@ -0,0 +1,52 @@ +string('position')->nullable()->after('department'); + $table->decimal('salary', 10, 2)->nullable()->after('hire_date'); + + // Personal information + $table->string('emergency_contact_name')->nullable()->after('emergency_contact'); + $table->string('emergency_contact_phone')->nullable()->after('emergency_contact_name'); + $table->text('address')->nullable()->after('emergency_contact_phone'); + $table->date('date_of_birth')->nullable()->after('address'); + $table->string('national_id')->nullable()->after('date_of_birth'); + + // System tracking + $table->timestamp('last_login_at')->nullable()->after('updated_at'); + + // Remove old emergency_contact field if it exists + // $table->dropColumn('emergency_contact'); // Uncomment if needed + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn([ + 'position', + 'salary', + 'emergency_contact_name', + 'emergency_contact_phone', + 'address', + 'date_of_birth', + 'national_id', + 'last_login_at', + ]); + }); + } +}; diff --git a/database/migrations/2025_07_30_000001_update_status_enum_in_users_table.php b/database/migrations/2025_07_30_000001_update_status_enum_in_users_table.php new file mode 100644 index 0000000..e4a9971 --- /dev/null +++ b/database/migrations/2025_07_30_000001_update_status_enum_in_users_table.php @@ -0,0 +1,30 @@ +enum('status', ['active', 'inactive', 'terminated', 'on_leave', 'suspended']) + ->default('active') + ->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('status'); + }); + } +}; diff --git a/database/migrations/2025_07_30_094950_create_activity_log_table.php b/database/migrations/2025_07_30_094950_create_activity_log_table.php new file mode 100644 index 0000000..7c05bc8 --- /dev/null +++ b/database/migrations/2025_07_30_094950_create_activity_log_table.php @@ -0,0 +1,27 @@ +create(config('activitylog.table_name'), function (Blueprint $table) { + $table->bigIncrements('id'); + $table->string('log_name')->nullable(); + $table->text('description'); + $table->nullableMorphs('subject', 'subject'); + $table->nullableMorphs('causer', 'causer'); + $table->json('properties')->nullable(); + $table->timestamps(); + $table->index('log_name'); + }); + } + + public function down() + { + Schema::connection(config('activitylog.database_connection'))->dropIfExists(config('activitylog.table_name')); + } +} diff --git a/database/migrations/2025_07_30_094951_add_event_column_to_activity_log_table.php b/database/migrations/2025_07_30_094951_add_event_column_to_activity_log_table.php new file mode 100644 index 0000000..7b797fd --- /dev/null +++ b/database/migrations/2025_07_30_094951_add_event_column_to_activity_log_table.php @@ -0,0 +1,22 @@ +table(config('activitylog.table_name'), function (Blueprint $table) { + $table->string('event')->nullable()->after('subject_type'); + }); + } + + public function down() + { + Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) { + $table->dropColumn('event'); + }); + } +} diff --git a/database/migrations/2025_07_30_094952_add_batch_uuid_column_to_activity_log_table.php b/database/migrations/2025_07_30_094952_add_batch_uuid_column_to_activity_log_table.php new file mode 100644 index 0000000..8f7db66 --- /dev/null +++ b/database/migrations/2025_07_30_094952_add_batch_uuid_column_to_activity_log_table.php @@ -0,0 +1,22 @@ +table(config('activitylog.table_name'), function (Blueprint $table) { + $table->uuid('batch_uuid')->nullable()->after('properties'); + }); + } + + public function down() + { + Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) { + $table->dropColumn('batch_uuid'); + }); + } +} diff --git a/database/migrations/2025_07_30_095042_create_branches_table.php b/database/migrations/2025_07_30_095042_create_branches_table.php new file mode 100644 index 0000000..0473c17 --- /dev/null +++ b/database/migrations/2025_07_30_095042_create_branches_table.php @@ -0,0 +1,39 @@ +id(); + $table->string('code')->unique(); + $table->string('name'); + $table->string('address')->nullable(); + $table->string('phone')->nullable(); + $table->string('email')->nullable(); + $table->string('manager_name')->nullable(); + $table->string('city')->nullable(); + $table->string('state')->nullable(); + $table->string('postal_code')->nullable(); + $table->boolean('is_active')->default(true); + $table->timestamps(); + + $table->index(['code', 'is_active']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('branches'); + } +}; diff --git a/database/migrations/2025_07_30_095441_add_password_changed_at_to_users_table.php b/database/migrations/2025_07_30_095441_add_password_changed_at_to_users_table.php new file mode 100644 index 0000000..4931867 --- /dev/null +++ b/database/migrations/2025_07_30_095441_add_password_changed_at_to_users_table.php @@ -0,0 +1,28 @@ +timestamp('password_changed_at')->nullable()->after('password'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('password_changed_at'); + }); + } +}; diff --git a/database/seeders/AppointmentSeeder.php b/database/seeders/AppointmentSeeder.php new file mode 100644 index 0000000..b53e982 --- /dev/null +++ b/database/seeders/AppointmentSeeder.php @@ -0,0 +1,137 @@ +get(); + $vehicles = Vehicle::limit(5)->get(); + $technicians = Technician::limit(5)->get(); + + if ($customers->isEmpty() || $vehicles->isEmpty() || $technicians->isEmpty()) { + $this->command->warn('Not enough seed data. Please run customer, vehicle, and technician seeders first.'); + return; + } + + // Clear existing appointments + Appointment::truncate(); + + $appointments = [ + // Today's appointments + [ + 'customer_id' => $customers[0]->id, + 'vehicle_id' => $vehicles[0]->id, + 'assigned_technician_id' => $technicians[0]->id, + 'scheduled_datetime' => Carbon::today()->setTime(10, 0), + 'estimated_duration_minutes' => 60, + 'appointment_type' => 'maintenance', + 'service_requested' => 'Oil Change & Filter', + 'status' => 'confirmed', + 'customer_notes' => 'Regular maintenance appointment', + ], + [ + 'customer_id' => $customers[1]->id, + 'vehicle_id' => $vehicles[1]->id, + 'assigned_technician_id' => $technicians[1]->id, + 'scheduled_datetime' => Carbon::today()->setTime(14, 30), + 'estimated_duration_minutes' => 120, + 'appointment_type' => 'repair', + 'service_requested' => 'Brake Inspection & Repair', + 'status' => 'scheduled', + 'customer_notes' => 'Customer reports squeaking brakes', + ], + + // Tomorrow's appointments + [ + 'customer_id' => $customers[2]->id, + 'vehicle_id' => $vehicles[2]->id, + 'assigned_technician_id' => $technicians[2]->id, + 'scheduled_datetime' => Carbon::tomorrow()->setTime(9, 0), + 'estimated_duration_minutes' => 90, + 'appointment_type' => 'inspection', + 'service_requested' => 'Annual State Inspection', + 'status' => 'confirmed', + 'customer_notes' => 'Annual inspection due', + ], + [ + 'customer_id' => $customers[0]->id, + 'vehicle_id' => $vehicles[3]->id, + 'assigned_technician_id' => $technicians[0]->id, + 'scheduled_datetime' => Carbon::tomorrow()->setTime(15, 0), + 'estimated_duration_minutes' => 180, + 'appointment_type' => 'repair', + 'service_requested' => 'Engine Diagnostic', + 'status' => 'scheduled', + 'customer_notes' => 'Check engine light is on', + ], + + // This week + [ + 'customer_id' => $customers[3]->id, + 'vehicle_id' => $vehicles[4]->id, + 'assigned_technician_id' => $technicians[3]->id, + 'scheduled_datetime' => Carbon::now()->addDays(2)->setTime(11, 0), + 'estimated_duration_minutes' => 60, + 'appointment_type' => 'maintenance', + 'service_requested' => 'Tire Rotation', + 'status' => 'confirmed', + 'customer_notes' => 'Rotate tires and check alignment', + ], + [ + 'customer_id' => $customers[4]->id, + 'vehicle_id' => $vehicles[0]->id, + 'assigned_technician_id' => $technicians[4]->id, + 'scheduled_datetime' => Carbon::now()->addDays(3)->setTime(13, 30), + 'estimated_duration_minutes' => 150, + 'appointment_type' => 'repair', + 'service_requested' => 'AC System Repair', + 'status' => 'scheduled', + 'customer_notes' => 'AC not cooling properly', + ], + + // Next week + [ + 'customer_id' => $customers[1]->id, + 'vehicle_id' => $vehicles[2]->id, + 'assigned_technician_id' => $technicians[1]->id, + 'scheduled_datetime' => Carbon::now()->addWeek()->setTime(10, 30), + 'estimated_duration_minutes' => 90, + 'appointment_type' => 'maintenance', + 'service_requested' => 'Transmission Service', + 'status' => 'confirmed', + 'customer_notes' => 'Regular transmission fluid change', + ], + [ + 'customer_id' => $customers[2]->id, + 'vehicle_id' => $vehicles[1]->id, + 'assigned_technician_id' => $technicians[2]->id, + 'scheduled_datetime' => Carbon::now()->addWeek()->addDay()->setTime(14, 0), + 'estimated_duration_minutes' => 120, + 'appointment_type' => 'estimate', + 'service_requested' => 'Body Work Estimate', + 'status' => 'scheduled', + 'customer_notes' => 'Minor fender damage estimate', + ], + ]; + + foreach ($appointments as $appointmentData) { + Appointment::create($appointmentData); + } + + $this->command->info('Created ' . count($appointments) . ' sample appointments'); + } +} diff --git a/database/seeders/BranchSeeder.php b/database/seeders/BranchSeeder.php new file mode 100644 index 0000000..798113b --- /dev/null +++ b/database/seeders/BranchSeeder.php @@ -0,0 +1,68 @@ + 'MAIN', + 'name' => 'Main Branch', + 'address' => '123 Main Street', + 'phone' => '+1-555-0100', + 'email' => 'main@carrepairshop.com', + 'manager_name' => 'John Manager', + 'city' => 'Downtown', + 'state' => 'State', + 'postal_code' => '12345', + 'is_active' => true, + ]); + + Branch::create([ + 'code' => 'NORTH', + 'name' => 'North Branch', + 'address' => '456 North Avenue', + 'phone' => '+1-555-0200', + 'email' => 'north@carrepairshop.com', + 'manager_name' => 'Jane North', + 'city' => 'Northside', + 'state' => 'State', + 'postal_code' => '12346', + 'is_active' => true, + ]); + + Branch::create([ + 'code' => 'SOUTH', + 'name' => 'South Branch', + 'address' => '789 South Boulevard', + 'phone' => '+1-555-0300', + 'email' => 'south@carrepairshop.com', + 'manager_name' => 'Bob South', + 'city' => 'Southside', + 'state' => 'State', + 'postal_code' => '12347', + 'is_active' => true, + ]); + + Branch::create([ + 'code' => 'EAST', + 'name' => 'East Branch', + 'address' => '321 East Road', + 'phone' => '+1-555-0400', + 'email' => 'east@carrepairshop.com', + 'manager_name' => 'Alice East', + 'city' => 'Eastside', + 'state' => 'State', + 'postal_code' => '12348', + 'is_active' => true, + ]); + } +} diff --git a/database/seeders/CustomerSeeder.php b/database/seeders/CustomerSeeder.php new file mode 100644 index 0000000..5d1bfc0 --- /dev/null +++ b/database/seeders/CustomerSeeder.php @@ -0,0 +1,17 @@ + 'admin@carrepairs.com'], + [ + 'name' => 'Shop Manager', + 'email_verified_at' => now(), + 'password' => bcrypt('password'), + ] + ); + + // Create additional users + User::factory(3)->create(); + + // Create technicians + Technician::factory(8)->create(); + + // Create customers with vehicles + Customer::factory(50) + ->has(Vehicle::factory()->count(rand(1, 3))) + ->create(); + + // Create common parts + $commonParts = [ + ['name' => 'Engine Oil Filter', 'part_number' => 'OF-001', 'category' => 'engine', 'cost_price' => 12.50, 'sell_price' => 18.99], + ['name' => 'Air Filter', 'part_number' => 'AF-001', 'category' => 'engine', 'cost_price' => 8.75, 'sell_price' => 14.99], + ['name' => 'Brake Pads - Front', 'part_number' => 'BP-F001', 'category' => 'brakes', 'cost_price' => 45.00, 'sell_price' => 79.99], + ['name' => 'Brake Pads - Rear', 'part_number' => 'BP-R001', 'category' => 'brakes', 'cost_price' => 38.00, 'sell_price' => 69.99], + ['name' => 'Spark Plugs (Set of 4)', 'part_number' => 'SP-001', 'category' => 'engine', 'cost_price' => 25.00, 'sell_price' => 44.99], + ['name' => 'Cabin Air Filter', 'part_number' => 'CAF-001', 'category' => 'hvac', 'cost_price' => 15.00, 'sell_price' => 24.99], + ['name' => 'Windshield Wipers', 'part_number' => 'WW-001', 'category' => 'exterior', 'cost_price' => 12.00, 'sell_price' => 19.99], + ['name' => 'Coolant (1 Gallon)', 'part_number' => 'CL-001', 'category' => 'cooling', 'cost_price' => 8.50, 'sell_price' => 16.99], + ['name' => 'Transmission Fluid', 'part_number' => 'TF-001', 'category' => 'transmission', 'cost_price' => 22.00, 'sell_price' => 34.99], + ['name' => 'Battery', 'part_number' => 'BAT-001', 'category' => 'electrical', 'cost_price' => 85.00, 'sell_price' => 149.99], + ]; + + foreach ($commonParts as $part) { + Part::firstOrCreate( + ['part_number' => $part['part_number']], + array_merge($part, [ + 'description' => "High quality {$part['name']} for various vehicle makes and models", + 'manufacturer' => 'OEM', + 'quantity_on_hand' => rand(10, 100), + 'minimum_stock_level' => 5, + 'location' => 'A' . rand(1, 5) . '-' . rand(1, 10), + 'status' => 'active', + ]) + ); + } + + // Create some service orders with realistic data + $vehicles = Vehicle::all(); + $technicians = Technician::where('status', 'active')->get(); + + foreach ($vehicles->random(min(30, $vehicles->count())) as $vehicle) { + ServiceOrder::factory()->create([ + 'customer_id' => $vehicle->customer_id, + 'vehicle_id' => $vehicle->id, + 'assigned_technician_id' => $technicians->random()->id, + 'status' => collect(['pending', 'in_progress', 'completed', 'waiting_parts'])->random(), + ]); + } + + $this->command->info('Database seeded successfully with car repair shop data!'); + } +} diff --git a/database/seeders/PartSeeder.php b/database/seeders/PartSeeder.php new file mode 100644 index 0000000..504a7e0 --- /dev/null +++ b/database/seeders/PartSeeder.php @@ -0,0 +1,17 @@ +insert([ + [ + 'name' => 'Q1 Revenue Analysis', + 'type' => 'revenue', + 'data' => json_encode([ + 'total_revenue' => 125000.50, + 'monthly_revenue' => [ + '2025-01' => 15000, + '2025-02' => 18000, + '2025-03' => 22000, + ], + 'service_revenue' => [ + 'Oil Change' => 8500, + 'Brake Repair' => 15000, + 'Engine Repair' => 35000, + ] + ]), + 'filters' => json_encode([ + 'date_from' => '2025-01-01', + 'date_to' => '2025-03-31', + ]), + 'generated_at' => now(), + 'generated_by' => 1, + 'created_at' => now(), + 'updated_at' => now(), + ], + [ + 'name' => 'Customer Analytics Report', + 'type' => 'customer', + 'data' => json_encode([ + 'total_customers' => 542, + 'new_customers' => 47, + 'retention_rate' => 78.5, + 'customer_segments' => [ + 'new' => 47, + 'regular' => 385, + 'vip' => 110, + ] + ]), + 'filters' => json_encode([ + 'date_from' => '2025-01-01', + 'date_to' => '2025-06-30', + ]), + 'generated_at' => now(), + 'generated_by' => 1, + 'created_at' => now(), + 'updated_at' => now(), + ], + ]); + } +} diff --git a/database/seeders/RolesAndPermissionsSeeder.php b/database/seeders/RolesAndPermissionsSeeder.php new file mode 100644 index 0000000..ee207b2 --- /dev/null +++ b/database/seeders/RolesAndPermissionsSeeder.php @@ -0,0 +1,386 @@ + [ + ['name' => 'dashboard.view', 'display_name' => 'View Dashboard', 'description' => 'Can view main dashboard'], + ['name' => 'analytics.view', 'display_name' => 'View Analytics', 'description' => 'Can view analytics and reports'], + ['name' => 'settings.manage', 'display_name' => 'Manage Settings', 'description' => 'Can manage system settings'], + ['name' => 'users.manage', 'display_name' => 'Manage Users', 'description' => 'Can manage user accounts'], + ['name' => 'roles.manage', 'display_name' => 'Manage Roles', 'description' => 'Can manage roles and permissions'], + ], + + // Customer Management + 'customers' => [ + ['name' => 'customers.view', 'display_name' => 'View Customers', 'description' => 'Can view customer list'], + ['name' => 'customers.create', 'display_name' => 'Create Customers', 'description' => 'Can create new customers'], + ['name' => 'customers.update', 'display_name' => 'Edit Customers', 'description' => 'Can edit customer information'], + ['name' => 'customers.delete', 'display_name' => 'Delete Customers', 'description' => 'Can delete customers'], + ['name' => 'customers.details', 'display_name' => 'View Customer Details', 'description' => 'Can view detailed customer information'], + ], + + // Vehicle Management + 'vehicles' => [ + ['name' => 'vehicles.view', 'display_name' => 'View Vehicles', 'description' => 'Can view vehicle list'], + ['name' => 'vehicles.create', 'display_name' => 'Create Vehicles', 'description' => 'Can register new vehicles'], + ['name' => 'vehicles.update', 'display_name' => 'Edit Vehicles', 'description' => 'Can edit vehicle information'], + ['name' => 'vehicles.delete', 'display_name' => 'Delete Vehicles', 'description' => 'Can delete vehicles'], + ['name' => 'vehicles.history', 'display_name' => 'View Vehicle History', 'description' => 'Can view vehicle service history'], + ], + + // Service Orders + 'service-orders' => [ + ['name' => 'service-orders.view', 'display_name' => 'View Service Orders', 'description' => 'Can view service orders'], + ['name' => 'service-orders.create', 'display_name' => 'Create Service Orders', 'description' => 'Can create new service orders'], + ['name' => 'service-orders.update', 'display_name' => 'Edit Service Orders', 'description' => 'Can edit service orders'], + ['name' => 'service-orders.delete', 'display_name' => 'Delete Service Orders', 'description' => 'Can delete service orders'], + ['name' => 'service-orders.approve', 'display_name' => 'Approve Service Orders', 'description' => 'Can approve service orders'], + ['name' => 'service-orders.complete', 'display_name' => 'Complete Service Orders', 'description' => 'Can mark service orders as complete'], + ], + + // Job Cards + 'job-cards' => [ + ['name' => 'job-cards.view', 'display_name' => 'View Job Cards', 'description' => 'Can view job cards'], + ['name' => 'job-cards.create', 'display_name' => 'Create Job Cards', 'description' => 'Can create new job cards'], + ['name' => 'job-cards.update', 'display_name' => 'Edit Job Cards', 'description' => 'Can edit job cards'], + ['name' => 'job-cards.delete', 'display_name' => 'Delete Job Cards', 'description' => 'Can delete job cards'], + ['name' => 'job-cards.assign', 'display_name' => 'Assign Job Cards', 'description' => 'Can assign job cards to technicians'], + ], + + // Work Orders + 'work-orders' => [ + ['name' => 'work-orders.view', 'display_name' => 'View Work Orders', 'description' => 'Can view work orders'], + ['name' => 'work-orders.create', 'display_name' => 'Create Work Orders', 'description' => 'Can create new work orders'], + ['name' => 'work-orders.update', 'display_name' => 'Edit Work Orders', 'description' => 'Can edit work orders'], + ['name' => 'work-orders.delete', 'display_name' => 'Delete Work Orders', 'description' => 'Can delete work orders'], + ['name' => 'work-orders.complete', 'display_name' => 'Complete Work Orders', 'description' => 'Can mark work orders as complete'], + ], + + // Appointments + 'appointments' => [ + ['name' => 'appointments.view', 'display_name' => 'View Appointments', 'description' => 'Can view appointments'], + ['name' => 'appointments.create', 'display_name' => 'Create Appointments', 'description' => 'Can schedule new appointments'], + ['name' => 'appointments.update', 'display_name' => 'Edit Appointments', 'description' => 'Can edit appointments'], + ['name' => 'appointments.delete', 'display_name' => 'Delete Appointments', 'description' => 'Can cancel appointments'], + ['name' => 'appointments.confirm', 'display_name' => 'Confirm Appointments', 'description' => 'Can confirm appointments'], + ], + + // Inventory Management + 'inventory' => [ + ['name' => 'inventory.view', 'display_name' => 'View Inventory', 'description' => 'Can view inventory items'], + ['name' => 'inventory.create', 'display_name' => 'Create Parts', 'description' => 'Can add new parts to inventory'], + ['name' => 'inventory.update', 'display_name' => 'Edit Parts', 'description' => 'Can edit part information'], + ['name' => 'inventory.delete', 'display_name' => 'Delete Parts', 'description' => 'Can delete parts from inventory'], + ['name' => 'inventory.stock', 'display_name' => 'Manage Stock', 'description' => 'Can manage stock levels'], + ['name' => 'inventory.stock-movements', 'display_name' => 'View Stock Movements', 'description' => 'Can view stock movement history'], + ['name' => 'inventory.purchase-orders', 'display_name' => 'Manage Purchase Orders', 'description' => 'Can create and manage purchase orders'], + ['name' => 'inventory.purchase-orders-approve', 'display_name' => 'Approve Purchase Orders', 'description' => 'Can approve purchase orders'], + ], + + // Technician Management + 'technicians' => [ + ['name' => 'technicians.view', 'display_name' => 'View Technicians', 'description' => 'Can view technician list'], + ['name' => 'technicians.create', 'display_name' => 'Create Technicians', 'description' => 'Can add new technicians'], + ['name' => 'technicians.update', 'display_name' => 'Edit Technicians', 'description' => 'Can edit technician information'], + ['name' => 'technicians.delete', 'display_name' => 'Delete Technicians', 'description' => 'Can remove technicians'], + ['name' => 'technicians.view-performance', 'display_name' => 'View Technician Performance', 'description' => 'Can view technician performance metrics'], + ['name' => 'technicians.schedules', 'display_name' => 'Manage Technician Schedules', 'description' => 'Can manage technician work schedules'], + ], + + // Inspections + 'inspections' => [ + ['name' => 'inspections.view', 'display_name' => 'View Inspections', 'description' => 'Can view vehicle inspections'], + ['name' => 'inspections.create', 'display_name' => 'Create Inspections', 'description' => 'Can create new inspections'], + ['name' => 'inspections.update', 'display_name' => 'Edit Inspections', 'description' => 'Can edit inspection results'], + ['name' => 'inspections.approve', 'display_name' => 'Approve Inspections', 'description' => 'Can approve inspection results'], + ['name' => 'inspections.reschedule', 'display_name' => 'Schedule Reinspections', 'description' => 'Can schedule reinspections'], + ], + + // Estimates & Diagnosis + 'estimates' => [ + ['name' => 'estimates.view', 'display_name' => 'View Estimates', 'description' => 'Can view estimates'], + ['name' => 'estimates.create', 'display_name' => 'Create Estimates', 'description' => 'Can create new estimates'], + ['name' => 'estimates.update', 'display_name' => 'Edit Estimates', 'description' => 'Can edit estimates'], + ['name' => 'estimates.approve', 'display_name' => 'Approve Estimates', 'description' => 'Can approve estimates'], + ['name' => 'diagnosis.view', 'display_name' => 'View Diagnosis', 'description' => 'Can view diagnosis results'], + ['name' => 'diagnosis.create', 'display_name' => 'Create Diagnosis', 'description' => 'Can create diagnosis reports'], + ], + + // Reports & Analytics + 'reports' => [ + ['name' => 'reports.view', 'display_name' => 'View Reports', 'description' => 'Can view business reports'], + ['name' => 'reports.create', 'display_name' => 'Create Reports', 'description' => 'Can generate new reports'], + ['name' => 'reports.export', 'display_name' => 'Export Reports', 'description' => 'Can export reports to various formats'], + ['name' => 'reports.financial', 'display_name' => 'View Financial Reports', 'description' => 'Can view financial and revenue reports'], + ], + + // Timesheets + 'timesheets' => [ + ['name' => 'timesheets.view', 'display_name' => 'View Timesheets', 'description' => 'Can view timesheet records'], + ['name' => 'timesheets.create', 'display_name' => 'Create Timesheets', 'description' => 'Can create timesheet entries'], + ['name' => 'timesheets.update', 'display_name' => 'Edit Timesheets', 'description' => 'Can edit timesheet entries'], + ['name' => 'timesheets.approve', 'display_name' => 'Approve Timesheets', 'description' => 'Can approve timesheet entries'], + ], + ]; + + // Create permissions + foreach ($permissions as $module => $modulePermissions) { + foreach ($modulePermissions as $permission) { + Permission::firstOrCreate( + ['name' => $permission['name']], + [ + 'display_name' => $permission['display_name'], + 'description' => $permission['description'], + 'module' => $module, + 'is_active' => true, + ] + ); + } + } + + // Create Roles + $roles = [ + [ + 'name' => 'super_admin', + 'display_name' => 'Super Administrator', + 'description' => 'Full system access with all permissions', + 'permissions' => 'all' // Special case - gets all permissions + ], + [ + 'name' => 'manager', + 'display_name' => 'Manager', + 'description' => 'Management level access with most permissions', + 'permissions' => [ + 'dashboard.view', 'analytics.view', 'settings.manage', + 'customers.view', 'customers.create', 'customers.update', 'customers.details', + 'vehicles.view', 'vehicles.create', 'vehicles.update', 'vehicles.history', + 'service-orders.view', 'service-orders.create', 'service-orders.update', 'service-orders.approve', 'service-orders.complete', + 'job-cards.view', 'job-cards.create', 'job-cards.update', 'job-cards.assign', + 'work-orders.view', 'work-orders.create', 'work-orders.update', 'work-orders.complete', + 'appointments.view', 'appointments.create', 'appointments.update', 'appointments.confirm', + 'inventory.view', 'inventory.create', 'inventory.update', 'inventory.stock', 'inventory.stock-movements', 'inventory.purchase-orders', 'inventory.purchase-orders-approve', + 'technicians.view', 'technicians.create', 'technicians.update', 'technicians.view-performance', 'technicians.schedules', + 'inspections.view', 'inspections.create', 'inspections.update', 'inspections.approve', 'inspections.reschedule', + 'estimates.view', 'estimates.create', 'estimates.update', 'estimates.approve', 'diagnosis.view', 'diagnosis.create', + 'reports.view', 'reports.create', 'reports.export', 'reports.financial', + 'timesheets.view', 'timesheets.approve', + ] + ], + [ + 'name' => 'service_advisor', + 'display_name' => 'Service Advisor', + 'description' => 'Customer service and appointment management', + 'permissions' => [ + 'dashboard.view', + 'customers.view', 'customers.create', 'customers.update', 'customers.details', + 'vehicles.view', 'vehicles.create', 'vehicles.update', 'vehicles.history', + 'service-orders.view', 'service-orders.create', 'service-orders.update', + 'appointments.view', 'appointments.create', 'appointments.update', 'appointments.confirm', + 'estimates.view', 'estimates.create', 'diagnosis.view', + 'inventory.view', 'inventory.stock-movements', + 'inspections.view', 'inspections.create', + ] + ], + [ + 'name' => 'technician', + 'display_name' => 'Technician', + 'description' => 'Workshop technician with job execution permissions', + 'permissions' => [ + 'dashboard.view', + 'customers.view', 'customers.details', + 'vehicles.view', 'vehicles.history', + 'service-orders.view', + 'job-cards.view', 'job-cards.update', + 'work-orders.view', 'work-orders.update', 'work-orders.complete', + 'inventory.view', 'inventory.stock-movements', + 'inspections.view', 'inspections.create', 'inspections.update', + 'diagnosis.view', 'diagnosis.create', + 'timesheets.view', 'timesheets.create', 'timesheets.update', + ] + ], + [ + 'name' => 'inventory_manager', + 'display_name' => 'Inventory Manager', + 'description' => 'Inventory and parts management specialist', + 'permissions' => [ + 'dashboard.view', + 'inventory.view', 'inventory.create', 'inventory.update', 'inventory.stock', 'inventory.stock-movements', + 'inventory.purchase-orders', 'inventory.purchase-orders-approve', + 'service-orders.view', 'work-orders.view', + 'reports.view', 'reports.create', 'reports.export', + ] + ], + [ + 'name' => 'customer_portal', + 'display_name' => 'Customer Portal', + 'description' => 'Limited customer portal access', + 'permissions' => [ + 'appointments.view', + 'vehicles.view', 'vehicles.history', + 'service-orders.view', + 'estimates.view', + ] + ], + ]; + + // Create roles and assign permissions + foreach ($roles as $roleData) { + $role = Role::firstOrCreate( + ['name' => $roleData['name']], + [ + 'display_name' => $roleData['display_name'], + 'description' => $roleData['description'], + 'is_active' => true, + ] + ); + + // Assign permissions to role + if ($roleData['permissions'] === 'all') { + // Super admin gets all permissions + $allPermissions = Permission::where('is_active', true)->get(); + $role->permissions()->sync($allPermissions->pluck('id')->toArray()); + } else { + // Get permission IDs by names + $permissionIds = Permission::whereIn('name', $roleData['permissions']) + ->where('is_active', true) + ->pluck('id') + ->toArray(); + + $role->permissions()->sync($permissionIds); + } + } + + // Create dedicated super admin user + $superAdminUser = User::firstOrCreate( + ['email' => 'admin@admin.com'], + [ + 'name' => 'Super Administrator', + 'password' => Hash::make('danewcash54899'), + 'email_verified_at' => now(), + 'employee_id' => 'ADMIN001', + 'position' => 'System Administrator', + 'department' => 'IT', + 'status' => 'active', + ] + ); + + // Assign super_admin role + $superAdminRole = Role::where('name', 'super_admin')->first(); + if ($superAdminRole) { + $superAdminUser->roles()->sync([$superAdminRole->id]); + } + + // Create a test manager user + $managerUser = User::firstOrCreate( + ['email' => 'manager@carrepairs.com'], + [ + 'name' => 'Workshop Manager', + 'password' => Hash::make('password'), + 'email_verified_at' => now(), + 'employee_id' => 'MGR001', + 'position' => 'Workshop Manager', + 'department' => 'Management', + 'status' => 'active', + ] + ); + + $managerRole = Role::where('name', 'manager')->first(); + if ($managerRole) { + $managerUser->roles()->sync([$managerRole->id]); + } + + // Create a test technician user + $technicianUser = User::firstOrCreate( + ['email' => 'technician@carrepairs.com'], + [ + 'name' => 'Lead Technician', + 'password' => Hash::make('password'), + 'email_verified_at' => now(), + 'employee_id' => 'TECH001', + 'position' => 'Lead Technician', + 'department' => 'Workshop', + 'status' => 'active', + ] + ); + + $technicianRole = Role::where('name', 'technician')->first(); + if ($technicianRole) { + $technicianUser->roles()->sync([$technicianRole->id]); + } + + // Create a test service advisor user + $advisorUser = User::firstOrCreate( + ['email' => 'advisor@carrepairs.com'], + [ + 'name' => 'Service Advisor', + 'password' => Hash::make('password'), + 'email_verified_at' => now(), + 'employee_id' => 'ADV001', + 'position' => 'Service Advisor', + 'department' => 'Customer Service', + 'status' => 'active', + ] + ); + + // Create a test technician user + $technicianUser = User::firstOrCreate( + ['email' => 'technician@carrepairs.com'], + [ + 'name' => 'Lead Technician', + 'password' => Hash::make('password'), + 'email_verified_at' => now(), + 'employee_id' => 'TECH001', + 'position' => 'Lead Technician', + 'department' => 'Workshop', + 'status' => 'active', + ] + ); + + $technicianRole = Role::where('name', 'technician')->first(); + if ($technicianRole) { + $technicianUser->roles()->sync([$technicianRole->id]); + } + + // Create a test service advisor user + $advisorUser = User::firstOrCreate( + ['email' => 'advisor@carrepairs.com'], + [ + 'name' => 'Service Advisor', + 'password' => Hash::make('password'), + 'email_verified_at' => now(), + 'employee_id' => 'ADV001', + 'position' => 'Service Advisor', + 'department' => 'Customer Service', + 'status' => 'active', + ] + ); + + $advisorRole = Role::where('name', 'service_advisor')->first(); + if ($advisorRole) { + $advisorUser->roles()->sync([$advisorRole->id]); + } + + $this->command->info('Roles, permissions, and users created successfully!'); + $this->command->info('Login credentials:'); + $this->command->info('Super Admin - Email: admin@admin.com, Password: danewcash54899'); + $this->command->info('Manager - Email: manager@carrepairs.com, Password: password'); + $this->command->info('Technician - Email: technician@carrepairs.com, Password: password'); + $this->command->info('Service Advisor - Email: advisor@carrepairs.com, Password: password'); + } +} diff --git a/database/seeders/ServiceItemSeeder.php b/database/seeders/ServiceItemSeeder.php new file mode 100644 index 0000000..255e492 --- /dev/null +++ b/database/seeders/ServiceItemSeeder.php @@ -0,0 +1,17 @@ +migrator->inGroup('general', function (Spatie\LaravelSettings\Migrations\SettingsBlueprint $blueprint): void { + // Business Information + $blueprint->add('shop_name', 'SafeTrack Auto Repair Shop'); + $blueprint->add('shop_address', '123 Main Street'); + $blueprint->add('shop_city', 'Anytown'); + $blueprint->add('shop_state', 'CA'); + $blueprint->add('shop_zip_code', '12345'); + $blueprint->add('shop_phone', '(555) 123-4567'); + $blueprint->add('shop_email', 'info@safetrack-auto.com'); + $blueprint->add('shop_website', 'https://safetrack-auto.com'); + $blueprint->add('shop_logo', null); + + // Business Hours (default Monday-Friday 8AM-6PM) + $blueprint->add('business_hours', [ + 'monday' => ['open' => '08:00', 'close' => '18:00', 'is_open' => true], + 'tuesday' => ['open' => '08:00', 'close' => '18:00', 'is_open' => true], + 'wednesday' => ['open' => '08:00', 'close' => '18:00', 'is_open' => true], + 'thursday' => ['open' => '08:00', 'close' => '18:00', 'is_open' => true], + 'friday' => ['open' => '08:00', 'close' => '18:00', 'is_open' => true], + 'saturday' => ['open' => '09:00', 'close' => '15:00', 'is_open' => true], + 'sunday' => ['open' => '00:00', 'close' => '00:00', 'is_open' => false], + ]); + $blueprint->add('holiday_hours', []); + $blueprint->add('is_open_weekends', true); + + // Tax & Financial Settings + $blueprint->add('default_tax_rate', 8.25); + $blueprint->add('currency', 'USD'); + $blueprint->add('currency_symbol', '$'); + + // System Settings + $blueprint->add('timezone', 'America/Los_Angeles'); + $blueprint->add('date_format', 'M d, Y'); + $blueprint->add('time_format', 'g:i A'); + $blueprint->add('enable_notifications', true); + $blueprint->add('enable_sms_notifications', true); + $blueprint->add('enable_email_notifications', true); + }); + } +}; diff --git a/database/settings/2025_07_29_090444_create_general_settings.php b/database/settings/2025_07_29_090444_create_general_settings.php new file mode 100644 index 0000000..81bd3db --- /dev/null +++ b/database/settings/2025_07_29_090444_create_general_settings.php @@ -0,0 +1,11 @@ +migrator->inGroup('service', function (Spatie\LaravelSettings\Migrations\SettingsBlueprint $blueprint): void { + // Default Labor Rates + $blueprint->add('standard_labor_rate', 95.00); + $blueprint->add('overtime_labor_rate', 142.50); + $blueprint->add('weekend_labor_rate', 120.00); + $blueprint->add('holiday_labor_rate', 150.00); + + // Service Intervals & Reminders + $blueprint->add('oil_change_interval', 5000); + $blueprint->add('tire_rotation_interval', 6000); + $blueprint->add('brake_inspection_interval', 12000); + $blueprint->add('general_inspection_interval', 12000); + $blueprint->add('enable_service_reminders', true); + $blueprint->add('reminder_advance_days', 14); + + // Warranty Settings + $blueprint->add('default_parts_warranty_days', 365); + $blueprint->add('default_labor_warranty_days', 90); + $blueprint->add('enable_extended_warranty', true); + + // Quality Control + $blueprint->add('require_quality_inspection', true); + $blueprint->add('require_technician_signature', true); + $blueprint->add('require_customer_signature', true); + $blueprint->add('enable_photo_documentation', true); + + // Service Categories + $blueprint->add('service_categories', [ + 'Oil Change', + 'Brake Service', + 'Tire Service', + 'Engine Repair', + 'Transmission Service', + 'Electrical Repair', + 'Air Conditioning', + 'Suspension Repair', + 'Diagnostic', + 'Preventive Maintenance', + 'Annual Inspection', + 'Emergency Repair' + ]); + + $blueprint->add('priority_levels', [ + 'Low' => ['color' => 'green', 'description' => 'Non-urgent service'], + 'Normal' => ['color' => 'blue', 'description' => 'Standard priority'], + 'High' => ['color' => 'yellow', 'description' => 'Important service'], + 'Critical' => ['color' => 'red', 'description' => 'Safety-critical repair'], + 'Emergency' => ['color' => 'red', 'description' => 'Immediate attention required'] + ]); + }); + } +}; diff --git a/database/settings/2025_07_29_090830_create_inventory_settings.php b/database/settings/2025_07_29_090830_create_inventory_settings.php new file mode 100644 index 0000000..d502fb9 --- /dev/null +++ b/database/settings/2025_07_29_090830_create_inventory_settings.php @@ -0,0 +1,59 @@ +migrator->inGroup('inventory', function (Spatie\LaravelSettings\Migrations\SettingsBlueprint $blueprint): void { + // Stock Management + $blueprint->add('low_stock_threshold', 10); + $blueprint->add('critical_stock_threshold', 3); + $blueprint->add('enable_auto_reorder', false); + $blueprint->add('default_reorder_quantity', 50); + $blueprint->add('default_lead_time_days', 7); + + // Pricing Settings + $blueprint->add('default_markup_percentage', 30.0); + $blueprint->add('enable_tiered_pricing', false); + $blueprint->add('price_tiers', [ + 'retail' => 1.0, + 'wholesale' => 0.85, + 'dealer' => 0.75 + ]); + $blueprint->add('include_labor_in_estimates', true); + + // Supplier Settings + $blueprint->add('preferred_supplier_count', 3); + $blueprint->add('require_multiple_quotes', false); + $blueprint->add('minimum_order_amount', 100.00); + + // Part Categories + $blueprint->add('part_categories', [ + 'Engine Parts', + 'Brake Components', + 'Suspension Parts', + 'Electrical Components', + 'Filters', + 'Fluids', + 'Belts & Hoses', + 'Tires', + 'Batteries', + 'Body Parts' + ]); + + $blueprint->add('part_conditions', [ + 'New', + 'Used', + 'Refurbished', + 'Remanufactured' + ]); + + // Barcode & Tracking + $blueprint->add('enable_barcode_scanning', false); + $blueprint->add('track_part_history', true); + $blueprint->add('enable_serial_tracking', false); + }); + } +}; diff --git a/database/settings/2025_07_29_090850_create_notification_settings.php b/database/settings/2025_07_29_090850_create_notification_settings.php new file mode 100644 index 0000000..d782ebf --- /dev/null +++ b/database/settings/2025_07_29_090850_create_notification_settings.php @@ -0,0 +1,51 @@ +migrator->inGroup('notifications', function (Spatie\LaravelSettings\Migrations\SettingsBlueprint $blueprint): void { + // Email Settings + $blueprint->add('from_email', 'noreply@safetrack-auto.com'); + $blueprint->add('from_name', 'SafeTrack Auto Repair'); + $blueprint->add('enable_customer_notifications', true); + $blueprint->add('enable_technician_notifications', true); + $blueprint->add('enable_manager_notifications', true); + + // SMS Settings + $blueprint->add('enable_sms', false); + $blueprint->add('sms_provider', null); + $blueprint->add('sms_api_key', null); + $blueprint->add('sms_from_number', null); + + // Customer Notification Preferences + $blueprint->add('customer_notification_types', [ + 'appointment_reminder' => true, + 'service_complete' => true, + 'estimate_ready' => true, + 'vehicle_ready' => true, + 'payment_reminder' => true, + 'service_due' => true + ]); + + $blueprint->add('notification_timing', [ + 'appointment_reminder_hours' => 24, + 'service_due_days' => 30 + ]); + + // Internal Notifications + $blueprint->add('notify_on_new_job', true); + $blueprint->add('notify_on_job_completion', true); + $blueprint->add('notify_on_low_stock', true); + $blueprint->add('notify_on_overdue_inspection', true); + $blueprint->add('notify_on_warranty_expiry', true); + + // Escalation Settings + $blueprint->add('enable_escalation', false); + $blueprint->add('escalation_hours', 24); + $blueprint->add('escalation_contacts', []); + }); + } +}; diff --git a/database/settings/2025_07_29_090902_create_security_settings.php b/database/settings/2025_07_29_090902_create_security_settings.php new file mode 100644 index 0000000..d8f86b7 --- /dev/null +++ b/database/settings/2025_07_29_090902_create_security_settings.php @@ -0,0 +1,41 @@ +migrator->inGroup('security', function (Spatie\LaravelSettings\Migrations\SettingsBlueprint $blueprint): void { + // Authentication Settings + $blueprint->add('enable_two_factor_auth', false); + $blueprint->add('session_timeout_minutes', 60); + $blueprint->add('password_expiry_days', 90); + $blueprint->add('max_login_attempts', 5); + $blueprint->add('lockout_duration_minutes', 15); + + // Password Requirements + $blueprint->add('min_password_length', 8); + $blueprint->add('require_uppercase', true); + $blueprint->add('require_lowercase', true); + $blueprint->add('require_numbers', true); + $blueprint->add('require_special_characters', false); + + // Data Protection + $blueprint->add('enable_data_encryption', true); + $blueprint->add('enable_audit_logging', true); + $blueprint->add('audit_log_retention_days', 365); + $blueprint->add('enable_backup_alerts', true); + + // API Security + $blueprint->add('enable_api_rate_limiting', true); + $blueprint->add('api_requests_per_minute', 60); + $blueprint->add('allowed_ip_addresses', []); + + // Customer Data Access + $blueprint->add('allow_customer_portal', true); + $blueprint->add('allow_customer_data_download', false); + $blueprint->add('customer_session_timeout_minutes', 30); + }); + } +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..7742053 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2194 @@ +{ + "name": "car-repairs-shop", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@tailwindcss/vite": "^4.0.7", + "autoprefixer": "^10.4.20", + "axios": "^1.7.4", + "concurrently": "^9.0.1", + "laravel-vite-plugin": "^1.0", + "tailwindcss": "^4.0.7", + "vite": "^6.0" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "4.9.5", + "@tailwindcss/oxide-linux-x64-gnu": "^4.0.1", + "lightningcss-linux-x64-gnu": "^1.29.1" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", + "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", + "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", + "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", + "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", + "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", + "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", + "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", + "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", + "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", + "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", + "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", + "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", + "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", + "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", + "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", + "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", + "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", + "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", + "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", + "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", + "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", + "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", + "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", + "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", + "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz", + "integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz", + "integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz", + "integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz", + "integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz", + "integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz", + "integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz", + "integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz", + "integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz", + "integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz", + "integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz", + "integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz", + "integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz", + "integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz", + "integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.5.tgz", + "integrity": "sha512-Dq1bqBdLaZ1Gb/l2e5/+o3B18+8TI9ANlA1SkejZqDgdU/jK/ThYaMPMJpVMMXy2uRHvGKbkz9vheVGdq3cJfA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz", + "integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz", + "integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz", + "integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz", + "integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tailwindcss/node": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.8.tgz", + "integrity": "sha512-FKArQpbrbwv08TNT0k7ejYXpF+R8knZFAatNc0acOxbgeqLzwb86r+P3LGOjIeI3Idqe9CVkZrh4GlsJLJKkkw==", + "license": "MIT", + "dependencies": { + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", + "tailwindcss": "4.0.8" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.8.tgz", + "integrity": "sha512-KfMcuAu/Iw+DcV1e8twrFyr2yN8/ZDC/odIGta4wuuJOGkrkHZbvJvRNIbQNhGh7erZTYV6Ie0IeD6WC9Y8Hcw==", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.0.8", + "@tailwindcss/oxide-darwin-arm64": "4.0.8", + "@tailwindcss/oxide-darwin-x64": "4.0.8", + "@tailwindcss/oxide-freebsd-x64": "4.0.8", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.8", + "@tailwindcss/oxide-linux-arm64-gnu": "4.0.8", + "@tailwindcss/oxide-linux-arm64-musl": "4.0.8", + "@tailwindcss/oxide-linux-x64-gnu": "4.0.8", + "@tailwindcss/oxide-linux-x64-musl": "4.0.8", + "@tailwindcss/oxide-win32-arm64-msvc": "4.0.8", + "@tailwindcss/oxide-win32-x64-msvc": "4.0.8" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.8.tgz", + "integrity": "sha512-We7K79+Sm4mwJHk26Yzu/GAj7C7myemm7PeXvpgMxyxO70SSFSL3uCcqFbz9JA5M5UPkrl7N9fkBe/Y0iazqpA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.8.tgz", + "integrity": "sha512-Lv9Isi2EwkCTG1sRHNDi0uRNN1UGFdEThUAGFrydRmQZnraGLMjN8gahzg2FFnOizDl7LB2TykLUuiw833DSNg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.8.tgz", + "integrity": "sha512-fWfywfYIlSWtKoqWTjukTHLWV3ARaBRjXCC2Eo0l6KVpaqGY4c2y8snUjp1xpxUtpqwMvCvFWFaleMoz1Vhzlw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.8.tgz", + "integrity": "sha512-SO+dyvjJV9G94bnmq2288Ke0BIdvrbSbvtPLaQdqjqHR83v5L2fWADyFO+1oecHo9Owsk8MxcXh1agGVPIKIqw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.8.tgz", + "integrity": "sha512-ZSHggWiEblQNV69V0qUK5vuAtHP+I+S2eGrKGJ5lPgwgJeAd6GjLsVBN+Mqn2SPVfYM3BOpS9jX/zVg9RWQVDQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.8.tgz", + "integrity": "sha512-xWpr6M0OZLDNsr7+bQz+3X7zcnDJZJ1N9gtBWCtfhkEtDjjxYEp+Lr5L5nc/yXlL4MyCHnn0uonGVXy3fhxaVA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.8.tgz", + "integrity": "sha512-5tz2IL7LN58ssGEq7h/staD7pu/izF/KeMWdlJ86WDe2Ah46LF3ET6ZGKTr5eZMrnEA0M9cVFuSPprKRHNgjeg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.8.tgz", + "integrity": "sha512-KSzMkhyrxAQyY2o194NKVKU9j/c+NFSoMvnHWFaNHKi3P1lb+Vq1UC19tLHrmxSkKapcMMu69D7+G1+FVGNDXQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.8.tgz", + "integrity": "sha512-yFYKG5UtHTRimjtqxUWXBgI4Tc6NJe3USjRIVdlTczpLRxq/SFwgzGl5JbatCxgSRDPBFwRrNPxq+ukfQFGdrw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.8.tgz", + "integrity": "sha512-tndGujmCSba85cRCnQzXgpA2jx5gXimyspsUYae5jlPyLRG0RjXbDshFKOheVXU4TLflo7FSG8EHCBJ0EHTKdQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.8.tgz", + "integrity": "sha512-T77jroAc0p4EHVVgTUiNeFn6Nj3jtD3IeNId2X+0k+N1XxfNipy81BEkYErpKLiOkNhpNFjPee8/ZVas29b2OQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.0.8.tgz", + "integrity": "sha512-+SAq44yLzYlzyrb7QTcFCdU8Xa7FOA0jp+Xby7fPMUie+MY9HhJysM7Vp+vL8qIp8ceQJfLD+FjgJuJ4lL6nyg==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.0.8", + "@tailwindcss/oxide": "4.0.8", + "lightningcss": "^1.29.1", + "tailwindcss": "4.0.8" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001700", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz", + "integrity": "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concurrently": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.1.2.tgz", + "integrity": "sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "license": "Apache-2.0", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.103", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.103.tgz", + "integrity": "sha512-P6+XzIkfndgsrjROJWfSvVEgNHtPgbhVyTkwLjUM2HU/h7pZRORgaTlHqfAikqxKmdJMLW8fftrdGWbd/Ds0FA==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", + "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.0", + "@esbuild/android-arm": "0.25.0", + "@esbuild/android-arm64": "0.25.0", + "@esbuild/android-x64": "0.25.0", + "@esbuild/darwin-arm64": "0.25.0", + "@esbuild/darwin-x64": "0.25.0", + "@esbuild/freebsd-arm64": "0.25.0", + "@esbuild/freebsd-x64": "0.25.0", + "@esbuild/linux-arm": "0.25.0", + "@esbuild/linux-arm64": "0.25.0", + "@esbuild/linux-ia32": "0.25.0", + "@esbuild/linux-loong64": "0.25.0", + "@esbuild/linux-mips64el": "0.25.0", + "@esbuild/linux-ppc64": "0.25.0", + "@esbuild/linux-riscv64": "0.25.0", + "@esbuild/linux-s390x": "0.25.0", + "@esbuild/linux-x64": "0.25.0", + "@esbuild/netbsd-arm64": "0.25.0", + "@esbuild/netbsd-x64": "0.25.0", + "@esbuild/openbsd-arm64": "0.25.0", + "@esbuild/openbsd-x64": "0.25.0", + "@esbuild/sunos-x64": "0.25.0", + "@esbuild/win32-arm64": "0.25.0", + "@esbuild/win32-ia32": "0.25.0", + "@esbuild/win32-x64": "0.25.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/laravel-vite-plugin": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.2.0.tgz", + "integrity": "sha512-R0pJ+IcTVeqEMoKz/B2Ij57QVq3sFTABiFmb06gAwFdivbOgsUtuhX6N2MGLEArajrS3U5JbberzwOe7uXHMHQ==", + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "vite-plugin-full-reload": "^1.1.0" + }, + "bin": { + "clean-orphaned-assets": "bin/clean.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0" + } + }, + "node_modules/lightningcss": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.1.tgz", + "integrity": "sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^1.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.29.1", + "lightningcss-darwin-x64": "1.29.1", + "lightningcss-freebsd-x64": "1.29.1", + "lightningcss-linux-arm-gnueabihf": "1.29.1", + "lightningcss-linux-arm64-gnu": "1.29.1", + "lightningcss-linux-arm64-musl": "1.29.1", + "lightningcss-linux-x64-gnu": "1.29.1", + "lightningcss-linux-x64-musl": "1.29.1", + "lightningcss-win32-arm64-msvc": "1.29.1", + "lightningcss-win32-x64-msvc": "1.29.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.1.tgz", + "integrity": "sha512-HtR5XJ5A0lvCqYAoSv2QdZZyoHNttBpa5EP9aNuzBQeKGfbyH5+UipLWvVzpP4Uml5ej4BYs5I9Lco9u1fECqw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.1.tgz", + "integrity": "sha512-k33G9IzKUpHy/J/3+9MCO4e+PzaFblsgBjSGlpAaFikeBFm8B/CkO3cKU9oI4g+fjS2KlkLM/Bza9K/aw8wsNA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.1.tgz", + "integrity": "sha512-0SUW22fv/8kln2LnIdOCmSuXnxgxVC276W5KLTwoehiO0hxkacBxjHOL5EtHD8BAXg2BvuhsJPmVMasvby3LiQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.1.tgz", + "integrity": "sha512-sD32pFvlR0kDlqsOZmYqH/68SqUMPNj+0pucGxToXZi4XZgZmqeX/NkxNKCPsswAXU3UeYgDSpGhu05eAufjDg==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.1.tgz", + "integrity": "sha512-0+vClRIZ6mmJl/dxGuRsE197o1HDEeeRk6nzycSy2GofC2JsY4ifCRnvUWf/CUBQmlrvMzt6SMQNMSEu22csWQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.1.tgz", + "integrity": "sha512-UKMFrG4rL/uHNgelBsDwJcBqVpzNJbzsKkbI3Ja5fg00sgQnHw/VrzUTEc4jhZ+AN2BvQYz/tkHu4vt1kLuJyw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.1.tgz", + "integrity": "sha512-u1S+xdODy/eEtjADqirA774y3jLcm8RPtYztwReEXoZKdzgsHYPl0s5V52Tst+GKzqjebkULT86XMSxejzfISw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.1.tgz", + "integrity": "sha512-L0Tx0DtaNUTzXv0lbGCLB/c/qEADanHbu4QdcNOXLIe1i8i22rZRpbT3gpWYsCh9aSL9zFujY/WmEXIatWvXbw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.1.tgz", + "integrity": "sha512-QoOVnkIEFfbW4xPi+dpdft/zAKmgLgsRHfJalEPYuJDOWf7cLQzYg0DEh8/sn737FaeMJxHZRc1oBreiwZCjog==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.1.tgz", + "integrity": "sha512-NygcbThNBe4JElP+olyTI/doBNGJvLs3bFCRPdvuCcxZCcCZ71B858IHpdm7L1btZex0FvCmM17FK98Y9MRy1Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "license": "MIT" + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz", + "integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.34.8", + "@rollup/rollup-android-arm64": "4.34.8", + "@rollup/rollup-darwin-arm64": "4.34.8", + "@rollup/rollup-darwin-x64": "4.34.8", + "@rollup/rollup-freebsd-arm64": "4.34.8", + "@rollup/rollup-freebsd-x64": "4.34.8", + "@rollup/rollup-linux-arm-gnueabihf": "4.34.8", + "@rollup/rollup-linux-arm-musleabihf": "4.34.8", + "@rollup/rollup-linux-arm64-gnu": "4.34.8", + "@rollup/rollup-linux-arm64-musl": "4.34.8", + "@rollup/rollup-linux-loongarch64-gnu": "4.34.8", + "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8", + "@rollup/rollup-linux-riscv64-gnu": "4.34.8", + "@rollup/rollup-linux-s390x-gnu": "4.34.8", + "@rollup/rollup-linux-x64-gnu": "4.34.8", + "@rollup/rollup-linux-x64-musl": "4.34.8", + "@rollup/rollup-win32-arm64-msvc": "4.34.8", + "@rollup/rollup-win32-ia32-msvc": "4.34.8", + "@rollup/rollup-win32-x64-msvc": "4.34.8", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz", + "integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/shell-quote": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tailwindcss": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.8.tgz", + "integrity": "sha512-Me7N5CKR+D2A1xdWA5t5+kjjT7bwnxZOE6/yDI/ixJdJokszsn2n++mdU5yJwrsTpqFX2B9ZNMBJDwcqk9C9lw==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "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/update-browserslist-db": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", + "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.0.tgz", + "integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "postcss": "^8.5.3", + "rollup": "^4.30.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-full-reload": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vite-plugin-full-reload/-/vite-plugin-full-reload-1.2.0.tgz", + "integrity": "sha512-kz18NW79x0IHbxRSHm0jttP4zoO9P9gXh+n6UTwlNKnviTTEpOlum6oS9SmecrTtSr+muHEn5TUuC75UovQzcA==", + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "picomatch": "^2.3.1" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..aeb9543 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "private": true, + "type": "module", + "scripts": { + "build": "vite build", + "dev": "vite" + }, + "dependencies": { + "@tailwindcss/vite": "^4.0.7", + "autoprefixer": "^10.4.20", + "axios": "^1.7.4", + "concurrently": "^9.0.1", + "laravel-vite-plugin": "^1.0", + "tailwindcss": "^4.0.7", + "vite": "^6.0" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "4.9.5", + "@tailwindcss/oxide-linux-x64-gnu": "^4.0.1", + "lightningcss-linux-x64-gnu": "^1.29.1" + } +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..61c031c --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,33 @@ + + + + + tests/Unit + + + tests/Feature + + + + + app + + + + + + + + + + + + + + + + diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..b574a59 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,25 @@ + + + Options -MultiViews -Indexes + + + RewriteEngine On + + # Handle Authorization Header + RewriteCond %{HTTP:Authorization} . + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + # Handle X-XSRF-Token Header + RewriteCond %{HTTP:x-xsrf-token} . + RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}] + + # Redirect Trailing Slashes If Not A Folder... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_URI} (.+)/$ + RewriteRule ^ %1 [L,R=301] + + # Send Requests To Front Controller... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c2efef6de4482ad4ec770787891fb3ab7f7b1f58 GIT binary patch literal 1662 zcmV-^27&pBP)40{{R3XI2D70000&P)t-s00030 zFd+XeApb8Q|1BW@E+GFcApbBR|1Kc^Eg=6bApb5P|1Kc^EFk|aApa~N|1BW@Eg=6e zApb5P|1BW@Eg&n7Ql0<+02OpnPE!CNzd)dXU=ZKm?|<)rkZ`ZhFmF)fpgb%900q`b zL_t(|+U%Oqj+-zLMaMYBBrGf>;s1ZNY7`jIiI_O8!Rm4!Tgf`>xtSR*$bTdkB!69t zVK_O-TcI8P=u%70x?P5+&ZXoke;#Hk@~pfohJ~`UXs9Q;_B2)gTk?{cvbc)et1K<* zr+Qaj9xAKsDzB#eYQpr7r@Sm0>dVCQfKCB0Kf{y|EP9n~<+TZwVye09V$sm9n9{PSY^C}VzXiE}GC#|%c)PWA z!&5y>`JP4VkDV6b8$Z_jJJu@A$QeFse^IT?qj=G4l>4y#(Kj|Guiq{iFk{=Fwl*76 z)xQ4|>IRkjy#1lI8JM_!yJWG>8ZJNf8dXXx zrk~GbPG4tWE+u0Q(BCgl#w@cPJ5nx)T9MuG26Ggy6OkTfT zvPjI^>P)^G7FA5^`aMfVOxu)|x2A9549ulPjgm!RI{t-}!yN(BSbnfZ0rTAmB^Yxa z0ShsC{k=0v223kLF@%7Hn5rZ4wL=GnpitveraoqSfe(_EIcO5du!CjnDf@wtxv3e z-u^bn6xYI{>k~_4b4*0{)`LMCWEV`GECi(~y~rMz#`428YDY}1JOdWj&DW^?F-Ib; zJl{lRfw{1#A0resWj2_y2v`;oie!a3x9HrIxnXL+G9OMEVvZJ7Q)Y@O(#)P#Gsb*Y zz6+8!=6Lpjd0-xx2j+o!U>=wU<`2TWpY@?U`fOKhgXs$-hQ`^*57T3Y7GFKsXEUm) zBmpLJD?F4LXCptnrod!)-s4>q&NlMH1ep8=0k2DW_S$RM1ei2s8;}?pXCpsMfQj(j zNDOf{^1}p}D=)N>81ii9hY2uO&CkS;XEQ%chDr0UB!)N}`C$S~^BV*vhRd^&A11-1 z`R$K=!+176!AgetySX#KX&8o~XtND<+Gs@?_dhEVl?dUf^KAF4`3KmZ#1o5M$8F`` z3P!Tl&&CLr3Ul6+|GA>4A2dj*!Zf~z9YX~DLkTA3XY8i74zs-cJ)DTceLfYYA-}08 zCfNm;#x)M#D5naO^M{f7;@Z>}U}BCre7ypnoy1IDM_|xHKkNy25-bsNjhURKb+p6ga^RJ>eNU6hI2E^Ow5A+W|JeZDC8Wqj7Y8Fr9@Uvc=cY`i& zDS4WCQI*lV*os&Gro@tzcjHTqy#y%$-R|Mc0;Z=P9R-{!<>DZ*bg~pkU*-o&W#<07*qo IM6N<$f;hnhc>n+a literal 0 HcmV?d00001 diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..236fadb94188e9189807c50aa8480b3070a0cc8d GIT binary patch literal 4286 zcmcJSYiv|S6vqb^Sj9>!kD_?#USEjFuB*k05m`ulKhQ)0{ZR3Nj~ES!#h9qrHiD>$ zFCbA83#r6d--z!Q-dZre@B@j4kBxrw(=r7ZdN&YYP!=ggUN z&s>a|z(3zN`fK`{jA=H;%m+kf8A$GajE&{z=dAJbc7mU^S=a@A(CVLujIm?&L%;DC zLe@6JdI;b*I0WCqqi~9wGmd_s)3`n7>{QyT;Y-k-FM>{Z23kR}*bD8AB1Y}dT<%4|D*yFG{ipz1dZ-md_9asjFVAL*Xjeow=eI@kwWU_SmSU;ft*cgCUqSjPbCwmKa-bR^DI`k=CZ?Ca;^-d5Ll<&7I3$q~lW zumU~>#c()c$ zznq=I*j4Zy`~=s7e6N}x_jxK`Zh-@E10+?J+sM~|@>qVCnUcYu{D&HP^q=+0M}32-cM_!3Ciyi+W;gJQ1O$7`Uq{%qEf zQ&BAEGXHHD1l564qg*VjMHc7h8MJr9F#HPgp`07>0%S*IU-OwO9s`}T@>+3T1B!b* zKArZ1un%4W-3_mTYTL(f9k{v?=^U+ny>m|ylSw>PH%q64pG5ofa4((kv;&-?dWdAkLy`uhG@CDoiOQ8?+ z9kmLbnp3s^M6T<5NBhy**WqFab58X;{Fc}ICS9`EhD_t_k?aMYs+oF+X2x}$w)C!s zub>y+giUTd`u=o2$VPbg%w}A5K=FGP&VnVNeaN?!pjv4BMJ}c{)BXz-le3+UDBqmk zP^aQH6SlzLumSXcGEJ%aDelxC<@f^F20w!KrQDna?i}Mq^k;zX6#3f)bNvO(k)BF^ zHzB_V0(cothr0F?dSz!aw81>*Q(|8=^W<+HdO$YTVt*Rwysv=|;RCn=q%&SVZcpiq z>|ITJ5A23ruovR@SbG{bo3)A(@mAf`d#C(4&3${+iCyjbvGi?Vt?lZx!zN>grl)PO zquU0hL7OTS3ys0yLdGmDv^2C7yp|~iFVkJ{(t`!Bd#K + + diff --git a/public/images/logo-safe.png b/public/images/logo-safe.png new file mode 100644 index 0000000000000000000000000000000000000000..c39022033dfe106042ac27b78bb09f0860c0afa2 GIT binary patch literal 500910 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

-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�o%C_R?0Vpku=o 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

6IpKfyQyMko3;kI{eO?Uilp!%mXJKh0AY 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
(`b&^&>%zF4vc_$O8nEUVHD0ts#_*Y% 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 {Izr~U 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 jFq1Kh5P;|B3FkY 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 literal 0 HcmV?d00001 diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..ee8f07e --- /dev/null +++ b/public/index.php @@ -0,0 +1,20 @@ +handleRequest(Request::capture()); diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..eb05362 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/resources/css/app.css b/resources/css/app.css new file mode 100644 index 0000000..a8a4ec2 --- /dev/null +++ b/resources/css/app.css @@ -0,0 +1,66 @@ +@import 'tailwindcss'; +@import '../../vendor/livewire/flux/dist/flux.css'; + +@source '../views'; +@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php'; +@source '../../vendor/livewire/flux-pro/stubs/**/*.blade.php'; +@source '../../vendor/livewire/flux/stubs/**/*.blade.php'; + +@custom-variant dark (&:where(.dark, .dark *)); + +@theme { + --font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + +--color-zinc-50: var(--color-neutral-50); +--color-zinc-100: var(--color-neutral-100); +--color-zinc-200: var(--color-neutral-200); +--color-zinc-300: var(--color-neutral-300); +--color-zinc-400: var(--color-neutral-400); +--color-zinc-500: var(--color-neutral-500); +--color-zinc-600: var(--color-neutral-600); +--color-zinc-700: var(--color-neutral-700); +--color-zinc-800: var(--color-neutral-800); +--color-zinc-900: var(--color-neutral-900); +--color-zinc-950: var(--color-neutral-950); + + --color-accent: var(--color-orange-500); + --color-accent-content: var(--color-orange-600); + --color-accent-foreground: var(--color-white); +} + +@layer theme { + .dark { + --color-accent: var(--color-orange-400); + --color-accent-content: var(--color-orange-400); + --color-accent-foreground: var(--color-orange-950); + } +} + +@layer base { + + *, + ::after, + ::before, + ::backdrop, + ::file-selector-button { + border-color: var(--color-gray-200, currentColor); + } +} + +[data-flux-field]:not(ui-radio, ui-checkbox) { + @apply grid gap-2; +} + +[data-flux-label] { + @apply !mb-0 !leading-tight; +} + +input:focus[data-flux-control], +textarea:focus[data-flux-control], +select:focus[data-flux-control] { + @apply outline-hidden ring-2 ring-accent ring-offset-2 ring-offset-accent-foreground; +} + +/* \[:where(&)\]:size-4 { + @apply size-4; +} */ diff --git a/resources/js/app.js b/resources/js/app.js new file mode 100644 index 0000000..e69de29 diff --git a/resources/views/appointments/index.blade.php b/resources/views/appointments/index.blade.php new file mode 100644 index 0000000..af482ab --- /dev/null +++ b/resources/views/appointments/index.blade.php @@ -0,0 +1,3 @@ + + + diff --git a/resources/views/components/action-message.blade.php b/resources/views/components/action-message.blade.php new file mode 100644 index 0000000..d313ee6 --- /dev/null +++ b/resources/views/components/action-message.blade.php @@ -0,0 +1,14 @@ +@props([ + 'on', +]) + +
merge(['class' => 'text-sm']) }} +> + {{ $slot->isEmpty() ? __('Saved.') : $slot }} +
diff --git a/resources/views/components/app-logo-icon.blade.php b/resources/views/components/app-logo-icon.blade.php new file mode 100644 index 0000000..a295b4a --- /dev/null +++ b/resources/views/components/app-logo-icon.blade.php @@ -0,0 +1 @@ +SafeTrack Systems \ No newline at end of file diff --git a/resources/views/components/app-logo.blade.php b/resources/views/components/app-logo.blade.php new file mode 100644 index 0000000..706f570 --- /dev/null +++ b/resources/views/components/app-logo.blade.php @@ -0,0 +1,6 @@ +
+ SafeTrack Systems Logo +
+
+ SafeTrack Systems +
diff --git a/resources/views/components/auth-header.blade.php b/resources/views/components/auth-header.blade.php new file mode 100644 index 0000000..e596a3f --- /dev/null +++ b/resources/views/components/auth-header.blade.php @@ -0,0 +1,9 @@ +@props([ + 'title', + 'description', +]) + +
+ {{ $title }} + {{ $description }} +
diff --git a/resources/views/components/auth-session-status.blade.php b/resources/views/components/auth-session-status.blade.php new file mode 100644 index 0000000..98e0011 --- /dev/null +++ b/resources/views/components/auth-session-status.blade.php @@ -0,0 +1,9 @@ +@props([ + 'status', +]) + +@if ($status) +
merge(['class' => 'font-medium text-sm text-green-600']) }}> + {{ $status }} +
+@endif diff --git a/resources/views/components/flux/card.blade.php b/resources/views/components/flux/card.blade.php new file mode 100644 index 0000000..386dda4 --- /dev/null +++ b/resources/views/components/flux/card.blade.php @@ -0,0 +1,3 @@ +
merge(['class' => 'bg-white border rounded-lg p-4 shadow']) }}> + {{ $slot }} +
diff --git a/resources/views/components/layouts/app.blade.php b/resources/views/components/layouts/app.blade.php new file mode 100644 index 0000000..681d507 --- /dev/null +++ b/resources/views/components/layouts/app.blade.php @@ -0,0 +1,5 @@ + +
+ {{ $slot }} +
+
diff --git a/resources/views/components/layouts/app/header.blade.php b/resources/views/components/layouts/app/header.blade.php new file mode 100644 index 0000000..0d21e86 --- /dev/null +++ b/resources/views/components/layouts/app/header.blade.php @@ -0,0 +1,106 @@ + + + + @include('partials.head') + + + + + +
+ + + + + + {{ __('Dashboard') }} + + + + + + + @include('partials.theme') + + + + + + +
+
+ + + {{ auth()->user()->initials() }} + + + +
+ {{ auth()->user()->name }} + {{ auth()->user()->email }} +
+
+
+
+ + + + + {{ __('Settings') }} + + + + +
+ @csrf + + {{ __('Log Out') }} + +
+
+ + + + + + + + + + + + + + + {{ __('Dashboard') }} + + + + + + + + + {{ __('Repository') }} + + + + {{ __('Documentation') }} + + + + + {{ $slot }} + + @fluxScripts + + diff --git a/resources/views/components/layouts/app/sidebar-new.blade.php b/resources/views/components/layouts/app/sidebar-new.blade.php new file mode 100644 index 0000000..d7c957a --- /dev/null +++ b/resources/views/components/layouts/app/sidebar-new.blade.php @@ -0,0 +1,157 @@ + + + + @include('partials.head') + @fluxAppearance + + + + + + + + + + + + Dashboard + Job Cards + Customers + Work Orders + + + + + + + + + + + + + + Profile + Settings + + + +
+ @csrf + Logout +
+
+
+
+ + + + + + + + + + + Dashboard + Job Cards + Customers + Vehicles + Appointments + Inspections + Diagnostics + Work Orders + + + Estimates + Invoices + + + + Inventory + Service Items + Technicians + + + + + + + Reports + @can('manage-users') + User Management + @endcan + Settings + + + + + +
+ +
+ @if(request()->is('job-cards*')) + + All Job Cards + Create New + Received + In Diagnosis + In Progress + Completed + + @elseif(request()->routeIs('customers.*')) + + All Customers + Add Customer + Recent Customers + Customer Reports + + @elseif(request()->is('work-orders*')) + + All Work Orders + Pending + In Progress + Completed + On Hold + + @elseif(request()->is('inventory*')) + + Dashboard + Parts + Low Stock + Purchase Orders + Suppliers + + @elseif(request()->is('reports*')) + + All Reports + Sales Reports + Technician Performance + Parts Usage + Customer Reports + + @else + + Dashboard + New Job Cards + Active Work Orders + Pending Estimates + Inventory Status + Reports + + @endif +
+ + + + +
+ {{ $slot }} +
+
+
+ + @livewireScripts + @fluxScripts + + diff --git a/resources/views/components/layouts/app/sidebar-old.blade.php b/resources/views/components/layouts/app/sidebar-old.blade.php new file mode 100644 index 0000000..eb7f01b --- /dev/null +++ b/resources/views/components/layouts/app/sidebar-old.blade.php @@ -0,0 +1,335 @@ + + + + @include('partials.head') + @fluxAppearance + + + + + + + + + + + + Dashboard + Job Cards + Customers + Work Orders + + + + + + + + + + + + + + Profile + Settings + + + +
+ @csrf + Logout +
+
+
+
+ + + + + + + + + + + + + @include('partials.theme') + + + + + + +
+ + + +
+ + + + +
+
+ + + + + +
+ {{ $slot }} +
+ + + + @livewireScripts + @fluxScripts + + diff --git a/resources/views/components/layouts/app/sidebar.blade.php b/resources/views/components/layouts/app/sidebar.blade.php new file mode 100644 index 0000000..e9eb9a1 --- /dev/null +++ b/resources/views/components/layouts/app/sidebar.blade.php @@ -0,0 +1,479 @@ + + + + @include('partials.head') + @fluxAppearance + + + +
+
+ +
+ + + + +
+ + +
+
+ +
+ +
+ + +
+ @if(auth()->user()->hasPermission('customers.create')) + + @endif + + @if(auth()->user()->hasPermission('job-cards.create')) + + @endif + + @if(auth()->user()->hasPermission('appointments.create')) + + @endif +
+ + + + + + + @if(auth()->user()->hasPermission('customers.create')) + + New Customer + + @endif + + @if(auth()->user()->hasPermission('vehicles.create')) + + New Vehicle + + @endif + + @if(auth()->user()->hasPermission('job-cards.create')) + + New Job Card + + @endif + + @if(auth()->user()->hasPermission('appointments.create')) + + New Appointment + + @endif + + + + @if(auth()->user()->hasPermission('estimates.create')) + + New Estimate + + @endif + + @if(auth()->user()->hasPermission('work-orders.create')) + + New Work Order + + @endif + + @if(auth()->user()->hasPermission('inspections.create')) + + New Inspection + + @endif + + +
+
+ + +
+ + + + + + 3 + + + +
+
Notifications
+
+ +
+
Low Stock Alert
+
Brake pads running low
+
+
+ +
+
Appointment Reminder
+
Service due in 1 hour
+
+
+ +
+
Job Completed
+
JC-2024-001 finished
+
+
+ + + View all notifications + +
+
+ + + + + + + + + + + + Light + Dark + System + + + + + + + + + + My Profile + + + + Change Password + + + @if(auth()->user()->hasPermission('settings.manage')) + + System Settings + + @endif + + + +
+
{{ auth()->user()->position ?? 'Employee' }}
+
{{ auth()->user()->department ?? 'General' }}
+
ID: {{ auth()->user()->employee_id ?? 'N/A' }}
+
+ + + +
+ @csrf + + Logout + +
+
+
+
+
+
+ + + + + + + + + @if(auth()->user()->hasPermission('dashboard.view')) + + Dashboard + + @endif + + + + @if(auth()->user()->hasPermission('job-cards.view')) + + Job Cards + + @endif + + @if(auth()->user()->hasPermission('work-orders.view')) + + Work Orders + + @endif + + @if(auth()->user()->hasPermission('inspections.view')) + + Inspections + + @endif + + @if(auth()->user()->hasPermission('diagnosis.view')) + + Diagnostics + + @endif + + @if(auth()->user()->hasPermission('timesheets.view')) + + Timesheets + + @endif + + + + + @if(auth()->user()->hasPermission('customers.view')) + + Customers + + @endif + + @if(auth()->user()->hasPermission('vehicles.view')) + + Vehicles + + @endif + + @if(auth()->user()->hasPermission('appointments.view')) + + Appointments + + @endif + + @if(auth()->user()->hasPermission('service-orders.view')) + + Service Orders + + @endif + + + + + @if(auth()->user()->hasPermission('estimates.view')) + + Estimates + + @endif + + @if(auth()->user()->hasPermission('service-orders.view')) + + Invoices + + @endif + + @if(auth()->user()->hasPermission('service-orders.view')) + + Payments + + @endif + + + + + @if(auth()->user()->hasPermission('inventory.view')) + + Inventory Dashboard + + @endif + + @if(auth()->user()->hasPermission('inventory.view')) + + Parts Catalog + + @endif + + @if(auth()->user()->hasPermission('inventory.view')) + + Suppliers + + @endif + + @if(auth()->user()->hasPermission('inventory.purchase-orders')) + + Purchase Orders + + @endif + + @if(auth()->user()->hasPermission('inventory.stock-movements')) + + Stock Movements + + @endif + + @if(auth()->user()->hasPermission('service-orders.view')) + + Service Items + + @endif + + + + + @if(auth()->user()->hasPermission('technicians.view')) + + Technicians + + @endif + + @if(auth()->user()->hasPermission('technicians.update')) + + Skills Management + + @endif + + @if(auth()->user()->hasPermission('technicians.view-performance')) + + Performance Reports + + @endif + + + + + + + + @if(auth()->user()->hasPermission('reports.view')) + + Reports & Analytics + + @endif + + @if(auth()->user()->hasPermission('users.manage')) + + User Management + + @endif + + @if(auth()->user()->hasPermission('settings.manage')) + + Settings + + @endif + + + + + + {{ $slot }} + + + + @if(session('success')) + + @endif + + @if(session('error')) + + @endif + + + + @livewireScripts + @fluxScripts + + diff --git a/resources/views/components/layouts/auth.blade.php b/resources/views/components/layouts/auth.blade.php new file mode 100644 index 0000000..fb7ef3e --- /dev/null +++ b/resources/views/components/layouts/auth.blade.php @@ -0,0 +1,13 @@ +{{-- + {{ $slot }} + --}} + +{{-- + + {{ $slot }} + +--}} + + + {{ $slot }} + \ No newline at end of file diff --git a/resources/views/components/layouts/auth/card.blade.php b/resources/views/components/layouts/auth/card.blade.php new file mode 100644 index 0000000..e8c320e --- /dev/null +++ b/resources/views/components/layouts/auth/card.blade.php @@ -0,0 +1,26 @@ + + + + @include('partials.head') + + +
+
+ + + + + + + + +
+
+
{{ $slot }}
+
+
+
+
+ @fluxScripts + + diff --git a/resources/views/components/layouts/auth/simple.blade.php b/resources/views/components/layouts/auth/simple.blade.php new file mode 100644 index 0000000..dbd66eb --- /dev/null +++ b/resources/views/components/layouts/auth/simple.blade.php @@ -0,0 +1,22 @@ + + + + @include('partials.head') + + +
+
+ + + + + + +
+ {{ $slot }} +
+
+
+ @fluxScripts + + diff --git a/resources/views/components/layouts/auth/split.blade.php b/resources/views/components/layouts/auth/split.blade.php new file mode 100644 index 0000000..e6bab57 --- /dev/null +++ b/resources/views/components/layouts/auth/split.blade.php @@ -0,0 +1,43 @@ + + + + @include('partials.head') + + +
+ + +
+ @fluxScripts + + diff --git a/resources/views/components/permission-check.blade.php b/resources/views/components/permission-check.blade.php new file mode 100644 index 0000000..3338f62 --- /dev/null +++ b/resources/views/components/permission-check.blade.php @@ -0,0 +1 @@ +{{ $slot }} diff --git a/resources/views/components/placeholder-pattern.blade.php b/resources/views/components/placeholder-pattern.blade.php new file mode 100644 index 0000000..8a434f0 --- /dev/null +++ b/resources/views/components/placeholder-pattern.blade.php @@ -0,0 +1,12 @@ +@props([ + 'id' => uniqid(), +]) + + + + + + + + + diff --git a/resources/views/components/settings-navigation.blade.php b/resources/views/components/settings-navigation.blade.php new file mode 100644 index 0000000..4bb2f42 --- /dev/null +++ b/resources/views/components/settings-navigation.blade.php @@ -0,0 +1,47 @@ + + diff --git a/resources/views/components/settings/layout.blade.php b/resources/views/components/settings/layout.blade.php new file mode 100644 index 0000000..48d95f0 --- /dev/null +++ b/resources/views/components/settings/layout.blade.php @@ -0,0 +1,19 @@ +
+
+ + {{ __('Profile') }} + {{ __('Password') }} + +
+ + + +
+ {{ $heading ?? '' }} + {{ $subheading ?? '' }} + +
+ {{ $slot }} +
+
+
diff --git a/resources/views/customers/create.blade.php b/resources/views/customers/create.blade.php new file mode 100644 index 0000000..77cc04c --- /dev/null +++ b/resources/views/customers/create.blade.php @@ -0,0 +1,3 @@ + + + diff --git a/resources/views/customers/edit.blade.php b/resources/views/customers/edit.blade.php new file mode 100644 index 0000000..32e35cf --- /dev/null +++ b/resources/views/customers/edit.blade.php @@ -0,0 +1,3 @@ + + + diff --git a/resources/views/customers/index.blade.php b/resources/views/customers/index.blade.php new file mode 100644 index 0000000..00cf3ab --- /dev/null +++ b/resources/views/customers/index.blade.php @@ -0,0 +1,3 @@ + + + diff --git a/resources/views/customers/show.blade.php b/resources/views/customers/show.blade.php new file mode 100644 index 0000000..f2d52ca --- /dev/null +++ b/resources/views/customers/show.blade.php @@ -0,0 +1,3 @@ + + + diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php new file mode 100644 index 0000000..f13859a --- /dev/null +++ b/resources/views/dashboard.blade.php @@ -0,0 +1,51 @@ + +
+ +
+
+

Car Repairs Shop Dashboard

+

{{ now()->format('l, F j, Y') }}

+
+
+ @can('job-cards.create') + + + + + New Job Card + + @endcan + + @can('appointments.create') + + + + + Schedule Appointment + + @endcan + + @can('customers.create') + + + + + Add Customer + + @endcan +
+
+ + + + + +
+ + + + + +
+
+
diff --git a/resources/views/flux/accent.blade.php b/resources/views/flux/accent.blade.php new file mode 100644 index 0000000..e4e1411 --- /dev/null +++ b/resources/views/flux/accent.blade.php @@ -0,0 +1,36 @@ +@props([ + 'color' => null, +]) + +@php + $classes = Flux::classes() + ->add(match ($color) { + 'slate' => '[--color-accent:var(--color-slate-800)] [--color-accent-content:var(--color-slate-800)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-white)] dark:[--color-accent-content:var(--color-white)] dark:[--color-accent-foreground:var(--color-slate-800)]', + 'gray' => '[--color-accent:var(--color-gray-800)] [--color-accent-content:var(--color-gray-800)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-white)] dark:[--color-accent-content:var(--color-white)] dark:[--color-accent-foreground:var(--color-gray-800)]', + 'zinc' => '[--color-accent:var(--color-zinc-800)] [--color-accent-content:var(--color-zinc-800)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-white)] dark:[--color-accent-content:var(--color-white)] dark:[--color-accent-foreground:var(--color-zinc-800)]', + 'neutral' => '[--color-accent:var(--color-neutral-800)] [--color-accent-content:var(--color-neutral-800)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-white)] dark:[--color-accent-content:var(--color-white)] dark:[--color-accent-foreground:var(--color-neutral-800)]', + 'stone' => '[--color-accent:var(--color-stone-800)] [--color-accent-content:var(--color-stone-800)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-white)] dark:[--color-accent-content:var(--color-white)] dark:[--color-accent-foreground:var(--color-stone-800)]', + 'red' => '[--color-accent:var(--color-red-500)] [--color-accent-content:var(--color-red-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-red-500)] dark:[--color-accent-content:var(--color-red-400)] dark:[--color-accent-foreground:var(--color-white)]', + 'orange' => '[--color-accent:var(--color-orange-500)] [--color-accent-content:var(--color-orange-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-orange-400)] dark:[--color-accent-content:var(--color-orange-400)] dark:[--color-accent-foreground:var(--color-orange-950)]', + 'amber' => '[--color-accent:var(--color-amber-400)] [--color-accent-content:var(--color-amber-600)] [--color-accent-foreground:var(--color-amber-950)] dark:[--color-accent:var(--color-amber-400)] dark:[--color-accent-content:var(--color-amber-400)] dark:[--color-accent-foreground:var(--color-amber-950)]', + 'yellow' => '[--color-accent:var(--color-yellow-400)] [--color-accent-content:var(--color-yellow-600)] [--color-accent-foreground:var(--color-yellow-950)] dark:[--color-accent:var(--color-yellow-400)] dark:[--color-accent-content:var(--color-yellow-400)] dark:[--color-accent-foreground:var(--color-yellow-950)]', + 'lime' => '[--color-accent:var(--color-lime-400)] [--color-accent-content:var(--color-lime-600)] [--color-accent-foreground:var(--color-lime-900)] dark:[--color-accent:var(--color-lime-400)] dark:[--color-accent-content:var(--color-lime-400)] dark:[--color-accent-foreground:var(--color-lime-950)]', + 'green' => '[--color-accent:var(--color-green-600)] [--color-accent-content:var(--color-green-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-green-600)] dark:[--color-accent-content:var(--color-green-400)] dark:[--color-accent-foreground:var(--color-white)]', + 'emerald' => '[--color-accent:var(--color-emerald-600)] [--color-accent-content:var(--color-emerald-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-emerald-600)] dark:[--color-accent-content:var(--color-emerald-400)] dark:[--color-accent-foreground:var(--color-white)]', + 'teal' => '[--color-accent:var(--color-teal-600)] [--color-accent-content:var(--color-teal-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-teal-600)] dark:[--color-accent-content:var(--color-teal-400)] dark:[--color-accent-foreground:var(--color-white)]', + 'cyan' => '[--color-accent:var(--color-cyan-600)] [--color-accent-content:var(--color-cyan-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-cyan-600)] dark:[--color-accent-content:var(--color-cyan-400)] dark:[--color-accent-foreground:var(--color-white)]', + 'sky' => '[--color-accent:var(--color-sky-600)] [--color-accent-content:var(--color-sky-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-sky-600)] dark:[--color-accent-content:var(--color-sky-400)] dark:[--color-accent-foreground:var(--color-white)]', + 'blue' => '[--color-accent:var(--color-blue-500)] [--color-accent-content:var(--color-blue-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-blue-500)] dark:[--color-accent-content:var(--color-blue-400)] dark:[--color-accent-foreground:var(--color-white)]', + 'indigo' => '[--color-accent:var(--color-indigo-500)] [--color-accent-content:var(--color-indigo-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-indigo-500)] dark:[--color-accent-content:var(--color-indigo-300)] dark:[--color-accent-foreground:var(--color-white)]', + 'violet' => '[--color-accent:var(--color-violet-500)] [--color-accent-content:var(--color-violet-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-violet-500)] dark:[--color-accent-content:var(--color-violet-400)] dark:[--color-accent-foreground:var(--color-white)]', + 'purple' => '[--color-accent:var(--color-purple-500)] [--color-accent-content:var(--color-purple-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-purple-500)] dark:[--color-accent-content:var(--color-purple-300)] dark:[--color-accent-foreground:var(--color-white)]', + 'fuchsia' => '[--color-accent:var(--color-fuchsia-600)] [--color-accent-content:var(--color-fuchsia-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-fuchsia-600)] dark:[--color-accent-content:var(--color-fuchsia-400)] dark:[--color-accent-foreground:var(--color-white)]', + 'pink' => '[--color-accent:var(--color-pink-600)] [--color-accent-content:var(--color-pink-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-pink-600)] dark:[--color-accent-content:var(--color-pink-400)] dark:[--color-accent-foreground:var(--color-white)]', + 'rose' => '[--color-accent:var(--color-rose-500)] [--color-accent-content:var(--color-rose-500)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-rose-500)] dark:[--color-accent-content:var(--color-rose-400)] dark:[--color-accent-foreground:var(--color-white)]', + }) + ; +@endphp + +
class($classes) }}> + {{ $slot }} +
\ No newline at end of file diff --git a/resources/views/flux/aside.blade.php b/resources/views/flux/aside.blade.php new file mode 100644 index 0000000..5f73c2c --- /dev/null +++ b/resources/views/flux/aside.blade.php @@ -0,0 +1,19 @@ +@props([ + 'sticky' => null, +]) + +@php +$classes = Flux::classes('[grid-area:aside]'); + +if ($sticky) { + $attributes = $attributes->merge([ + 'x-data' => '', + 'x-bind:style' => '{ position: \'sticky\', top: $el.offsetTop + \'px\', \'max-height\': \'calc(100dvh - \' + $el.offsetTop + \'px)\' }', + 'class' => 'max-h-[100vh] overflow-y-auto', + ]); +} +@endphp + +
class($classes) }} data-flux-aside> + {{ $slot }} +
diff --git a/resources/views/flux/avatar/group.blade.php b/resources/views/flux/avatar/group.blade.php new file mode 100644 index 0000000..aff9203 --- /dev/null +++ b/resources/views/flux/avatar/group.blade.php @@ -0,0 +1,12 @@ + +@php +$classes = Flux::classes() + ->add('flex isolate') + ->add('*:not-first:-ml-2 **:ring-white **:dark:ring-zinc-900') + ->add('**:data-[slot=avatar]:ring-4 **:data-[slot=avatar]:data-[size=sm]:ring-2 **:data-[slot=avatar]:data-[size=xs]:ring-2') + ; +@endphp + +
class($classes) }}> + {{ $slot }} +
diff --git a/resources/views/flux/avatar/index.blade.php b/resources/views/flux/avatar/index.blade.php new file mode 100644 index 0000000..8981899 --- /dev/null +++ b/resources/views/flux/avatar/index.blade.php @@ -0,0 +1,187 @@ +@php $iconVariant = $iconVariant ??= $attributes->pluck('icon:variant'); @endphp + +@props([ + 'iconVariant' => 'solid', + 'initials' => null, + 'tooltip' => null, + 'circle' => null, + 'color' => null, + 'badge' => null, + 'name' => null, + 'icon' => null, + 'size' => 'md', + 'src' => null, + 'href' => null, + 'alt' => null, + 'as' => 'div', +]) + +@php +if ($name && ! $initials) { + $parts = explode(' ', trim($name)); + + if ($attributes->pluck('initials:single')) { + $initials = strtoupper(mb_substr($parts[0], 0, 1)); + } else { + // Remove empty strings from the array... + $parts = collect($parts)->filter()->values()->all(); + + if (count($parts) > 1) { + $initials = strtoupper(mb_substr($parts[0], 0, 1) . mb_substr($parts[1], 0, 1)); + } else if (count($parts) === 1) { + $initials = strtoupper(mb_substr($parts[0], 0, 1)) . strtolower(mb_substr($parts[0], 1, 1)); + } + } +} + +if ($name && $tooltip === true) { + $tooltip = $name; +} + +$hasTextContent = $icon ?? $initials ?? $slot->isNotEmpty(); + +// If there's no text content, we'll fallback to using the user icon otherwise there will be an empty white square... +if (! $hasTextContent) { + $icon = 'user'; + $hasTextContent = true; +} + +// Be careful not to change the order of these colors. +// They're used in the hash function below and changing them would change actual user avatar colors that they might have grown to identify with. +$colors = ['red', 'orange', 'amber', 'yellow', 'lime', 'green', 'emerald', 'teal', 'cyan', 'sky', 'blue', 'indigo', 'violet', 'purple', 'fuchsia', 'pink', 'rose']; + +if ($hasTextContent && $color === 'auto') { + $colorSeed = $attributes->pluck('color:seed') ?? $name ?? $icon ?? $initials ?? $slot; + $hash = crc32((string) $colorSeed); + $color = $colors[$hash % count($colors)]; +} + +$classes = Flux::classes() + ->add(match($size) { + 'xl' => '[:where(&)]:size-16 [:where(&)]:text-base', + 'lg' => '[:where(&)]:size-12 [:where(&)]:text-base', + default => '[:where(&)]:size-10 [:where(&)]:text-sm', + 'sm' => '[:where(&)]:size-8 [:where(&)]:text-sm', + 'xs' => '[:where(&)]:size-6 [:where(&)]:text-xs', + }) + ->add($circle ? '[--avatar-radius:calc(infinity*1px)]' : match($size) { + 'xl' => '[--avatar-radius:var(--radius-xl)]', + 'lg' => '[--avatar-radius:var(--radius-lg)]', + default => '[--avatar-radius:var(--radius-lg)]', + 'sm' => '[--avatar-radius:var(--radius-md)]', + 'xs' => '[--avatar-radius:var(--radius-sm)]', + }) + ->add('relative flex-none isolate flex items-center justify-center') + ->add('[:where(&)]:font-medium') + ->add('rounded-[var(--avatar-radius)]') + ->add($hasTextContent ? '[:where(&)]:bg-zinc-200 [:where(&)]:dark:bg-zinc-600 [:where(&)]:text-zinc-800 [:where(&)]:dark:text-white' : '') + ->add(match($color) { + 'red' => 'bg-red-200 text-red-800', + 'orange' => 'bg-orange-200 text-orange-800', + 'amber' => 'bg-amber-200 text-amber-800', + 'yellow' => 'bg-yellow-200 text-yellow-800', + 'lime' => 'bg-lime-200 text-lime-800', + 'green' => 'bg-green-200 text-green-800', + 'emerald' => 'bg-emerald-200 text-emerald-800', + 'teal' => 'bg-teal-200 text-teal-800', + 'cyan' => 'bg-cyan-200 text-cyan-800', + 'sky' => 'bg-sky-200 text-sky-800', + 'blue' => 'bg-blue-200 text-blue-800', + 'indigo' => 'bg-indigo-200 text-indigo-800', + 'violet' => 'bg-violet-200 text-violet-800', + 'purple' => 'bg-purple-200 text-purple-800', + 'fuchsia' => 'bg-fuchsia-200 text-fuchsia-800', + 'pink' => 'bg-pink-200 text-pink-800', + 'rose' => 'bg-rose-200 text-rose-800', + default => '', + }) + ->add(true ? [ + 'after:absolute after:inset-0 after:inset-ring-[1px] after:inset-ring-black/7 dark:after:inset-ring-white/10', + $circle ? 'after:rounded-full' : match($size) { + 'xl' => 'after:rounded-xl', + 'lg' => 'after:rounded-lg', + default => 'after:rounded-lg', + 'sm' => 'after:rounded-md', + 'xs' => 'after:rounded-sm', + }, + ] : []); + +$iconClasses = Flux::classes() + ->add('opacity-75') + ->add(match($size) { + 'lg' => 'size-8', + default => 'size-6', + 'sm' => 'size-5', + 'xs' => 'size-4', + }); + +$badgeColor = $attributes->pluck('badge:color') ?: (is_object($badge) ? $badge?->attributes?->pluck('color') : null); +$badgeCircle = $attributes->pluck('badge:circle') ?: (is_object($badge) ? $badge?->attributes?->pluck('circle') : null); +$badgePosition = $attributes->pluck('badge:position') ?: (is_object($badge) ? $badge?->attributes?->pluck('position') : null); +$badgeVariant = $attributes->pluck('badge:variant') ?: (is_object($badge) ? $badge?->attributes?->pluck('variant') : null); + +$badgeClasses = Flux::classes() + ->add('absolute ring-[2px] ring-white dark:ring-zinc-900 z-10') + ->add(match($size) { + default => 'h-3 min-w-3', + 'sm' => 'h-2 min-w-2', + 'xs' => 'h-2 min-w-2', + }) + ->add('flex items-center justify-center tabular-nums overflow-hidden') + ->add('text-[.625rem] text-zinc-800 dark:text-white font-medium') + ->add($badgeCircle ? 'rounded-full' : 'rounded-[3px]') + ->add($badgeVariant === 'outline' ? [ + 'after:absolute after:inset-[3px] after:bg-white dark:after:bg-zinc-900', + $badgeCircle ? 'after:rounded-full' : 'after:rounded-[1px]', + ] : []) + ->add(match($badgePosition) { + 'top left' => 'top-0 left-0', + 'top right' => 'top-0 right-0', + 'bottom left' => 'bottom-0 left-0', + 'bottom right' => 'bottom-0 right-0', + default => 'bottom-0 right-0', + }) + ->add(match($badgeColor) { + 'red' => 'bg-red-500 dark:bg-red-400', + 'orange' => 'bg-orange-500 dark:bg-orange-400', + 'amber' => 'bg-amber-500 dark:bg-amber-400', + 'yellow' => 'bg-yellow-500 dark:bg-yellow-400', + 'lime' => 'bg-lime-500 dark:bg-lime-400', + 'green' => 'bg-green-500 dark:bg-green-400', + 'emerald' => 'bg-emerald-500 dark:bg-emerald-400', + 'teal' => 'bg-teal-500 dark:bg-teal-400', + 'cyan' => 'bg-cyan-500 dark:bg-cyan-400', + 'sky' => 'bg-sky-500 dark:bg-sky-400', + 'blue' => 'bg-blue-500 dark:bg-blue-400', + 'indigo' => 'bg-indigo-500 dark:bg-indigo-400', + 'violet' => 'bg-violet-500 dark:bg-violet-400', + 'purple' => 'bg-purple-500 dark:bg-purple-400', + 'fuchsia' => 'bg-fuchsia-500 dark:bg-fuchsia-400', + 'pink' => 'bg-pink-500 dark:bg-pink-400', + 'rose' => 'bg-rose-500 dark:bg-rose-400', + 'zinc' => 'bg-zinc-400 dark:bg-zinc-300', + 'gray' => 'bg-zinc-400 dark:bg-zinc-300', + default => 'bg-white dark:bg-zinc-900', + }) + ; + +$label = $alt ?? $name; +@endphp + + + + + {{ $alt ?? $name }} + + + + {{ $initials ?? $slot }} + + + +
attributes->class($badgeClasses) }} aria-hidden="true">{{ $badge }}
+ + + +
+
diff --git a/resources/views/flux/badge/close.blade.php b/resources/views/flux/badge/close.blade.php new file mode 100644 index 0000000..4817c15 --- /dev/null +++ b/resources/views/flux/badge/close.blade.php @@ -0,0 +1,23 @@ +@php $iconVariant = $iconVariant ??= $attributes->pluck('icon:variant'); @endphp + +@props([ + 'iconVariant' => 'micro', + 'icon' => 'x-mark', +]) + +@php +// When using the outline icon variant, we need to size it down to match the default icon sizes... +$iconClasses = Flux::classes()->add($iconVariant === 'outline' ? 'size-4' : ''); + +$classes = Flux::classes() + ->add('p-1 -my-1 -me-1 opacity-50 hover:opacity-100') + ; +@endphp + + diff --git a/resources/views/flux/badge/index.blade.php b/resources/views/flux/badge/index.blade.php new file mode 100644 index 0000000..26fbb59 --- /dev/null +++ b/resources/views/flux/badge/index.blade.php @@ -0,0 +1,96 @@ +@php $iconTrailing = $iconTrailing ??= $attributes->pluck('icon:trailing'); @endphp +@php $iconVariant = $iconVariant ??= $attributes->pluck('icon:variant'); @endphp + +@props([ + 'iconVariant' => 'micro', + 'iconTrailing' => null, + 'variant' => null, + 'color' => null, + 'inset' => null, + 'size' => null, + 'icon' => null, +]) + +@php +$insetClasses = Flux::applyInset($inset, top: '-mt-1', right: '-me-2', bottom: '-mb-1', left: '-ms-2'); + +// When using the outline icon variant, we need to size it down to match the default icon sizes... +$iconClasses = Flux::classes()->add($iconVariant === 'outline' ? 'size-4' : ''); + +$classes = Flux::classes() + ->add('inline-flex items-center font-medium whitespace-nowrap') + ->add($insetClasses) + ->add('[print-color-adjust:exact]') + ->add(match ($size) { + 'lg' => 'text-sm py-1.5 **:data-flux-badge-icon:me-2', + default => 'text-sm py-1 **:data-flux-badge-icon:me-1.5', + 'sm' => 'text-xs py-1 **:data-flux-badge-icon:size-3 **:data-flux-badge-icon:me-1', + }) + ->add(match ($variant) { + 'pill' => 'rounded-full px-3', + default => 'rounded-md px-2', + }) + /** + * We can't compile classes for each color because of variants color to color and Tailwind's JIT compiler. + * We instead need to write out each one by hand. Sorry... + */ + ->add($variant === 'solid' ? match ($color) { + default => 'text-white dark:text-white bg-zinc-600 dark:bg-zinc-600 [&:is(button)]:hover:bg-zinc-700 dark:[button]:hover:bg-zinc-500', + 'red' => 'text-white dark:text-white bg-red-500 dark:bg-red-600 [&:is(button)]:hover:bg-red-600 dark:[button]:hover:bg-red-500', + 'orange' => 'text-white dark:text-white bg-orange-500 dark:bg-orange-600 [&:is(button)]:hover:bg-orange-600 dark:[button]:hover:bg-orange-500', + 'amber' => 'text-white dark:text-zinc-950 bg-amber-500 dark:bg-amber-500 [&:is(button)]:hover:bg-amber-600 dark:[button]:hover:bg-amber-400', + 'yellow' => 'text-white dark:text-zinc-950 bg-yellow-500 dark:bg-yellow-400 [&:is(button)]:hover:bg-yellow-600 dark:[button]:hover:bg-yellow-300', + 'lime' => 'text-white dark:text-white bg-lime-500 dark:bg-lime-600 [&:is(button)]:hover:bg-lime-600 dark:[button]:hover:bg-lime-500', + 'green' => 'text-white dark:text-white bg-green-500 dark:bg-green-600 [&:is(button)]:hover:bg-green-600 dark:[button]:hover:bg-green-500', + 'emerald' => 'text-white dark:text-white bg-emerald-500 dark:bg-emerald-600 [&:is(button)]:hover:bg-emerald-600 dark:[button]:hover:bg-emerald-500', + 'teal' => 'text-white dark:text-white bg-teal-500 dark:bg-teal-600 [&:is(button)]:hover:bg-teal-600 dark:[button]:hover:bg-teal-500', + 'cyan' => 'text-white dark:text-white bg-cyan-500 dark:bg-cyan-600 [&:is(button)]:hover:bg-cyan-600 dark:[button]:hover:bg-cyan-500', + 'sky' => 'text-white dark:text-white bg-sky-500 dark:bg-sky-600 [&:is(button)]:hover:bg-sky-600 dark:[button]:hover:bg-sky-500', + 'blue' => 'text-white dark:text-white bg-blue-500 dark:bg-blue-600 [&:is(button)]:hover:bg-blue-600 dark:[button]:hover:bg-blue-500', + 'indigo' => 'text-white dark:text-white bg-indigo-500 dark:bg-indigo-600 [&:is(button)]:hover:bg-indigo-600 dark:[button]:hover:bg-indigo-500', + 'violet' => 'text-white dark:text-white bg-violet-500 dark:bg-violet-600 [&:is(button)]:hover:bg-violet-600 dark:[button]:hover:bg-violet-500', + 'purple' => 'text-white dark:text-white bg-purple-500 dark:bg-purple-600 [&:is(button)]:hover:bg-purple-600 dark:[button]:hover:bg-purple-500', + 'fuchsia' => 'text-white dark:text-white bg-fuchsia-500 dark:bg-fuchsia-600 [&:is(button)]:hover:bg-fuchsia-600 dark:[button]:hover:bg-fuchsia-500', + 'pink' => 'text-white dark:text-white bg-pink-500 dark:bg-pink-600 [&:is(button)]:hover:bg-pink-600 dark:[button]:hover:bg-pink-500', + 'rose' => 'text-white dark:text-white bg-rose-500 dark:bg-rose-600 [&:is(button)]:hover:bg-rose-600 dark:[button]:hover:bg-rose-500', + } : match ($color) { + default => 'text-zinc-700 [&_button]:text-zinc-700! dark:text-zinc-200 dark:[&_button]:text-zinc-200! bg-zinc-400/15 dark:bg-zinc-400/40 [&:is(button)]:hover:bg-zinc-400/25 dark:[button]:hover:bg-zinc-400/50', + 'red' => 'text-red-700 [&_button]:text-red-700! dark:text-red-200 dark:[&_button]:text-red-200! bg-red-400/20 dark:bg-red-400/40 [&:is(button)]:hover:bg-red-400/30 dark:[button]:hover:bg-red-400/50', + 'orange' => 'text-orange-700 [&_button]:text-orange-700! dark:text-orange-200 dark:[&_button]:text-orange-200! bg-orange-400/20 dark:bg-orange-400/40 [&:is(button)]:hover:bg-orange-400/30 dark:[button]:hover:bg-orange-400/50', + 'amber' => 'text-amber-700 [&_button]:text-amber-700! dark:text-amber-200 dark:[&_button]:text-amber-200! bg-amber-400/25 dark:bg-amber-400/40 [&:is(button)]:hover:bg-amber-400/40 dark:[button]:hover:bg-amber-400/50', + 'yellow' => 'text-yellow-800 [&_button]:text-yellow-800! dark:text-yellow-200 dark:[&_button]:text-yellow-200! bg-yellow-400/25 dark:bg-yellow-400/40 [&:is(button)]:hover:bg-yellow-400/40 dark:[button]:hover:bg-yellow-400/50', + 'lime' => 'text-lime-800 [&_button]:text-lime-800! dark:text-lime-200 dark:[&_button]:text-lime-200! bg-lime-400/25 dark:bg-lime-400/40 [&:is(button)]:hover:bg-lime-400/35 dark:[button]:hover:bg-lime-400/50', + 'green' => 'text-green-800 [&_button]:text-green-800! dark:text-green-200 dark:[&_button]:text-green-200! bg-green-400/20 dark:bg-green-400/40 [&:is(button)]:hover:bg-green-400/30 dark:[button]:hover:bg-green-400/50', + 'emerald' => 'text-emerald-800 [&_button]:text-emerald-800! dark:text-emerald-200 dark:[&_button]:text-emerald-200! bg-emerald-400/20 dark:bg-emerald-400/40 [&:is(button)]:hover:bg-emerald-400/30 dark:[button]:hover:bg-emerald-400/50', + 'teal' => 'text-teal-800 [&_button]:text-teal-800! dark:text-teal-200 dark:[&_button]:text-teal-200! bg-teal-400/20 dark:bg-teal-400/40 [&:is(button)]:hover:bg-teal-400/30 dark:[button]:hover:bg-teal-400/50', + 'cyan' => 'text-cyan-800 [&_button]:text-cyan-800! dark:text-cyan-200 dark:[&_button]:text-cyan-200! bg-cyan-400/20 dark:bg-cyan-400/40 [&:is(button)]:hover:bg-cyan-400/30 dark:[button]:hover:bg-cyan-400/50', + 'sky' => 'text-sky-800 [&_button]:text-sky-800! dark:text-sky-200 dark:[&_button]:text-sky-200! bg-sky-400/20 dark:bg-sky-400/40 [&:is(button)]:hover:bg-sky-400/30 dark:[button]:hover:bg-sky-400/50', + 'blue' => 'text-blue-800 [&_button]:text-blue-800! dark:text-blue-200 dark:[&_button]:text-blue-200! bg-blue-400/20 dark:bg-blue-400/40 [&:is(button)]:hover:bg-blue-400/30 dark:[button]:hover:bg-blue-400/50', + 'indigo' => 'text-indigo-700 [&_button]:text-indigo-700! dark:text-indigo-200 dark:[&_button]:text-indigo-200! bg-indigo-400/20 dark:bg-indigo-400/40 [&:is(button)]:hover:bg-indigo-400/30 dark:[button]:hover:bg-indigo-400/50', + 'violet' => 'text-violet-700 [&_button]:text-violet-700! dark:text-violet-200 dark:[&_button]:text-violet-200! bg-violet-400/20 dark:bg-violet-400/40 [&:is(button)]:hover:bg-violet-400/30 dark:[button]:hover:bg-violet-400/50', + 'purple' => 'text-purple-700 [&_button]:text-purple-700! dark:text-purple-200 dark:[&_button]:text-purple-200! bg-purple-400/20 dark:bg-purple-400/40 [&:is(button)]:hover:bg-purple-400/30 dark:[button]:hover:bg-purple-400/50', + 'fuchsia' => 'text-fuchsia-700 [&_button]:text-fuchsia-700! dark:text-fuchsia-200 dark:[&_button]:text-fuchsia-200! bg-fuchsia-400/20 dark:bg-fuchsia-400/40 [&:is(button)]:hover:bg-fuchsia-400/30 dark:[button]:hover:bg-fuchsia-400/50', + 'pink' => 'text-pink-700 [&_button]:text-pink-700! dark:text-pink-200 dark:[&_button]:text-pink-200! bg-pink-400/20 dark:bg-pink-400/40 [&:is(button)]:hover:bg-pink-400/30 dark:[button]:hover:bg-pink-400/50', + 'rose' => 'text-rose-700 [&_button]:text-rose-700! dark:text-rose-200 dark:[&_button]:text-rose-200! bg-rose-400/20 dark:bg-rose-400/40 [&:is(button)]:hover:bg-rose-400/30 dark:[button]:hover:bg-rose-400/50', + }); +@endphp + + + + + + {{ $icon }} + + + {{ $slot }} + + +
+ + + + {{ $iconTrailing }} + +
+ +
diff --git a/resources/views/flux/brand.blade.php b/resources/views/flux/brand.blade.php new file mode 100644 index 0000000..f23c9ba --- /dev/null +++ b/resources/views/flux/brand.blade.php @@ -0,0 +1,52 @@ +@props([ + 'name' => null, + 'logo' => null, + 'alt' => null, + 'href' => '/', +]) + +@php +$classes = Flux::classes() + ->add('h-10 flex items-center me-4') + ; + +$textClasses = Flux::classes() + ->add('text-sm font-medium truncate [:where(&)]:text-zinc-800 dark:[:where(&)]:text-zinc-100') + ; +@endphp + + + class([ $classes, 'gap-2' ]) }} data-flux-brand> + +
attributes->class('flex items-center justify-center [:where(&)]:h-6 [:where(&)]:min-w-6 [:where(&)]:rounded-sm overflow-hidden shrink-0') }}> + {{ $logo }} +
+ +
+ + {{ $alt }} + + {{ $slot }} + +
+ + +
{{ $name }}
+
+ + class($classes) }} data-flux-brand> + +
attributes->class('flex items-center justify-center [:where(&)]:h-6 [:where(&)]:min-w-6 [:where(&)]:rounded-sm overflow-hidden shrink-0') }}> + {{ $logo }} +
+ +
+ + {{ $alt }} + + {{ $slot }} + +
+ +
+ diff --git a/resources/views/flux/breadcrumbs/index.blade.php b/resources/views/flux/breadcrumbs/index.blade.php new file mode 100644 index 0000000..7f4af2e --- /dev/null +++ b/resources/views/flux/breadcrumbs/index.blade.php @@ -0,0 +1,4 @@ + +
class('flex') }} data-flux-breadcrumbs> + {{ $slot }} +
diff --git a/resources/views/flux/breadcrumbs/item.blade.php b/resources/views/flux/breadcrumbs/item.blade.php new file mode 100644 index 0000000..94adecc --- /dev/null +++ b/resources/views/flux/breadcrumbs/item.blade.php @@ -0,0 +1,67 @@ +@php $iconVariant = $iconVariant ??= $attributes->pluck('icon:variant'); @endphp + +@props([ + 'separator' => null, + 'iconVariant' => 'mini', + 'icon' => null, + 'href' => null, +]) + +@php +$classes = Flux::classes() + ->add('flex items-center') + ->add('text-sm font-medium') + ->add('group/breadcrumb') + ; + +$linkClasses = Flux::classes() + ->add('text-zinc-800 dark:text-white') + ->add('hover:underline decoration-zinc-800/20 underline-offset-4'); + +$staticTextClasses = Flux::classes() + ->add('text-gray-500 dark:text-white/80') + ; + +$separatorClasses = Flux::classes() + ->add('mx-1 text-zinc-300 dark:text-white/80') + ->add('group-last/breadcrumb:hidden') + ; + +$iconClasses = Flux::classes() + // When using the outline icon variant, we need to size it down to match the default icon sizes... + ->add($iconVariant === 'outline' ? 'size-5' : '') + ; + +[ $styleAttributes, $attributes ] = Flux::splitAttributes($attributes); +@endphp + +
class($classes) }} data-flux-breadcrumbs-item> + + class($linkClasses) }} href="{{ $href }}"> + + + + {{ $slot }} + + + +
class($staticTextClasses) }}> + + + + {{ $slot }} + +
+ + + @if ($separator == null) + + + @elseif (! is_string($separator)) + {{ $separator }} + @elseif ($separator === 'slash') + + @else + + @endif +
diff --git a/resources/views/flux/button/group.blade.php b/resources/views/flux/button/group.blade.php new file mode 100644 index 0000000..67aecc4 --- /dev/null +++ b/resources/views/flux/button/group.blade.php @@ -0,0 +1,45 @@ +@php +$classes = Flux::classes() + ->add('flex group/button') + ->add([ + // With the external borders, let's always make sure the first and last children have outside borders. + // For internal borders, we will ensure that all left borders are removed, but the right borders remain. + // But when there is a input groupsuffix, then there should be no right internal border. + // That way we shouldn't ever have a double border... + + // All inputs borders... + '[&>[data-flux-input]:last-child:not(:first-child)>[data-flux-group-target]:not([data-invalid])]:border-s-0', + '[&>[data-flux-input]:not(:first-child):not(:last-child)>[data-flux-group-target]:not([data-invalid])]:border-s-0', + '[&>[data-flux-input]:has(+[data-flux-input-group-suffix])>[data-flux-group-target]:not([data-invalid])]:border-e-0', + + // Selects and date pickers borders... + '[&>*:last-child:not(:first-child)>[data-flux-group-target]:not([data-invalid])]:border-s-0', + '[&>*:not(:first-child):not(:last-child)>[data-flux-group-target]:not([data-invalid])]:border-s-0', + '[&>*:has(+[data-flux-input-group-suffix])>[data-flux-group-target]:not([data-invalid])]:border-e-0', + + // Buttons borders... + '[&>[data-flux-group-target]:last-child:not(:first-child)]:border-s-0', + '[&>[data-flux-group-target]:not(:first-child):not(:last-child)]:border-s-0', + '[&>[data-flux-group-target]:has(+[data-flux-input-group-suffix])]:border-e-0', + + // "Weld" the borders of inputs together by overriding their border radiuses... + '[&>[data-flux-group-target]:not(:first-child):not(:last-child)]:rounded-none', + '[&>[data-flux-group-target]:first-child:not(:last-child)]:rounded-e-none', + '[&>[data-flux-group-target]:last-child:not(:first-child)]:rounded-s-none', + + // "Weld" borders for sub-children of group targets (button element inside ui-select element, etc.)... + '[&>*:not(:first-child):not(:last-child):not(:only-child)>[data-flux-group-target]]:rounded-none', + '[&>*:first-child:not(:last-child)>[data-flux-group-target]]:rounded-e-none', + '[&>*:last-child:not(:first-child)>[data-flux-group-target]]:rounded-s-none', + + // "Weld" borders for sub-sub-children of group targets (input element inside div inside ui-select element (combobox))... + '[&>*:not(:first-child):not(:last-child):not(:only-child)>[data-flux-input]>[data-flux-group-target]]:rounded-none', + '[&>*:first-child:not(:last-child)>[data-flux-input]>[data-flux-group-target]]:rounded-e-none', + '[&>*:last-child:not(:first-child)>[data-flux-input]>[data-flux-group-target]]:rounded-s-none', + ]) + ; +@endphp + +
class($classes) }} data-flux-button-group> + {{ $slot }} +
diff --git a/resources/views/flux/button/index.blade.php b/resources/views/flux/button/index.blade.php new file mode 100644 index 0000000..7c676cc --- /dev/null +++ b/resources/views/flux/button/index.blade.php @@ -0,0 +1,198 @@ +@php $iconTrailing = $iconTrailing ??= $attributes->pluck('icon:trailing'); @endphp +@php $iconLeading = $iconLeading ??= $attributes->pluck('icon:leading'); @endphp +@php $iconVariant = $iconVariant ??= $attributes->pluck('icon:variant'); @endphp + +@props([ + 'iconTrailing' => null, + 'variant' => 'outline', + 'iconVariant' => null, + 'iconLeading' => null, + 'type' => 'button', + 'loading' => null, + 'size' => 'base', + 'square' => null, + 'color' => null, + 'inset' => null, + 'icon' => null, + 'kbd' => null, +]) + +@php +$iconLeading = $icon ??= $iconLeading; + +// Button should be a square if it has no text contents... +$square ??= $slot->isEmpty(); + +// Size-up icons in square/icon-only buttons... (xs buttons just get micro size/style...) +$iconVariant ??= ($size === 'xs') + ? ($square ? 'micro' : 'micro') + : ($square ? 'mini' : 'micro'); + +$iconTrailingVariant ??= $attributes->pluck('icon-trailing:variant', $iconVariant); + +// When using the outline icon variant, we need to size it down to match the default icon sizes... +$iconClasses = Flux::classes() + ->add($iconVariant === 'outline' ? ($square && $size !== 'xs' ? 'size-5' : 'size-4') : '') + ->add($attributes->pluck('icon:class')) + ; + +$iconTrailingClasses = Flux::classes() + ->add($iconTrailingVariant === 'outline' ? ($square && $size !== 'xs' ? 'size-5' : 'size-4') : '') + ->add($attributes->pluck('icon-trailing:class')) + ; + +$isTypeSubmitAndNotDisabledOnRender = $type === 'submit' && ! $attributes->has('disabled'); + +$isJsMethod = str_starts_with($attributes->whereStartsWith('wire:click')->first() ?? '', '$js.'); + +$loading ??= $loading ?? ($isTypeSubmitAndNotDisabledOnRender || $attributes->whereStartsWith('wire:click')->isNotEmpty() && ! $isJsMethod); + +if ($loading && $type !== 'submit' && ! $isJsMethod) { + $attributes = $attributes->merge(['wire:loading.attr' => 'data-flux-loading']); + + // We need to add `wire:target` here because without it the loading indicator won't be scoped + // by method params, causing multiple buttons with the same method but different params to + // trigger each other's loading indicators... + if (! $attributes->has('wire:target') && $target = $attributes->whereStartsWith('wire:click')->first()) { + $attributes = $attributes->merge(['wire:target' => $target], escape: false); + } +} + +$classes = Flux::classes() + ->add('relative items-center font-medium justify-center gap-2 whitespace-nowrap') + ->add('disabled:opacity-75 dark:disabled:opacity-75 disabled:cursor-default disabled:pointer-events-none') + ->add(match ($size) { // Size... + 'base' => 'h-10 text-sm rounded-lg' . ' ' . ( + $square + ? 'w-10' + // If we have an icon, we want to reduce the padding on the side that has the icon... + : ($iconLeading && $iconLeading !== '' ? 'ps-3' : 'ps-4') . ' ' . ($iconTrailing && $iconTrailing !== '' ? 'pe-3' : 'pe-4') + ), + 'sm' => 'h-8 text-sm rounded-md' . ' ' . ($square ? 'w-8' : 'px-3'), + 'xs' => 'h-6 text-xs rounded-md' . ' ' . ($square ? 'w-6' : 'px-2'), + }) + ->add('inline-flex') // Buttons are inline by default but links are blocks, so inline-flex is needed here to ensure link-buttons are displayed the same as buttons... + ->add($inset ? match ($size) { // Inset... + 'base' => $square + ? Flux::applyInset($inset, top: '-mt-2.5', right: '-me-2.5', bottom: '-mb-2.5', left: '-ms-2.5') + : Flux::applyInset($inset, top: '-mt-2.5', right: '-me-4', bottom: '-mb-3', left: '-ms-4'), + 'sm' => $square + ? Flux::applyInset($inset, top: '-mt-1.5', right: '-me-1.5', bottom: '-mb-1.5', left: '-ms-1.5') + : Flux::applyInset($inset, top: '-mt-1.5', right: '-me-3', bottom: '-mb-1.5', left: '-ms-3'), + 'xs' => $square + ? Flux::applyInset($inset, top: '-mt-1', right: '-me-1', bottom: '-mb-1', left: '-ms-1') + : Flux::applyInset($inset, top: '-mt-1', right: '-me-2', bottom: '-mb-1', left: '-ms-2'), + } : '') + ->add(match ($variant) { // Background color... + 'primary' => 'bg-[var(--color-accent)] hover:bg-[color-mix(in_oklab,_var(--color-accent),_transparent_10%)]', + 'filled' => 'bg-zinc-800/5 hover:bg-zinc-800/10 dark:bg-white/10 dark:hover:bg-white/20', + 'outline' => 'bg-white hover:bg-zinc-50 dark:bg-zinc-700 dark:hover:bg-zinc-600/75', + 'danger' => 'bg-red-500 hover:bg-red-600 dark:bg-red-600 dark:hover:bg-red-500', + 'ghost' => 'bg-transparent hover:bg-zinc-800/5 dark:hover:bg-white/15', + 'subtle' => 'bg-transparent hover:bg-zinc-800/5 dark:hover:bg-white/15', + }) + ->add(match ($variant) { // Text color... + 'primary' => 'text-[var(--color-accent-foreground)]', + 'filled' => 'text-zinc-800 dark:text-white', + 'outline' => 'text-zinc-800 dark:text-white', + 'danger' => 'text-white', + 'ghost' => 'text-zinc-800 dark:text-white', + 'subtle' => 'text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-white', + }) + ->add(match ($variant) { // Border color... + 'primary' => 'border border-black/10 dark:border-0', + 'outline' => 'border border-zinc-200 hover:border-zinc-200 border-b-zinc-300/80 dark:border-zinc-600 dark:hover:border-zinc-600', + default => '', + }) + ->add(match ($variant) { // Shadows... + 'primary' => 'shadow-[inset_0px_1px_--theme(--color-white/.2)]', + 'danger' => 'shadow-[inset_0px_1px_var(--color-red-500),inset_0px_2px_--theme(--color-white/.15)] dark:shadow-none', + 'outline' => match ($size) { + 'base' => 'shadow-xs', + 'sm' => 'shadow-xs', + 'xs' => 'shadow-none', + }, + default => '', + }) + ->add(match ($variant) { // Grouped border treatments... + 'ghost' => '', + 'subtle' => '', + 'outline' => '[[data-flux-button-group]_&]:border-s-0 [:is([data-flux-button-group]>&:first-child,_[data-flux-button-group]_:first-child>&)]:border-s-[1px]', + 'filled' => '[[data-flux-button-group]_&]:border-e [:is([data-flux-button-group]>&:last-child,_[data-flux-button-group]_:last-child>&)]:border-e-0 [[data-flux-button-group]_&]:border-zinc-200/80 dark:[[data-flux-button-group]_&]:border-zinc-900/50', + 'danger' => '[[data-flux-button-group]_&]:border-e [:is([data-flux-button-group]>&:last-child,_[data-flux-button-group]_:last-child>&)]:border-e-0 [[data-flux-button-group]_&]:border-red-600 dark:[[data-flux-button-group]_&]:border-red-900/25', + 'primary' => '[[data-flux-button-group]_&]:border-e-0 [:is([data-flux-button-group]>&:last-child,_[data-flux-button-group]_:last-child>&)]:border-e-[1px] dark:[:is([data-flux-button-group]>&:last-child,_[data-flux-button-group]_:last-child>&)]:border-e-0 dark:[:is([data-flux-button-group]>&:last-child,_[data-flux-button-group]_:last-child>&)]:border-s-[1px] [:is([data-flux-button-group]>&:not(:first-child),_[data-flux-button-group]_:not(:first-child)>&)]:border-s-[color-mix(in_srgb,var(--color-accent-foreground),transparent_85%)]', + }) + ->add($loading ? [ // Loading states... + '*:transition-opacity', + $type === 'submit' ? '[&[disabled]>:not([data-flux-loading-indicator])]:opacity-0' : '[&[data-flux-loading]>:not([data-flux-loading-indicator])]:opacity-0', + $type === 'submit' ? '[&[disabled]>[data-flux-loading-indicator]]:opacity-100' : '[&[data-flux-loading]>[data-flux-loading-indicator]]:opacity-100', + $type === 'submit' ? '[&[disabled]]:pointer-events-none' : 'data-flux-loading:pointer-events-none', + ] : []) + ->add($variant === 'primary' ? match ($color) { + 'slate' => '[--color-accent:var(--color-slate-800)] [--color-accent-content:var(--color-slate-800)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-white)] dark:[--color-accent-content:var(--color-white)] dark:[--color-accent-foreground:var(--color-slate-800)]', + 'gray' => '[--color-accent:var(--color-gray-800)] [--color-accent-content:var(--color-gray-800)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-white)] dark:[--color-accent-content:var(--color-white)] dark:[--color-accent-foreground:var(--color-gray-800)]', + 'zinc' => '[--color-accent:var(--color-zinc-800)] [--color-accent-content:var(--color-zinc-800)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-white)] dark:[--color-accent-content:var(--color-white)] dark:[--color-accent-foreground:var(--color-zinc-800)]', + 'neutral' => '[--color-accent:var(--color-neutral-800)] [--color-accent-content:var(--color-neutral-800)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-white)] dark:[--color-accent-content:var(--color-white)] dark:[--color-accent-foreground:var(--color-neutral-800)]', + 'stone' => '[--color-accent:var(--color-stone-800)] [--color-accent-content:var(--color-stone-800)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-white)] dark:[--color-accent-content:var(--color-white)] dark:[--color-accent-foreground:var(--color-stone-800)]', + 'red' => '[--color-accent:var(--color-red-500)] [--color-accent-content:var(--color-red-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-red-500)] dark:[--color-accent-content:var(--color-red-400)] dark:[--color-accent-foreground:var(--color-white)]', + 'orange' => '[--color-accent:var(--color-orange-500)] [--color-accent-content:var(--color-orange-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-orange-400)] dark:[--color-accent-content:var(--color-orange-400)] dark:[--color-accent-foreground:var(--color-orange-950)]', + 'amber' => '[--color-accent:var(--color-amber-400)] [--color-accent-content:var(--color-amber-600)] [--color-accent-foreground:var(--color-amber-950)] dark:[--color-accent:var(--color-amber-400)] dark:[--color-accent-content:var(--color-amber-400)] dark:[--color-accent-foreground:var(--color-amber-950)]', + 'yellow' => '[--color-accent:var(--color-yellow-400)] [--color-accent-content:var(--color-yellow-600)] [--color-accent-foreground:var(--color-yellow-950)] dark:[--color-accent:var(--color-yellow-400)] dark:[--color-accent-content:var(--color-yellow-400)] dark:[--color-accent-foreground:var(--color-yellow-950)]', + 'lime' => '[--color-accent:var(--color-lime-400)] [--color-accent-content:var(--color-lime-600)] [--color-accent-foreground:var(--color-lime-900)] dark:[--color-accent:var(--color-lime-400)] dark:[--color-accent-content:var(--color-lime-400)] dark:[--color-accent-foreground:var(--color-lime-950)]', + 'green' => '[--color-accent:var(--color-green-600)] [--color-accent-content:var(--color-green-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-green-600)] dark:[--color-accent-content:var(--color-green-400)] dark:[--color-accent-foreground:var(--color-white)]', + 'emerald' => '[--color-accent:var(--color-emerald-600)] [--color-accent-content:var(--color-emerald-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-emerald-600)] dark:[--color-accent-content:var(--color-emerald-400)] dark:[--color-accent-foreground:var(--color-white)]', + 'teal' => '[--color-accent:var(--color-teal-600)] [--color-accent-content:var(--color-teal-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-teal-600)] dark:[--color-accent-content:var(--color-teal-400)] dark:[--color-accent-foreground:var(--color-white)]', + 'cyan' => '[--color-accent:var(--color-cyan-600)] [--color-accent-content:var(--color-cyan-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-cyan-600)] dark:[--color-accent-content:var(--color-cyan-400)] dark:[--color-accent-foreground:var(--color-white)]', + 'sky' => '[--color-accent:var(--color-sky-600)] [--color-accent-content:var(--color-sky-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-sky-600)] dark:[--color-accent-content:var(--color-sky-400)] dark:[--color-accent-foreground:var(--color-white)]', + 'blue' => '[--color-accent:var(--color-blue-500)] [--color-accent-content:var(--color-blue-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-blue-500)] dark:[--color-accent-content:var(--color-blue-400)] dark:[--color-accent-foreground:var(--color-white)]', + 'indigo' => '[--color-accent:var(--color-indigo-500)] [--color-accent-content:var(--color-indigo-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-indigo-500)] dark:[--color-accent-content:var(--color-indigo-300)] dark:[--color-accent-foreground:var(--color-white)]', + 'violet' => '[--color-accent:var(--color-violet-500)] [--color-accent-content:var(--color-violet-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-violet-500)] dark:[--color-accent-content:var(--color-violet-400)] dark:[--color-accent-foreground:var(--color-white)]', + 'purple' => '[--color-accent:var(--color-purple-500)] [--color-accent-content:var(--color-purple-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-purple-500)] dark:[--color-accent-content:var(--color-purple-300)] dark:[--color-accent-foreground:var(--color-white)]', + 'fuchsia' => '[--color-accent:var(--color-fuchsia-600)] [--color-accent-content:var(--color-fuchsia-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-fuchsia-600)] dark:[--color-accent-content:var(--color-fuchsia-400)] dark:[--color-accent-foreground:var(--color-white)]', + 'pink' => '[--color-accent:var(--color-pink-600)] [--color-accent-content:var(--color-pink-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-pink-600)] dark:[--color-accent-content:var(--color-pink-400)] dark:[--color-accent-foreground:var(--color-white)]', + 'rose' => '[--color-accent:var(--color-rose-500)] [--color-accent-content:var(--color-rose-500)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-rose-500)] dark:[--color-accent-content:var(--color-rose-400)] dark:[--color-accent-foreground:var(--color-white)]', + default => '', + } : '') + ; + + // Exempt subtle and ghost buttons from receiving border roundness overrides from button.group... + $attributes = $attributes->merge([ + 'data-flux-group-target' => ! in_array($variant, ['subtle', 'ghost']), + ]); +@endphp + + + + +
+ +
+ + + + + + {{ $iconLeading }} + + + isEmpty()): ?> + {{-- If we have a loading indicator, we need to wrap it in a span so it can be a target of *:opacity-0... --}} + {{-- Also, if we have an icon, we need to wrap it in a span so it can be recognized as a child of the button for :first-child selectors... --}} + {{ $slot }} + + {{ $slot }} + + + +
{{ $kbd }}
+ + + + {{-- Adding the extra margin class inline on the icon component below was causing a double up, so it needs to be added here first... --}} + add($square ? '' : '-ms-1'); ?> + + + {{ $iconTrailing }} + +
+
diff --git a/resources/views/flux/checkbox/all.blade.php b/resources/views/flux/checkbox/all.blade.php new file mode 100644 index 0000000..72a78b7 --- /dev/null +++ b/resources/views/flux/checkbox/all.blade.php @@ -0,0 +1,2 @@ + + diff --git a/resources/views/flux/checkbox/group/index.blade.php b/resources/views/flux/checkbox/group/index.blade.php new file mode 100644 index 0000000..b91a1a5 --- /dev/null +++ b/resources/views/flux/checkbox/group/index.blade.php @@ -0,0 +1,5 @@ +@props([ + 'variant' => 'default', +]) + +{{ $slot }} diff --git a/resources/views/flux/checkbox/group/variants/default.blade.php b/resources/views/flux/checkbox/group/variants/default.blade.php new file mode 100644 index 0000000..9612cb0 --- /dev/null +++ b/resources/views/flux/checkbox/group/variants/default.blade.php @@ -0,0 +1,22 @@ +@php +$classes = Flux::classes() + ->add('*:data-flux-field:mb-3') + ->add('[&>[data-flux-field]:has(>[data-flux-description])]:mb-4') + ->add('[&>[data-flux-field]:last-child]:mb-0!') + ; + +// Support adding the .self modifier to the wire:model directive... +if (($wireModel = $attributes->wire('model')) && $wireModel->directive && ! $wireModel->hasModifier('self')) { + unset($attributes[$wireModel->directive]); + + $wireModel->directive .= '.self'; + + $attributes = $attributes->merge([$wireModel->directive => $wireModel->value]); +} +@endphp + + + class($classes) }} data-flux-checkbox-group> + {{ $slot }} + + diff --git a/resources/views/flux/checkbox/index.blade.php b/resources/views/flux/checkbox/index.blade.php new file mode 100644 index 0000000..2fddc79 --- /dev/null +++ b/resources/views/flux/checkbox/index.blade.php @@ -0,0 +1,14 @@ +@aware([ 'variant' ]) + +@props([ + 'variant' => 'default', +]) + +@php +// This prevents variants picked up by `@aware()` from other wrapping components like flux::modal from being used here... +$variant = $variant !== 'default' && Flux::componentExists('checkbox.variants.' . $variant) + ? $variant + : 'default'; +@endphp + +{{ $slot }} diff --git a/resources/views/flux/checkbox/indicator.blade.php b/resources/views/flux/checkbox/indicator.blade.php new file mode 100644 index 0000000..ca260ef --- /dev/null +++ b/resources/views/flux/checkbox/indicator.blade.php @@ -0,0 +1,31 @@ + +@php +$classes = Flux::classes() + ->add('shrink-0 size-[1.125rem] rounded-[.3rem] flex justify-center items-center') + ->add('text-sm text-zinc-700 dark:text-zinc-800') + ->add('shadow-xs [ui-checkbox[disabled]_&]:opacity-75 [ui-checkbox[data-checked][disabled]_&]:opacity-50 [ui-checkbox[disabled]_&]:shadow-none [ui-checkbox[data-checked]_&]:shadow-none [ui-checkbox[data-indeterminate]_&]:shadow-none') + ->add('[ui-checkbox[data-checked]:not([data-indeterminate])_&>svg:first-child]:block [ui-checkbox[data-indeterminate]_&>svg:last-child]:block') + ->add([ + 'border', + 'border-zinc-300 dark:border-white/10', + '[ui-checkbox[disabled]_&]:border-zinc-200 dark:[ui-checkbox[disabled]_&]:border-white/5', + '[ui-checkbox[data-checked]_&]:border-transparent [ui-checkbox[data-indeterminate]_&]:border-transparent', + '[ui-checkbox[disabled][data-checked]_&]:border-transparent [ui-checkbox[disabled][data-indeterminate]_&]:border-transparent', + '[print-color-adjust:exact]', + ]) + ->add([ + 'bg-white dark:bg-white/10', + '[ui-checkbox[data-checked]_&]:bg-[var(--color-accent)]', + 'hover:[ui-checkbox[data-checked]_&]:bg-(--color-accent)', + 'focus:[ui-checkbox[data-checked]_&]:bg-(--color-accent)', + '[ui-checkbox[data-indeterminate]_&]:bg-[var(--color-accent)]', + 'hover:[ui-checkbox[data-indeterminate]_&]:bg-(--color-accent)', + 'focus:[ui-checkbox[data-indeterminate]_&]:bg-(--color-accent)', + ]) + ; +@endphp + +
class($classes) }} data-flux-checkbox-indicator> +
diff --git a/resources/views/flux/checkbox/variants/default.blade.php b/resources/views/flux/checkbox/variants/default.blade.php new file mode 100644 index 0000000..601975a --- /dev/null +++ b/resources/views/flux/checkbox/variants/default.blade.php @@ -0,0 +1,23 @@ +@props([ + 'name' => null, +]) + +@php +// We only want to show the name attribute on the checkbox if it has been set +// manually, but not if it has been set from the wire:model attribute... +$showName = isset($name); + +if (! isset($name)) { + $name = $attributes->whereStartsWith('wire:model')->first(); +} + +$classes = Flux::classes() + ->add('flex size-[1.125rem] rounded-[.3rem] mt-px outline-offset-2') + ; +@endphp + + + class($classes) }} @if($showName) name="{{ $name }}" @endif data-flux-control data-flux-checkbox> + + + diff --git a/resources/views/flux/container.blade.php b/resources/views/flux/container.blade.php new file mode 100644 index 0000000..c804382 --- /dev/null +++ b/resources/views/flux/container.blade.php @@ -0,0 +1,9 @@ +@php +$classes = Flux::classes() + ->add('mx-auto w-full [:where(&)]:max-w-7xl px-6 lg:px-8') + ; +@endphp + +
class($classes) }} data-flux-container> + {{ $slot }} +
diff --git a/resources/views/flux/description.blade.php b/resources/views/flux/description.blade.php new file mode 100644 index 0000000..d01bb28 --- /dev/null +++ b/resources/views/flux/description.blade.php @@ -0,0 +1,4 @@ + +class('text-sm text-zinc-500 dark:text-white/60') }} data-flux-description> + {{ $slot }} + diff --git a/resources/views/flux/dropdown.blade.php b/resources/views/flux/dropdown.blade.php new file mode 100644 index 0000000..2c6f798 --- /dev/null +++ b/resources/views/flux/dropdown.blade.php @@ -0,0 +1,19 @@ +@props([ + 'position' => 'bottom', + 'align' => 'start', +]) + +@php +// Support adding the .self modifier to the wire:model directive... +if (($wireModel = $attributes->wire('model')) && $wireModel->directive && ! $wireModel->hasModifier('self')) { + unset($attributes[$wireModel->directive]); + + $wireModel->directive .= '.self'; + + $attributes = $attributes->merge([$wireModel->directive => $wireModel->value]); +} +@endphp + + + {{ $slot }} + diff --git a/resources/views/flux/error.blade.php b/resources/views/flux/error.blade.php new file mode 100644 index 0000000..feff045 --- /dev/null +++ b/resources/views/flux/error.blade.php @@ -0,0 +1,24 @@ +@props([ + 'name' => null, + 'message' => null, + 'nested' => true, +]) + +@php +$message ??= $name ? $errors->first($name) : null; + +if ($name && (is_null($message) || $message === '') && filter_var($nested, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) !== false) { + $message = $errors->first($name . '.*'); +} + +$classes = Flux::classes('mt-3 text-sm font-medium text-red-500 dark:text-red-400') + ->add($message ? '' : 'hidden'); +@endphp + +
class($classes) }} data-flux-error> + + + + {{ $message }} + +
diff --git a/resources/views/flux/field.blade.php b/resources/views/flux/field.blade.php new file mode 100644 index 0000000..c526b29 --- /dev/null +++ b/resources/views/flux/field.blade.php @@ -0,0 +1,40 @@ +@props([ + 'variant' => 'block', +]) + +@php +$classes = Flux::classes() + ->add('min-w-0') // This is here to allow nested input elements like flux::input.file to truncate properly... + ->add('[&:not(:has([data-flux-field])):has([data-flux-control][disabled])>[data-flux-label]]:opacity-50') // Dim labels for fields with no nested fields when a control is disabled... + ->add('[&:has(>[data-flux-radio-group][disabled])>[data-flux-label]]:opacity-50') // Special case for radio groups because they are nested fields... + ->add('[&:has(>[data-flux-checkbox-group][disabled])>[data-flux-label]]:opacity-50') // Special case for checkbox groups because they are nested fields... + ->add(match ($variant) { + default => 'block', + 'inline' => [ + 'grid gap-x-3 gap-y-1.5', + 'has-[[data-flux-label]~[data-flux-control]]:grid-cols-[1fr_auto]', + 'has-[[data-flux-control]~[data-flux-label]]:grid-cols-[auto_1fr]', + '[&>[data-flux-control]~[data-flux-description]]:row-start-2 [&>[data-flux-control]~[data-flux-description]]:col-start-2', + '[&>[data-flux-control]~[data-flux-error]]:col-span-2 [&>[data-flux-control]~[data-flux-error]]:mt-1', // Position error messages... + '[&>[data-flux-label]~[data-flux-control]]:row-start-1 [&>[data-flux-label]~[data-flux-control]]:col-start-2', + ], + }) + ->add(match ($variant) { + default => [ // Adjust spacing around label... + '*:data-flux-label:mb-3 [&>[data-flux-label]:has(+[data-flux-description])]:mb-2', + ], + 'inline' => '', + }) + ->add(match ($variant) { + default => [ // Adjust spacing around description... + '[&>[data-flux-label]+[data-flux-description]]:mt-0', + '[&>[data-flux-label]+[data-flux-description]]:mb-3', + '[&>*:not([data-flux-label])+[data-flux-description]]:mt-3', + ], + 'inline' => '', + }); +@endphp + +class($classes) }} data-flux-field> + {{ $slot }} + diff --git a/resources/views/flux/fieldset.blade.php b/resources/views/flux/fieldset.blade.php new file mode 100644 index 0000000..7d27713 --- /dev/null +++ b/resources/views/flux/fieldset.blade.php @@ -0,0 +1,38 @@ +@props([ + 'legend' => null, + 'description' => null, +]) + +@php +$classes = Flux::classes() + ->add('[&[disabled]_[data-flux-label]]:opacity-50') // Dim labels when the fieldset is disabled... + ->add('[&[disabled]_[data-flux-legend]]:opacity-50') // Dim legend when the fieldset is disabled... + + // Adjust spacing between fields... + ->add('*:data-flux-field:mb-3') + + // Adjust spacing between fields... + ->add('*:data-flux-field:mb-3') + ->add('[&>[data-flux-field]:has(>[data-flux-description])]:mb-4') + ->add('[&>[data-flux-field]:last-child]:mb-0!') + + // Adjust spacing below legend... + ->add('[&>legend]:mb-4') + ->add('[&>legend:has(+[data-flux-description])]:mb-2') + + // Adjust spacing below description... + ->add('[&>legend+[data-flux-description]]:mb-4') + ; +@endphp + +
class($classes) }} data-flux-fieldset> + + {{ $legend }} + + + + {{ $description }} + + + {{ $slot }} +
diff --git a/resources/views/flux/footer.blade.php b/resources/views/flux/footer.blade.php new file mode 100644 index 0000000..e2e5523 --- /dev/null +++ b/resources/views/flux/footer.blade.php @@ -0,0 +1,11 @@ +@php +$classes = Flux::classes('[grid-area:footer]') + ->add($attributes->has('container') ? '' : 'p-6 lg:p-8') + ; +@endphp + +
class($classes) }} data-flux-footer> + + {{ $slot }} + +
diff --git a/resources/views/flux/header.blade.php b/resources/views/flux/header.blade.php new file mode 100644 index 0000000..9940d9b --- /dev/null +++ b/resources/views/flux/header.blade.php @@ -0,0 +1,28 @@ +@props([ + 'sticky' => null, + 'container' => null, +]) + +@php +$classes = Flux::classes('[grid-area:header]') + ->add('z-10 min-h-14') + ->add($container ? '' : 'flex items-center px-6 lg:px-8') + ; + +if ($sticky) { + $attributes = $attributes->merge([ + 'x-data' => '', + 'x-bind:style' => '{ position: \'sticky\', top: $el.offsetTop + \'px\', \'max-height\': \'calc(100vh - \' + $el.offsetTop + \'px)\' }', + ]); +} +@endphp + +
class($classes) }} data-flux-header> + @if ($container) +
+ {{ $slot }} +
+ @else + {{ $slot }} + @endif +
diff --git a/resources/views/flux/heading.blade.php b/resources/views/flux/heading.blade.php new file mode 100644 index 0000000..60fcced --- /dev/null +++ b/resources/views/flux/heading.blade.php @@ -0,0 +1,40 @@ +@props([ + 'size' => 'base', + 'accent' => false, + 'level' => null, +]) + +@php +$classes = Flux::classes() + ->add('font-medium') + ->add(match ($accent) { + true => 'text-[var(--color-accent-content)]', + default => '[:where(&)]:text-zinc-800 [:where(&)]:dark:text-white', + }) + ->add(match ($size) { + 'xl' => 'text-2xl [&:has(+[data-flux-subheading])]:mb-2 [[data-flux-subheading]+&]:mt-2', + 'lg' => 'text-base [&:has(+[data-flux-subheading])]:mb-2 [[data-flux-subheading]+&]:mt-2', + default => 'text-sm [&:has(+[data-flux-subheading])]:mb-2 [[data-flux-subheading]+&]:mt-2', + }) + ; +@endphp + + +

class($classes) }} data-flux-heading>{{ $slot }}

+ + @break + +

class($classes) }} data-flux-heading>{{ $slot }}

+ + @break + +

class($classes) }} data-flux-heading>{{ $slot }}

+ + @break + +

class($classes) }} data-flux-heading>{{ $slot }}

+ + @break + +
class($classes) }} data-flux-heading>{{ $slot }}
+ diff --git a/resources/views/flux/icon/academic-cap.blade.php b/resources/views/flux/icon/academic-cap.blade.php new file mode 100644 index 0000000..5e03b20 --- /dev/null +++ b/resources/views/flux/icon/academic-cap.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/adjustments-horizontal.blade.php b/resources/views/flux/icon/adjustments-horizontal.blade.php new file mode 100644 index 0000000..8d4cb37 --- /dev/null +++ b/resources/views/flux/icon/adjustments-horizontal.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/adjustments-vertical.blade.php b/resources/views/flux/icon/adjustments-vertical.blade.php new file mode 100644 index 0000000..dae23f6 --- /dev/null +++ b/resources/views/flux/icon/adjustments-vertical.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/archive-box-arrow-down.blade.php b/resources/views/flux/icon/archive-box-arrow-down.blade.php new file mode 100644 index 0000000..a4dfa81 --- /dev/null +++ b/resources/views/flux/icon/archive-box-arrow-down.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/archive-box-x-mark.blade.php b/resources/views/flux/icon/archive-box-x-mark.blade.php new file mode 100644 index 0000000..9949aed --- /dev/null +++ b/resources/views/flux/icon/archive-box-x-mark.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/archive-box.blade.php b/resources/views/flux/icon/archive-box.blade.php new file mode 100644 index 0000000..9c3c05c --- /dev/null +++ b/resources/views/flux/icon/archive-box.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/arrow-down-circle.blade.php b/resources/views/flux/icon/arrow-down-circle.blade.php new file mode 100644 index 0000000..42f45c9 --- /dev/null +++ b/resources/views/flux/icon/arrow-down-circle.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-down-left.blade.php b/resources/views/flux/icon/arrow-down-left.blade.php new file mode 100644 index 0000000..717b91d --- /dev/null +++ b/resources/views/flux/icon/arrow-down-left.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-down-on-square-stack.blade.php b/resources/views/flux/icon/arrow-down-on-square-stack.blade.php new file mode 100644 index 0000000..c0dd86d --- /dev/null +++ b/resources/views/flux/icon/arrow-down-on-square-stack.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/arrow-down-on-square.blade.php b/resources/views/flux/icon/arrow-down-on-square.blade.php new file mode 100644 index 0000000..a1475b6 --- /dev/null +++ b/resources/views/flux/icon/arrow-down-on-square.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-down-right.blade.php b/resources/views/flux/icon/arrow-down-right.blade.php new file mode 100644 index 0000000..4143d88 --- /dev/null +++ b/resources/views/flux/icon/arrow-down-right.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-down-tray.blade.php b/resources/views/flux/icon/arrow-down-tray.blade.php new file mode 100644 index 0000000..b0a01aa --- /dev/null +++ b/resources/views/flux/icon/arrow-down-tray.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/arrow-down.blade.php b/resources/views/flux/icon/arrow-down.blade.php new file mode 100644 index 0000000..680134e --- /dev/null +++ b/resources/views/flux/icon/arrow-down.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-left-circle.blade.php b/resources/views/flux/icon/arrow-left-circle.blade.php new file mode 100644 index 0000000..df5ccc6 --- /dev/null +++ b/resources/views/flux/icon/arrow-left-circle.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-left-end-on-rectangle.blade.php b/resources/views/flux/icon/arrow-left-end-on-rectangle.blade.php new file mode 100644 index 0000000..bfbe3fc --- /dev/null +++ b/resources/views/flux/icon/arrow-left-end-on-rectangle.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/arrow-left-start-on-rectangle.blade.php b/resources/views/flux/icon/arrow-left-start-on-rectangle.blade.php new file mode 100644 index 0000000..532239b --- /dev/null +++ b/resources/views/flux/icon/arrow-left-start-on-rectangle.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-left.blade.php b/resources/views/flux/icon/arrow-left.blade.php new file mode 100644 index 0000000..6aecedc --- /dev/null +++ b/resources/views/flux/icon/arrow-left.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-long-down.blade.php b/resources/views/flux/icon/arrow-long-down.blade.php new file mode 100644 index 0000000..4b28d29 --- /dev/null +++ b/resources/views/flux/icon/arrow-long-down.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-long-left.blade.php b/resources/views/flux/icon/arrow-long-left.blade.php new file mode 100644 index 0000000..261b79c --- /dev/null +++ b/resources/views/flux/icon/arrow-long-left.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-long-right.blade.php b/resources/views/flux/icon/arrow-long-right.blade.php new file mode 100644 index 0000000..bc0a5bd --- /dev/null +++ b/resources/views/flux/icon/arrow-long-right.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-long-up.blade.php b/resources/views/flux/icon/arrow-long-up.blade.php new file mode 100644 index 0000000..9685813 --- /dev/null +++ b/resources/views/flux/icon/arrow-long-up.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-path-rounded-square.blade.php b/resources/views/flux/icon/arrow-path-rounded-square.blade.php new file mode 100644 index 0000000..c412c82 --- /dev/null +++ b/resources/views/flux/icon/arrow-path-rounded-square.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-path.blade.php b/resources/views/flux/icon/arrow-path.blade.php new file mode 100644 index 0000000..99e8fc1 --- /dev/null +++ b/resources/views/flux/icon/arrow-path.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-right-circle.blade.php b/resources/views/flux/icon/arrow-right-circle.blade.php new file mode 100644 index 0000000..14e4ffd --- /dev/null +++ b/resources/views/flux/icon/arrow-right-circle.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-right-end-on-rectangle.blade.php b/resources/views/flux/icon/arrow-right-end-on-rectangle.blade.php new file mode 100644 index 0000000..cc0d0ff --- /dev/null +++ b/resources/views/flux/icon/arrow-right-end-on-rectangle.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/arrow-right-start-on-rectangle.blade.php b/resources/views/flux/icon/arrow-right-start-on-rectangle.blade.php new file mode 100644 index 0000000..b714416 --- /dev/null +++ b/resources/views/flux/icon/arrow-right-start-on-rectangle.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-right.blade.php b/resources/views/flux/icon/arrow-right.blade.php new file mode 100644 index 0000000..609c0e3 --- /dev/null +++ b/resources/views/flux/icon/arrow-right.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-top-right-on-square.blade.php b/resources/views/flux/icon/arrow-top-right-on-square.blade.php new file mode 100644 index 0000000..c686905 --- /dev/null +++ b/resources/views/flux/icon/arrow-top-right-on-square.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/arrow-trending-down.blade.php b/resources/views/flux/icon/arrow-trending-down.blade.php new file mode 100644 index 0000000..586210b --- /dev/null +++ b/resources/views/flux/icon/arrow-trending-down.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-trending-up.blade.php b/resources/views/flux/icon/arrow-trending-up.blade.php new file mode 100644 index 0000000..3aa69f3 --- /dev/null +++ b/resources/views/flux/icon/arrow-trending-up.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-turn-down-left.blade.php b/resources/views/flux/icon/arrow-turn-down-left.blade.php new file mode 100644 index 0000000..c4ff66e --- /dev/null +++ b/resources/views/flux/icon/arrow-turn-down-left.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-turn-down-right.blade.php b/resources/views/flux/icon/arrow-turn-down-right.blade.php new file mode 100644 index 0000000..a28217b --- /dev/null +++ b/resources/views/flux/icon/arrow-turn-down-right.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-turn-left-down.blade.php b/resources/views/flux/icon/arrow-turn-left-down.blade.php new file mode 100644 index 0000000..57ab796 --- /dev/null +++ b/resources/views/flux/icon/arrow-turn-left-down.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-turn-left-up.blade.php b/resources/views/flux/icon/arrow-turn-left-up.blade.php new file mode 100644 index 0000000..e1b5251 --- /dev/null +++ b/resources/views/flux/icon/arrow-turn-left-up.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-turn-right-down.blade.php b/resources/views/flux/icon/arrow-turn-right-down.blade.php new file mode 100644 index 0000000..294e7fe --- /dev/null +++ b/resources/views/flux/icon/arrow-turn-right-down.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-turn-right-up.blade.php b/resources/views/flux/icon/arrow-turn-right-up.blade.php new file mode 100644 index 0000000..c1cf59a --- /dev/null +++ b/resources/views/flux/icon/arrow-turn-right-up.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-turn-up-left.blade.php b/resources/views/flux/icon/arrow-turn-up-left.blade.php new file mode 100644 index 0000000..df5efbc --- /dev/null +++ b/resources/views/flux/icon/arrow-turn-up-left.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-turn-up-right.blade.php b/resources/views/flux/icon/arrow-turn-up-right.blade.php new file mode 100644 index 0000000..3f58f4b --- /dev/null +++ b/resources/views/flux/icon/arrow-turn-up-right.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-up-circle.blade.php b/resources/views/flux/icon/arrow-up-circle.blade.php new file mode 100644 index 0000000..fded673 --- /dev/null +++ b/resources/views/flux/icon/arrow-up-circle.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-up-left.blade.php b/resources/views/flux/icon/arrow-up-left.blade.php new file mode 100644 index 0000000..9a206c9 --- /dev/null +++ b/resources/views/flux/icon/arrow-up-left.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-up-on-square-stack.blade.php b/resources/views/flux/icon/arrow-up-on-square-stack.blade.php new file mode 100644 index 0000000..d35fd37 --- /dev/null +++ b/resources/views/flux/icon/arrow-up-on-square-stack.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/arrow-up-on-square.blade.php b/resources/views/flux/icon/arrow-up-on-square.blade.php new file mode 100644 index 0000000..5f960cb --- /dev/null +++ b/resources/views/flux/icon/arrow-up-on-square.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-up-right.blade.php b/resources/views/flux/icon/arrow-up-right.blade.php new file mode 100644 index 0000000..0651181 --- /dev/null +++ b/resources/views/flux/icon/arrow-up-right.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-up-tray.blade.php b/resources/views/flux/icon/arrow-up-tray.blade.php new file mode 100644 index 0000000..3bd2189 --- /dev/null +++ b/resources/views/flux/icon/arrow-up-tray.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/arrow-up.blade.php b/resources/views/flux/icon/arrow-up.blade.php new file mode 100644 index 0000000..09ee998 --- /dev/null +++ b/resources/views/flux/icon/arrow-up.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-uturn-down.blade.php b/resources/views/flux/icon/arrow-uturn-down.blade.php new file mode 100644 index 0000000..4226637 --- /dev/null +++ b/resources/views/flux/icon/arrow-uturn-down.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-uturn-left.blade.php b/resources/views/flux/icon/arrow-uturn-left.blade.php new file mode 100644 index 0000000..3d67193 --- /dev/null +++ b/resources/views/flux/icon/arrow-uturn-left.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-uturn-right.blade.php b/resources/views/flux/icon/arrow-uturn-right.blade.php new file mode 100644 index 0000000..095b929 --- /dev/null +++ b/resources/views/flux/icon/arrow-uturn-right.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrow-uturn-up.blade.php b/resources/views/flux/icon/arrow-uturn-up.blade.php new file mode 100644 index 0000000..780f3aa --- /dev/null +++ b/resources/views/flux/icon/arrow-uturn-up.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrows-pointing-in.blade.php b/resources/views/flux/icon/arrows-pointing-in.blade.php new file mode 100644 index 0000000..2788860 --- /dev/null +++ b/resources/views/flux/icon/arrows-pointing-in.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrows-pointing-out.blade.php b/resources/views/flux/icon/arrows-pointing-out.blade.php new file mode 100644 index 0000000..1feb5c6 --- /dev/null +++ b/resources/views/flux/icon/arrows-pointing-out.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrows-right-left.blade.php b/resources/views/flux/icon/arrows-right-left.blade.php new file mode 100644 index 0000000..7b3696f --- /dev/null +++ b/resources/views/flux/icon/arrows-right-left.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/arrows-up-down.blade.php b/resources/views/flux/icon/arrows-up-down.blade.php new file mode 100644 index 0000000..1359186 --- /dev/null +++ b/resources/views/flux/icon/arrows-up-down.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/at-symbol.blade.php b/resources/views/flux/icon/at-symbol.blade.php new file mode 100644 index 0000000..e2dde9d --- /dev/null +++ b/resources/views/flux/icon/at-symbol.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/backspace.blade.php b/resources/views/flux/icon/backspace.blade.php new file mode 100644 index 0000000..083f064 --- /dev/null +++ b/resources/views/flux/icon/backspace.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/backward.blade.php b/resources/views/flux/icon/backward.blade.php new file mode 100644 index 0000000..4ab3d1e --- /dev/null +++ b/resources/views/flux/icon/backward.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/banknotes.blade.php b/resources/views/flux/icon/banknotes.blade.php new file mode 100644 index 0000000..5fc4882 --- /dev/null +++ b/resources/views/flux/icon/banknotes.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/bars-2.blade.php b/resources/views/flux/icon/bars-2.blade.php new file mode 100644 index 0000000..da11205 --- /dev/null +++ b/resources/views/flux/icon/bars-2.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/bars-3-bottom-left.blade.php b/resources/views/flux/icon/bars-3-bottom-left.blade.php new file mode 100644 index 0000000..4bac79d --- /dev/null +++ b/resources/views/flux/icon/bars-3-bottom-left.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/bars-3-bottom-right.blade.php b/resources/views/flux/icon/bars-3-bottom-right.blade.php new file mode 100644 index 0000000..9d750f5 --- /dev/null +++ b/resources/views/flux/icon/bars-3-bottom-right.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/bars-3-center-left.blade.php b/resources/views/flux/icon/bars-3-center-left.blade.php new file mode 100644 index 0000000..ebfc55a --- /dev/null +++ b/resources/views/flux/icon/bars-3-center-left.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/bars-3.blade.php b/resources/views/flux/icon/bars-3.blade.php new file mode 100644 index 0000000..c7defe6 --- /dev/null +++ b/resources/views/flux/icon/bars-3.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/bars-4.blade.php b/resources/views/flux/icon/bars-4.blade.php new file mode 100644 index 0000000..a67e274 --- /dev/null +++ b/resources/views/flux/icon/bars-4.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/bars-arrow-down.blade.php b/resources/views/flux/icon/bars-arrow-down.blade.php new file mode 100644 index 0000000..3e843a5 --- /dev/null +++ b/resources/views/flux/icon/bars-arrow-down.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/bars-arrow-up.blade.php b/resources/views/flux/icon/bars-arrow-up.blade.php new file mode 100644 index 0000000..effeaa4 --- /dev/null +++ b/resources/views/flux/icon/bars-arrow-up.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/battery-0.blade.php b/resources/views/flux/icon/battery-0.blade.php new file mode 100644 index 0000000..ab1b790 --- /dev/null +++ b/resources/views/flux/icon/battery-0.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/battery-100.blade.php b/resources/views/flux/icon/battery-100.blade.php new file mode 100644 index 0000000..72ec26e --- /dev/null +++ b/resources/views/flux/icon/battery-100.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/battery-50.blade.php b/resources/views/flux/icon/battery-50.blade.php new file mode 100644 index 0000000..074c747 --- /dev/null +++ b/resources/views/flux/icon/battery-50.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/beaker.blade.php b/resources/views/flux/icon/beaker.blade.php new file mode 100644 index 0000000..d3d69b8 --- /dev/null +++ b/resources/views/flux/icon/beaker.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/bell-alert.blade.php b/resources/views/flux/icon/bell-alert.blade.php new file mode 100644 index 0000000..cf20fd1 --- /dev/null +++ b/resources/views/flux/icon/bell-alert.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/bell-slash.blade.php b/resources/views/flux/icon/bell-slash.blade.php new file mode 100644 index 0000000..c47213f --- /dev/null +++ b/resources/views/flux/icon/bell-slash.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/bell-snooze.blade.php b/resources/views/flux/icon/bell-snooze.blade.php new file mode 100644 index 0000000..b3c95c2 --- /dev/null +++ b/resources/views/flux/icon/bell-snooze.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/bell.blade.php b/resources/views/flux/icon/bell.blade.php new file mode 100644 index 0000000..5001311 --- /dev/null +++ b/resources/views/flux/icon/bell.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/bold.blade.php b/resources/views/flux/icon/bold.blade.php new file mode 100644 index 0000000..176f0c3 --- /dev/null +++ b/resources/views/flux/icon/bold.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/bolt-slash.blade.php b/resources/views/flux/icon/bolt-slash.blade.php new file mode 100644 index 0000000..530ff5e --- /dev/null +++ b/resources/views/flux/icon/bolt-slash.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/bolt.blade.php b/resources/views/flux/icon/bolt.blade.php new file mode 100644 index 0000000..cd92bef --- /dev/null +++ b/resources/views/flux/icon/bolt.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/book-open-text.blade.php b/resources/views/flux/icon/book-open-text.blade.php new file mode 100644 index 0000000..bff20a3 --- /dev/null +++ b/resources/views/flux/icon/book-open-text.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Lucide (https://lucide.dev) --}} + +@props([ + 'variant' => 'outline', +]) + +@php + if ($variant === 'solid') { + throw new \Exception('The "solid" variant is not supported in Lucide.'); + } + + $classes = Flux::classes('shrink-0')->add( + match ($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }, + ); + + $strokeWidth = match ($variant) { + 'outline' => 2, + 'mini' => 2.25, + 'micro' => 2.5, + }; +@endphp + +class($classes) }} + data-flux-icon + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + stroke-width="{{ $strokeWidth }}" + stroke-linecap="round" + stroke-linejoin="round" + aria-hidden="true" + data-slot="icon" +> + + + + + + + diff --git a/resources/views/flux/icon/book-open.blade.php b/resources/views/flux/icon/book-open.blade.php new file mode 100644 index 0000000..cc666a4 --- /dev/null +++ b/resources/views/flux/icon/book-open.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/bookmark-slash.blade.php b/resources/views/flux/icon/bookmark-slash.blade.php new file mode 100644 index 0000000..8cd42b6 --- /dev/null +++ b/resources/views/flux/icon/bookmark-slash.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/bookmark-square.blade.php b/resources/views/flux/icon/bookmark-square.blade.php new file mode 100644 index 0000000..87e9734 --- /dev/null +++ b/resources/views/flux/icon/bookmark-square.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/bookmark.blade.php b/resources/views/flux/icon/bookmark.blade.php new file mode 100644 index 0000000..30aa9c0 --- /dev/null +++ b/resources/views/flux/icon/bookmark.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/briefcase.blade.php b/resources/views/flux/icon/briefcase.blade.php new file mode 100644 index 0000000..0c81106 --- /dev/null +++ b/resources/views/flux/icon/briefcase.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/bug-ant.blade.php b/resources/views/flux/icon/bug-ant.blade.php new file mode 100644 index 0000000..8d1d10b --- /dev/null +++ b/resources/views/flux/icon/bug-ant.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/building-library.blade.php b/resources/views/flux/icon/building-library.blade.php new file mode 100644 index 0000000..c6172f6 --- /dev/null +++ b/resources/views/flux/icon/building-library.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/building-office-2.blade.php b/resources/views/flux/icon/building-office-2.blade.php new file mode 100644 index 0000000..3d3aa2f --- /dev/null +++ b/resources/views/flux/icon/building-office-2.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/building-office.blade.php b/resources/views/flux/icon/building-office.blade.php new file mode 100644 index 0000000..3ab9179 --- /dev/null +++ b/resources/views/flux/icon/building-office.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/building-storefront.blade.php b/resources/views/flux/icon/building-storefront.blade.php new file mode 100644 index 0000000..59137a2 --- /dev/null +++ b/resources/views/flux/icon/building-storefront.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/cake.blade.php b/resources/views/flux/icon/cake.blade.php new file mode 100644 index 0000000..54d6c9f --- /dev/null +++ b/resources/views/flux/icon/cake.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/calculator.blade.php b/resources/views/flux/icon/calculator.blade.php new file mode 100644 index 0000000..63e8dab --- /dev/null +++ b/resources/views/flux/icon/calculator.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/calendar-date-range.blade.php b/resources/views/flux/icon/calendar-date-range.blade.php new file mode 100644 index 0000000..8243eef --- /dev/null +++ b/resources/views/flux/icon/calendar-date-range.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/calendar-days.blade.php b/resources/views/flux/icon/calendar-days.blade.php new file mode 100644 index 0000000..684cc1f --- /dev/null +++ b/resources/views/flux/icon/calendar-days.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/calendar.blade.php b/resources/views/flux/icon/calendar.blade.php new file mode 100644 index 0000000..4d4ccf0 --- /dev/null +++ b/resources/views/flux/icon/calendar.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/camera.blade.php b/resources/views/flux/icon/camera.blade.php new file mode 100644 index 0000000..9f762a3 --- /dev/null +++ b/resources/views/flux/icon/camera.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/chart-bar-square.blade.php b/resources/views/flux/icon/chart-bar-square.blade.php new file mode 100644 index 0000000..1168f45 --- /dev/null +++ b/resources/views/flux/icon/chart-bar-square.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/chart-bar.blade.php b/resources/views/flux/icon/chart-bar.blade.php new file mode 100644 index 0000000..b9bdcb2 --- /dev/null +++ b/resources/views/flux/icon/chart-bar.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/chart-pie.blade.php b/resources/views/flux/icon/chart-pie.blade.php new file mode 100644 index 0000000..85d0ef2 --- /dev/null +++ b/resources/views/flux/icon/chart-pie.blade.php @@ -0,0 +1,49 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/chat-bubble-bottom-center-text.blade.php b/resources/views/flux/icon/chat-bubble-bottom-center-text.blade.php new file mode 100644 index 0000000..26d200a --- /dev/null +++ b/resources/views/flux/icon/chat-bubble-bottom-center-text.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/chat-bubble-bottom-center.blade.php b/resources/views/flux/icon/chat-bubble-bottom-center.blade.php new file mode 100644 index 0000000..e722293 --- /dev/null +++ b/resources/views/flux/icon/chat-bubble-bottom-center.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/chat-bubble-left-ellipsis.blade.php b/resources/views/flux/icon/chat-bubble-left-ellipsis.blade.php new file mode 100644 index 0000000..0fe3132 --- /dev/null +++ b/resources/views/flux/icon/chat-bubble-left-ellipsis.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/chat-bubble-left-right.blade.php b/resources/views/flux/icon/chat-bubble-left-right.blade.php new file mode 100644 index 0000000..2adfab9 --- /dev/null +++ b/resources/views/flux/icon/chat-bubble-left-right.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/chat-bubble-left.blade.php b/resources/views/flux/icon/chat-bubble-left.blade.php new file mode 100644 index 0000000..aea41a9 --- /dev/null +++ b/resources/views/flux/icon/chat-bubble-left.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/chat-bubble-oval-left-ellipsis.blade.php b/resources/views/flux/icon/chat-bubble-oval-left-ellipsis.blade.php new file mode 100644 index 0000000..9e8a569 --- /dev/null +++ b/resources/views/flux/icon/chat-bubble-oval-left-ellipsis.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/chat-bubble-oval-left.blade.php b/resources/views/flux/icon/chat-bubble-oval-left.blade.php new file mode 100644 index 0000000..baa79e8 --- /dev/null +++ b/resources/views/flux/icon/chat-bubble-oval-left.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/check-badge.blade.php b/resources/views/flux/icon/check-badge.blade.php new file mode 100644 index 0000000..b94415e --- /dev/null +++ b/resources/views/flux/icon/check-badge.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/check-circle.blade.php b/resources/views/flux/icon/check-circle.blade.php new file mode 100644 index 0000000..0d1ab60 --- /dev/null +++ b/resources/views/flux/icon/check-circle.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/check.blade.php b/resources/views/flux/icon/check.blade.php new file mode 100644 index 0000000..e78c36f --- /dev/null +++ b/resources/views/flux/icon/check.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/chevron-double-down.blade.php b/resources/views/flux/icon/chevron-double-down.blade.php new file mode 100644 index 0000000..f778f7b --- /dev/null +++ b/resources/views/flux/icon/chevron-double-down.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/chevron-double-left.blade.php b/resources/views/flux/icon/chevron-double-left.blade.php new file mode 100644 index 0000000..e1dc712 --- /dev/null +++ b/resources/views/flux/icon/chevron-double-left.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/chevron-double-right.blade.php b/resources/views/flux/icon/chevron-double-right.blade.php new file mode 100644 index 0000000..8e25acd --- /dev/null +++ b/resources/views/flux/icon/chevron-double-right.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/chevron-double-up.blade.php b/resources/views/flux/icon/chevron-double-up.blade.php new file mode 100644 index 0000000..b830c58 --- /dev/null +++ b/resources/views/flux/icon/chevron-double-up.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/chevron-down.blade.php b/resources/views/flux/icon/chevron-down.blade.php new file mode 100644 index 0000000..46f7d6d --- /dev/null +++ b/resources/views/flux/icon/chevron-down.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/chevron-left.blade.php b/resources/views/flux/icon/chevron-left.blade.php new file mode 100644 index 0000000..d7f1efa --- /dev/null +++ b/resources/views/flux/icon/chevron-left.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/chevron-right.blade.php b/resources/views/flux/icon/chevron-right.blade.php new file mode 100644 index 0000000..c36bd81 --- /dev/null +++ b/resources/views/flux/icon/chevron-right.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/chevron-up-down.blade.php b/resources/views/flux/icon/chevron-up-down.blade.php new file mode 100644 index 0000000..9e34e03 --- /dev/null +++ b/resources/views/flux/icon/chevron-up-down.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/chevron-up.blade.php b/resources/views/flux/icon/chevron-up.blade.php new file mode 100644 index 0000000..6b9b4ef --- /dev/null +++ b/resources/views/flux/icon/chevron-up.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/chevrons-up-down.blade.php b/resources/views/flux/icon/chevrons-up-down.blade.php new file mode 100644 index 0000000..bf1ba2b --- /dev/null +++ b/resources/views/flux/icon/chevrons-up-down.blade.php @@ -0,0 +1,43 @@ +{{-- Credit: Lucide (https://lucide.dev) --}} + +@props([ + 'variant' => 'outline', +]) + +@php + if ($variant === 'solid') { + throw new \Exception('The "solid" variant is not supported in Lucide.'); + } + + $classes = Flux::classes('shrink-0')->add( + match ($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }, + ); + + $strokeWidth = match ($variant) { + 'outline' => 2, + 'mini' => 2.25, + 'micro' => 2.5, + }; +@endphp + +class($classes) }} + data-flux-icon + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + stroke-width="{{ $strokeWidth }}" + stroke-linecap="round" + stroke-linejoin="round" + aria-hidden="true" + data-slot="icon" +> + + + diff --git a/resources/views/flux/icon/circle-stack.blade.php b/resources/views/flux/icon/circle-stack.blade.php new file mode 100644 index 0000000..9f2f0ef --- /dev/null +++ b/resources/views/flux/icon/circle-stack.blade.php @@ -0,0 +1,50 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + + diff --git a/resources/views/flux/icon/clipboard-document-check.blade.php b/resources/views/flux/icon/clipboard-document-check.blade.php new file mode 100644 index 0000000..8af8792 --- /dev/null +++ b/resources/views/flux/icon/clipboard-document-check.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/clipboard-document-list.blade.php b/resources/views/flux/icon/clipboard-document-list.blade.php new file mode 100644 index 0000000..80cf704 --- /dev/null +++ b/resources/views/flux/icon/clipboard-document-list.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/clipboard-document.blade.php b/resources/views/flux/icon/clipboard-document.blade.php new file mode 100644 index 0000000..b652de9 --- /dev/null +++ b/resources/views/flux/icon/clipboard-document.blade.php @@ -0,0 +1,49 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/clipboard.blade.php b/resources/views/flux/icon/clipboard.blade.php new file mode 100644 index 0000000..0ee9fe9 --- /dev/null +++ b/resources/views/flux/icon/clipboard.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/clock.blade.php b/resources/views/flux/icon/clock.blade.php new file mode 100644 index 0000000..3bd9655 --- /dev/null +++ b/resources/views/flux/icon/clock.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/cloud-arrow-down.blade.php b/resources/views/flux/icon/cloud-arrow-down.blade.php new file mode 100644 index 0000000..e12bef9 --- /dev/null +++ b/resources/views/flux/icon/cloud-arrow-down.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/cloud-arrow-up.blade.php b/resources/views/flux/icon/cloud-arrow-up.blade.php new file mode 100644 index 0000000..40c865b --- /dev/null +++ b/resources/views/flux/icon/cloud-arrow-up.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/cloud.blade.php b/resources/views/flux/icon/cloud.blade.php new file mode 100644 index 0000000..0a0f12f --- /dev/null +++ b/resources/views/flux/icon/cloud.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/code-bracket-square.blade.php b/resources/views/flux/icon/code-bracket-square.blade.php new file mode 100644 index 0000000..1283588 --- /dev/null +++ b/resources/views/flux/icon/code-bracket-square.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/code-bracket.blade.php b/resources/views/flux/icon/code-bracket.blade.php new file mode 100644 index 0000000..56cd9be --- /dev/null +++ b/resources/views/flux/icon/code-bracket.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/cog-6-tooth.blade.php b/resources/views/flux/icon/cog-6-tooth.blade.php new file mode 100644 index 0000000..a3f4ace --- /dev/null +++ b/resources/views/flux/icon/cog-6-tooth.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/cog-8-tooth.blade.php b/resources/views/flux/icon/cog-8-tooth.blade.php new file mode 100644 index 0000000..01f21f8 --- /dev/null +++ b/resources/views/flux/icon/cog-8-tooth.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/cog.blade.php b/resources/views/flux/icon/cog.blade.php new file mode 100644 index 0000000..66ea9f9 --- /dev/null +++ b/resources/views/flux/icon/cog.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/command-line.blade.php b/resources/views/flux/icon/command-line.blade.php new file mode 100644 index 0000000..a8511cf --- /dev/null +++ b/resources/views/flux/icon/command-line.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/computer-desktop.blade.php b/resources/views/flux/icon/computer-desktop.blade.php new file mode 100644 index 0000000..e30926a --- /dev/null +++ b/resources/views/flux/icon/computer-desktop.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/cpu-chip.blade.php b/resources/views/flux/icon/cpu-chip.blade.php new file mode 100644 index 0000000..f7020c8 --- /dev/null +++ b/resources/views/flux/icon/cpu-chip.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/credit-card.blade.php b/resources/views/flux/icon/credit-card.blade.php new file mode 100644 index 0000000..6439172 --- /dev/null +++ b/resources/views/flux/icon/credit-card.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/cube-transparent.blade.php b/resources/views/flux/icon/cube-transparent.blade.php new file mode 100644 index 0000000..5ce3ddc --- /dev/null +++ b/resources/views/flux/icon/cube-transparent.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/cube.blade.php b/resources/views/flux/icon/cube.blade.php new file mode 100644 index 0000000..10f36d8 --- /dev/null +++ b/resources/views/flux/icon/cube.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/currency-bangladeshi.blade.php b/resources/views/flux/icon/currency-bangladeshi.blade.php new file mode 100644 index 0000000..37516cc --- /dev/null +++ b/resources/views/flux/icon/currency-bangladeshi.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/currency-dollar.blade.php b/resources/views/flux/icon/currency-dollar.blade.php new file mode 100644 index 0000000..fd83437 --- /dev/null +++ b/resources/views/flux/icon/currency-dollar.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/currency-euro.blade.php b/resources/views/flux/icon/currency-euro.blade.php new file mode 100644 index 0000000..b3d78d7 --- /dev/null +++ b/resources/views/flux/icon/currency-euro.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/currency-pound.blade.php b/resources/views/flux/icon/currency-pound.blade.php new file mode 100644 index 0000000..b05fe72 --- /dev/null +++ b/resources/views/flux/icon/currency-pound.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/currency-rupee.blade.php b/resources/views/flux/icon/currency-rupee.blade.php new file mode 100644 index 0000000..459a921 --- /dev/null +++ b/resources/views/flux/icon/currency-rupee.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/currency-yen.blade.php b/resources/views/flux/icon/currency-yen.blade.php new file mode 100644 index 0000000..d43f8cd --- /dev/null +++ b/resources/views/flux/icon/currency-yen.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/cursor-arrow-rays.blade.php b/resources/views/flux/icon/cursor-arrow-rays.blade.php new file mode 100644 index 0000000..289e9a0 --- /dev/null +++ b/resources/views/flux/icon/cursor-arrow-rays.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/cursor-arrow-ripple.blade.php b/resources/views/flux/icon/cursor-arrow-ripple.blade.php new file mode 100644 index 0000000..8f09543 --- /dev/null +++ b/resources/views/flux/icon/cursor-arrow-ripple.blade.php @@ -0,0 +1,49 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + + diff --git a/resources/views/flux/icon/device-phone-mobile.blade.php b/resources/views/flux/icon/device-phone-mobile.blade.php new file mode 100644 index 0000000..6c7e366 --- /dev/null +++ b/resources/views/flux/icon/device-phone-mobile.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/device-tablet.blade.php b/resources/views/flux/icon/device-tablet.blade.php new file mode 100644 index 0000000..ccdc6ed --- /dev/null +++ b/resources/views/flux/icon/device-tablet.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/divide.blade.php b/resources/views/flux/icon/divide.blade.php new file mode 100644 index 0000000..68b1f68 --- /dev/null +++ b/resources/views/flux/icon/divide.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/document-arrow-down.blade.php b/resources/views/flux/icon/document-arrow-down.blade.php new file mode 100644 index 0000000..04aac2a --- /dev/null +++ b/resources/views/flux/icon/document-arrow-down.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/document-arrow-up.blade.php b/resources/views/flux/icon/document-arrow-up.blade.php new file mode 100644 index 0000000..c9fedcc --- /dev/null +++ b/resources/views/flux/icon/document-arrow-up.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/document-chart-bar.blade.php b/resources/views/flux/icon/document-chart-bar.blade.php new file mode 100644 index 0000000..978a8d4 --- /dev/null +++ b/resources/views/flux/icon/document-chart-bar.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/document-check.blade.php b/resources/views/flux/icon/document-check.blade.php new file mode 100644 index 0000000..69829b0 --- /dev/null +++ b/resources/views/flux/icon/document-check.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/document-currency-bangladeshi.blade.php b/resources/views/flux/icon/document-currency-bangladeshi.blade.php new file mode 100644 index 0000000..684e23a --- /dev/null +++ b/resources/views/flux/icon/document-currency-bangladeshi.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/document-currency-dollar.blade.php b/resources/views/flux/icon/document-currency-dollar.blade.php new file mode 100644 index 0000000..8df1114 --- /dev/null +++ b/resources/views/flux/icon/document-currency-dollar.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/document-currency-euro.blade.php b/resources/views/flux/icon/document-currency-euro.blade.php new file mode 100644 index 0000000..9eefa2a --- /dev/null +++ b/resources/views/flux/icon/document-currency-euro.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/document-currency-pound.blade.php b/resources/views/flux/icon/document-currency-pound.blade.php new file mode 100644 index 0000000..8f36ba9 --- /dev/null +++ b/resources/views/flux/icon/document-currency-pound.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/document-currency-rupee.blade.php b/resources/views/flux/icon/document-currency-rupee.blade.php new file mode 100644 index 0000000..82d7797 --- /dev/null +++ b/resources/views/flux/icon/document-currency-rupee.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/document-currency-yen.blade.php b/resources/views/flux/icon/document-currency-yen.blade.php new file mode 100644 index 0000000..f1a00c9 --- /dev/null +++ b/resources/views/flux/icon/document-currency-yen.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/document-duplicate.blade.php b/resources/views/flux/icon/document-duplicate.blade.php new file mode 100644 index 0000000..74bf21e --- /dev/null +++ b/resources/views/flux/icon/document-duplicate.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/document-magnifying-glass.blade.php b/resources/views/flux/icon/document-magnifying-glass.blade.php new file mode 100644 index 0000000..3ba65d1 --- /dev/null +++ b/resources/views/flux/icon/document-magnifying-glass.blade.php @@ -0,0 +1,49 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/document-minus.blade.php b/resources/views/flux/icon/document-minus.blade.php new file mode 100644 index 0000000..657bc6a --- /dev/null +++ b/resources/views/flux/icon/document-minus.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/document-plus.blade.php b/resources/views/flux/icon/document-plus.blade.php new file mode 100644 index 0000000..08d6696 --- /dev/null +++ b/resources/views/flux/icon/document-plus.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/document-text.blade.php b/resources/views/flux/icon/document-text.blade.php new file mode 100644 index 0000000..69de413 --- /dev/null +++ b/resources/views/flux/icon/document-text.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/document.blade.php b/resources/views/flux/icon/document.blade.php new file mode 100644 index 0000000..4692858 --- /dev/null +++ b/resources/views/flux/icon/document.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/ellipsis-horizontal-circle.blade.php b/resources/views/flux/icon/ellipsis-horizontal-circle.blade.php new file mode 100644 index 0000000..5729e08 --- /dev/null +++ b/resources/views/flux/icon/ellipsis-horizontal-circle.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/ellipsis-horizontal.blade.php b/resources/views/flux/icon/ellipsis-horizontal.blade.php new file mode 100644 index 0000000..f3b2df8 --- /dev/null +++ b/resources/views/flux/icon/ellipsis-horizontal.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/ellipsis-vertical.blade.php b/resources/views/flux/icon/ellipsis-vertical.blade.php new file mode 100644 index 0000000..b2e4c26 --- /dev/null +++ b/resources/views/flux/icon/ellipsis-vertical.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/envelope-open.blade.php b/resources/views/flux/icon/envelope-open.blade.php new file mode 100644 index 0000000..702a956 --- /dev/null +++ b/resources/views/flux/icon/envelope-open.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/envelope.blade.php b/resources/views/flux/icon/envelope.blade.php new file mode 100644 index 0000000..4056a50 --- /dev/null +++ b/resources/views/flux/icon/envelope.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/equals.blade.php b/resources/views/flux/icon/equals.blade.php new file mode 100644 index 0000000..416bc3e --- /dev/null +++ b/resources/views/flux/icon/equals.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/exclamation-circle.blade.php b/resources/views/flux/icon/exclamation-circle.blade.php new file mode 100644 index 0000000..91b2bce --- /dev/null +++ b/resources/views/flux/icon/exclamation-circle.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/exclamation-triangle.blade.php b/resources/views/flux/icon/exclamation-triangle.blade.php new file mode 100644 index 0000000..f801f26 --- /dev/null +++ b/resources/views/flux/icon/exclamation-triangle.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/eye-dropper.blade.php b/resources/views/flux/icon/eye-dropper.blade.php new file mode 100644 index 0000000..d602cdb --- /dev/null +++ b/resources/views/flux/icon/eye-dropper.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/eye-slash.blade.php b/resources/views/flux/icon/eye-slash.blade.php new file mode 100644 index 0000000..932848d --- /dev/null +++ b/resources/views/flux/icon/eye-slash.blade.php @@ -0,0 +1,49 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/eye.blade.php b/resources/views/flux/icon/eye.blade.php new file mode 100644 index 0000000..dd4f013 --- /dev/null +++ b/resources/views/flux/icon/eye.blade.php @@ -0,0 +1,49 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/face-frown.blade.php b/resources/views/flux/icon/face-frown.blade.php new file mode 100644 index 0000000..4a08b0b --- /dev/null +++ b/resources/views/flux/icon/face-frown.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/face-smile.blade.php b/resources/views/flux/icon/face-smile.blade.php new file mode 100644 index 0000000..ae89134 --- /dev/null +++ b/resources/views/flux/icon/face-smile.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/film.blade.php b/resources/views/flux/icon/film.blade.php new file mode 100644 index 0000000..73e3e32 --- /dev/null +++ b/resources/views/flux/icon/film.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/finger-print.blade.php b/resources/views/flux/icon/finger-print.blade.php new file mode 100644 index 0000000..cd6d67c --- /dev/null +++ b/resources/views/flux/icon/finger-print.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/fire.blade.php b/resources/views/flux/icon/fire.blade.php new file mode 100644 index 0000000..4d3895d --- /dev/null +++ b/resources/views/flux/icon/fire.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/flag.blade.php b/resources/views/flux/icon/flag.blade.php new file mode 100644 index 0000000..d5b0140 --- /dev/null +++ b/resources/views/flux/icon/flag.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/folder-arrow-down.blade.php b/resources/views/flux/icon/folder-arrow-down.blade.php new file mode 100644 index 0000000..fec8aa5 --- /dev/null +++ b/resources/views/flux/icon/folder-arrow-down.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/folder-git-2.blade.php b/resources/views/flux/icon/folder-git-2.blade.php new file mode 100644 index 0000000..292171b --- /dev/null +++ b/resources/views/flux/icon/folder-git-2.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Lucide (https://lucide.dev) --}} + +@props([ + 'variant' => 'outline', +]) + +@php + if ($variant === 'solid') { + throw new \Exception('The "solid" variant is not supported in Lucide.'); + } + + $classes = Flux::classes('shrink-0')->add( + match ($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }, + ); + + $strokeWidth = match ($variant) { + 'outline' => 2, + 'mini' => 2.25, + 'micro' => 2.5, + }; +@endphp + +class($classes) }} + data-flux-icon + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + stroke-width="{{ $strokeWidth }}" + stroke-linecap="round" + stroke-linejoin="round" + aria-hidden="true" + data-slot="icon" +> + + + + + diff --git a/resources/views/flux/icon/folder-minus.blade.php b/resources/views/flux/icon/folder-minus.blade.php new file mode 100644 index 0000000..45cee03 --- /dev/null +++ b/resources/views/flux/icon/folder-minus.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/folder-open.blade.php b/resources/views/flux/icon/folder-open.blade.php new file mode 100644 index 0000000..fe1b0f0 --- /dev/null +++ b/resources/views/flux/icon/folder-open.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/folder-plus.blade.php b/resources/views/flux/icon/folder-plus.blade.php new file mode 100644 index 0000000..d078529 --- /dev/null +++ b/resources/views/flux/icon/folder-plus.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/folder.blade.php b/resources/views/flux/icon/folder.blade.php new file mode 100644 index 0000000..1eebf16 --- /dev/null +++ b/resources/views/flux/icon/folder.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/forward.blade.php b/resources/views/flux/icon/forward.blade.php new file mode 100644 index 0000000..a4798a6 --- /dev/null +++ b/resources/views/flux/icon/forward.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/funnel.blade.php b/resources/views/flux/icon/funnel.blade.php new file mode 100644 index 0000000..19c11d9 --- /dev/null +++ b/resources/views/flux/icon/funnel.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/gif.blade.php b/resources/views/flux/icon/gif.blade.php new file mode 100644 index 0000000..981df7a --- /dev/null +++ b/resources/views/flux/icon/gif.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/gift-top.blade.php b/resources/views/flux/icon/gift-top.blade.php new file mode 100644 index 0000000..71abecd --- /dev/null +++ b/resources/views/flux/icon/gift-top.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/gift.blade.php b/resources/views/flux/icon/gift.blade.php new file mode 100644 index 0000000..e178f7f --- /dev/null +++ b/resources/views/flux/icon/gift.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/globe-alt.blade.php b/resources/views/flux/icon/globe-alt.blade.php new file mode 100644 index 0000000..59cc5d4 --- /dev/null +++ b/resources/views/flux/icon/globe-alt.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/globe-americas.blade.php b/resources/views/flux/icon/globe-americas.blade.php new file mode 100644 index 0000000..aa4b112 --- /dev/null +++ b/resources/views/flux/icon/globe-americas.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/globe-asia-australia.blade.php b/resources/views/flux/icon/globe-asia-australia.blade.php new file mode 100644 index 0000000..88c8067 --- /dev/null +++ b/resources/views/flux/icon/globe-asia-australia.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/globe-europe-africa.blade.php b/resources/views/flux/icon/globe-europe-africa.blade.php new file mode 100644 index 0000000..9a1484b --- /dev/null +++ b/resources/views/flux/icon/globe-europe-africa.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/h1.blade.php b/resources/views/flux/icon/h1.blade.php new file mode 100644 index 0000000..8576a18 --- /dev/null +++ b/resources/views/flux/icon/h1.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/h2.blade.php b/resources/views/flux/icon/h2.blade.php new file mode 100644 index 0000000..28cb4bb --- /dev/null +++ b/resources/views/flux/icon/h2.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/h3.blade.php b/resources/views/flux/icon/h3.blade.php new file mode 100644 index 0000000..84542e3 --- /dev/null +++ b/resources/views/flux/icon/h3.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/hand-raised.blade.php b/resources/views/flux/icon/hand-raised.blade.php new file mode 100644 index 0000000..4c16ef9 --- /dev/null +++ b/resources/views/flux/icon/hand-raised.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/hand-thumb-down.blade.php b/resources/views/flux/icon/hand-thumb-down.blade.php new file mode 100644 index 0000000..47f7382 --- /dev/null +++ b/resources/views/flux/icon/hand-thumb-down.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/hand-thumb-up.blade.php b/resources/views/flux/icon/hand-thumb-up.blade.php new file mode 100644 index 0000000..a068638 --- /dev/null +++ b/resources/views/flux/icon/hand-thumb-up.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/hashtag.blade.php b/resources/views/flux/icon/hashtag.blade.php new file mode 100644 index 0000000..f2e411a --- /dev/null +++ b/resources/views/flux/icon/hashtag.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/heart.blade.php b/resources/views/flux/icon/heart.blade.php new file mode 100644 index 0000000..9586a07 --- /dev/null +++ b/resources/views/flux/icon/heart.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/home-modern.blade.php b/resources/views/flux/icon/home-modern.blade.php new file mode 100644 index 0000000..b02fe40 --- /dev/null +++ b/resources/views/flux/icon/home-modern.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/home.blade.php b/resources/views/flux/icon/home.blade.php new file mode 100644 index 0000000..8e5b754 --- /dev/null +++ b/resources/views/flux/icon/home.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/identification.blade.php b/resources/views/flux/icon/identification.blade.php new file mode 100644 index 0000000..8210e1b --- /dev/null +++ b/resources/views/flux/icon/identification.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/inbox-arrow-down.blade.php b/resources/views/flux/icon/inbox-arrow-down.blade.php new file mode 100644 index 0000000..c071784 --- /dev/null +++ b/resources/views/flux/icon/inbox-arrow-down.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/inbox-stack.blade.php b/resources/views/flux/icon/inbox-stack.blade.php new file mode 100644 index 0000000..0a269f0 --- /dev/null +++ b/resources/views/flux/icon/inbox-stack.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/inbox.blade.php b/resources/views/flux/icon/inbox.blade.php new file mode 100644 index 0000000..dd45984 --- /dev/null +++ b/resources/views/flux/icon/inbox.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/index.blade.php b/resources/views/flux/icon/index.blade.php new file mode 100644 index 0000000..674bc81 --- /dev/null +++ b/resources/views/flux/icon/index.blade.php @@ -0,0 +1,12 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'icon' => null, + 'name' => null, +]) + +@php +$icon = $name ?? $icon; +@endphp + +{{ $slot }} diff --git a/resources/views/flux/icon/information-circle.blade.php b/resources/views/flux/icon/information-circle.blade.php new file mode 100644 index 0000000..3ac4394 --- /dev/null +++ b/resources/views/flux/icon/information-circle.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/italic.blade.php b/resources/views/flux/icon/italic.blade.php new file mode 100644 index 0000000..d8c0174 --- /dev/null +++ b/resources/views/flux/icon/italic.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/key.blade.php b/resources/views/flux/icon/key.blade.php new file mode 100644 index 0000000..b1c71a5 --- /dev/null +++ b/resources/views/flux/icon/key.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/language.blade.php b/resources/views/flux/icon/language.blade.php new file mode 100644 index 0000000..2611edf --- /dev/null +++ b/resources/views/flux/icon/language.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/layout-grid.blade.php b/resources/views/flux/icon/layout-grid.blade.php new file mode 100644 index 0000000..88c5698 --- /dev/null +++ b/resources/views/flux/icon/layout-grid.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Lucide (https://lucide.dev) --}} + +@props([ + 'variant' => 'outline', +]) + +@php + if ($variant === 'solid') { + throw new \Exception('The "solid" variant is not supported in Lucide.'); + } + + $classes = Flux::classes('shrink-0')->add( + match ($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }, + ); + + $strokeWidth = match ($variant) { + 'outline' => 2, + 'mini' => 2.25, + 'micro' => 2.5, + }; +@endphp + +class($classes) }} + data-flux-icon + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + stroke-width="{{ $strokeWidth }}" + stroke-linecap="round" + stroke-linejoin="round" + aria-hidden="true" + data-slot="icon" +> + + + + + diff --git a/resources/views/flux/icon/lifebuoy.blade.php b/resources/views/flux/icon/lifebuoy.blade.php new file mode 100644 index 0000000..d63e92c --- /dev/null +++ b/resources/views/flux/icon/lifebuoy.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/light-bulb.blade.php b/resources/views/flux/icon/light-bulb.blade.php new file mode 100644 index 0000000..81a809d --- /dev/null +++ b/resources/views/flux/icon/light-bulb.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/link-slash.blade.php b/resources/views/flux/icon/link-slash.blade.php new file mode 100644 index 0000000..7f46ba5 --- /dev/null +++ b/resources/views/flux/icon/link-slash.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/link.blade.php b/resources/views/flux/icon/link.blade.php new file mode 100644 index 0000000..3a65cda --- /dev/null +++ b/resources/views/flux/icon/link.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/list-bullet.blade.php b/resources/views/flux/icon/list-bullet.blade.php new file mode 100644 index 0000000..6b5f8c2 --- /dev/null +++ b/resources/views/flux/icon/list-bullet.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/loading.blade.php b/resources/views/flux/icon/loading.blade.php new file mode 100644 index 0000000..d141b87 --- /dev/null +++ b/resources/views/flux/icon/loading.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class([$classes, 'animate-spin']) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true" data-slot="icon"> + + + + + + +class([$classes, 'animate-spin']) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true" data-slot="icon"> + + + + + + +class([$classes, 'animate-spin']) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true" data-slot="icon"> + + + + + + +class([$classes, 'animate-spin']) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/lock-closed.blade.php b/resources/views/flux/icon/lock-closed.blade.php new file mode 100644 index 0000000..9a7650f --- /dev/null +++ b/resources/views/flux/icon/lock-closed.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/lock-open.blade.php b/resources/views/flux/icon/lock-open.blade.php new file mode 100644 index 0000000..407cad2 --- /dev/null +++ b/resources/views/flux/icon/lock-open.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/magnifying-glass-circle.blade.php b/resources/views/flux/icon/magnifying-glass-circle.blade.php new file mode 100644 index 0000000..81ff6ba --- /dev/null +++ b/resources/views/flux/icon/magnifying-glass-circle.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/magnifying-glass-minus.blade.php b/resources/views/flux/icon/magnifying-glass-minus.blade.php new file mode 100644 index 0000000..0f04ffa --- /dev/null +++ b/resources/views/flux/icon/magnifying-glass-minus.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/magnifying-glass-plus.blade.php b/resources/views/flux/icon/magnifying-glass-plus.blade.php new file mode 100644 index 0000000..d5f13bd --- /dev/null +++ b/resources/views/flux/icon/magnifying-glass-plus.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/magnifying-glass.blade.php b/resources/views/flux/icon/magnifying-glass.blade.php new file mode 100644 index 0000000..50bff8e --- /dev/null +++ b/resources/views/flux/icon/magnifying-glass.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/map-pin.blade.php b/resources/views/flux/icon/map-pin.blade.php new file mode 100644 index 0000000..4c6e72b --- /dev/null +++ b/resources/views/flux/icon/map-pin.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/map.blade.php b/resources/views/flux/icon/map.blade.php new file mode 100644 index 0000000..ce07757 --- /dev/null +++ b/resources/views/flux/icon/map.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/megaphone.blade.php b/resources/views/flux/icon/megaphone.blade.php new file mode 100644 index 0000000..9c2950e --- /dev/null +++ b/resources/views/flux/icon/megaphone.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/microphone.blade.php b/resources/views/flux/icon/microphone.blade.php new file mode 100644 index 0000000..736aed6 --- /dev/null +++ b/resources/views/flux/icon/microphone.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/minus-circle.blade.php b/resources/views/flux/icon/minus-circle.blade.php new file mode 100644 index 0000000..c05b3e7 --- /dev/null +++ b/resources/views/flux/icon/minus-circle.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/minus.blade.php b/resources/views/flux/icon/minus.blade.php new file mode 100644 index 0000000..e32053c --- /dev/null +++ b/resources/views/flux/icon/minus.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/moon.blade.php b/resources/views/flux/icon/moon.blade.php new file mode 100644 index 0000000..f9033ab --- /dev/null +++ b/resources/views/flux/icon/moon.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/musical-note.blade.php b/resources/views/flux/icon/musical-note.blade.php new file mode 100644 index 0000000..ce2e5f6 --- /dev/null +++ b/resources/views/flux/icon/musical-note.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/newspaper.blade.php b/resources/views/flux/icon/newspaper.blade.php new file mode 100644 index 0000000..cf28a98 --- /dev/null +++ b/resources/views/flux/icon/newspaper.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/no-symbol.blade.php b/resources/views/flux/icon/no-symbol.blade.php new file mode 100644 index 0000000..8daf54e --- /dev/null +++ b/resources/views/flux/icon/no-symbol.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/numbered-list.blade.php b/resources/views/flux/icon/numbered-list.blade.php new file mode 100644 index 0000000..f732ea6 --- /dev/null +++ b/resources/views/flux/icon/numbered-list.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/paint-brush.blade.php b/resources/views/flux/icon/paint-brush.blade.php new file mode 100644 index 0000000..1bb5ac5 --- /dev/null +++ b/resources/views/flux/icon/paint-brush.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/paper-airplane.blade.php b/resources/views/flux/icon/paper-airplane.blade.php new file mode 100644 index 0000000..12c16db --- /dev/null +++ b/resources/views/flux/icon/paper-airplane.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/paper-clip.blade.php b/resources/views/flux/icon/paper-clip.blade.php new file mode 100644 index 0000000..a014608 --- /dev/null +++ b/resources/views/flux/icon/paper-clip.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/pause-circle.blade.php b/resources/views/flux/icon/pause-circle.blade.php new file mode 100644 index 0000000..5fab7e4 --- /dev/null +++ b/resources/views/flux/icon/pause-circle.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/pause.blade.php b/resources/views/flux/icon/pause.blade.php new file mode 100644 index 0000000..198ee2e --- /dev/null +++ b/resources/views/flux/icon/pause.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/pencil-square.blade.php b/resources/views/flux/icon/pencil-square.blade.php new file mode 100644 index 0000000..b5974a1 --- /dev/null +++ b/resources/views/flux/icon/pencil-square.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/pencil.blade.php b/resources/views/flux/icon/pencil.blade.php new file mode 100644 index 0000000..6012f96 --- /dev/null +++ b/resources/views/flux/icon/pencil.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/percent-badge.blade.php b/resources/views/flux/icon/percent-badge.blade.php new file mode 100644 index 0000000..7fa2bfd --- /dev/null +++ b/resources/views/flux/icon/percent-badge.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/phone-arrow-down-left.blade.php b/resources/views/flux/icon/phone-arrow-down-left.blade.php new file mode 100644 index 0000000..f0d1b6c --- /dev/null +++ b/resources/views/flux/icon/phone-arrow-down-left.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/phone-arrow-up-right.blade.php b/resources/views/flux/icon/phone-arrow-up-right.blade.php new file mode 100644 index 0000000..f8ca56b --- /dev/null +++ b/resources/views/flux/icon/phone-arrow-up-right.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/phone-x-mark.blade.php b/resources/views/flux/icon/phone-x-mark.blade.php new file mode 100644 index 0000000..9100946 --- /dev/null +++ b/resources/views/flux/icon/phone-x-mark.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/phone.blade.php b/resources/views/flux/icon/phone.blade.php new file mode 100644 index 0000000..2c0348f --- /dev/null +++ b/resources/views/flux/icon/phone.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/photo.blade.php b/resources/views/flux/icon/photo.blade.php new file mode 100644 index 0000000..8bb8540 --- /dev/null +++ b/resources/views/flux/icon/photo.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/play-circle.blade.php b/resources/views/flux/icon/play-circle.blade.php new file mode 100644 index 0000000..e987012 --- /dev/null +++ b/resources/views/flux/icon/play-circle.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/play-pause.blade.php b/resources/views/flux/icon/play-pause.blade.php new file mode 100644 index 0000000..d3737ca --- /dev/null +++ b/resources/views/flux/icon/play-pause.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/play.blade.php b/resources/views/flux/icon/play.blade.php new file mode 100644 index 0000000..717ae70 --- /dev/null +++ b/resources/views/flux/icon/play.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/plus-circle.blade.php b/resources/views/flux/icon/plus-circle.blade.php new file mode 100644 index 0000000..fc8d6b1 --- /dev/null +++ b/resources/views/flux/icon/plus-circle.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/plus.blade.php b/resources/views/flux/icon/plus.blade.php new file mode 100644 index 0000000..6d94b09 --- /dev/null +++ b/resources/views/flux/icon/plus.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/power.blade.php b/resources/views/flux/icon/power.blade.php new file mode 100644 index 0000000..bfcb4c5 --- /dev/null +++ b/resources/views/flux/icon/power.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/presentation-chart-bar.blade.php b/resources/views/flux/icon/presentation-chart-bar.blade.php new file mode 100644 index 0000000..f44f659 --- /dev/null +++ b/resources/views/flux/icon/presentation-chart-bar.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/presentation-chart-line.blade.php b/resources/views/flux/icon/presentation-chart-line.blade.php new file mode 100644 index 0000000..ce23742 --- /dev/null +++ b/resources/views/flux/icon/presentation-chart-line.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/printer.blade.php b/resources/views/flux/icon/printer.blade.php new file mode 100644 index 0000000..e08de77 --- /dev/null +++ b/resources/views/flux/icon/printer.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/puzzle-piece.blade.php b/resources/views/flux/icon/puzzle-piece.blade.php new file mode 100644 index 0000000..51771c4 --- /dev/null +++ b/resources/views/flux/icon/puzzle-piece.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/qr-code.blade.php b/resources/views/flux/icon/qr-code.blade.php new file mode 100644 index 0000000..901c8c2 --- /dev/null +++ b/resources/views/flux/icon/qr-code.blade.php @@ -0,0 +1,52 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + + + + + + diff --git a/resources/views/flux/icon/question-mark-circle.blade.php b/resources/views/flux/icon/question-mark-circle.blade.php new file mode 100644 index 0000000..d3e87a0 --- /dev/null +++ b/resources/views/flux/icon/question-mark-circle.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/queue-list.blade.php b/resources/views/flux/icon/queue-list.blade.php new file mode 100644 index 0000000..310a6a2 --- /dev/null +++ b/resources/views/flux/icon/queue-list.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/radio.blade.php b/resources/views/flux/icon/radio.blade.php new file mode 100644 index 0000000..9ef6dfc --- /dev/null +++ b/resources/views/flux/icon/radio.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/receipt-percent.blade.php b/resources/views/flux/icon/receipt-percent.blade.php new file mode 100644 index 0000000..4b6ee99 --- /dev/null +++ b/resources/views/flux/icon/receipt-percent.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/receipt-refund.blade.php b/resources/views/flux/icon/receipt-refund.blade.php new file mode 100644 index 0000000..eb0e720 --- /dev/null +++ b/resources/views/flux/icon/receipt-refund.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/rectangle-group.blade.php b/resources/views/flux/icon/rectangle-group.blade.php new file mode 100644 index 0000000..02c1d1b --- /dev/null +++ b/resources/views/flux/icon/rectangle-group.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/rectangle-stack.blade.php b/resources/views/flux/icon/rectangle-stack.blade.php new file mode 100644 index 0000000..892cc6c --- /dev/null +++ b/resources/views/flux/icon/rectangle-stack.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/rocket-launch.blade.php b/resources/views/flux/icon/rocket-launch.blade.php new file mode 100644 index 0000000..036a873 --- /dev/null +++ b/resources/views/flux/icon/rocket-launch.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/rss.blade.php b/resources/views/flux/icon/rss.blade.php new file mode 100644 index 0000000..cc78902 --- /dev/null +++ b/resources/views/flux/icon/rss.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/scale.blade.php b/resources/views/flux/icon/scale.blade.php new file mode 100644 index 0000000..0c799e3 --- /dev/null +++ b/resources/views/flux/icon/scale.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/scissors.blade.php b/resources/views/flux/icon/scissors.blade.php new file mode 100644 index 0000000..284c11c --- /dev/null +++ b/resources/views/flux/icon/scissors.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/server-stack.blade.php b/resources/views/flux/icon/server-stack.blade.php new file mode 100644 index 0000000..a947762 --- /dev/null +++ b/resources/views/flux/icon/server-stack.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/server.blade.php b/resources/views/flux/icon/server.blade.php new file mode 100644 index 0000000..6d80c95 --- /dev/null +++ b/resources/views/flux/icon/server.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/share.blade.php b/resources/views/flux/icon/share.blade.php new file mode 100644 index 0000000..955ad19 --- /dev/null +++ b/resources/views/flux/icon/share.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/shield-check.blade.php b/resources/views/flux/icon/shield-check.blade.php new file mode 100644 index 0000000..6d64719 --- /dev/null +++ b/resources/views/flux/icon/shield-check.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/shield-exclamation.blade.php b/resources/views/flux/icon/shield-exclamation.blade.php new file mode 100644 index 0000000..50620de --- /dev/null +++ b/resources/views/flux/icon/shield-exclamation.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/shopping-bag.blade.php b/resources/views/flux/icon/shopping-bag.blade.php new file mode 100644 index 0000000..32a9ec5 --- /dev/null +++ b/resources/views/flux/icon/shopping-bag.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/shopping-cart.blade.php b/resources/views/flux/icon/shopping-cart.blade.php new file mode 100644 index 0000000..d511a6b --- /dev/null +++ b/resources/views/flux/icon/shopping-cart.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/signal-slash.blade.php b/resources/views/flux/icon/signal-slash.blade.php new file mode 100644 index 0000000..0b5dc51 --- /dev/null +++ b/resources/views/flux/icon/signal-slash.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/signal.blade.php b/resources/views/flux/icon/signal.blade.php new file mode 100644 index 0000000..b50b36c --- /dev/null +++ b/resources/views/flux/icon/signal.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + + diff --git a/resources/views/flux/icon/slash.blade.php b/resources/views/flux/icon/slash.blade.php new file mode 100644 index 0000000..0e32d23 --- /dev/null +++ b/resources/views/flux/icon/slash.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/sparkles.blade.php b/resources/views/flux/icon/sparkles.blade.php new file mode 100644 index 0000000..6320975 --- /dev/null +++ b/resources/views/flux/icon/sparkles.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/speaker-wave.blade.php b/resources/views/flux/icon/speaker-wave.blade.php new file mode 100644 index 0000000..e1e6308 --- /dev/null +++ b/resources/views/flux/icon/speaker-wave.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/speaker-x-mark.blade.php b/resources/views/flux/icon/speaker-x-mark.blade.php new file mode 100644 index 0000000..6a8bf93 --- /dev/null +++ b/resources/views/flux/icon/speaker-x-mark.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/square-2-stack.blade.php b/resources/views/flux/icon/square-2-stack.blade.php new file mode 100644 index 0000000..123ad5e --- /dev/null +++ b/resources/views/flux/icon/square-2-stack.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/square-3-stack-3d.blade.php b/resources/views/flux/icon/square-3-stack-3d.blade.php new file mode 100644 index 0000000..36edac9 --- /dev/null +++ b/resources/views/flux/icon/square-3-stack-3d.blade.php @@ -0,0 +1,51 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + + diff --git a/resources/views/flux/icon/squares-2x2.blade.php b/resources/views/flux/icon/squares-2x2.blade.php new file mode 100644 index 0000000..71378b7 --- /dev/null +++ b/resources/views/flux/icon/squares-2x2.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/squares-plus.blade.php b/resources/views/flux/icon/squares-plus.blade.php new file mode 100644 index 0000000..3ffb49a --- /dev/null +++ b/resources/views/flux/icon/squares-plus.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/star.blade.php b/resources/views/flux/icon/star.blade.php new file mode 100644 index 0000000..f931969 --- /dev/null +++ b/resources/views/flux/icon/star.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/stop-circle.blade.php b/resources/views/flux/icon/stop-circle.blade.php new file mode 100644 index 0000000..7926202 --- /dev/null +++ b/resources/views/flux/icon/stop-circle.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/stop.blade.php b/resources/views/flux/icon/stop.blade.php new file mode 100644 index 0000000..25fa03f --- /dev/null +++ b/resources/views/flux/icon/stop.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/strikethrough.blade.php b/resources/views/flux/icon/strikethrough.blade.php new file mode 100644 index 0000000..6fa1bf0 --- /dev/null +++ b/resources/views/flux/icon/strikethrough.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/sun.blade.php b/resources/views/flux/icon/sun.blade.php new file mode 100644 index 0000000..40cee74 --- /dev/null +++ b/resources/views/flux/icon/sun.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/swatch.blade.php b/resources/views/flux/icon/swatch.blade.php new file mode 100644 index 0000000..8147d96 --- /dev/null +++ b/resources/views/flux/icon/swatch.blade.php @@ -0,0 +1,47 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/table-cells.blade.php b/resources/views/flux/icon/table-cells.blade.php new file mode 100644 index 0000000..7d2bc52 --- /dev/null +++ b/resources/views/flux/icon/table-cells.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/tag.blade.php b/resources/views/flux/icon/tag.blade.php new file mode 100644 index 0000000..7590734 --- /dev/null +++ b/resources/views/flux/icon/tag.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/ticket.blade.php b/resources/views/flux/icon/ticket.blade.php new file mode 100644 index 0000000..41f1f54 --- /dev/null +++ b/resources/views/flux/icon/ticket.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/trash.blade.php b/resources/views/flux/icon/trash.blade.php new file mode 100644 index 0000000..84bccbb --- /dev/null +++ b/resources/views/flux/icon/trash.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/trophy.blade.php b/resources/views/flux/icon/trophy.blade.php new file mode 100644 index 0000000..b1c74d4 --- /dev/null +++ b/resources/views/flux/icon/trophy.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/truck.blade.php b/resources/views/flux/icon/truck.blade.php new file mode 100644 index 0000000..b591821 --- /dev/null +++ b/resources/views/flux/icon/truck.blade.php @@ -0,0 +1,50 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + + diff --git a/resources/views/flux/icon/tv.blade.php b/resources/views/flux/icon/tv.blade.php new file mode 100644 index 0000000..b325f2b --- /dev/null +++ b/resources/views/flux/icon/tv.blade.php @@ -0,0 +1,48 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/underline.blade.php b/resources/views/flux/icon/underline.blade.php new file mode 100644 index 0000000..a355a4e --- /dev/null +++ b/resources/views/flux/icon/underline.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/user-circle.blade.php b/resources/views/flux/icon/user-circle.blade.php new file mode 100644 index 0000000..04a6113 --- /dev/null +++ b/resources/views/flux/icon/user-circle.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/user-group.blade.php b/resources/views/flux/icon/user-group.blade.php new file mode 100644 index 0000000..403fda7 --- /dev/null +++ b/resources/views/flux/icon/user-group.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/user-minus.blade.php b/resources/views/flux/icon/user-minus.blade.php new file mode 100644 index 0000000..5560669 --- /dev/null +++ b/resources/views/flux/icon/user-minus.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/user-plus.blade.php b/resources/views/flux/icon/user-plus.blade.php new file mode 100644 index 0000000..2f03539 --- /dev/null +++ b/resources/views/flux/icon/user-plus.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/user.blade.php b/resources/views/flux/icon/user.blade.php new file mode 100644 index 0000000..d574276 --- /dev/null +++ b/resources/views/flux/icon/user.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/users.blade.php b/resources/views/flux/icon/users.blade.php new file mode 100644 index 0000000..e9a0976 --- /dev/null +++ b/resources/views/flux/icon/users.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/variable.blade.php b/resources/views/flux/icon/variable.blade.php new file mode 100644 index 0000000..6aec7e8 --- /dev/null +++ b/resources/views/flux/icon/variable.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/video-camera-slash.blade.php b/resources/views/flux/icon/video-camera-slash.blade.php new file mode 100644 index 0000000..39f52e2 --- /dev/null +++ b/resources/views/flux/icon/video-camera-slash.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/video-camera.blade.php b/resources/views/flux/icon/video-camera.blade.php new file mode 100644 index 0000000..2778bcb --- /dev/null +++ b/resources/views/flux/icon/video-camera.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/view-columns.blade.php b/resources/views/flux/icon/view-columns.blade.php new file mode 100644 index 0000000..b2b5e68 --- /dev/null +++ b/resources/views/flux/icon/view-columns.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/viewfinder-circle.blade.php b/resources/views/flux/icon/viewfinder-circle.blade.php new file mode 100644 index 0000000..c8a7062 --- /dev/null +++ b/resources/views/flux/icon/viewfinder-circle.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/wallet.blade.php b/resources/views/flux/icon/wallet.blade.php new file mode 100644 index 0000000..0363dda --- /dev/null +++ b/resources/views/flux/icon/wallet.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/wifi.blade.php b/resources/views/flux/icon/wifi.blade.php new file mode 100644 index 0000000..2d2d73c --- /dev/null +++ b/resources/views/flux/icon/wifi.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/window.blade.php b/resources/views/flux/icon/window.blade.php new file mode 100644 index 0000000..b16505d --- /dev/null +++ b/resources/views/flux/icon/window.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/wrench-screwdriver.blade.php b/resources/views/flux/icon/wrench-screwdriver.blade.php new file mode 100644 index 0000000..d7c6ef7 --- /dev/null +++ b/resources/views/flux/icon/wrench-screwdriver.blade.php @@ -0,0 +1,49 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + diff --git a/resources/views/flux/icon/wrench.blade.php b/resources/views/flux/icon/wrench.blade.php new file mode 100644 index 0000000..bcd35fc --- /dev/null +++ b/resources/views/flux/icon/wrench.blade.php @@ -0,0 +1,46 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/x-circle.blade.php b/resources/views/flux/icon/x-circle.blade.php new file mode 100644 index 0000000..670e845 --- /dev/null +++ b/resources/views/flux/icon/x-circle.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/icon/x-mark.blade.php b/resources/views/flux/icon/x-mark.blade.php new file mode 100644 index 0000000..2b02a21 --- /dev/null +++ b/resources/views/flux/icon/x-mark.blade.php @@ -0,0 +1,45 @@ +{{-- Credit: Heroicons (https://heroicons.com) --}} + +@props([ + 'variant' => 'outline', +]) + +@php +$classes = Flux::classes('shrink-0') + ->add(match($variant) { + 'outline' => '[:where(&)]:size-6', + 'solid' => '[:where(&)]:size-6', + 'mini' => '[:where(&)]:size-5', + 'micro' => '[:where(&)]:size-4', + }); +@endphp + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + +class($classes) }} data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon"> + + + + + + diff --git a/resources/views/flux/input/clearable.blade.php b/resources/views/flux/input/clearable.blade.php new file mode 100644 index 0000000..8368b66 --- /dev/null +++ b/resources/views/flux/input/clearable.blade.php @@ -0,0 +1,20 @@ +@php +$attributes = $attributes->merge([ + 'variant' => 'subtle', + 'class' => '-me-1 [[data-flux-input]:has(input:placeholder-shown)_&]:hidden [[data-flux-input]:has(input[disabled])_&]:hidden', + 'square' => true, + 'size' => null, +]); +@endphp + + + + diff --git a/resources/views/flux/input/copyable.blade.php b/resources/views/flux/input/copyable.blade.php new file mode 100644 index 0000000..ef9bfc2 --- /dev/null +++ b/resources/views/flux/input/copyable.blade.php @@ -0,0 +1,20 @@ +@php +$attributes = $attributes->merge([ + 'variant' => 'subtle', + 'class' => '-me-1', + 'square' => true, + 'size' => null, +]); +@endphp + + + diff --git a/resources/views/flux/input/expandable.blade.php b/resources/views/flux/input/expandable.blade.php new file mode 100644 index 0000000..4c3ae1e --- /dev/null +++ b/resources/views/flux/input/expandable.blade.php @@ -0,0 +1,16 @@ +@php +$attributes = $attributes->merge([ + 'variant' => 'subtle', + 'class' => '-me-1', + 'square' => true, + 'size' => null, +]); +@endphp + + + + diff --git a/resources/views/flux/input/file.blade.php b/resources/views/flux/input/file.blade.php new file mode 100644 index 0000000..d970dec --- /dev/null +++ b/resources/views/flux/input/file.blade.php @@ -0,0 +1,68 @@ +@php +extract(Flux::forwardedAttributes($attributes, [ + 'name', + 'multiple', + 'size', +])); +@endphp + +@props([ + 'name' => $attributes->whereStartsWith('wire:model')->first(), + 'multiple' => null, + 'size' => null, +]) + +@php +$classes = Flux::classes() + ->add('w-full flex items-center gap-4') + // NOTE: We need to add relative positioning here to prevent odd overflow behaviors because of + // "sr-only": https://github.com/tailwindlabs/tailwindcss/discussions/12429 + ->add('relative') + ; + +[ $styleAttributes, $attributes ] = Flux::splitAttributes($attributes); +@endphp + +
class($classes) }} + data-flux-input-file + wire:ignore + tabindex="0" + x-data {{-- This is here to "scope" the x-ref references inside this component from interfering with others outside... --}} + x-on:click.prevent.stop="$refs.input.click()" + x-on:keydown.enter.prevent.stop="$refs.input.click()" + x-on:keydown.space.prevent.stop + x-on:keyup.space.prevent.stop="$refs.input.click()" + x-on:change="$refs.name.textContent = $event.target.files[1] ? ($event.target.files.length + ' {!! __('files') !!}') : ($event.target.files[0]?.name || '{!! __('No file chosen') !!}')" +> + + + + + +
diff --git a/resources/views/flux/input/group/affix.blade.php b/resources/views/flux/input/group/affix.blade.php new file mode 100644 index 0000000..92d34a4 --- /dev/null +++ b/resources/views/flux/input/group/affix.blade.php @@ -0,0 +1,13 @@ +@php +$classes = Flux::classes([ + 'flex items-center px-4 text-sm whitespace-nowrap', + 'text-zinc-800 dark:text-zinc-200', + 'bg-zinc-800/5 dark:bg-white/20', + 'border-zinc-200 dark:border-white/10', + 'border border-x-zinc-100 shadow-xs', +]); +@endphp + +
class($classes) }} data-flux-input-group-label> + {{ $slot }} +
diff --git a/resources/views/flux/input/group/index.blade.php b/resources/views/flux/input/group/index.blade.php new file mode 100644 index 0000000..e8bf961 --- /dev/null +++ b/resources/views/flux/input/group/index.blade.php @@ -0,0 +1,52 @@ +@props([ + 'name' => $attributes->whereStartsWith('wire:model')->first(), +]) + +@php +$classes = Flux::classes() + ->add('w-full flex') + ->add('*:data-flux-input:grow') + ->add([ + // With the external borders, let's always make sure the first and last children have outside borders. + // For internal borders, we will ensure that all left borders are removed, but the right borders remain. + // But when there is a input groupsuffix, then there should be no right internal border. + // That way we shouldn't ever have a double border... + + // All inputs borders... + '[&>[data-flux-input]:last-child:not(:first-child)>[data-flux-group-target]:not([data-invalid])]:border-s-0', + '[&>[data-flux-input]:not(:first-child):not(:last-child)>[data-flux-group-target]:not([data-invalid])]:border-s-0', + '[&>[data-flux-input]:has(+[data-flux-input-group-suffix])>[data-flux-group-target]:not([data-invalid])]:border-e-0', + + // Selects and date pickers borders... + '[&>*:last-child:not(:first-child)>[data-flux-group-target]:not([data-invalid])]:border-s-0', + '[&>*:not(:first-child):not(:last-child)>[data-flux-group-target]:not([data-invalid])]:border-s-0', + '[&>*:has(+[data-flux-input-group-suffix])>[data-flux-group-target]:not([data-invalid])]:border-e-0', + + // Buttons borders... + '[&>[data-flux-group-target]:last-child:not(:first-child)]:border-s-0', + '[&>[data-flux-group-target]:not(:first-child):not(:last-child)]:border-s-0', + '[&>[data-flux-group-target]:has(+[data-flux-input-group-suffix])]:border-e-0', + + // "Weld" the borders of inputs together by overriding their border radiuses... + '[&>[data-flux-group-target]:not(:first-child):not(:last-child)]:rounded-none', + '[&>[data-flux-group-target]:first-child:not(:last-child)]:rounded-e-none', + '[&>[data-flux-group-target]:last-child:not(:first-child)]:rounded-s-none', + + // "Weld" borders for sub-children of group targets (button element inside ui-select element, etc.)... + '[&>*:not(:first-child):not(:last-child):not(:only-child)>[data-flux-group-target]]:rounded-none', + '[&>*:first-child:not(:last-child)>[data-flux-group-target]]:rounded-e-none', + '[&>*:last-child:not(:first-child)>[data-flux-group-target]]:rounded-s-none', + + // "Weld" borders for sub-sub-children of group targets (input element inside div inside ui-select element (combobox))... + '[&>*:not(:first-child):not(:last-child):not(:only-child)>[data-flux-input]>[data-flux-group-target]]:rounded-none', + '[&>*:first-child:not(:last-child)>[data-flux-input]>[data-flux-group-target]]:rounded-e-none', + '[&>*:last-child:not(:first-child)>[data-flux-input]>[data-flux-group-target]]:rounded-s-none', + ]) + ; +@endphp + + +
class($classes) }} data-flux-input-group> + {{ $slot }} +
+
diff --git a/resources/views/flux/input/group/prefix.blade.php b/resources/views/flux/input/group/prefix.blade.php new file mode 100644 index 0000000..a8c1a3e --- /dev/null +++ b/resources/views/flux/input/group/prefix.blade.php @@ -0,0 +1,14 @@ +@php +$classes = Flux::classes([ + 'flex items-center px-4 text-sm whitespace-nowrap', + 'text-zinc-800 dark:text-zinc-200', + 'bg-zinc-800/5 dark:bg-white/20', + 'border-zinc-200 dark:border-white/10', + 'rounded-s-lg', + 'border-s border-t border-b shadow-xs', +]); +@endphp + +
class($classes) }} data-flux-input-group-prefix> + {{ $slot }} +
diff --git a/resources/views/flux/input/group/suffix.blade.php b/resources/views/flux/input/group/suffix.blade.php new file mode 100644 index 0000000..135dd20 --- /dev/null +++ b/resources/views/flux/input/group/suffix.blade.php @@ -0,0 +1,14 @@ +@php +$classes = Flux::classes([ + 'flex items-center px-4 text-sm whitespace-nowrap', + 'text-zinc-800 dark:text-zinc-200', + 'bg-zinc-800/5 dark:bg-white/20', + 'border-zinc-200 dark:border-white/10', + 'rounded-e-lg', + 'border-e border-t border-b shadow-xs', +]); +@endphp + +
class($classes) }} data-flux-input-group-suffix> + {{ $slot }} +
diff --git a/resources/views/flux/input/index.blade.php b/resources/views/flux/input/index.blade.php new file mode 100644 index 0000000..c04966f --- /dev/null +++ b/resources/views/flux/input/index.blade.php @@ -0,0 +1,225 @@ +@php $iconTrailing = $iconTrailing ??= $attributes->pluck('icon:trailing'); @endphp +@php $iconLeading = $iconLeading ??= $attributes->pluck('icon:leading'); @endphp +@php $iconVariant = $iconVariant ??= $attributes->pluck('icon:variant'); @endphp + +@props([ + 'name' => $attributes->whereStartsWith('wire:model')->first(), + 'iconVariant' => 'mini', + 'variant' => 'outline', + 'iconTrailing' => null, + 'iconLeading' => null, + 'expandable' => null, + 'clearable' => null, + 'copyable' => null, + 'viewable' => null, + 'invalid' => null, + 'loading' => null, + 'type' => 'text', + 'mask' => null, + 'size' => null, + 'icon' => null, + 'kbd' => null, + 'as' => null, +]) + +@php + +// There are a few loading scenarios that this covers: +// If `:loading="false"` then never show loading. +// If `:loading="true"` then always show loading. +// If `:loading="foo"` then show loading when `foo` request is happening. +// If `wire:model` then never show loading. +// If `wire:model.live` then show loading when the `wire:model` value request is happening. +$wireModel = $attributes->wire('model'); +$wireTarget = null; + +if ($loading !== false) { + if ($loading === true) { + $loading = true; + } elseif ($wireModel?->directive) { + $loading = $wireModel->hasModifier('live'); + $wireTarget = $loading ? $wireModel->value() : null; + } else { + $wireTarget = $loading; + $loading = (bool) $loading; + } +} + +$invalid ??= ($name && $errors->has($name)); + +$iconLeading ??= $icon; + +$hasLeadingIcon = (bool) ($iconLeading); +$countOfTrailingIcons = collect([ + (bool) $iconTrailing, + (bool) $kbd, + (bool) $clearable, + (bool) $copyable, + (bool) $viewable, + (bool) $expandable, +])->filter()->count(); + +$iconClasses = Flux::classes() + // When using the outline icon variant, we need to size it down to match the default icon sizes... + ->add($iconVariant === 'outline' ? 'size-5' : '') + ; + +$inputLoadingClasses = Flux::classes() + // When loading, we need to add some extra padding to the input to account for the loading icon... + ->add(match ($countOfTrailingIcons) { + 0 => 'pe-10', + 1 => 'pe-16', + 2 => 'pe-23', + 3 => 'pe-30', + 4 => 'pe-37', + 5 => 'pe-44', + 6 => 'pe-51', + }) + ; + +$classes = Flux::classes() + ->add('w-full border rounded-lg block disabled:shadow-none dark:shadow-none') + ->add('appearance-none') // Without this, input[type="date"] on mobile doesn't respect w-full... + ->add(match ($size) { + default => 'text-base sm:text-sm py-2 h-10 leading-[1.375rem]', // This makes the height of the input 40px (same as buttons and such...) + 'sm' => 'text-sm py-1.5 h-8 leading-[1.125rem]', + 'xs' => 'text-xs py-1.5 h-6 leading-[1.125rem]', + }) + ->add(match ($hasLeadingIcon) { + true => 'ps-10', + false => 'ps-3', + }) + ->add(match ($countOfTrailingIcons) { + // Make sure there's enough padding on the right side of the input to account for all the icons... + 0 => 'pe-3', + 1 => 'pe-10', + 2 => 'pe-16', + 3 => 'pe-23', + 4 => 'pe-30', + 5 => 'pe-37', + 6 => 'pe-44', + }) + ->add(match ($variant) { // Background... + 'outline' => 'bg-white dark:bg-white/10 dark:disabled:bg-white/[7%]', + 'filled' => 'bg-zinc-800/5 dark:bg-white/10 dark:disabled:bg-white/[7%]', + }) + ->add(match ($variant) { // Text color + 'outline' => 'text-zinc-700 disabled:text-zinc-500 placeholder-zinc-400 disabled:placeholder-zinc-400/70 dark:text-zinc-300 dark:disabled:text-zinc-400 dark:placeholder-zinc-400 dark:disabled:placeholder-zinc-500', + 'filled' => 'text-zinc-700 placeholder-zinc-500 disabled:placeholder-zinc-400 dark:text-zinc-200 dark:placeholder-white/60 dark:disabled:placeholder-white/40', + }) + ->add(match ($variant) { // Border... + 'outline' => $invalid ? 'border-red-500' : 'shadow-xs border-zinc-200 border-b-zinc-300/80 disabled:border-b-zinc-200 dark:border-white/10 dark:disabled:border-white/5', + 'filled' => $invalid ? 'border-red-500' : 'border-0', + }) + ->add($attributes->pluck('class:input')) + ; +@endphp + + + + + + + +
only('class')->class('w-full relative block group/input') }} data-flux-input> + +
+ +
+ +
attributes->class('absolute top-0 bottom-0 flex items-center justify-center text-xs text-zinc-400/75 ps-3 start-0') }}> + {{ $iconLeading }} +
+ + + except('class')->class($type === 'file' ? '' : $classes) }} + @isset ($name) name="{{ $name }}" @endisset + @if ($mask) x-mask="{{ $mask }}" @endif + @if ($invalid) aria-invalid="true" data-invalid @endif + @if (is_numeric($size)) size="{{ $size }}" @endif + data-flux-control + data-flux-group-target + @if ($loading) wire:loading.class="{{ $inputLoadingClasses }}" @endif + @if ($loading && $wireTarget) wire:target="{{ $wireTarget }}" @endif + > + +
+ {{-- Icon should be text-zinc-400/75 --}} + + + + + + + + + + {{ $kbd }} + + + + + + + + + + + + + + + + add('pointer-events-none text-zinc-400/75'); + ?> + + + {{ $iconTrailing }} + +
+
+
+ + + diff --git a/resources/views/flux/input/viewable.blade.php b/resources/views/flux/input/viewable.blade.php new file mode 100644 index 0000000..e14b586 --- /dev/null +++ b/resources/views/flux/input/viewable.blade.php @@ -0,0 +1,35 @@ +@php +$attributes = $attributes->merge([ + 'variant' => 'subtle', + 'class' => '-me-1', + 'square' => true, + 'size' => null, +]); +@endphp + + + diff --git a/resources/views/flux/label.blade.php b/resources/views/flux/label.blade.php new file mode 100644 index 0000000..7cdde21 --- /dev/null +++ b/resources/views/flux/label.blade.php @@ -0,0 +1,32 @@ +@props([ + 'badge' => null, + 'aside' => null, +]) + +@php + $classes = Flux::classes() + ->add('inline-flex items-center') + ->add('text-sm font-medium') + ->add('[:where(&)]:text-zinc-800 [:where(&)]:dark:text-white') + ; +@endphp + +class($classes) }} data-flux-label> + {{ $slot }} + + + + + + + + + + + diff --git a/resources/views/flux/legend.blade.php b/resources/views/flux/legend.blade.php new file mode 100644 index 0000000..4168b50 --- /dev/null +++ b/resources/views/flux/legend.blade.php @@ -0,0 +1,4 @@ + +class('text-base font-medium text-zinc-800 dark:text-white') }} data-flux-legend> + {{ $slot }} + diff --git a/resources/views/flux/link.blade.php b/resources/views/flux/link.blade.php new file mode 100644 index 0000000..bd470cd --- /dev/null +++ b/resources/views/flux/link.blade.php @@ -0,0 +1,28 @@ +@props([ + 'external' => null, + 'accent' => true, + 'variant' => null, + 'strong' => false, +]) + +@php +$classes = Flux::classes() + ->add('inline font-medium') + ->add('underline-offset-[6px] hover:decoration-current') + ->add(match ($variant) { + 'ghost' => 'no-underline hover:underline', + 'subtle' => 'no-underline', + default => 'underline', + }) + ->add('[[data-color]>&]:text-inherit [[data-color]>&]:decoration-current/20 dark:[[data-color]>&]:decoration-current/50 [[data-color]>&]:hover:decoration-current') + ->add(match ($variant) { + 'subtle' => 'text-zinc-500 dark:text-white/70 hover:text-zinc-800 dark:hover:text-white', + default => match ($accent) { + true => 'text-[var(--color-accent-content)] decoration-[color-mix(in_oklab,var(--color-accent-content),transparent_80%)]', + false => 'text-zinc-800 dark:text-white decoration-zinc-800/20 dark:decoration-white/20', + }, + }) + ; +@endphp +{{-- NOTE: It's important that this file has NO newline at the end of the file. --}} +class($classes) }} data-flux-link target="_blank">{{ $slot }} \ No newline at end of file diff --git a/resources/views/flux/main.blade.php b/resources/views/flux/main.blade.php new file mode 100644 index 0000000..da14826 --- /dev/null +++ b/resources/views/flux/main.blade.php @@ -0,0 +1,15 @@ +@props([ + 'container' => null, +]) + +@php +$classes = Flux::classes('[grid-area:main]') + ->add('p-10 lg:p-10 mt-8') + ->add('[[data-flux-container]_&]:px-0') // If there is a wrapping container, let IT handle the x padding... + ->add($container ? 'mx-auto w-full [:where(&)]:max-w-7xl' : '') + ; +@endphp + +
class($classes) }} data-flux-main> + {{ $slot }} +
diff --git a/resources/views/flux/menu/checkbox/group.blade.php b/resources/views/flux/menu/checkbox/group.blade.php new file mode 100644 index 0000000..6e31197 --- /dev/null +++ b/resources/views/flux/menu/checkbox/group.blade.php @@ -0,0 +1,4 @@ + + + {{ $slot }} + diff --git a/resources/views/flux/menu/checkbox/index.blade.php b/resources/views/flux/menu/checkbox/index.blade.php new file mode 100644 index 0000000..0a2c0bf --- /dev/null +++ b/resources/views/flux/menu/checkbox/index.blade.php @@ -0,0 +1,60 @@ +@php $iconTrailing = $iconTrailing ??= $attributes->pluck('icon:trailing'); @endphp +@php $iconVariant = $iconVariant ??= $attributes->pluck('icon:variant'); @endphp + +@props([ + 'iconVariant' => 'mini', + 'iconTrailing' => null, + 'variant' => 'default', + 'indent' => false, + 'suffix' => null, + 'label' => null, + 'kbd' => null, +]) + +@php +if ($kbd) $suffix = $kbd; + +$iconClasses = Flux::classes() + // When using the outline icon variant, we need to size it down to match the default icon sizes... + ->add($iconVariant === 'outline' ? 'size-5' : null) + ; + +$iconTrailingClasses = Flux::classes() + ->add('ms-auto') + // When using the outline icon variant, we need to size it down to match the default icon sizes... + ->add($iconVariant === 'outline' ? 'size-5' : null) + ; + +$classes = Flux::classes() + ->add('group/menu-checkbox flex items-center px-2 py-1.5 w-full focus:outline-hidden') + ->add('rounded-md') + ->add('text-start text-sm font-medium') + ->add('[[disabled]_&]:opacity-50 [&[disabled]]:opacity-50') + ->add([ + 'text-zinc-800 data-active:bg-zinc-50 dark:text-white dark:data-active:bg-zinc-600', + '**:data-flux-menu-item-icon:text-zinc-400 dark:**:data-flux-menu-item-icon:text-white/60 [&[data-active]_[data-flux-menu-item-icon]]:text-current', + ]) + ; +@endphp + +class($classes) }} data-flux-menu-item-has-icon data-flux-menu-checkbox> +
+ +
+ + {{ $label ?? $slot }} + + +
+ {{ $suffix }} +
+ + + + + + {{ $iconTrailing }} + +
diff --git a/resources/views/flux/menu/group.blade.php b/resources/views/flux/menu/group.blade.php new file mode 100644 index 0000000..e949124 --- /dev/null +++ b/resources/views/flux/menu/group.blade.php @@ -0,0 +1,22 @@ +@props([ + 'heading' => null, +]) + +@php +$classes = Flux::classes() + ->add('-mx-[.3125rem] px-[.3125rem]') + ->add('[&+&>[data-flux-menu-separator-top]]:hidden [&:first-child>[data-flux-menu-separator-top]]:hidden [&:last-child>[data-flux-menu-separator-bottom]]:hidden') + ; +@endphp + +
class($classes) }} role="group" data-flux-menu-group> + + + + {{ $heading }} + + + {{ $slot }} + + +
diff --git a/resources/views/flux/menu/heading.blade.php b/resources/views/flux/menu/heading.blade.php new file mode 100644 index 0000000..f5cb563 --- /dev/null +++ b/resources/views/flux/menu/heading.blade.php @@ -0,0 +1,14 @@ +@php +$classes = Flux::classes([ + 'p-2 pb-1 w-full', + 'flex items-center', + 'text-start text-xs font-medium', + 'text-zinc-500 font-medium dark:text-zinc-300', +]); +@endphp + +
class($classes) }} data-flux-menu-heading> + + +
{{ $slot }}
+
diff --git a/resources/views/flux/menu/index.blade.php b/resources/views/flux/menu/index.blade.php new file mode 100644 index 0000000..f12f45c --- /dev/null +++ b/resources/views/flux/menu/index.blade.php @@ -0,0 +1,17 @@ +@php +$classes = Flux::classes() + ->add('[:where(&)]:min-w-48 p-[.3125rem]') + ->add('rounded-lg shadow-xs') + ->add('border border-zinc-200 dark:border-zinc-600') + ->add('bg-white dark:bg-zinc-700') + ->add('focus:outline-hidden') + ; +@endphp + +class($classes) }} + popover="manual" + data-flux-menu +> + {{ $slot }} + diff --git a/resources/views/flux/menu/item.blade.php b/resources/views/flux/menu/item.blade.php new file mode 100644 index 0000000..a8f8260 --- /dev/null +++ b/resources/views/flux/menu/item.blade.php @@ -0,0 +1,79 @@ +@php $iconTrailing = $iconTrailing ??= $attributes->pluck('icon:trailing'); @endphp +@php $iconVariant = $iconVariant ??= $attributes->pluck('icon:variant'); @endphp + +@props([ + 'iconTrailing' => null, + 'iconVariant' => 'mini', + 'variant' => 'default', + 'suffix' => null, + 'value' => null, + 'icon' => null, + 'kbd' => null, +]) + +@php +if ($kbd) $suffix = $kbd; + +$iconClasses = Flux::classes() + ->add('me-2') + // When using the outline icon variant, we need to size it down to match the default icon sizes... + ->add($iconVariant === 'outline' ? 'size-5' : null) + ; + +$trailingIconClasses = Flux::classes() + ->add('ms-auto text-zinc-400 [[data-flux-menu-item-icon]:hover_&]:text-current') + // When using the outline icon variant, we need to size it down to match the default icon sizes... + ->add($iconVariant === 'outline' ? 'size-5' : null) + ; + +$classes = Flux::classes() + ->add('flex items-center px-2 py-1.5 w-full focus:outline-hidden') + ->add('rounded-md') + ->add('text-start text-sm font-medium') + ->add('[&[disabled]]:opacity-50') + ->add(match ($variant) { + 'danger' => [ + 'text-zinc-800 data-active:text-red-600 data-active:bg-red-50 dark:text-white dark:data-active:bg-red-400/20 dark:data-active:text-red-400', + '**:data-flux-menu-item-icon:text-zinc-400 dark:**:data-flux-menu-item-icon:text-white/60 [&[data-active]_[data-flux-menu-item-icon]]:text-current', + ], + 'default' => [ + 'text-zinc-800 data-active:bg-zinc-50 dark:text-white dark:data-active:bg-zinc-600', + '**:data-flux-menu-item-icon:text-zinc-400 dark:**:data-flux-menu-item-icon:text-white/60 [&[data-active]_[data-flux-menu-item-icon]]:text-current', + ] + }) + ; + +$suffixClasses = Flux::classes() + ->add('ms-auto text-xs text-zinc-400') + ; +@endphp + + + + + + {{ $icon }} + + + + + {{ $slot }} + + + +
+ {{ $suffix }} +
+ + {{ $suffix }} + + + + + + + {{ $iconTrailing }} + + + {{ $submenu ?? '' }} +
diff --git a/resources/views/flux/menu/radio/group.blade.php b/resources/views/flux/menu/radio/group.blade.php new file mode 100644 index 0000000..24619dd --- /dev/null +++ b/resources/views/flux/menu/radio/group.blade.php @@ -0,0 +1,4 @@ + + + {{ $slot }} + diff --git a/resources/views/flux/menu/radio/index.blade.php b/resources/views/flux/menu/radio/index.blade.php new file mode 100644 index 0000000..e9a74bb --- /dev/null +++ b/resources/views/flux/menu/radio/index.blade.php @@ -0,0 +1,60 @@ +@php $iconTrailing = $iconTrailing ??= $attributes->pluck('icon:trailing'); @endphp +@php $iconVariant = $iconVariant ??= $attributes->pluck('icon:variant'); @endphp + +@props([ + 'iconVariant' => 'mini', + 'iconTrailing' => null, + 'variant' => 'default', + 'indent' => false, + 'suffix' => null, + 'label' => null, + 'kbd' => null, +]) + +@php +if ($kbd) $suffix = $kbd; + +$iconClasses = Flux::classes() + // When using the outline icon variant, we need to size it down to match the default icon sizes... + ->add($iconVariant === 'outline' ? 'size-5' : null) + ; + +$iconTrailingClasses = Flux::classes() + ->add('ms-auto') + // When using the outline icon variant, we need to size it down to match the default icon sizes... + ->add($iconVariant === 'outline' ? 'size-5' : null) + ; + +$classes = Flux::classes() + ->add('group/menu-radio flex items-center px-2 py-1.5 w-full focus:outline-hidden') + ->add('rounded-md') + ->add('text-start text-sm font-medium') + ->add('[[disabled]_&]:opacity-50 [&[disabled]]:opacity-50') + ->add([ + 'text-zinc-800 data-active:bg-zinc-50 dark:text-white dark:data-active:bg-zinc-600', + '**:data-flux-menu-item-icon:text-zinc-400 dark:**:data-flux-menu-item-icon:text-white/60 [&[data-active]_[data-flux-menu-item-icon]]:text-current', + ]) + ; +@endphp + +class($classes) }} data-flux-menu-item-has-icon data-flux-menu-radio> +
+ +
+ + {{ $label ?? $slot }} + + +
+ {{ $suffix }} +
+ + + + + + {{ $iconTrailing }} + +
diff --git a/resources/views/flux/menu/separator.blade.php b/resources/views/flux/menu/separator.blade.php new file mode 100644 index 0000000..9f44388 --- /dev/null +++ b/resources/views/flux/menu/separator.blade.php @@ -0,0 +1,4 @@ + +
+ +
diff --git a/resources/views/flux/menu/submenu.blade.php b/resources/views/flux/menu/submenu.blade.php new file mode 100644 index 0000000..57b7e34 --- /dev/null +++ b/resources/views/flux/menu/submenu.blade.php @@ -0,0 +1,38 @@ +@php $iconTrailing = $iconTrailing ??= $attributes->pluck('icon:trailing'); @endphp +@php $iconVariant = $iconVariant ??= $attributes->pluck('icon:variant'); @endphp + +@props([ + 'iconVariant' => 'mini', + 'iconTrailing' => null, + 'heading' => '', + 'icon' => null, + 'keepOpen' => false, +]) + +@php +$iconClasses = Flux::classes() + ->add('ms-auto text-zinc-400 [[data-flux-menu-item]:hover_&]:text-current') + // When using the outline icon variant, we need to size it down to match the default icon sizes... + ->add($iconVariant === 'outline' ? 'size-5' : ''); +@endphp + + + + {{ $heading }} + + + + + + {{ $iconTrailing }} + + + + + + + + + {{ $slot }} + + diff --git a/resources/views/flux/modal/close.blade.php b/resources/views/flux/modal/close.blade.php new file mode 100644 index 0000000..91934b9 --- /dev/null +++ b/resources/views/flux/modal/close.blade.php @@ -0,0 +1,4 @@ + + + {{ $slot }} + diff --git a/resources/views/flux/modal/index.blade.php b/resources/views/flux/modal/index.blade.php new file mode 100644 index 0000000..26ce1da --- /dev/null +++ b/resources/views/flux/modal/index.blade.php @@ -0,0 +1,94 @@ +@props([ + 'dismissible' => null, + 'position' => null, + 'closable' => null, + 'trigger' => null, + 'variant' => null, + 'name' => null, +]) + +@php +$closable ??= $variant === 'bare' ? false : true; + +$classes = Flux::classes() + ->add(match ($variant) { + default => 'p-6 [:where(&)]:max-w-xl shadow-lg rounded-xl', + 'flyout' => match($position) { + 'bottom' => 'fixed m-0 p-8 min-w-[100vw] overflow-y-auto mt-auto [--fx-flyout-translate:translateY(50px)] border-t', + 'left' => 'fixed m-0 p-8 max-h-dvh min-h-dvh md:[:where(&)]:min-w-[25rem] overflow-y-auto mr-auto [--fx-flyout-translate:translateX(-50px)] border-e rtl:mr-0 rtl:ml-auto rtl:[--fx-flyout-translate:translateX(50px)]', + default => 'fixed m-0 p-8 max-h-dvh min-h-dvh md:[:where(&)]:min-w-[25rem] overflow-y-auto ml-auto [--fx-flyout-translate:translateX(50px)] border-s rtl:ml-0 rtl:mr-auto rtl:[--fx-flyout-translate:translateX(-50px)]', + }, + 'bare' => '', + }) + ->add(match ($variant) { + default => 'bg-white dark:bg-zinc-800 border border-transparent dark:border-zinc-700', + 'flyout' => 'bg-white dark:bg-zinc-800 border-transparent dark:border-zinc-700', + 'bare' => 'bg-transparent', + }); + +// Support adding the .self modifier to the wire:model directive... +if (($wireModel = $attributes->wire('model')) && $wireModel->directive && ! $wireModel->hasModifier('self')) { + unset($attributes[$wireModel->directive]); + + $wireModel->directive .= '.self'; + + $attributes = $attributes->merge([$wireModel->directive => $wireModel->value]); +} + +// Support syntax... +if ($attributes['@close'] ?? null) { + $attributes['wire:close'] = $attributes['@close']; + + unset($attributes['@close']); +} + +// Support syntax... +if ($attributes['@cancel'] ?? null) { + $attributes['wire:cancel'] = $attributes['@cancel']; + + unset($attributes['@cancel']); +} + +if ($dismissible === false) { + $attributes = $attributes->merge(['disable-click-outside' => '']); +} + +[ $styleAttributes, $attributes ] = Flux::splitAttributes($attributes, ['autofocus', 'class', 'style', 'wire:close', 'x-on:close', 'wire:cancel', 'x-on:cancel']); +@endphp + + + + {{ $trigger }} + + + class($classes) }} + @if ($name) data-modal="{{ $name }}" @endif + @if ($variant === 'flyout') data-flux-flyout @endif + x-data + @isset($__livewire) + x-on:modal-show.document=" + if ($event.detail.name === @js($name) && ($event.detail.scope === @js($__livewire->getId()))) $el.showModal(); + if ($event.detail.name === @js($name) && (! $event.detail.scope)) $el.showModal(); + " + x-on:modal-close.document=" + if ($event.detail.name === @js($name) && ($event.detail.scope === @js($__livewire->getId()))) $el.close(); + if (! $event.detail.name || ($event.detail.name === @js($name) && (! $event.detail.scope))) $el.close(); + " + @else + x-on:modal-show.document="if ($event.detail.name === @js($name) && (! $event.detail.scope)) $el.showModal()" + x-on:modal-close.document="if (! $event.detail.name || ($event.detail.name === @js($name) && (! $event.detail.scope))) $el.close()" + @endif + > + {{ $slot }} + + +
+ + + +
+ +
+
diff --git a/resources/views/flux/modal/trigger.blade.php b/resources/views/flux/modal/trigger.blade.php new file mode 100644 index 0000000..6374c71 --- /dev/null +++ b/resources/views/flux/modal/trigger.blade.php @@ -0,0 +1,16 @@ +@props([ + 'shortcut' => null, + 'name' => null, +]) + +
class('contents') }} + x-data + x-on:click="$el.querySelector('button[disabled]') || $dispatch('modal-show', { name: '{{ $name }}' })" + @if ($shortcut) + x-on:keydown.{{ $shortcut }}.document="$event.preventDefault(); $dispatch('modal-show', { name: '{{ $name }}' })" + @endif + data-flux-modal-trigger +> + {{ $slot }} +
diff --git a/resources/views/flux/navbar/badge.blade.php b/resources/views/flux/navbar/badge.blade.php new file mode 100644 index 0000000..b9ca2b3 --- /dev/null +++ b/resources/views/flux/navbar/badge.blade.php @@ -0,0 +1,30 @@ +@props([ + 'color' => null, +]) + +@php +$class = Flux::classes() + ->add('text-xs font-medium rounded-sm px-1 py-0.5') + ->add(match ($color) { + default => 'text-zinc-700 dark:text-zinc-200 bg-zinc-400/15 dark:bg-white/10', + 'red' => 'text-red-700 dark:text-red-200 bg-red-400/20 dark:bg-red-400/40', + 'orange' => 'text-orange-700 dark:text-orange-200 bg-orange-400/20 dark:bg-orange-400/40', + 'amber' => 'text-amber-700 dark:text-amber-200 bg-amber-400/25 dark:bg-amber-400/40', + 'yellow' => 'text-yellow-800 dark:text-yellow-200 bg-yellow-400/25 dark:bg-yellow-400/40', + 'lime' => 'text-lime-800 dark:text-lime-200 bg-lime-400/25 dark:bg-lime-400/40', + 'green' => 'text-green-800 dark:text-green-200 bg-green-400/20 dark:bg-green-400/40', + 'emerald' => 'text-emerald-800 dark:text-emerald-200 bg-emerald-400/20 dark:bg-emerald-400/40', + 'teal' => 'text-teal-800 dark:text-teal-200 bg-teal-400/20 dark:bg-teal-400/40', + 'cyan' => 'text-cyan-800 dark:text-cyan-200 bg-cyan-400/20 dark:bg-cyan-400/40', + 'sky' => 'text-sky-800 dark:text-sky-200 bg-sky-400/20 dark:bg-sky-400/40', + 'blue' => 'text-blue-800 dark:text-blue-200 bg-blue-400/20 dark:bg-blue-400/40', + 'indigo' => 'text-indigo-700 dark:text-indigo-200 bg-indigo-400/20 dark:bg-indigo-400/40', + 'violet' => 'text-violet-700 dark:text-violet-200 bg-violet-400/20 dark:bg-violet-400/40', + 'purple' => 'text-purple-700 dark:text-purple-200 bg-purple-400/20 dark:bg-purple-400/40', + 'fuchsia' => 'text-fuchsia-700 dark:text-fuchsia-200 bg-fuchsia-400/20 dark:bg-fuchsia-400/40', + 'pink' => 'text-pink-700 dark:text-pink-200 bg-pink-400/20 dark:bg-pink-400/40', + 'rose' => 'text-rose-700 dark:text-rose-200 bg-rose-400/20 dark:bg-rose-400/40', + }); +@endphp + +class($class) }}>{{ $slot }} diff --git a/resources/views/flux/navbar/index.blade.php b/resources/views/flux/navbar/index.blade.php new file mode 100644 index 0000000..3d5df47 --- /dev/null +++ b/resources/views/flux/navbar/index.blade.php @@ -0,0 +1,15 @@ +@props([ + 'scrollable' => false, + 'variant' => null, +]) + +@php +$classes = Flux::classes() + ->add('flex items-center gap-1 py-3') + ->add($scrollable ? ['overflow-x-auto overflow-y-hidden'] : []) + ; +@endphp + + diff --git a/resources/views/flux/navbar/item.blade.php b/resources/views/flux/navbar/item.blade.php new file mode 100644 index 0000000..af212de --- /dev/null +++ b/resources/views/flux/navbar/item.blade.php @@ -0,0 +1,81 @@ +@php $iconTrailing = $iconTrailing ??= $attributes->pluck('icon:trailing'); @endphp +@php $iconVariant = $iconVariant ??= $attributes->pluck('icon:variant'); @endphp + +@aware([ 'variant' ]) + +@props([ + 'iconVariant' => 'outline', + 'iconTrailing' => null, + 'badgeColor' => null, + 'variant' => null, + 'iconDot' => null, + 'accent' => true, + 'square' => null, + 'badge' => null, + 'icon' => null, +]) + +@php +// Button should be a square if it has no text contents... +$square ??= $slot->isEmpty(); + +// Size-up icons in square/icon-only buttons... +$iconClasses = Flux::classes($square ? 'size-6' : 'size-5'); + +$classes = Flux::classes() + ->add('px-3 h-8 flex items-center rounded-lg') + ->add('relative') // This is here for the "active" bar at the bottom to be positioned correctly... + ->add($square ? '' : 'px-2.5!') + ->add('text-zinc-500 dark:text-white/80 ') + // Styles for when this link is the "current" one... + ->add('data-current:after:absolute data-current:after:-bottom-3 data-current:after:inset-x-0 data-current:after:h-[2px]') + ->add([ + '[--hover-fill:color-mix(in_oklab,_var(--color-accent-content),_transparent_90%)]', + + ]) + ->add(match ($accent) { + true => [ + 'hover:text-zinc-800 dark:hover:text-white', + 'data-current:text-(--color-accent-content) hover:data-current:text-(--color-accent-content) hover:bg-zinc-800/5 dark:hover:bg-white/10 hover:data-current:bg-(--hover-fill)', + 'data-current:after:bg-(--color-accent-content)', + ], + false => [ + 'hover:text-zinc-800 dark:hover:text-white', + 'data-current:text-zinc-800 dark:data-current:text-zinc-100 hover:bg-zinc-100 dark:hover:bg-white/10', + 'data-current:after:bg-zinc-800 dark:data-current:after:bg-white', + ], + }) + ; +@endphp + + + +
+ + + + {{ $icon }} + + + +
+
+
+ +
+ + + isNotEmpty()): ?> +
{{ $slot }}
+ + + + + + {{ $iconTrailing }} + + + + {{ $badge }} + +
diff --git a/resources/views/flux/navlist/badge.blade.php b/resources/views/flux/navlist/badge.blade.php new file mode 100644 index 0000000..b9ca2b3 --- /dev/null +++ b/resources/views/flux/navlist/badge.blade.php @@ -0,0 +1,30 @@ +@props([ + 'color' => null, +]) + +@php +$class = Flux::classes() + ->add('text-xs font-medium rounded-sm px-1 py-0.5') + ->add(match ($color) { + default => 'text-zinc-700 dark:text-zinc-200 bg-zinc-400/15 dark:bg-white/10', + 'red' => 'text-red-700 dark:text-red-200 bg-red-400/20 dark:bg-red-400/40', + 'orange' => 'text-orange-700 dark:text-orange-200 bg-orange-400/20 dark:bg-orange-400/40', + 'amber' => 'text-amber-700 dark:text-amber-200 bg-amber-400/25 dark:bg-amber-400/40', + 'yellow' => 'text-yellow-800 dark:text-yellow-200 bg-yellow-400/25 dark:bg-yellow-400/40', + 'lime' => 'text-lime-800 dark:text-lime-200 bg-lime-400/25 dark:bg-lime-400/40', + 'green' => 'text-green-800 dark:text-green-200 bg-green-400/20 dark:bg-green-400/40', + 'emerald' => 'text-emerald-800 dark:text-emerald-200 bg-emerald-400/20 dark:bg-emerald-400/40', + 'teal' => 'text-teal-800 dark:text-teal-200 bg-teal-400/20 dark:bg-teal-400/40', + 'cyan' => 'text-cyan-800 dark:text-cyan-200 bg-cyan-400/20 dark:bg-cyan-400/40', + 'sky' => 'text-sky-800 dark:text-sky-200 bg-sky-400/20 dark:bg-sky-400/40', + 'blue' => 'text-blue-800 dark:text-blue-200 bg-blue-400/20 dark:bg-blue-400/40', + 'indigo' => 'text-indigo-700 dark:text-indigo-200 bg-indigo-400/20 dark:bg-indigo-400/40', + 'violet' => 'text-violet-700 dark:text-violet-200 bg-violet-400/20 dark:bg-violet-400/40', + 'purple' => 'text-purple-700 dark:text-purple-200 bg-purple-400/20 dark:bg-purple-400/40', + 'fuchsia' => 'text-fuchsia-700 dark:text-fuchsia-200 bg-fuchsia-400/20 dark:bg-fuchsia-400/40', + 'pink' => 'text-pink-700 dark:text-pink-200 bg-pink-400/20 dark:bg-pink-400/40', + 'rose' => 'text-rose-700 dark:text-rose-200 bg-rose-400/20 dark:bg-rose-400/40', + }); +@endphp + +class($class) }}>{{ $slot }} diff --git a/resources/views/flux/navlist/group.blade.php b/resources/views/flux/navlist/group.blade.php new file mode 100644 index 0000000..038219d --- /dev/null +++ b/resources/views/flux/navlist/group.blade.php @@ -0,0 +1,38 @@ +@props([ + 'expandable' => false, + 'expanded' => true, + 'heading' => null, +]) + + + class('group/disclosure') }} @if ($expanded === true) open @endif data-flux-navlist-group> + + + + + +
class('block space-y-[2px]') }}> +
+
{{ $heading }}
+
+ +
+ {{ $slot }} +
+
+ +
class('block space-y-[2px]') }}> + {{ $slot }} +
+ diff --git a/resources/views/flux/navlist/index.blade.php b/resources/views/flux/navlist/index.blade.php new file mode 100644 index 0000000..8aa99c8 --- /dev/null +++ b/resources/views/flux/navlist/index.blade.php @@ -0,0 +1,14 @@ +@props([ + 'variant' => null, +]) + +@php +$classes = Flux::classes() + ->add('flex flex-col') + ->add('overflow-visible min-h-auto') + ; +@endphp + + diff --git a/resources/views/flux/navlist/item.blade.php b/resources/views/flux/navlist/item.blade.php new file mode 100644 index 0000000..cedc66b --- /dev/null +++ b/resources/views/flux/navlist/item.blade.php @@ -0,0 +1,89 @@ +@php $iconTrailing = $iconTrailing ??= $attributes->pluck('icon:trailing'); @endphp +@php $iconVariant = $iconVariant ??= $attributes->pluck('icon:variant'); @endphp + +@aware([ 'variant' ]) + +@props([ + 'iconVariant' => 'outline', + 'iconTrailing' => null, + 'badgeColor' => null, + 'variant' => null, + 'iconDot' => null, + 'accent' => true, + 'badge' => null, + 'icon' => null, +]) + +@php +// Button should be a square if it has no text contents... +$square ??= $slot->isEmpty(); + +// Size-up icons in square/icon-only buttons... +$iconClasses = Flux::classes($square ? 'size-5!' : 'size-4!'); + +$classes = Flux::classes() + ->add('h-10 lg:h-8 relative flex items-center gap-3 rounded-lg') + ->add($square ? 'px-2.5!' : '') + ->add('py-0 text-start w-full px-3 my-px') + ->add('text-zinc-500 dark:text-white/80') + ->add(match ($variant) { + 'outline' => match ($accent) { + true => [ + 'data-current:text-(--color-accent-content) hover:data-current:text-(--color-accent-content)', + 'data-current:bg-white dark:data-current:bg-white/[7%] data-current:border data-current:border-zinc-200 dark:data-current:border-transparent', + 'hover:text-zinc-800 dark:hover:text-white dark:hover:bg-white/[7%] hover:bg-zinc-800/5 ', + 'border border-transparent', + ], + false => [ + 'data-current:text-zinc-800 dark:data-current:text-zinc-100 data-current:border-zinc-200', + 'data-current:bg-white dark:data-current:bg-white/10 data-current:border data-current:border-zinc-200 dark:data-current:border-white/10 data-current:shadow-xs', + 'hover:text-zinc-800 dark:hover:text-white', + ], + }, + default => match ($accent) { + true => [ + 'data-current:text-(--color-accent-content) hover:data-current:text-(--color-accent-content)', + 'data-current:bg-zinc-800/[4%] dark:data-current:bg-white/[7%]', + 'hover:text-zinc-800 dark:hover:text-white hover:bg-zinc-800/[4%] dark:hover:bg-white/[7%]', + ], + false => [ + 'data-current:text-zinc-800 dark:data-current:text-zinc-100', + 'data-current:bg-zinc-800/[4%] dark:data-current:bg-white/10', + 'hover:text-zinc-800 dark:hover:text-white hover:bg-zinc-800/[4%] dark:hover:bg-white/10', + ], + }, + }) + ; +@endphp + + + +
+ + + + {{ $icon }} + + + +
+
+
+ +
+ + + isNotEmpty()): ?> +
{{ $slot }}
+ + + + + + {{ $iconTrailing }} + + + + {{ $badge }} + +
diff --git a/resources/views/flux/navmenu/index.blade.php b/resources/views/flux/navmenu/index.blade.php new file mode 100644 index 0000000..bfcc400 --- /dev/null +++ b/resources/views/flux/navmenu/index.blade.php @@ -0,0 +1,12 @@ +@php +$classes = Flux::classes() + ->add('[:where(&)]:min-w-48 p-[.3125rem]') + ->add('rounded-lg shadow-xs') + ->add('border border-zinc-200 dark:border-zinc-600') + ->add('bg-white dark:bg-zinc-700') + ; +@endphp + + diff --git a/resources/views/flux/navmenu/item.blade.php b/resources/views/flux/navmenu/item.blade.php new file mode 100644 index 0000000..c96fdd3 --- /dev/null +++ b/resources/views/flux/navmenu/item.blade.php @@ -0,0 +1,77 @@ +@php $iconTrailing = $iconTrailing ??= $attributes->pluck('icon:trailing'); @endphp +@php $iconVariant = $iconVariant ??= $attributes->pluck('icon:variant'); @endphp + +@props([ + 'iconVariant' => 'mini', + 'iconTrailing' => null, + 'variant' => 'default', + 'disabled' => false, + 'indent' => false, + 'suffix' => null, + 'value' => null, + 'icon' => null, + 'kbd' => null, +]) + +@php +if ($kbd) $suffix = $kbd; + +$iconClasses = Flux::classes() + ->add('me-2') + // When using the outline icon variant, we need to size it down to match the default icon sizes... + ->add($iconVariant === 'outline' ? 'size-5' : null) + ; + +$trailingIconClasses = Flux::classes() + ->add('ms-auto') + // When using the outline icon variant, we need to size it down to match the default icon sizes... + ->add($iconVariant === 'outline' ? 'size-5' : null) + ; + +$classes = Flux::classes() + ->add('group flex items-center px-2 py-2 lg:py-1.5 w-full') + ->add('rounded-md') + ->add('text-start text-sm font-medium') + ->add(match ($variant) { + 'danger' => [ + 'text-zinc-800 hover:text-red-600 hover:bg-red-50 dark:text-white dark:hover:bg-red-400/20 dark:hover:text-red-400', + '**:data-navmenu-icon:text-zinc-400 dark:**:data-navmenu-icon:text-white/60 [&:hover_[data-navmenu-icon]]:text-current', + ], + 'default' => [ + 'text-zinc-800 hover:bg-zinc-50 dark:text-white dark:hover:bg-zinc-600', + '**:data-navmenu-icon:text-zinc-400 dark:**:data-navmenu-icon:text-white/60 [&:hover_[data-navmenu-icon]]:text-current', + ] + }) + ->add($disabled ? 'text-zinc-400' : '') + ; +@endphp + + + +
+ + + + + + {{ $icon }} + + + {{ $slot }} + + + +
+ {{ $suffix }} +
+ + {{ $suffix }} + + + + + + + {{ $iconTrailing }} + +
diff --git a/resources/views/flux/navmenu/separator.blade.php b/resources/views/flux/navmenu/separator.blade.php new file mode 100644 index 0000000..b8c0e82 --- /dev/null +++ b/resources/views/flux/navmenu/separator.blade.php @@ -0,0 +1,4 @@ + +
+ +
diff --git a/resources/views/flux/profile.blade.php b/resources/views/flux/profile.blade.php new file mode 100644 index 0000000..85ea690 --- /dev/null +++ b/resources/views/flux/profile.blade.php @@ -0,0 +1,58 @@ +@php $iconVariant = $iconVariant ??= $attributes->pluck('icon:variant'); @endphp +@php $iconTrailing = $iconTrailing ??= $attributes->pluck('icon:trailing'); @endphp + +@props([ + 'iconVariant' => 'micro', + 'iconTrailing' => null, + 'initials' => null, + 'chevron' => true, + 'circle' => null, + 'avatar' => null, + 'name' => null, +]) + +@php +$iconTrailing = $iconTrailing ?? ($chevron ? 'chevron-down' : null); + +// If no initials are provided, we'll try to generate them from the name by taking the first letter of the first and last name... +$initials ??= collect(explode(' ', $name ?? '')) + ->map(fn($part) => Str::substr($part, 0, 1)) + ->filter() + ->only([0, count(explode(' ', $name ?? '')) - 1]) + ->implode(''); + +// When using the outline icon variant, we need to size it down to match the default icon sizes... +$iconClasses = Flux::classes('text-zinc-400 dark:text-white/80 group-hover:text-zinc-800 dark:group-hover:text-white') + ->add($iconVariant === 'outline' ? 'size-4' : ''); + +$classes = Flux::classes() + ->add('group flex items-center') + ->add('rounded-lg has-data-[circle=true]:rounded-full') + ->add('[ui-dropdown>&]:w-full') // Without this, the "name" won't get truncated in a sidebar dropdown... + ->add('p-1 hover:bg-zinc-800/5 dark:hover:bg-white/10') + ; +@endphp + + diff --git a/resources/views/flux/radio/group/index.blade.php b/resources/views/flux/radio/group/index.blade.php new file mode 100644 index 0000000..97f65e3 --- /dev/null +++ b/resources/views/flux/radio/group/index.blade.php @@ -0,0 +1,5 @@ +@props([ + 'variant' => 'default', +]) + +{{ $slot }} diff --git a/resources/views/flux/radio/group/variants/default.blade.php b/resources/views/flux/radio/group/variants/default.blade.php new file mode 100644 index 0000000..7c0713b --- /dev/null +++ b/resources/views/flux/radio/group/variants/default.blade.php @@ -0,0 +1,26 @@ +@props([ + 'name' => null, + 'variant' => null, +]) + +@php +// We only want to show the name attribute it has been set manually +// but not if it has been set from the `wire:model` attribute... +$showName = isset($name); +if (! isset($name)) { + $name = $attributes->whereStartsWith('wire:model')->first(); +} + +$classes = Flux::classes() + // Adjust spacing between fields... + ->add('*:data-flux-field:mb-3') + ->add('[&>[data-flux-field]:has(>[data-flux-description])]:mb-4') + ->add('[&>[data-flux-field]:last-child]:mb-0!') + ; +@endphp + + + class($classes) }} @if($showName) name="{{ $name }}" @endif data-flux-radio-group> + {{ $slot }} + + diff --git a/resources/views/flux/radio/group/variants/segmented.blade.php b/resources/views/flux/radio/group/variants/segmented.blade.php new file mode 100644 index 0000000..18e8fda --- /dev/null +++ b/resources/views/flux/radio/group/variants/segmented.blade.php @@ -0,0 +1,28 @@ +@props([ + 'name' => null, + 'variant' => null, + 'size' => null, +]) + +@php +// We only want to show the name attribute on the checkbox if it has been set +// manually, but not if it has been set from the wire:model attribute... +$showName = isset($name); + +if (! isset($name)) { + $name = $attributes->whereStartsWith('wire:model')->first(); +} + +$classes = Flux::classes() + ->add('block flex p-1') + ->add('rounded-lg bg-zinc-800/5 dark:bg-white/10') + ->add($size === 'sm' ? 'h-8 py-[3px] px-[3px]' : 'h-10 p-1') + ->add($size === 'sm' ? '-my-px h-[calc(2rem+2px)]' : '') + ; +@endphp + + + class($classes) }} @if($showName) name="{{ $name }}" @endif data-flux-radio-group-segmented> + {{ $slot }} + + diff --git a/resources/views/flux/radio/index.blade.php b/resources/views/flux/radio/index.blade.php new file mode 100644 index 0000000..ab425fc --- /dev/null +++ b/resources/views/flux/radio/index.blade.php @@ -0,0 +1,14 @@ +@aware([ 'variant' ]) + +@props([ + 'variant' => 'default', +]) + +@php +// This prevents variants picked up by `@aware()` from other wrapping components like flux::modal from being used here... +$variant = $variant !== 'default' && Flux::componentExists('radio.variants.' . $variant) + ? $variant + : 'default'; +@endphp + +{{ $slot }} diff --git a/resources/views/flux/radio/indicator.blade.php b/resources/views/flux/radio/indicator.blade.php new file mode 100644 index 0000000..a347522 --- /dev/null +++ b/resources/views/flux/radio/indicator.blade.php @@ -0,0 +1,27 @@ + +@php +$classes = Flux::classes() + ->add('shrink-0 size-[1.125rem] rounded-full') + ->add('text-sm text-zinc-700 dark:text-zinc-800') + ->add('shadow-xs [ui-radio[disabled]_&]:opacity-75 [ui-radio[data-checked][disabled]_&]:opacity-50 [ui-radio[disabled]_&]:shadow-none [ui-radio[data-checked]_&]:shadow-none') + ->add('flex justify-center items-center [ui-radio[data-checked]_&>div]:block') + ->add([ + 'border', + 'border-zinc-300 dark:border-white/10', + '[ui-radio[disabled]_&]:border-zinc-200 dark:[ui-radio[disabled]_&]:border-white/5', + '[ui-radio[data-checked]_&]:border-transparent data-indeterminate:border-transparent', + '[ui-radio[data-checked]_&]:[ui-radio[disabled]_&]:border-transparent data-indeterminate:border-transparent', + '[print-color-adjust:exact]', + ]) + ->add([ + 'bg-white dark:bg-white/10', + '[ui-radio[data-checked]_&]:bg-[var(--color-accent)]', + 'hover:[ui-radio[data-checked]_&]:bg-(--color-accent)', + 'focus:[ui-radio[data-checked]_&]:bg-(--color-accent)', + ]) + ; +@endphp + +
class($classes) }} data-flux-radio-indicator> + +
diff --git a/resources/views/flux/radio/variants/default.blade.php b/resources/views/flux/radio/variants/default.blade.php new file mode 100644 index 0000000..f2fe592 --- /dev/null +++ b/resources/views/flux/radio/variants/default.blade.php @@ -0,0 +1,12 @@ +@props([ + 'name' => $attributes->whereStartsWith('wire:model')->first(), +]) + + + {{-- We have to put tabindex="-1" here because otherwise, Livewire requests will wipe out tabindex state, --}} + {{-- even with durable attributes for some reason... --}} + {{-- We are redundantly setting the size of this container to 1.125rem so that the focus outline isn't oblong. --}} + class('flex size-[1.125rem] rounded-full mt-px outline-offset-2') }} data-flux-control data-flux-radio tabindex="-1"> + + + diff --git a/resources/views/flux/radio/variants/segmented.blade.php b/resources/views/flux/radio/variants/segmented.blade.php new file mode 100644 index 0000000..c7b100a --- /dev/null +++ b/resources/views/flux/radio/variants/segmented.blade.php @@ -0,0 +1,52 @@ +@php $iconTrailing = $iconTrailing ??= $attributes->pluck('icon:trailing'); @endphp +@php $iconVariant = $iconVariant ??= $attributes->pluck('icon:variant'); @endphp + +@aware([ 'size' ]) + +@props([ + 'iconTrailing' => null, + 'iconVariant' => null, + 'label' => null, + 'icon' => null, + 'size' => null, +]) + +@php +$classes = Flux::classes() + ->add('flex whitespace-nowrap flex-1 justify-center items-center gap-2') + ->add('rounded-md data-checked:shadow-xs') + ->add('text-sm font-medium text-zinc-600 hover:text-zinc-800 dark:hover:text-white dark:text-white/70 data-checked:text-zinc-800 dark:data-checked:text-white') + ->add('data-checked:bg-white dark:data-checked:bg-white/20') + ->add('[&[disabled]]:opacity-50 dark:[&[disabled]]:opacity-75 [&[disabled]]:cursor-default [&[disabled]]:pointer-events-none') + ->add(match ($size) { + 'sm' => 'px-3 text-sm', + default => 'px-4', + }) + ; + +$iconVariant ??= 'mini'; + +$iconClasses = Flux::classes('text-zinc-500 dark:text-zinc-400 [ui-radio[data-checked]_&]:text-zinc-800 dark:[ui-radio[data-checked]_&]:text-white') + // When using the outline icon variant, we need to size it down to match the default icon sizes... + ->add($iconVariant === 'outline' ? 'size-5' : '') + ; + +@endphp + +{{-- We have to put tabindex="-1" here because otherwise, Livewire requests will wipe out tabindex state, --}} +{{-- even with durable attributes for some reason... --}} +class($classes) }} data-flux-control data-flux-radio-segmented tabindex="-1"> + + + + {{ $icon }} + + + {{ $label ?? $slot }} + + + + + {{ $iconTrailing }} + + diff --git a/resources/views/flux/select/index.blade.php b/resources/views/flux/select/index.blade.php new file mode 100644 index 0000000..dcb91b4 --- /dev/null +++ b/resources/views/flux/select/index.blade.php @@ -0,0 +1,7 @@ +@props([ + 'variant' => 'default', +]) + + + {{ $slot }} + diff --git a/resources/views/flux/select/option/index.blade.php b/resources/views/flux/select/option/index.blade.php new file mode 100644 index 0000000..59150aa --- /dev/null +++ b/resources/views/flux/select/option/index.blade.php @@ -0,0 +1,14 @@ +@aware([ 'variant' ]) + +@props([ + 'variant' => 'default', +]) + +@php +// This prevents variants picked up by `@aware()` from other wrapping components like flux::modal from being used here... +$variant = $variant !== 'default' && Flux::componentExists('select.variants.' . $variant) + ? 'custom' + : 'default'; +@endphp + +{{ $slot }} diff --git a/resources/views/flux/select/option/variants/default.blade.php b/resources/views/flux/select/option/variants/default.blade.php new file mode 100644 index 0000000..c0cd845 --- /dev/null +++ b/resources/views/flux/select/option/variants/default.blade.php @@ -0,0 +1,9 @@ +@props([ + 'value' => null, +]) + + \ No newline at end of file diff --git a/resources/views/flux/select/variants/default.blade.php b/resources/views/flux/select/variants/default.blade.php new file mode 100644 index 0000000..4b36502 --- /dev/null +++ b/resources/views/flux/select/variants/default.blade.php @@ -0,0 +1,48 @@ +@props([ + 'name' => $attributes->whereStartsWith('wire:model')->first(), + 'placeholder' => null, + 'invalid' => null, + 'size' => null, +]) + +@php +$invalid ??= ($name && $errors->has($name)); + +$classes = Flux::classes() + ->add('appearance-none') // Strip the browser's default class($classes) }} + @if ($invalid) aria-invalid="true" data-invalid @endif + @isset ($name) name="{{ $name }}" @endisset + @if (is_numeric($size)) size="{{ $size }}" @endif + data-flux-control + data-flux-select-native + data-flux-group-target +> + + + + + {{ $slot }} + diff --git a/resources/views/flux/separator.blade.php b/resources/views/flux/separator.blade.php new file mode 100644 index 0000000..520f6dd --- /dev/null +++ b/resources/views/flux/separator.blade.php @@ -0,0 +1,34 @@ +@props([ + 'orientation' => null, + 'vertical' => false, + 'variant' => null, + 'faint' => false, + 'text' => null, +]) + +@php +$orientation ??= $vertical ? 'vertical' : 'horizontal'; + +$classes = Flux::classes('border-0 [print-color-adjust:exact]') + ->add(match ($variant) { + 'subtle' => 'bg-zinc-800/5 dark:bg-white/10', + default => 'bg-zinc-800/15 dark:bg-white/20', + }) + ->add(match ($orientation) { + 'horizontal' => 'h-px w-full', + 'vertical' => 'self-stretch self-center w-px', + }) + ; +@endphp + + +
+
class([$classes, 'grow']) }}>
+ + {{ $text }} + +
class([$classes, 'grow']) }}>
+
+ +
class($classes, 'shrink-0') }} data-flux-separator>
+ diff --git a/resources/views/flux/sidebar/backdrop.blade.php b/resources/views/flux/sidebar/backdrop.blade.php new file mode 100644 index 0000000..f8dd14c --- /dev/null +++ b/resources/views/flux/sidebar/backdrop.blade.php @@ -0,0 +1,5 @@ + + diff --git a/resources/views/flux/sidebar/index.blade.php b/resources/views/flux/sidebar/index.blade.php new file mode 100644 index 0000000..2066236 --- /dev/null +++ b/resources/views/flux/sidebar/index.blade.php @@ -0,0 +1,37 @@ +@props([ + 'stashable' => null, + 'sticky' => null, +]) + +@php +$classes = Flux::classes('[grid-area:sidebar]') + ->add('z-1 flex flex-col gap-4 [:where(&)]:w-64 p-4') + ; + +if ($sticky) { + $attributes = $attributes->merge([ + 'x-bind:style' => '{ position: \'sticky\', top: $el.offsetTop + \'px\', \'max-height\': \'calc(100dvh - \' + $el.offsetTop + \'px)\' }', + 'class' => 'max-h-dvh overflow-y-auto overscroll-contain', + ]); +} + +if ($stashable) { + $attributes = $attributes->merge([ + 'x-bind:data-stashed' => '! screenLg', + 'x-resize.document' => 'screenLg = window.innerWidth >= 1024', + 'x-init' => '$el.classList.add(\'-translate-x-full\', \'rtl:translate-x-full\'); $el.removeAttribute(\'data-mobile-cloak\'); $el.classList.add(\'transition-transform\')', + ])->class([ + 'max-lg:data-mobile-cloak:hidden', + '[[data-show-stashed-sidebar]_&]:translate-x-0! lg:translate-x-0!', + 'z-20! data-stashed:start-0! data-stashed:fixed! data-stashed:top-0! data-stashed:min-h-dvh! data-stashed:max-h-dvh!' + ]); +} +@endphp + +@if ($stashable) + +@endif + +
class($classes) }} x-data="{ screenLg: window.innerWidth >= 1024 }" data-mobile-cloak data-flux-sidebar> + {{ $slot }} +
diff --git a/resources/views/flux/sidebar/toggle.blade.php b/resources/views/flux/sidebar/toggle.blade.php new file mode 100644 index 0000000..da0f544 --- /dev/null +++ b/resources/views/flux/sidebar/toggle.blade.php @@ -0,0 +1,12 @@ + + diff --git a/resources/views/flux/spacer.blade.php b/resources/views/flux/spacer.blade.php new file mode 100644 index 0000000..c52dfd3 --- /dev/null +++ b/resources/views/flux/spacer.blade.php @@ -0,0 +1 @@ +
class('flex-1') }} data-flux-spacer>
diff --git a/resources/views/flux/subheading.blade.php b/resources/views/flux/subheading.blade.php new file mode 100644 index 0000000..74060e8 --- /dev/null +++ b/resources/views/flux/subheading.blade.php @@ -0,0 +1,19 @@ +@props([ + 'size' => 'base', +]) + +@php +$classes = Flux::classes() + ->add(match ($size) { + 'xl' => 'text-lg', + 'lg' => 'text-base', + default => 'text-sm', + 'sm' => 'text-xs', + }) + ->add('[:where(&)]:text-zinc-500 [:where(&)]:dark:text-white/70') + ; +@endphp + +
class($classes) }} data-flux-subheading> + {{ $slot }} +
diff --git a/resources/views/flux/switch.blade.php b/resources/views/flux/switch.blade.php new file mode 100644 index 0000000..9abebe5 --- /dev/null +++ b/resources/views/flux/switch.blade.php @@ -0,0 +1,49 @@ +@props([ + 'name' => null, + 'align' => 'right', +]) + +@php +// We only want to show the name attribute it has been set manually +// but not if it has been set from the `wire:model` attribute... +$showName = isset($name); +if (! isset($name)) { + $name = $attributes->whereStartsWith('wire:model')->first(); +} + +$classes = Flux::classes() + ->add('group h-5 w-8 min-w-8 relative inline-flex items-center outline-offset-2') + ->add('rounded-full') + ->add('transition') + ->add('bg-zinc-800/15 [&[disabled]]:opacity-50 dark:bg-transparent dark:border dark:border-white/20 dark:[&[disabled]]:border-white/10') + ->add('[print-color-adjust:exact]') + ->add([ + 'data-checked:bg-(--color-accent)', + 'data-checked:border-0', + ]) + ; + +$indicatorClasses = Flux::classes() + ->add('size-3.5') + ->add('rounded-full') + ->add('transition translate-x-[3px] dark:translate-x-[2px] rtl:-translate-x-[3px] dark:rtl:-translate-x-[2px]') + ->add('bg-white') + ->add([ + 'group-data-checked:translate-x-[15px] rtl:group-data-checked:-translate-x-[15px]', + 'group-data-checked:bg-(--color-accent-foreground)', + ]); +@endphp + +@if ($align === 'left' || $align === 'start') + + class($classes) }} @if($showName) name="{{ $name }}" @endif data-flux-control data-flux-switch> + + + +@else + + class($classes) }} @if($showName) name="{{ $name }}" @endif data-flux-control data-flux-switch> + + + +@endif diff --git a/resources/views/flux/text.blade.php b/resources/views/flux/text.blade.php new file mode 100644 index 0000000..23f336f --- /dev/null +++ b/resources/views/flux/text.blade.php @@ -0,0 +1,42 @@ +@props([ + 'inline' => false, + 'variant' => null, + 'color' => null, + 'size' => null, +]) + +@php +$classes = Flux::classes() + ->add(match ($size) { + 'xl' => 'text-lg', + 'lg' => 'text-base', + default => '[:where(&)]:text-sm', + 'sm' => 'text-xs', + }) + ->add($color ? match($color) { + 'red' => 'text-red-600 dark:text-red-400', + 'orange' => 'text-orange-600 dark:text-orange-400', + 'amber' => 'text-amber-600 dark:text-amber-500', + 'yellow' => 'text-yellow-600 dark:text-yellow-500', + 'lime' => 'text-lime-600 dark:text-lime-500', + 'green' => 'text-green-600 dark:text-green-500', + 'emerald' => 'text-emerald-600 dark:text-emerald-400', + 'teal' => 'text-teal-600 dark:text-teal-400', + 'cyan' => 'text-cyan-600 dark:text-cyan-400', + 'sky' => 'text-sky-600 dark:text-sky-400', + 'blue' => 'text-blue-600 dark:text-blue-400', + 'indigo' => 'text-indigo-600 dark:text-indigo-400', + 'violet' => 'text-violet-600 dark:text-violet-400', + 'purple' => 'text-purple-600 dark:text-purple-400', + 'fuchsia' => 'text-fuchsia-600 dark:text-fuchsia-400', + 'pink' => 'text-pink-600 dark:text-pink-400', + 'rose' => 'text-rose-600 dark:text-rose-400', + } : match ($variant) { + 'strong' => '[:where(&)]:text-zinc-800 [:where(&)]:dark:text-white', + 'subtle' => '[:where(&)]:text-zinc-400 [:where(&)]:dark:text-white/50', + default => '[:where(&)]:text-zinc-500 [:where(&)]:dark:text-white/70', + }) + ; +@endphp +{{-- NOTE: It's important that this file has NO newline at the end of the file. --}} +class($classes) }} data-flux-text @if ($color) color="{{ $color }}" @endif>{{ $slot }}
class($classes) }} data-flux-text @if ($color) data-color="{{ $color }}" @endif>{{ $slot }}
\ No newline at end of file diff --git a/resources/views/flux/textarea.blade.php b/resources/views/flux/textarea.blade.php new file mode 100644 index 0000000..620014f --- /dev/null +++ b/resources/views/flux/textarea.blade.php @@ -0,0 +1,38 @@ +@props([ + 'name' => $attributes->whereStartsWith('wire:model')->first(), + 'resize' => 'vertical', + 'invalid' => null, + 'rows' => 4, +]) + +@php +$invalid ??= ($name && $errors->has($name)); + +$classes = Flux::classes() + ->add('block p-3 w-full') + ->add('shadow-xs disabled:shadow-none border rounded-lg') + ->add('bg-white dark:bg-white/10 dark:disabled:bg-white/[7%]') + ->add($resize ? 'resize-y' : 'resize-none') + ->add('text-base sm:text-sm text-zinc-700 disabled:text-zinc-500 placeholder-zinc-400 disabled:placeholder-zinc-400/70 dark:text-zinc-300 dark:disabled:text-zinc-400 dark:placeholder-zinc-400 dark:disabled:placeholder-zinc-500') + ->add($invalid ? 'border-red-500' : 'border-zinc-200 border-b-zinc-300/80 dark:border-white/10') + ; + +$resizeStyle = match ($resize) { + 'none' => 'resize: none', + 'both' => 'resize: both', + 'horizontal' => 'resize: horizontal', + 'vertical' => 'resize: vertical', +}; +@endphp + + + + diff --git a/resources/views/flux/tooltip/content.blade.php b/resources/views/flux/tooltip/content.blade.php new file mode 100644 index 0000000..88fb3b7 --- /dev/null +++ b/resources/views/flux/tooltip/content.blade.php @@ -0,0 +1,21 @@ +@props([ + 'kbd' => null, +]) + +@php +$classes = Flux::classes([ + 'relative py-2 px-2.5', + 'rounded-md', + 'text-xs text-white font-medium', + 'bg-zinc-800 dark:bg-zinc-700 dark:border dark:border-white/10', + 'p-0 overflow-visible', +]); +@endphp + +
class($classes) }} data-flux-tooltip-content> + {{ $slot }} + + + {{ $kbd }} + +
diff --git a/resources/views/flux/tooltip/index.blade.php b/resources/views/flux/tooltip/index.blade.php new file mode 100644 index 0000000..fcba868 --- /dev/null +++ b/resources/views/flux/tooltip/index.blade.php @@ -0,0 +1,37 @@ +@props([ + 'interactive' => null, + 'position' => 'top', + 'align' => 'center', + 'content' => null, + 'kbd' => null, + 'toggleable' => null, +]) + +@php +// Support adding the .self modifier to the wire:model directive... +if (($wireModel = $attributes->wire('model')) && $wireModel->directive && ! $wireModel->hasModifier('self')) { + unset($attributes[$wireModel->directive]); + + $wireModel->directive .= '.self'; + + $attributes = $attributes->merge([$wireModel->directive => $wireModel->value]); +} +@endphp + + + + {{ $slot }} + + + {{ $content }} + + + + + {{ $slot }} + + + {{ $content }} + + + diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php new file mode 100644 index 0000000..5f34d0b --- /dev/null +++ b/resources/views/layouts/app.blade.php @@ -0,0 +1,57 @@ + + + + + + + + {{ config('app.name', 'Laravel') }} + + + + + + + @vite(['resources/css/app.css', 'resources/js/app.js']) + @livewireStyles + + +
+ + + + +
+
+ {{ $slot }} +
+
+
+ + @livewireScripts + + diff --git a/resources/views/livewire/appointments/calendar.blade.php b/resources/views/livewire/appointments/calendar.blade.php new file mode 100644 index 0000000..ea809c7 --- /dev/null +++ b/resources/views/livewire/appointments/calendar.blade.php @@ -0,0 +1,430 @@ +
+ {{-- Calendar Header --}} +
+
+ {{-- Top Row: Title and Today Button --}} +
+

+ {{ $currentPeriodLabel }} +

+ +
+ + {{-- Bottom Row: Controls --}} +
+ {{-- Left Side: Technician Filter --}} +
+ + +
+ + {{-- Right Side: View Controls and Navigation --}} +
+ {{-- View Type Toggles --}} +
+ + + +
+ + {{-- Navigation --}} +
+ + +
+
+
+
+
+ + {{-- Calendar Content --}} +
+ @if($viewType === 'month') + {{-- Month View --}} +
+ {{-- Day Headers --}} +
+ @foreach(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] as $day) +
+ {{ $day }} +
+ @endforeach +
+ + {{-- Calendar Days --}} +
+ @foreach($calendarDays as $day) +
+
+ {{ $day['day'] }} +
+ + {{-- Appointments for this day --}} + @if(isset($appointments[$day['date']])) +
+ @foreach(array_slice($appointments[$day['date']], 0, 3) as $appointment) +
+
+ {{ \Carbon\Carbon::parse($appointment['scheduled_datetime'])->format('g:i A') }} +
+
+ {{ $appointment['customer']['first_name'] ?? 'Unknown' }} +
+
+ @endforeach + @if(count($appointments[$day['date']]) > 3) +
+ +{{ count($appointments[$day['date']]) - 3 }} more +
+ @endif +
+ @endif +
+ @endforeach +
+
+ + @elseif($viewType === 'week') + {{-- Week View --}} +
+ {{-- Time Column --}} +
+
+ @foreach($timeSlots as $slot) +
+ {{ $slot['label'] }} +
+ @endforeach +
+ + {{-- Days Columns --}} +
+ @foreach($calendarDays as $day) +
+ {{-- Day Header --}} +
+
+ {{ $day['dayName'] }} +
+
+ {{ $day['day'] }} +
+
+ + {{-- Time Slots --}} +
+ @foreach($timeSlots as $slot) +
+ {{-- Appointments for this time slot --}} + @if(isset($appointments[$day['date']])) + @foreach($appointments[$day['date']] as $appointment) + @php + $appointmentTime = \Carbon\Carbon::parse($appointment['scheduled_datetime'])->format('H:i'); + @endphp + @if($appointmentTime === $slot['time']) +
+
{{ $appointment['customer']['first_name'] ?? '' }} {{ $appointment['customer']['last_name'] ?? '' }}
+
{{ $appointment['service_requested'] }}
+
+ @endif + @endforeach + @endif +
+ @endforeach +
+
+ @endforeach +
+
+ + @else + {{-- Day View --}} +
+ {{-- Time Column --}} +
+
+
+ {{ $calendarDays[0]['fullDate'] ?? '' }} +
+
+ @foreach($timeSlots as $slot) +
+ {{ $slot['label'] }} +
+ @endforeach +
+ + {{-- Day Content --}} +
+
+
+ Schedule +
+
+ +
+ @foreach($timeSlots as $slot) +
+ {{-- Appointments for this time slot --}} + @if(isset($appointments[$selectedDate])) + @foreach($appointments[$selectedDate] as $appointment) + @php + $appointmentTime = \Carbon\Carbon::parse($appointment['scheduled_datetime'])->format('H:i'); + @endphp + @if($appointmentTime === $slot['time']) +
+
+
+
+ {{ $appointment['customer']['first_name'] ?? '' }} {{ $appointment['customer']['last_name'] ?? '' }} +
+
+ {{ $appointment['service_requested'] }} +
+
+ {{ $appointment['assigned_technician']['first_name'] ?? '' }} {{ $appointment['assigned_technician']['last_name'] ?? '' }} +
+
+
+ {{ \Carbon\Carbon::parse($appointment['scheduled_datetime'])->format('g:i A') }} +
+
+
+ @endif + @endforeach + @endif +
+ @endforeach +
+
+
+ @endif +
+ + {{-- Appointment Details Modal --}} + @if($showAppointmentModal && $selectedAppointment) +
+
+
+
+

+ Appointment Details +

+ +
+
+ +
+
+ + {{ ucfirst(str_replace('_', ' ', $selectedAppointment->status)) }} + +
+ +
+ +

+ {{ $selectedAppointment->customer->first_name }} {{ $selectedAppointment->customer->last_name }} +

+
+ +
+ +

+ {{ $selectedAppointment->vehicle->year }} {{ $selectedAppointment->vehicle->make }} {{ $selectedAppointment->vehicle->model }} +

+
+ +
+ +

+ {{ $selectedAppointment->assignedTechnician->first_name }} {{ $selectedAppointment->assignedTechnician->last_name }} +

+
+ +
+ +

+ {{ $selectedAppointment->formatted_date_time }} +

+
+ +
+ +

+ {{ $selectedAppointment->service_requested }} +

+
+ + @if($selectedAppointment->customer_notes) +
+ +

+ {{ $selectedAppointment->customer_notes }} +

+
+ @endif +
+ +
+ +
+
+
+ @endif +
diff --git a/resources/views/livewire/appointments/create.blade.php b/resources/views/livewire/appointments/create.blade.php new file mode 100644 index 0000000..0319da3 --- /dev/null +++ b/resources/views/livewire/appointments/create.blade.php @@ -0,0 +1,185 @@ +
+
+ +
+
+

Schedule Appointment

+

Create a new appointment for a customer

+
+ + + + + Back to Appointments + +
+ + + @if (session()->has('message')) +
+
+ + + +

{{ session('message') }}

+
+
+ @endif + + + @if ($errors->has('general')) +
+
+ + + +

{{ $errors->first('general') }}

+
+
+ @endif + +
+ +
+
+ + + +

Customer & Vehicle

+
+ +
+
+ +
+ + + @error('customer_id')

{{ $message }}

@enderror +
+ + +
+ + + @error('vehicle_id')

{{ $message }}

@enderror +
+
+
+
+ + +
+
+ + + +

Appointment Details

+
+ +
+
+ +
+ + + @error('scheduled_date')

{{ $message }}

@enderror +
+ + +
+ + + @error('scheduled_time')

{{ $message }}

@enderror +
+ + +
+ + +
+
+ +
+ +
+ + +
+ + +
+ + +
+
+ + +
+ + + @error('service_requested')

{{ $message }}

@enderror +
+ +
+ +
+ + +
+ + +
+ + +
+
+
+
+ + +
+
+ + Cancel + + +
+
+
+
+
diff --git a/resources/views/livewire/appointments/form.blade.php b/resources/views/livewire/appointments/form.blade.php new file mode 100644 index 0000000..abb201b --- /dev/null +++ b/resources/views/livewire/appointments/form.blade.php @@ -0,0 +1,157 @@ +
+ + @if($showModal) +
+
+
+ + + +
+ +
+

+ {{ $editing ? 'Edit Appointment' : 'Schedule New Appointment' }} +

+ +
+ + +
+ +
+
+ + + @error('customer_id') {{ $message }} @enderror +
+
+ + + @error('vehicle_id') {{ $message }} @enderror +
+
+ + +
+
+ + + @error('scheduled_date') {{ $message }} @enderror +
+
+ + + @error('scheduled_time') {{ $message }} @enderror +
+
+ + + @error('estimated_duration_minutes') {{ $message }} @enderror +
+
+ + +
+
+ + + @error('appointment_type') {{ $message }} @enderror +
+
+ + + @error('assigned_technician_id') {{ $message }} @enderror +
+
+ + +
+ + + @error('service_requested') {{ $message }} @enderror +
+ + +
+
+ + + @error('customer_notes') {{ $message }} @enderror +
+
+ + + @error('internal_notes') {{ $message }} @enderror +
+
+ + + @if($scheduled_date && $scheduled_time && $assigned_technician_id) +
+
+ + + +
+ Appointment Time: + {{ \Carbon\Carbon::parse($scheduled_date . ' ' . $scheduled_time)->format('M j, Y g:i A') }} - + {{ \Carbon\Carbon::parse($scheduled_date . ' ' . $scheduled_time)->addMinutes($estimated_duration_minutes)->format('g:i A') }} +
+
+
+ @endif + + +
+ + +
+
+
+
+
+ @endif +
diff --git a/resources/views/livewire/appointments/index.blade.php b/resources/views/livewire/appointments/index.blade.php new file mode 100644 index 0000000..3deb3e6 --- /dev/null +++ b/resources/views/livewire/appointments/index.blade.php @@ -0,0 +1,471 @@ +
+ +
+
+ +
+
+
+
+ + + +
+
+

Appointment Management

+

Schedule, track, and manage customer appointments

+
+
+
+ +
+ +
+ + + +
+
+
+ + +
+
+
+
+

Today's Total

+

{{ $todayStats['total'] }}

+

Scheduled appointments

+
+
+ + + +
+
+
+ +
+
+
+

Confirmed

+

{{ $todayStats['confirmed'] }}

+

Ready to serve

+
+
+ + + +
+
+
+ +
+
+
+

In Progress

+

{{ $todayStats['in_progress'] }}

+

Currently working

+
+
+ + + +
+
+
+ +
+
+
+

Completed

+

{{ $todayStats['completed'] }}

+

Successfully finished

+
+
+ + + +
+
+
+
+ + +
+
+ +
+ +
+
+ + + +
+ +
+
+ + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ + +
+
+ @php + $activeFilters = collect([$search, $statusFilter, $typeFilter, $technicianFilter, $dateFilter])->filter()->count(); + @endphp + @if($activeFilters > 0) + {{ $activeFilters }} filter{{ $activeFilters !== 1 ? 's' : '' }} active + @else + No filters applied + @endif +
+ @if($activeFilters > 0) + + @endif +
+
+
+
+ + +
+ @if($appointments->count() > 0) + +
+
+
+

Appointments

+ + {{ $appointments->total() }} total + +
+
+ + + + Showing {{ $appointments->firstItem() }}-{{ $appointments->lastItem() }} of {{ $appointments->total() }} +
+
+
+ +
+ + + + + + + + + + + + + @foreach($appointments as $appointment) + + + + + + + + + @endforeach + +
+
+ Date & Time + + + +
+
+ Customer & Vehicle + + Service Details + + Technician + + Status + + Actions +
+
+
+
+ + + +
+
+
+
+ {{ $appointment->formatted_date }} +
+
+ {{ $appointment->formatted_time }} - {{ $appointment->formatted_end_time }} + ({{ $appointment->getDurationInHours() }}h) +
+ @if($appointment->isOverdue()) + + + + + Overdue + + @endif +
+
+
+
+
+
+ + + +
+
+
+
+ {{ $appointment->customer->full_name }} +
+
+ {{ $appointment->vehicle->year }} {{ $appointment->vehicle->make }} {{ $appointment->vehicle->model }} +
+
+ {{ $appointment->vehicle->license_plate }} +
+
+
+
+
+
+ + {{ ucfirst(str_replace('_', ' ', $appointment->appointment_type)) }} + +
+
+ {{ Str::limit($appointment->service_requested, 60) }} +
+
+
+ @if($appointment->assignedTechnician) +
+
+
+ + + +
+
+
+
+ {{ $appointment->assignedTechnician->full_name }} +
+
+
+ @else +
+ + + + Unassigned +
+ @endif +
+ + @if($appointment->status === 'scheduled') + + + + @elseif($appointment->status === 'confirmed') + + + + @elseif($appointment->status === 'in_progress') + + + + + @elseif($appointment->status === 'completed') + + + + @endif + {{ ucfirst(str_replace('_', ' ', $appointment->status)) }} + + +
+ @if($appointment->status === 'scheduled') + + @endif + + @if($appointment->canBeCheckedIn()) + + @endif + + @if($appointment->canBeCompleted()) + + @endif + + @if($appointment->canBeModified()) + + + @endif + + @if($appointment->isOverdue() && $appointment->status === 'scheduled') + + @endif +
+
+
+ + +
+ {{ $appointments->links() }} +
+ @else +
+ + + +

No appointments found

+

+ @if($search || $statusFilter || $technicianFilter || $dateFilter || $typeFilter) + Try adjusting your filters to see more results. + @else + Get started by scheduling your first appointment. + @endif +

+ +
+ @endif +
+ + + @if($showForm) + + @endif +
diff --git a/resources/views/livewire/appointments/time-slots.blade.php b/resources/views/livewire/appointments/time-slots.blade.php new file mode 100644 index 0000000..51d5409 --- /dev/null +++ b/resources/views/livewire/appointments/time-slots.blade.php @@ -0,0 +1,242 @@ +
+ {{-- Date and Technician Selection --}} +
+

+ Select Date & Technician +

+ +
+ {{-- Date Selection --}} +
+ + +
+ + {{-- Technician Selection --}} +
+ + +
+ + {{-- Service Duration --}} +
+ + +
+
+ + @if($selectedDate && \Carbon\Carbon::parse($selectedDate)->isPast() && !\Carbon\Carbon::parse($selectedDate)->isToday()) +
+
+ + + +
+

+ Cannot schedule appointments for past dates. Please select a future date. +

+
+
+
+ @endif +
+ + {{-- Time Slots Grid --}} + @if(!empty($timeSlots)) +
+
+
+

+ Available Time Slots +

+
+ {{ \Carbon\Carbon::parse($selectedDate)->format('l, F j, Y') }} +
+
+ + @if($selectedSlot) +
+ + Selected: {{ \Carbon\Carbon::parse($selectedDate . ' ' . $selectedSlot)->format('g:i A') }} + + +
+ @endif +
+ +
+ {{-- Legend --}} +
+
+
+ Available +
+
+
+ Booked +
+
+
+ Unavailable +
+
+
+ Selected +
+
+ + {{-- Time Slots Grid --}} +
+ @foreach($timeSlots as $slot) + @php + $slotStatus = $this->getSlotStatus($slot['time']); + $isSelected = $this->isSlotSelected($slot['time']); + @endphp + +
+ @if($slotStatus['status'] === 'available') + + + @elseif($slotStatus['status'] === 'booked') +
+
{{ $slot['label'] }}
+
+ {{ $slotStatus['data']['customer_name'] ?? 'Booked' }} +
+
+ + {{-- Tooltip for booked slot --}} + @if($slotStatus['data']) + + @endif + + @else +
+
{{ $slot['label'] }}
+
Unavailable
+
+ @endif +
+ @endforeach +
+ + {{-- No available slots message --}} + @if(empty($availableSlots)) +
+ + + +

+ No Available Slots +

+

+ All time slots are booked for this date + @if($selectedTechnician) + and technician + @endif + . +

+ + @if($nextAvailableDate) + + @else +

+ No availability found in the next 30 days. +

+ @endif +
+ @endif +
+
+ + {{-- Business Hours Info --}} +
+
+ + + +
+

Business Hours

+
+

Monday - Friday: {{ \Carbon\Carbon::parse($businessStart)->format('g:i A') }} - {{ \Carbon\Carbon::parse($businessEnd)->format('g:i A') }}

+

Lunch Break: {{ \Carbon\Carbon::parse($lunchStart)->format('g:i A') }} - {{ \Carbon\Carbon::parse($lunchEnd)->format('g:i A') }}

+

Time slots are available in {{ $slotInterval }}-minute intervals.

+
+
+
+
+ + @else + {{-- No slots for selected date --}} +
+ + + +

+ Select a Date +

+

+ Choose a date to view available time slots. +

+
+ @endif +
+ +@script + +@endscript diff --git a/resources/views/livewire/auth/confirm-password.blade.php b/resources/views/livewire/auth/confirm-password.blade.php new file mode 100644 index 0000000..78fae81 --- /dev/null +++ b/resources/views/livewire/auth/confirm-password.blade.php @@ -0,0 +1,58 @@ +validate([ + 'password' => ['required', 'string'], + ]); + + if (! Auth::guard('web')->validate([ + 'email' => Auth::user()->email, + 'password' => $this->password, + ])) { + throw ValidationException::withMessages([ + 'password' => __('auth.password'), + ]); + } + + session(['auth.password_confirmed_at' => time()]); + + $this->redirectIntended(default: route('dashboard', absolute: false), navigate: true); + } +}; ?> + +
+ + + + + +
+ + + + {{ __('Confirm') }} + +
diff --git a/resources/views/livewire/auth/forgot-password.blade.php b/resources/views/livewire/auth/forgot-password.blade.php new file mode 100644 index 0000000..f223342 --- /dev/null +++ b/resources/views/livewire/auth/forgot-password.blade.php @@ -0,0 +1,49 @@ +validate([ + 'email' => ['required', 'string', 'email'], + ]); + + Password::sendResetLink($this->only('email')); + + session()->flash('status', __('A reset link will be sent if the account exists.')); + } +}; ?> + +
+ + + + + +
+ + + + {{ __('Email password reset link') }} + + +
+ {{ __('Or, return to') }} + {{ __('log in') }} +
+
diff --git a/resources/views/livewire/auth/login.blade.php b/resources/views/livewire/auth/login.blade.php new file mode 100644 index 0000000..2aa8748 --- /dev/null +++ b/resources/views/livewire/auth/login.blade.php @@ -0,0 +1,127 @@ +validate(); + + $this->ensureIsNotRateLimited(); + + if (! Auth::attempt(['email' => $this->email, 'password' => $this->password], $this->remember)) { + RateLimiter::hit($this->throttleKey()); + + throw ValidationException::withMessages([ + 'email' => __('auth.failed'), + ]); + } + + RateLimiter::clear($this->throttleKey()); + Session::regenerate(); + + $this->redirectIntended(default: route('dashboard', absolute: false), navigate: true); + } + + /** + * Ensure the authentication request is not rate limited. + */ + protected function ensureIsNotRateLimited(): void + { + if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) { + return; + } + + event(new Lockout(request())); + + $seconds = RateLimiter::availableIn($this->throttleKey()); + + throw ValidationException::withMessages([ + 'email' => __('auth.throttle', [ + 'seconds' => $seconds, + 'minutes' => ceil($seconds / 60), + ]), + ]); + } + + /** + * Get the authentication rate limiting throttle key. + */ + protected function throttleKey(): string + { + return Str::transliterate(Str::lower($this->email).'|'.request()->ip()); + } +}; ?> + +
+ + + + + +
+ + + + +
+ + + @if (Route::has('password.request')) + + {{ __('Forgot your password?') }} + + @endif +
+ + + + +
+ {{ __('Log in') }} +
+ + + @if (Route::has('register')) +
+ {{ __('Don\'t have an account?') }} + {{ __('Sign up') }} +
+ @endif +
diff --git a/resources/views/livewire/auth/register.blade.php b/resources/views/livewire/auth/register.blade.php new file mode 100644 index 0000000..5306673 --- /dev/null +++ b/resources/views/livewire/auth/register.blade.php @@ -0,0 +1,99 @@ +validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:' . User::class], + 'password' => ['required', 'string', 'confirmed', Rules\Password::defaults()], + ]); + + $validated['password'] = Hash::make($validated['password']); + + event(new Registered(($user = User::create($validated)))); + + Auth::login($user); + + $this->redirectIntended(route('dashboard', absolute: false), navigate: true); + } +}; ?> + +
+ + + + + +
+ + + + + + + + + + + + +
+ + {{ __('Create account') }} + +
+ + +
+ {{ __('Already have an account?') }} + {{ __('Log in') }} +
+
diff --git a/resources/views/livewire/auth/reset-password.blade.php b/resources/views/livewire/auth/reset-password.blade.php new file mode 100644 index 0000000..d6bcd98 --- /dev/null +++ b/resources/views/livewire/auth/reset-password.blade.php @@ -0,0 +1,115 @@ +token = $token; + + $this->email = request()->string('email'); + } + + /** + * Reset the password for the given user. + */ + public function resetPassword(): void + { + $this->validate([ + 'token' => ['required'], + 'email' => ['required', 'string', 'email'], + 'password' => ['required', 'string', 'confirmed', Rules\Password::defaults()], + ]); + + // Here we will attempt to reset the user's password. If it is successful we + // will update the password on an actual user model and persist it to the + // database. Otherwise we will parse the error and return the response. + $status = Password::reset( + $this->only('email', 'password', 'password_confirmation', 'token'), + function ($user) { + $user->forceFill([ + 'password' => Hash::make($this->password), + 'remember_token' => Str::random(60), + ])->save(); + + event(new PasswordReset($user)); + } + ); + + // If the password was successfully reset, we will redirect the user back to + // the application's home authenticated view. If there is an error we can + // redirect them back to where they came from with their error message. + if ($status != Password::PasswordReset) { + $this->addError('email', __($status)); + + return; + } + + Session::flash('status', __($status)); + + $this->redirectRoute('login', navigate: true); + } +}; ?> + +
+ + + + + +
+ + + + + + + + + +
+ + {{ __('Reset password') }} + +
+ +
diff --git a/resources/views/livewire/auth/verify-email.blade.php b/resources/views/livewire/auth/verify-email.blade.php new file mode 100644 index 0000000..c771ff2 --- /dev/null +++ b/resources/views/livewire/auth/verify-email.blade.php @@ -0,0 +1,57 @@ +hasVerifiedEmail()) { + $this->redirectIntended(default: route('dashboard', absolute: false), navigate: true); + + return; + } + + Auth::user()->sendEmailVerificationNotification(); + + Session::flash('status', 'verification-link-sent'); + } + + /** + * Log the current user out of the application. + */ + public function logout(Logout $logout): void + { + $logout(); + + $this->redirect('/', navigate: true); + } +}; ?> + +
+ + {{ __('Please verify your email address by clicking on the link we just emailed to you.') }} + + + @if (session('status') == 'verification-link-sent') + + {{ __('A new verification link has been sent to the email address you provided during registration.') }} + + @endif + +
+ + {{ __('Resend verification email') }} + + + + {{ __('Log out') }} + +
+
diff --git a/resources/views/livewire/customer-portal/estimate-view.blade.php b/resources/views/livewire/customer-portal/estimate-view.blade.php new file mode 100644 index 0000000..a40248d --- /dev/null +++ b/resources/views/livewire/customer-portal/estimate-view.blade.php @@ -0,0 +1,3 @@ +
+ {{-- Knowing others is intelligence; knowing yourself is true wisdom. --}} +
diff --git a/resources/views/livewire/customer-portal/job-status.blade.php b/resources/views/livewire/customer-portal/job-status.blade.php new file mode 100644 index 0000000..a573dbb --- /dev/null +++ b/resources/views/livewire/customer-portal/job-status.blade.php @@ -0,0 +1,3 @@ +
+ {{-- Success is as dangerous as failure. --}} +
diff --git a/resources/views/livewire/customers/create.blade.php b/resources/views/livewire/customers/create.blade.php new file mode 100644 index 0000000..3056541 --- /dev/null +++ b/resources/views/livewire/customers/create.blade.php @@ -0,0 +1,138 @@ +
+ +
+
+ Add New Customer + Create a new customer profile +
+ + + Back to Customers + +
+ + +
+
+
+ +
+ Personal Information +
+
+ + First Name * + + + +
+ +
+ + Last Name * + + + +
+ +
+ + Email Address * + + + +
+ +
+ + Phone Number * + + + +
+ +
+ + Secondary Phone + + + +
+ +
+ + Status * + + + +
+
+
+ + +
+ Address Information +
+
+ + Street Address * + + + +
+ +
+
+ + City * + + + +
+ +
+ + State * + + + +
+ +
+ + ZIP Code * + + + +
+
+
+
+ + +
+ Additional Information +
+ + Notes + + + +
+
+ + +
+ Cancel + + + Create Customer + +
+
+
+
+
diff --git a/resources/views/livewire/customers/edit.blade.php b/resources/views/livewire/customers/edit.blade.php new file mode 100644 index 0000000..ae66ee2 --- /dev/null +++ b/resources/views/livewire/customers/edit.blade.php @@ -0,0 +1,157 @@ +
+ +
+
+ Edit Customer + Update customer information for {{ $customer->full_name }} +
+ + + Back to Customer + +
+ + + @if (session()->has('success')) +
+
+ +
+

{{ session('success') }}

+
+
+
+ @endif + + +
+
+ +
+ Personal Information +
+
+ + First Name * + + + +
+
+ + Last Name * + + + +
+
+
+ + +
+ Contact Information +
+
+ + Email * + + + +
+
+ + Phone * + + + +
+
+ + Secondary Phone + + + +
+
+
+ + +
+ Address Information +
+
+ + Street Address * + + + +
+
+
+ + City * + + + +
+
+ + State * + + + +
+
+ + ZIP Code * + + + +
+
+
+
+ + +
+ Additional Information +
+
+ + Customer Status * +
+ + +
+ +
+
+
+ + Notes + + + +
+
+
+ + +
+ + Cancel + + + + Update Customer + +
+
+
+
diff --git a/resources/views/livewire/customers/index.blade.php b/resources/views/livewire/customers/index.blade.php new file mode 100644 index 0000000..ef27e8e --- /dev/null +++ b/resources/views/livewire/customers/index.blade.php @@ -0,0 +1,181 @@ +
+ +
+ Customer Management + + + Add New Customer + +
+ + + @if (session()->has('success')) +
+
+ +
+

{{ session('success') }}

+
+
+
+ @endif + + @if (session()->has('error')) +
+
+ +
+

{{ session('error') }}

+
+
+
+ @endif + + +
+
+ + + + +
+ + + Refresh + +
+
+
+ + +
+
+ + + + + + + + + + + + + + @forelse($customers as $customer) + + + + + + + + + + @empty + + + + @endforelse + +
+ + + + AddressVehicles + + StatusActions
+
+
{{ $customer->full_name }}
+
ID: {{ $customer->id }}
+
+
+
+
{{ $customer->email }}
+
{{ $customer->phone }}
+
+
+
+ {{ $customer->city }}, {{ $customer->state }} +
+
+
+ + {{ $customer->vehicles->count() }} vehicle(s) + +
+
+
+ @if($customer->last_service_date) + {{ $customer->last_service_date->format('M j, Y') }} + @else + Never + @endif +
+
+ + {{ ucfirst($customer->status) }} + + +
+ + View + + + Edit + + + New Order + + + Delete + +
+
+ @if($search) + No customers found matching "{{ $search }}" + @else + No customers found. Add your first customer + @endif +
+
+ + @if($customers->hasPages()) +
+ {{ $customers->links() }} +
+ @endif +
+
diff --git a/resources/views/livewire/customers/show.blade.php b/resources/views/livewire/customers/show.blade.php new file mode 100644 index 0000000..dd342e4 --- /dev/null +++ b/resources/views/livewire/customers/show.blade.php @@ -0,0 +1,224 @@ +
+ +
+
+ {{ $customer->full_name }} + Customer #{{ $customer->id }} - {{ ucfirst($customer->status) }} +
+
+ + + Back to Customers + + + + Edit Customer + + + + New Service Order + +
+
+ +
+ +
+ +
+
+ Contact Information +
+
+
+
+ Full Name +
{{ $customer->full_name }}
+
+ + + @if($customer->secondary_phone) +
+ Secondary Phone + +
+ @endif +
+ Address +
{{ $customer->formatted_address }}
+
+ @if($customer->notes) +
+ Notes +
{{ $customer->notes }}
+
+ @endif +
+
+
+ + +
+
+ Vehicles ({{ $customer->vehicles->count() }}) + + + Add Vehicle + +
+
+ @forelse($customer->vehicles as $vehicle) +
+
+
+
{{ $vehicle->display_name }}
+
VIN: {{ $vehicle->vin_display }} • {{ number_format($vehicle->mileage) }} miles
+
{{ $vehicle->color }} • {{ $vehicle->license_plate }}
+
+
+ View + Service +
+
+
+ @empty +
+ No vehicles registered yet. + Add the first vehicle +
+ @endforelse +
+
+ + +
+
+ Service History +
+
+ + + + + + + + + + + + + + @forelse($customer->serviceOrders as $order) + + + + + + + + + + @empty + + + + @endforelse + +
Order #VehicleDateTechnicianStatusTotalActions
{{ $order->order_number }}{{ $order->vehicle->display_name }}{{ $order->created_at->format('M j, Y') }}{{ $order->assignedTechnician?->full_name ?? 'Unassigned' }} + + {{ ucfirst(str_replace('_', ' ', $order->status)) }} + + ${{ number_format($order->total_amount, 2) }} + View +
No service history yet.
+
+
+
+ + +
+ +
+
+ Quick Stats +
+
+
+ Total Vehicles + {{ $customer->vehicles->count() }} +
+
+ Service Orders + {{ $customer->serviceOrders->count() }} +
+
+ Total Spent + ${{ number_format($customer->serviceOrders->sum('total_amount'), 2) }} +
+
+ Last Service + + @if($customer->last_service_date) + {{ $customer->last_service_date->format('M j, Y') }} + @else + Never + @endif + +
+
+ Customer Since + {{ $customer->created_at->format('M j, Y') }} +
+
+
+ + +
+
+ Upcoming Appointments + + + Schedule + +
+
+ @forelse($customer->appointments->where('scheduled_datetime', '>=', now())->take(3) as $appointment) +
+
{{ $appointment->scheduled_datetime->format('M j, Y g:i A') }}
+
{{ $appointment->service_requested }}
+
+ + {{ ucfirst($appointment->status) }} + +
+
+ @empty +
No upcoming appointments
+ @endforelse +
+
+
+
+
diff --git a/resources/views/livewire/dashboard/daily-schedule.blade.php b/resources/views/livewire/dashboard/daily-schedule.blade.php new file mode 100644 index 0000000..e7fbd6c --- /dev/null +++ b/resources/views/livewire/dashboard/daily-schedule.blade.php @@ -0,0 +1,133 @@ +
+ +
+
+ + + +

Today's Appointments

+ + {{ $schedule['appointments']->count() }} + +
+ + @if($schedule['appointments']->count() > 0) +
+ @foreach($schedule['appointments'] as $appointment) +
+
+
+
+

+ {{ $appointment->customer->name }} +

+

+ {{ $appointment->vehicle->year }} {{ $appointment->vehicle->make }} {{ $appointment->vehicle->model }} +

+
+
+
+

+ {{ $appointment->scheduled_time ? $appointment->scheduled_time->format('g:i A') : 'TBD' }} +

+

+ {{ $appointment->service_type ?? 'General Service' }} +

+
+
+ @endforeach +
+ @else +
+ + + +

No appointments scheduled for today

+
+ @endif +
+ + +
+
+ + + +

Ready for Pickup

+ + {{ $schedule['pickups']->count() }} + +
+ + @if($schedule['pickups']->count() > 0) +
+ @foreach($schedule['pickups'] as $pickup) +
+
+
+
+

+ {{ $pickup->customer->name }} +

+

+ Job #{{ $pickup->job_number }} - {{ $pickup->vehicle->year }} {{ $pickup->vehicle->make }} +

+
+
+ + Completed + +
+ @endforeach +
+ @else +
+ + + +

No vehicles ready for pickup

+
+ @endif +
+ + + @if($schedule['overdue']->count() > 0) +
+
+ + + +

Overdue Items

+ + {{ $schedule['overdue']->count() }} + +
+ +
+ @foreach($schedule['overdue'] as $overdue) +
+
+
+
+

+ {{ $overdue->customer->name }} +

+

+ Job #{{ $overdue->job_number }} - {{ $overdue->vehicle->year }} {{ $overdue->vehicle->make }} +

+
+
+
+ + {{ ucfirst($overdue->status) }} + +

+ Due: {{ $overdue->expected_completion_date?->format('M j') }} +

+
+
+ @endforeach +
+
+ @endif +
diff --git a/resources/views/livewire/dashboard/overview.blade.php b/resources/views/livewire/dashboard/overview.blade.php new file mode 100644 index 0000000..bc86773 --- /dev/null +++ b/resources/views/livewire/dashboard/overview.blade.php @@ -0,0 +1,151 @@ +
+ +
+
+
+
{{ number_format($stats['total_customers']) }}
+
Active Customers
+
+
+ +
+
+
{{ number_format($stats['total_vehicles']) }}
+
Vehicles
+
+
+ +
+
+
{{ number_format($stats['pending_orders']) }}
+
Pending Orders
+
+
+ +
+
+
{{ number_format($stats['today_appointments']) }}
+
Today's Appointments
+
+
+ +
+
+
${{ number_format($stats['monthly_revenue'], 2) }}
+
Monthly Revenue
+
+
+ +
+
+
{{ number_format($stats['orders_this_week']) }}
+
Orders This Week
+
+
+
+ +
+ +
+
+

Today's Appointments

+
+ +
+ @forelse($todayAppointments as $appointment) +
+
+
{{ $appointment->customer->full_name }}
+
{{ $appointment->vehicle->display_name }}
+
{{ $appointment->service_requested }}
+
+
+
{{ $appointment->scheduled_datetime->format('g:i A') }}
+ + {{ ucfirst($appointment->status) }} + +
+
+ @empty +
No appointments scheduled for today
+ @endforelse +
+
+ + +
+
+

Pending Orders

+
+ +
+ @forelse($pendingOrders as $order) +
+
+
{{ $order->order_number }}
+
{{ $order->customer->full_name }}
+
{{ $order->vehicle->display_name }}
+
+
+
${{ number_format($order->total_amount, 2) }}
+ + {{ ucfirst(str_replace('_', ' ', $order->status)) }} + +
+
+ @empty +
No pending orders
+ @endforelse +
+
+
+ + +
+
+

Recent Service Orders

+
+ +
+ + + + + + + + + + + + + + @forelse($recentServiceOrders as $order) + + + + + + + + + + @empty + + + + @endforelse + +
Order #CustomerVehicleTechnicianStatusTotalDate
{{ $order->order_number }}{{ $order->customer->full_name }}{{ $order->vehicle->display_name }}{{ $order->assignedTechnician?->first_name }} {{ $order->assignedTechnician?->last_name }} + + {{ ucfirst(str_replace('_', ' ', $order->status)) }} + + ${{ number_format($order->total_amount, 2) }}{{ $order->created_at->format('M j, Y') }}
No service orders yet
+
+
+
diff --git a/resources/views/livewire/dashboard/performance-metrics.blade.php b/resources/views/livewire/dashboard/performance-metrics.blade.php new file mode 100644 index 0000000..8c09076 --- /dev/null +++ b/resources/views/livewire/dashboard/performance-metrics.blade.php @@ -0,0 +1,98 @@ +
+
+

Performance Metrics

+

This week vs last week

+
+ +
+ +
+
+
{{ $metrics['this_week']['jobs_completed'] }}
+
Jobs Completed
+
+
+ @if($metrics['growth']['jobs'] > 0) + + + + + +{{ $metrics['growth']['jobs'] }}% + + @elseif($metrics['growth']['jobs'] < 0) + + + + + {{ $metrics['growth']['jobs'] }}% + + @else + No change + @endif +
+
+ + +
+
+
${{ number_format($metrics['this_week']['revenue'], 0) }}
+
Revenue
+
+
+ @if($metrics['growth']['revenue'] > 0) + + + + + +{{ $metrics['growth']['revenue'] }}% + + @elseif($metrics['growth']['revenue'] < 0) + + + + + {{ $metrics['growth']['revenue'] }}% + + @else + No change + @endif +
+
+ + +
+
+
{{ $metrics['this_week']['avg_completion_time'] }}h
+
Avg. Completion Time
+
+ + + +
+ + +
+
+
{{ $metrics['this_week']['customer_satisfaction'] }}/5
+
Customer Satisfaction
+
+
+ @for($i = 1; $i <= 5; $i++) + @if($i <= floor($metrics['this_week']['customer_satisfaction'])) + + + + @elseif($i <= $metrics['this_week']['customer_satisfaction']) + + + + @else + + + + @endif + @endfor +
+
+
+
diff --git a/resources/views/livewire/dashboard/workflow-overview-backup.blade.php b/resources/views/livewire/dashboard/workflow-overview-backup.blade.php new file mode 100644 index 0000000..7216d6e --- /dev/null +++ b/resources/views/livewire/dashboard/workflow-overview-backup.blade.php @@ -0,0 +1,90 @@ +
+ +
+

{{ $roleSpecificData['title'] }}

+ + {{ now()->format('g:i A') }} + +
+ + +
+
+
Pending Inspection
+
{{ $stats['pending_inspection'] }}
+
+ +
+
Diagnosis Assigned
+
{{ $stats['assigned_for_diagnosis'] }}
+
+ +
+
Diagnosis In Progress
+
{{ $stats['diagnosis_in_progress'] }}
+
+ +
+
Estimates Pending
+
{{ $stats['estimates_pending_approval'] }}
+
+ +
+
Work Orders Active
+
{{ $stats['work_orders_active'] }}
+
+ +
+
Quality Inspections
+
{{ $stats['quality_inspections_pending'] }}
+
+
+ + +
+
+

Quick Actions

+
+
+
+ @foreach($roleSpecificData['tasks'] as $taskName => $count) +
+
{{ $taskName }}
+
{{ $count }}
+
+ @endforeach +
+
+
+ + +
+
+

Recent Job Cards

+
+
+ @forelse($recentJobCards as $jobCard) +
+
+
+
+ Job #{{ $jobCard->job_number ?? $jobCard->id }} +
+
+ {{ $jobCard->customer->name ?? 'Unknown Customer' }} - + {{ $jobCard->vehicle->year ?? '' }} {{ $jobCard->vehicle->make ?? '' }} {{ $jobCard->vehicle->model ?? '' }} +
+
+ + {{ ucfirst(str_replace('_', ' ', $jobCard->status)) }} + +
+
+ @empty +
+ No recent job cards found. +
+ @endforelse +
+
+
diff --git a/resources/views/livewire/dashboard/workflow-overview.blade.php b/resources/views/livewire/dashboard/workflow-overview.blade.php new file mode 100644 index 0000000..7128751 --- /dev/null +++ b/resources/views/livewire/dashboard/workflow-overview.blade.php @@ -0,0 +1,190 @@ +
+ +
+

{{ $roleSpecificData['title'] }}

+ + {{ now()->format('g:i A') }} + +
+ + +
+
+
Pending Inspection
+
{{ $stats['pending_inspection'] }}
+ + + +
+ +
+
Diagnosis
+
{{ $stats['assigned_for_diagnosis'] + $stats['diagnosis_in_progress'] }}
+ + + +
+ +
+
Estimates Pending
+
{{ $stats['estimates_pending_approval'] }}
+ + + +
+ +
+
Active Work Orders
+
{{ $stats['work_orders_active'] }}
+ + + + +
+ +
+
Quality Inspection
+
{{ $stats['quality_inspections_pending'] }}
+ + + +
+ +
+
Total Active
+
{{ array_sum($stats) }}
+ + + +
+
+ + +
+ +
+
+

My Tasks

+
+ +
+ @if(!empty($roleSpecificData['tasks'])) +
+ @foreach($roleSpecificData['tasks'] as $task => $count) +
+ {{ $task }} + + {{ $count }} + +
+ @endforeach +
+ @else +
+ + + +

All caught up! No pending tasks.

+
+ @endif +
+
+ + +
+
+

Recent Job Cards

+
+ +
+ @if($recentJobCards->count() > 0) +
+ @foreach($recentJobCards as $jobCard) +
+
+
{{ $jobCard->job_card_number ?? $jobCard->job_number }}
+
+ {{ $jobCard->customer->name ?? $jobCard->customer->first_name . ' ' . $jobCard->customer->last_name }} - + {{ $jobCard->vehicle->year }} {{ $jobCard->vehicle->make }} {{ $jobCard->vehicle->model }} +
+
+ {{ $jobCard->created_at->diffForHumans() }} +
+
+ + {{ ucfirst(str_replace('_', ' ', $jobCard->status)) }} + +
+ @endforeach +
+ + + @else +
+ + + +

No recent job cards.

+ + Create First Job Card + +
+ @endif +
+
+
+ + +
+
+

Quick Actions

+
+ +
+
+ @can('job-cards.create') + + + + + New Job Card + + @endcan + + @can('inspections.view') + + + + + Inspections + + @endcan + + @can('estimates.view') + + + + + Estimates + + @endcan + + @can('work-orders.view') + + + + + Work Orders + + @endcan +
+
+
+
diff --git a/resources/views/livewire/diagnosis/create.blade.php b/resources/views/livewire/diagnosis/create.blade.php new file mode 100644 index 0000000..d14241d --- /dev/null +++ b/resources/views/livewire/diagnosis/create.blade.php @@ -0,0 +1,897 @@ +
+ +
+
+
+

Vehicle Diagnosis

+

+ Complete diagnostic analysis for Job Card #{{ $jobCard->job_number }} +

+
+
+
+
{{ $jobCard->customer->first_name }} {{ $jobCard->customer->last_name }}
+
{{ $jobCard->vehicle->year }} {{ $jobCard->vehicle->make }} {{ $jobCard->vehicle->model }}
+
+
+ + + +
+
+
+
+ + + @if (session()->has('progress_saved')) +
+
+ + + +
+

{{ session('progress_saved') }}

+
+
+
+ @endif + +
+ +
+
+

Vehicle Information

+
+
+
+
+ +

{{ $jobCard->customer->first_name }} {{ $jobCard->customer->last_name }}

+

{{ $jobCard->customer->phone }}

+
+
+ +

{{ $jobCard->vehicle->year }} {{ $jobCard->vehicle->make }} {{ $jobCard->vehicle->model }}

+

{{ $jobCard->vehicle->license_plate }}

+
+
+ +

{{ number_format($jobCard->vehicle->mileage ?? 0) }} miles

+
+
+ +

{{ $jobCard->arrival_datetime->format('M j, Y') }}

+

{{ $jobCard->arrival_datetime->format('g:i A') }}

+
+
+
+
+ + +
+
+

Customer Reported Issues

+
+
+
+ +
+
+
+ + +
+ + + @if($showTimesheetSection) +
+ +
+
+
+

Current Diagnosis Session

+

Track time spent on diagnosis activities

+
+ @if($currentTimesheet) +
+
Session Active
+
Started: {{ \Carbon\Carbon::parse($currentTimesheet['start_time'])->format('g:i A') }}
+
+ @endif +
+ +
+
+ + + @error('selectedDiagnosisType') +

{{ $message }}

+ @enderror +
+ +
+ @if(!$currentTimesheet) + + @else + + @endif +
+ +
+ @if($currentTimesheet) +
+ {{ \Carbon\Carbon::parse($currentTimesheet['start_time'])->diffForHumans(null, true) }} +
+
Elapsed Time
+ @else +
+ Ready to Start +
+ @endif +
+
+
+ + + @if(count($timesheets) > 0) +
+

Previous Sessions

+
+ + + + + + + + + + + + + @foreach($timesheets as $timesheet) + + + + + + + + + @endforeach + +
TypeStart TimeEnd TimeDurationTechnician NameStatus
{{ $timesheet['description'] }} + {{ \Carbon\Carbon::parse($timesheet['start_time'])->format('M j, g:i A') }} + + @if($timesheet['end_time']) + {{ \Carbon\Carbon::parse($timesheet['end_time'])->format('M j, g:i A') }} + @else + Active + @endif + + @if($timesheet['end_time']) + {{ number_format(\Carbon\Carbon::parse($timesheet['start_time'])->diffInHours(\Carbon\Carbon::parse($timesheet['end_time'])), 2) }} hours + @else + In Progress + @endif + + {{ $timesheet['user']['name'] ?? 'Unknown' }} + + + {{ $timesheet['status'] === 'submitted' ? 'Completed' : 'In Progress' }} + +
+
+
+ @endif +
+ @endif +
+ + +
+
+

Diagnostic Analysis

+

Complete your diagnostic findings and analysis

+
+
+ +
+ + + @error('diagnostic_findings') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('recommended_repairs') +

{{ $message }}

+ @enderror +
+ + +
+ + +
+ + +
+ + +
+
+
+ +
+ +
+ + + @if($showPartsSection) +
+ +
+

+ Parts Catalog + + ({{ \App\Models\Part::where('status', 'active')->count() }} parts available) + +

+
+
+ +
+
+ +
+
+ + @if($filteredParts && $filteredParts->count() > 0) +
+
+ @foreach($filteredParts as $part) +
+
+
+
+

{{ $part->name }}

+

{{ $part->part_number }} • {{ $part->category }}

+
+
+

${{ number_format($part->sell_price, 2) }}

+

Stock: {{ $part->quantity_on_hand }}

+
+
+
+ +
+ @endforeach +
+
+ @elseif($partSearchTerm) +
+

No parts found matching "{{ $partSearchTerm }}"

+
+ @elseif($partSearchTerm === '' && $partCategoryFilter === '') +
+

Start typing to search for parts...

+

+ {{ \App\Models\Part::where('status', 'active')->count() }} parts available +

+
+ @endif +
+ + + @if(count($parts_required) > 0) +
+

Selected Parts

+
+ @foreach($parts_required as $index => $part) +
+
+ +
+ @if($part['part_id']) + +

{{ $part['part_name'] }}

+

{{ $part['part_number'] }}

+ Catalog + @else + +
+ + + Custom +
+ @endif +
+ + +
+ + +
+ + +
+ + @if($part['part_id']) + +

${{ number_format($part['estimated_cost'], 2) }}

+ @else + +
+ $ + +
+ @endif +
+ + + @if(!$part['part_id']) +
+ + +
+ @endif + + +
+

Total

+

+ ${{ number_format(($part['estimated_cost'] ?? 0) * ($part['quantity'] ?? 1), 2) }} +

+
+
+ +
+ @endforeach +
+ + +
+
+ Parts Total: + + ${{ number_format(collect($parts_required)->sum(fn($part) => ($part['estimated_cost'] ?? 0) * ($part['quantity'] ?? 1)), 2) }} + +
+
+
+ @endif + + +
+ +
+
+ @endif +
+ + +
+ + + @if($showLaborSection) +
+ +
+

Service Operations Catalog

+
+
+ +
+
+ +
+
+ + @if($filteredServiceItems && $filteredServiceItems->count() > 0) +
+
+ @foreach($filteredServiceItems as $serviceItem) +
+
+
+
+

{{ $serviceItem->name }}

+

{{ $serviceItem->category }}

+ @if($serviceItem->description) +

{{ Str::limit($serviceItem->description, 60) }}

+ @endif +
+
+

{{ $serviceItem->estimated_hours }}h

+

${{ number_format($serviceItem->labor_rate, 2) }}/hr

+

+ ${{ number_format($serviceItem->estimated_hours * $serviceItem->labor_rate, 2) }} +

+
+
+
+ +
+ @endforeach +
+
+ @elseif($serviceSearchTerm) +
+

No service operations found matching "{{ $serviceSearchTerm }}"

+
+ @endif +
+ + + @if(count($labor_operations) > 0) +
+

Selected Labor Operations

+
+ @foreach($labor_operations as $index => $operation) +
+
+
+

{{ $operation['operation'] }}

+ @if(!empty($operation['category'])) +

{{ $operation['category'] }}

+ @endif +
+
+ + +
+
+ +

${{ number_format($operation['labor_rate'], 2) }}/hr

+
+
+

Total

+

+ ${{ number_format($operation['estimated_hours'] * $operation['labor_rate'], 2) }} +

+
+
+ +
+ @endforeach +
+ + +
+
+ Labor Total: + + ${{ number_format(collect($labor_operations)->sum(fn($op) => $op['estimated_hours'] * $op['labor_rate']), 2) }} + +
+
+
+ @endif + + +
+ +
+
+ @endif +
+ + +
+ + + @if($showDiagnosticCodesSection) +
+
+ @foreach($diagnostic_codes as $index => $code) +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ @endforeach + + +
+
+ @endif +
+ + +
+
+

Diagnostic Photos

+

Upload photos to document your findings (Max 5MB per image)

+
+
+
+ + +
+ @error('photos.*') +

{{ $message }}

+ @enderror +
+
+ + +
+ + + @if($showAdvancedOptions) +
+ +
+ + +
+ + +
+ +
+ + +
+ + +
+
+ @endif +
+
+ + + @if(count($parts_required) > 0 || count($labor_operations) > 0 || count($timesheets) > 0) +
+

+ + + + + Cost Summary + +

+ +
+ + @if(count($timesheets) > 0) +
+
+ + + +

Diagnostic Time

+
+
+

+ {{ number_format(collect($timesheets)->sum('billable_hours'), 2) }} hours +

+

+ ${{ number_format(collect($timesheets)->sum('total_amount'), 2) }} +

+
+
+ @endif + + + @if(count($parts_required) > 0) +
+
+ + + +

Parts Cost

+
+
+

+ {{ count($parts_required) }} part{{ count($parts_required) !== 1 ? 's' : '' }} +

+

+ ${{ number_format(collect($parts_required)->sum(fn($part) => $part['estimated_cost'] * $part['quantity']), 2) }} +

+
+
+ @endif + + + @if(count($labor_operations) > 0) +
+
+ + + + +

Labor Cost

+
+
+

+ {{ number_format(collect($labor_operations)->sum('estimated_hours'), 2) }} hours +

+

+ ${{ number_format(collect($labor_operations)->sum(fn($op) => $op['estimated_hours'] * $op['labor_rate']), 2) }} +

+
+
+ @endif + + +
+
+ + + +

Total Estimate

+
+
+

+ Complete Repair Cost +

+

+ ${{ number_format($this->calculateTotalEstimatedCost(), 2) }} +

+
+
+
+ + @if($createEstimateAutomatically) +
+
+ + + +
+

+ Automatic Estimate Creation Enabled +

+

+ A detailed estimate will be automatically generated and sent to the customer when you complete this diagnosis. +

+
+
+
+ @endif +
+ @endif + + +
+
+
+

This diagnosis will update the job card status to "Diagnosis Completed"

+

An estimate can be created based on these findings.

+
+
+ + Cancel + + + +
+
+
+
+
diff --git a/resources/views/livewire/diagnosis/edit.blade.php b/resources/views/livewire/diagnosis/edit.blade.php new file mode 100644 index 0000000..0bf1ae7 --- /dev/null +++ b/resources/views/livewire/diagnosis/edit.blade.php @@ -0,0 +1,3 @@ +
+ {{-- Care about people's approval and you will be their prisoner. --}} +
diff --git a/resources/views/livewire/diagnosis/index.blade.php b/resources/views/livewire/diagnosis/index.blade.php new file mode 100644 index 0000000..ad58cc8 --- /dev/null +++ b/resources/views/livewire/diagnosis/index.blade.php @@ -0,0 +1,3 @@ +
+ {{-- Stop trying to control. --}} +
diff --git a/resources/views/livewire/diagnosis/show.blade.php b/resources/views/livewire/diagnosis/show.blade.php new file mode 100644 index 0000000..a573dbb --- /dev/null +++ b/resources/views/livewire/diagnosis/show.blade.php @@ -0,0 +1,3 @@ +
+ {{-- Success is as dangerous as failure. --}} +
diff --git a/resources/views/livewire/estimates/create.blade.php b/resources/views/livewire/estimates/create.blade.php new file mode 100644 index 0000000..15e5660 --- /dev/null +++ b/resources/views/livewire/estimates/create.blade.php @@ -0,0 +1,3 @@ +
+ {{-- The whole world belongs to you. --}} +
diff --git a/resources/views/livewire/estimates/edit.blade.php b/resources/views/livewire/estimates/edit.blade.php new file mode 100644 index 0000000..7a4f210 --- /dev/null +++ b/resources/views/livewire/estimates/edit.blade.php @@ -0,0 +1,3 @@ +
+ {{-- Do your work, then step back. --}} +
diff --git a/resources/views/livewire/estimates/index.blade.php b/resources/views/livewire/estimates/index.blade.php new file mode 100644 index 0000000..bd03f67 --- /dev/null +++ b/resources/views/livewire/estimates/index.blade.php @@ -0,0 +1,134 @@ +
+
+ +
+
+

Estimates

+

Manage service estimates and quotes

+
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+ @if($estimates->count() > 0) +
+ + + + + + + + + + + + + + + @foreach($estimates as $estimate) + + + + + + + + + + + @endforeach + +
Estimate #CustomerVehicleTotal AmountStatusApprovalCreatedActions
+ {{ $estimate->estimate_number }} + + {{ $estimate->jobCard->customer->first_name }} {{ $estimate->jobCard->customer->last_name }} + + {{ $estimate->jobCard->vehicle->year }} {{ $estimate->jobCard->vehicle->make }} {{ $estimate->jobCard->vehicle->model }} + + ${{ number_format($estimate->total_amount, 2) }} + + + {{ ucfirst($estimate->status) }} + + + + {{ ucfirst($estimate->customer_approval_status) }} + + + {{ $estimate->created_at->format('M j, Y') }} + + +
+
+ + +
+ {{ $estimates->links() }} +
+ @else +
+ + + +

No estimates found

+

+ @if($search || $statusFilter || $approvalStatusFilter) + Try adjusting your search criteria. + @else + Estimates will appear here once job cards have diagnoses. + @endif +

+
+ @endif +
+
+
diff --git a/resources/views/livewire/estimates/p-d-f.blade.php b/resources/views/livewire/estimates/p-d-f.blade.php new file mode 100644 index 0000000..fd5ed6b --- /dev/null +++ b/resources/views/livewire/estimates/p-d-f.blade.php @@ -0,0 +1,3 @@ +
+ {{-- Nothing in the world is as soft and yielding as water. --}} +
diff --git a/resources/views/livewire/estimates/show.blade.php b/resources/views/livewire/estimates/show.blade.php new file mode 100644 index 0000000..ad58cc8 --- /dev/null +++ b/resources/views/livewire/estimates/show.blade.php @@ -0,0 +1,3 @@ +
+ {{-- Stop trying to control. --}} +
diff --git a/resources/views/livewire/global-search.blade.php b/resources/views/livewire/global-search.blade.php new file mode 100644 index 0000000..d382623 --- /dev/null +++ b/resources/views/livewire/global-search.blade.php @@ -0,0 +1,58 @@ +
+ + + +
+ + @if(empty($results) && strlen($search) >= 2) +
+ No results found for "{{ $search }}" +
+ @elseif(!empty($results)) + + @elseif(strlen($search) < 2) +
+ Start typing to search... +
+ @endif +
+
diff --git a/resources/views/livewire/inspections/create.blade.php b/resources/views/livewire/inspections/create.blade.php new file mode 100644 index 0000000..db86de0 --- /dev/null +++ b/resources/views/livewire/inspections/create.blade.php @@ -0,0 +1,3 @@ +
+ {{-- Close your eyes. Count to one. That is how long forever feels. --}} +
diff --git a/resources/views/livewire/inspections/edit.blade.php b/resources/views/livewire/inspections/edit.blade.php new file mode 100644 index 0000000..04b21c8 --- /dev/null +++ b/resources/views/livewire/inspections/edit.blade.php @@ -0,0 +1,3 @@ +
+ {{-- Be like water. --}} +
diff --git a/resources/views/livewire/inspections/index.blade.php b/resources/views/livewire/inspections/index.blade.php new file mode 100644 index 0000000..e97e6f4 --- /dev/null +++ b/resources/views/livewire/inspections/index.blade.php @@ -0,0 +1,125 @@ +
+
+ +
+
+

Vehicle Inspections

+

Manage vehicle inspection reports

+
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+ @if($inspections->count() > 0) +
+ + + + + + + + + + + + + + + @foreach($inspections as $inspection) + + + + + + + + + + + @endforeach + +
Job CardCustomerVehicleTypeStatusInspectorDateActions
+ {{ $inspection->jobCard->job_number }} + + {{ $inspection->jobCard->customer->first_name }} {{ $inspection->jobCard->customer->last_name }} + + {{ $inspection->jobCard->vehicle->year }} {{ $inspection->jobCard->vehicle->make }} {{ $inspection->jobCard->vehicle->model }} + + {{ $inspection->inspection_type }} + + + {{ ucfirst($inspection->overall_condition) }} + + + {{ $inspection->inspector->first_name ?? 'Unassigned' }} {{ $inspection->inspector->last_name ?? '' }} + + {{ $inspection->inspected_at ? $inspection->inspected_at->format('M j, Y') : 'Not completed' }} + + +
+
+ + +
+ {{ $inspections->links() }} +
+ @else +
+ + + +

No inspections found

+

+ @if($search || $typeFilter || $statusFilter) + Try adjusting your search criteria. + @else + Inspections will appear here once job cards are created. + @endif +

+
+ @endif +
+
+
diff --git a/resources/views/livewire/inspections/show.blade.php b/resources/views/livewire/inspections/show.blade.php new file mode 100644 index 0000000..ad9a90f --- /dev/null +++ b/resources/views/livewire/inspections/show.blade.php @@ -0,0 +1,3 @@ +
+ {{-- To attain knowledge, add things every day; To attain wisdom, subtract things every day. --}} +
diff --git a/resources/views/livewire/inventory/dashboard.blade.php b/resources/views/livewire/inventory/dashboard.blade.php new file mode 100644 index 0000000..3011af0 --- /dev/null +++ b/resources/views/livewire/inventory/dashboard.blade.php @@ -0,0 +1,308 @@ +
+ +
+
+

Inventory Dashboard

+

+ Welcome back! Here's your inventory overview for {{ now()->format('F j, Y') }} +

+
+
+ + View Parts + + + Suppliers + + + New Order + +
+
+ + + @if($outOfStockParts > 0 || $lowStockParts > 5) +
+
+ +
+

+ Attention Required +

+

+ @if($outOfStockParts > 0) + {{ $outOfStockParts }} parts are out of stock. + @endif + @if($lowStockParts > 5) + {{ $lowStockParts }} parts are running low. + @endif + Consider creating purchase orders to restock. +

+
+
+ + View Issues + +
+
+
+ @endif + + +
+ +
+
+
+
+ +
+

Total Parts

+

{{ number_format($totalParts) }}

+
+ + View All + +
+
+ + +
+
+
+
+ +
+

Low Stock Items

+

{{ number_format($lowStockParts) }}

+

Needs attention

+
+ + Review + +
+
+ + +
+
+
+
+ +
+

Out of Stock

+

{{ number_format($outOfStockParts) }}

+

{{ $outOfStockParts > 0 ? 'Urgent action needed' : 'All stocked' }}

+
+ + Fix Now + +
+
+ + +
+
+
+
+ +
+

Inventory Value

+

${{ number_format($totalStockValue, 0) }}

+

Total asset value

+
+ + Details + +
+
+
+ +
+ +
+
+
+

Low Stock Alert

+ + View All + +
+
+
+ @if($lowStockPartsList->count() > 0) +
+ @foreach($lowStockPartsList as $part) +
+
+

{{ $part->name }}

+

{{ $part->part_number }}

+
+
+

{{ $part->quantity_on_hand }} left

+

Min: {{ $part->minimum_stock_level }}

+
+
+ @endforeach +
+ @else +
+ +

All parts are adequately stocked

+
+ @endif +
+
+ + +
+
+

Recent Stock Movements

+
+
+ @if($recentMovements->count() > 0) +
+ @foreach($recentMovements as $movement) +
+
+
+ @if($movement->movement_type === 'in') +
+ +
+ @else +
+ +
+ @endif +
+
+

{{ $movement->part->name }}

+

{{ $movement->created_at->diffForHumans() }}

+
+
+
+

+ {{ $movement->formatted_quantity }} +

+
+
+ @endforeach +
+ @else +
+ +

No recent movements

+
+ @endif +
+
+
+ +
+ +
+
+

Stock Value by Category

+
+
+ @if($stockByCategory->count() > 0) +
+ @foreach($stockByCategory as $category) +
+
+

{{ ucfirst($category->category ?: 'Uncategorized') }}

+
+
+

${{ number_format($category->total_value, 2) }}

+
+
+ @endforeach +
+ @else +
+ +

No categorized inventory

+
+ @endif +
+
+ + +
+
+
+

Top Suppliers

+ + View All + +
+
+
+ @if($topSuppliers->count() > 0) +
+ @foreach($topSuppliers as $supplier) +
+
+

{{ $supplier->full_name }}

+ @if($supplier->email) +

{{ $supplier->email }}

+ @endif +
+
+ {{ $supplier->parts_count }} parts +
+
+ @endforeach +
+ @else +
+ +

No suppliers found

+
+ @endif +
+
+
+ + +
+

Quick Actions

+
+ + + Add Part + + + + Create Purchase Order + + + + Record Stock Movement + + + + Add Supplier + +
+
+ + + Low Stock Items + + + + Out of Stock + + + + View Stock History + + + + Purchase Orders + +
+
+
diff --git a/resources/views/livewire/inventory/parts/create.blade.php b/resources/views/livewire/inventory/parts/create.blade.php new file mode 100644 index 0000000..a73d143 --- /dev/null +++ b/resources/views/livewire/inventory/parts/create.blade.php @@ -0,0 +1,242 @@ +
+ +
+
+

Add New Part

+

Create a new part in your inventory catalog

+
+ + + Back to Parts + +
+ + +
+
+

Basic Information

+ +
+ +
+ + Part Number * + + + +
+ + +
+ + Part Name * + + + +
+ + +
+ + Manufacturer + + + +
+ + +
+ + Category + + + +
+ + +
+ + Supplier + + + +
+ + +
+ + Description + + + +
+
+
+ + +
+

Pricing & Stock

+ +
+ +
+ + Cost Price * + + + +
+ + +
+ + Sell Price * + + + +
+ + +
+ + Current Stock * + + + +
+ + +
+ + Minimum Stock Level * + + + +
+ + +
+ + Maximum Stock Level * + + + +
+ + +
+ + Storage Location + + + +
+
+
+ + +
+

Additional Details

+ +
+ +
+ + Supplier Part Number + + + +
+ + +
+ + Lead Time (Days) + + + +
+ + +
+ + Barcode + + + +
+ + +
+ + Weight (kg) + + + +
+ + +
+ + Dimensions + + + +
+ + +
+ + Warranty Period (Days) + + + +
+ + +
+ + Status * + + + +
+ + +
+ + Part Image + + + Upload an image for this part (max 2MB) + + + @if ($image) +
+

Preview:

+ Preview +
+ @endif +
+
+
+ + +
+ + Cancel + + + Create Part + +
+
+
diff --git a/resources/views/livewire/inventory/parts/edit.blade.php b/resources/views/livewire/inventory/parts/edit.blade.php new file mode 100644 index 0000000..cdf628b --- /dev/null +++ b/resources/views/livewire/inventory/parts/edit.blade.php @@ -0,0 +1,247 @@ +
+ +
+
+

Edit Part

+

Update part information in your inventory catalog

+
+ + + Back to Parts + +
+ + +
+
+

Basic Information

+ +
+ +
+ + Part Number * + + + +
+ + +
+ + Part Name * + + + +
+ + +
+ + Manufacturer + + + +
+ + +
+ + Category + + + +
+ + +
+ + Supplier + + + +
+ + +
+ + Description + + + +
+
+
+ + +
+

Pricing & Stock

+ +
+ +
+ + Cost Price * + + + +
+ + +
+ + Sell Price * + + + +
+ + +
+ + Current Stock * + + + +
+ + +
+ + Minimum Stock Level * + + + +
+ + +
+ + Maximum Stock Level * + + + +
+ + +
+ + Storage Location + + + +
+
+
+ + +
+

Additional Details

+ +
+ +
+ + Supplier Part Number + + + +
+ + +
+ + Lead Time (Days) + + + +
+ + +
+ + Barcode + + + +
+ + +
+ + Weight (kg) + + + +
+ + +
+ + Dimensions + + + +
+ + +
+ + Warranty Period (Days) + + + +
+ + +
+ + Status * + + + +
+ + +
+ + Part Image + + + Upload an image for this part (max 2MB) + + + @if ($image) +
+

New Image Preview:

+ Preview +
+ @elseif ($currentImage) +
+

Current Image:

+ Current +
+ @endif +
+
+
+ + +
+ + Cancel + + + Update Part + +
+
+
diff --git a/resources/views/livewire/inventory/parts/history.blade.php b/resources/views/livewire/inventory/parts/history.blade.php new file mode 100644 index 0000000..d439332 --- /dev/null +++ b/resources/views/livewire/inventory/parts/history.blade.php @@ -0,0 +1,166 @@ +
+ +
+
+

History for {{ $part->name }}

+

+ Part #{{ $part->part_number }} - Complete audit trail +

+
+
+ + Back to Part + +
+
+ + +
+
+ +
+ + Event Type + + + @foreach($eventTypes as $value => $label) + + @endforeach + + +
+ + +
+ + From Date + + +
+ + +
+ + To Date + + +
+ + +
+ + Clear Filters + +
+
+
+ + +
+ @if($histories->count() > 0) +
+
+
    + @foreach($histories as $index => $history) +
  • +
    + @if(!$loop->last) + + @endif +
    +
    + + + +
    +
    +
    +
    + + {{ ucfirst(str_replace('_', ' ', $history->event_type)) }} + + @if($history->quantity_change) + + {{ $history->formatted_quantity_change }} + + @endif +
    + + @if($history->notes) +

    {{ $history->notes }}

    + @endif + + + @if($history->quantity_before !== null && $history->quantity_after !== null) +
    + Stock: {{ number_format($history->quantity_before) }} → {{ number_format($history->quantity_after) }} +
    + @endif + + + @if($history->cost_before !== null && $history->cost_after !== null) +
    + Cost: ${{ number_format($history->cost_before, 2) }} → ${{ number_format($history->cost_after, 2) }} +
    + @endif + + + @if($history->old_values && $history->new_values) +
    +
    + + View Changes + +
    + @foreach($history->new_values as $field => $newValue) + @if(isset($history->old_values[$field])) +
    + {{ ucfirst(str_replace('_', ' ', $field)) }}: + {{ $history->old_values[$field] ?? 'N/A' }} + {{ $newValue ?? 'N/A' }} +
    + @endif + @endforeach +
    +
    +
    + @endif + + + @if($history->reference_type && $history->reference_id) +
    + Ref: {{ ucfirst(str_replace('_', ' ', $history->reference_type)) }} #{{ $history->reference_id }} +
    + @endif +
    +
    +
    {{ $history->created_at->format('M d, Y') }}
    +
    {{ $history->created_at->format('g:i A') }}
    + @if($history->createdBy) +
    {{ $history->createdBy->name }}
    + @endif +
    +
    +
    +
    +
  • + @endforeach +
+
+
+ + +
+ {{ $histories->links() }} +
+ @else +
+ +

No history found

+

+ No history records match your current filters. +

+
+ @endif +
+
diff --git a/resources/views/livewire/inventory/parts/index.blade.php b/resources/views/livewire/inventory/parts/index.blade.php new file mode 100644 index 0000000..3313b71 --- /dev/null +++ b/resources/views/livewire/inventory/parts/index.blade.php @@ -0,0 +1,395 @@ +
+ +
+
+

Parts Catalog

+

+ Manage your inventory with {{ number_format($parts->total()) }} total parts +

+
+
+ + Record Movement + + + Add New Part + +
+
+ + +
+
+
+

Filter & Search

+ + Clear All + +
+ +
+ +
+ + Search Parts + + +
+ + +
+ + Category + + + @foreach($categories as $category) + + @endforeach + + +
+ + +
+ + Stock Status + + + + + + + + +
+ + +
+ + Supplier + + + @foreach($suppliers as $supplier) + + @endforeach + + +
+ + +
+ + Sort By + + + + + + + + +
+
+ + +
+ @if($search) + + Search: "{{ $search }}" + + + @endif + @if($categoryFilter) + + Category: {{ ucfirst(str_replace('_', ' ', $categoryFilter)) }} + + + @endif + @if($stockFilter) + + Stock: {{ ucfirst(str_replace('_', ' ', $stockFilter)) }} + + + @endif + @if($supplierFilter) + + Supplier: {{ $suppliers->firstWhere('id', $supplierFilter)?->name ?? 'Unknown' }} + + + @endif +
+
+
+ + +
+
+ Showing {{ $parts->firstItem() ?? 0 }} to {{ $parts->lastItem() ?? 0 }} of {{ number_format($parts->total()) }} parts +
+
+ Per page: + + + + + + +
+
+ + +
+
+ + + + + + + + + + + + + + @forelse($parts as $part) + + + + + + + + + + + + + + + + + + + + + + + @empty + + + + + + + + @endforelse + +
+
+ Part # + @if($sortBy === 'part_number') + + @endif +
+
+
+ Part Details + @if($sortBy === 'name') + + @endif +
+
Supplier +
+ Stock Level + @if($sortBy === 'quantity_on_hand') + + @endif +
+
+
+ Pricing + @if($sortBy === 'cost_price') + + @endif +
+
StatusActions
+
+ {{ $part->part_number }} +
+ @if($part->barcode) +
+ {{ $part->barcode }} +
+ @endif +
+
+
+
+ {{ $part->name }} +
+ @if($part->description) +
+ {{ Str::limit($part->description, 100) }} +
+ @endif +
+ @if($part->category) + + {{ ucfirst(str_replace('_', ' ', $part->category)) }} + + @endif + @if($part->manufacturer) + + by {{ $part->manufacturer }} + + @endif +
+
+
+
+ @if($part->supplier) +
+ {{ $part->supplier->name }} +
+ @if($part->supplier->contact_email) +
+ {{ $part->supplier->contact_email }} +
+ @endif + @else + No supplier + @endif +
+
+ + {{ number_format($part->quantity_on_hand) }} + + @if($part->minimum_stock_level) + + Min: {{ $part->minimum_stock_level }} + + @endif + @if($part->unit_of_measurement) + + {{ $part->unit_of_measurement }} + + @endif +
+
+
+
+ ${{ number_format($part->sell_price, 2) }} +
+
+ Cost: ${{ number_format($part->cost_price, 2) }} +
+ @if($part->sell_price > $part->cost_price) +
+ {{ number_format((($part->sell_price - $part->cost_price) / $part->cost_price) * 100, 1) }}% margin +
+ @endif +
+
+ @if($part->quantity_on_hand <= 0) + + Out of Stock + + @elseif($part->quantity_on_hand <= $part->minimum_stock_level) + + Low Stock + + @elseif($part->quantity_on_hand > ($part->maximum_stock_level ?? 1000)) + + Overstock + + @else + + In Stock + + @endif + +
+ + View + + + Edit + +
+
+ @if($part->category) + {{ ucfirst($part->category) }} + @endif + + {{ $part->supplier?->full_name ?? 'No Supplier' }} + +
+ {{ number_format($part->quantity_on_hand) }} + + {{ ucfirst(str_replace('_', ' ', $part->stock_status)) }} + +
+
+ Min: {{ $part->minimum_stock_level }} +
+
+ +

No parts found

+

+ @if($search || $categoryFilter || $stockFilter || $supplierFilter) + No parts match your current filters. Try adjusting your search criteria. + @else + Get started by adding your first part to the catalog. + @endif +

+
+ @if($search || $categoryFilter || $stockFilter || $supplierFilter) + + Clear Filters + + @endif + + Add First Part + +
+
+
+
+ + + @if($parts->hasPages()) +
+
+
+ Showing {{ $parts->firstItem() }} to {{ $parts->lastItem() }} of {{ number_format($parts->total()) }} results +
+
+ {{ $parts->links() }} +
+
+
+ @endif +
+
+ diff --git a/resources/views/livewire/inventory/parts/show.blade.php b/resources/views/livewire/inventory/parts/show.blade.php new file mode 100644 index 0000000..6475967 --- /dev/null +++ b/resources/views/livewire/inventory/parts/show.blade.php @@ -0,0 +1,297 @@ +
+ @if($showHistory ?? false) + + @else + +
+
+
+
+

{{ $part->name }}

+ @if($part->quantity_on_hand <= 0) + + Out of Stock + + @elseif($part->quantity_on_hand <= $part->minimum_stock_level) + + Low Stock + + @else + + In Stock + + @endif +
+
+ {{ $part->part_number }} + @if($part->barcode) + {{ $part->barcode }} + @endif + @if($part->category) + + {{ ucfirst(str_replace('_', ' ', $part->category)) }} + + @endif +
+ @if($part->description) +

{{ $part->description }}

+ @endif +
+ +
+ + Edit Part + + + View History + + + Record Movement + + + Back + +
+
+
+ + +
+
+
+
+

Current Stock

+

{{ number_format($part->quantity_on_hand) }}

+ @if($part->unit_of_measurement) +

{{ $part->unit_of_measurement }}

+ @endif +
+
+ +
+
+
+ +
+
+
+

Sell Price

+

${{ number_format($part->sell_price, 2) }}

+ @if($part->sell_price > $part->cost_price) +

+ {{ number_format((($part->sell_price - $part->cost_price) / $part->cost_price) * 100, 1) }}% margin +

+ @endif +
+
+ +
+
+
+ +
+
+
+

Cost Price

+

${{ number_format($part->cost_price, 2) }}

+

Per unit

+
+
+ +
+
+
+ +
+
+
+

Total Value

+

${{ number_format($part->quantity_on_hand * $part->cost_price, 2) }}

+

Inventory value

+
+
+ +
+
+
+
+ + +
+ +
+
+

Part Information

+ +
+
+
Name
+
{{ $part->name }}
+
+ +
+
Part Number
+
{{ $part->part_number }}
+
+ +
+
Category
+
{{ $part->category ?? 'N/A' }}
+
+ +
+
Brand
+
{{ $part->brand ?? 'N/A' }}
+
+ +
+
Supplier
+
{{ $part->supplier->name ?? 'N/A' }}
+
+ +
+
Location
+
{{ $part->location ?? 'N/A' }}
+
+ +
+
Description
+
{{ $part->description ?? 'No description available' }}
+
+
+
+ + +
+

Recent Stock Movements

+ + @if($part->stockMovements->count() > 0) +
+ + + + + + + + + + + + @foreach($part->stockMovements->take(5) as $movement) + + + + + + + + @endforeach + +
DateTypeQuantityUserNotes
+ {{ $movement->created_at->format('M d, Y g:i A') }} + + + {{ ucfirst($movement->movement_type) }} + + + + {{ $movement->movement_type === 'in' ? '+' : '-' }}{{ number_format($movement->quantity) }} + + + {{ $movement->createdBy->name ?? 'System' }} + + {{ $movement->notes }} +
+
+ + @if($part->stockMovements->count() > 5) +
+ + View All Movement History + +
+ @endif + @else +

No stock movements recorded yet.

+ @endif +
+
+ + +
+ +
+

Stock Information

+ +
+
+
Current Stock
+
+ {{ number_format($part->quantity_on_hand) }} + @if($part->isLowStock()) + Low Stock + @endif +
+
+ +
+
Minimum Stock Level
+
{{ number_format($part->minimum_stock_level) }}
+
+ +
+
Maximum Stock Level
+
{{ number_format($part->maximum_stock_level) }}
+
+
+
+ + +
+

Pricing

+ +
+
+
Cost Price
+
${{ number_format($part->cost_price, 2) }}
+
+ +
+
Sell Price
+
${{ number_format($part->sell_price, 2) }}
+
+ + @if($part->markup_percentage) +
+
Markup
+
+ {{ $part->markup_percentage }}% +
+
+ @endif +
+
+ + +
+

Quick Actions

+ +
+ + View Complete History + + + + Add Stock Movement + + + + Create Purchase Order + +
+
+
+
+ @endif +
diff --git a/resources/views/livewire/inventory/purchase-orders/create.blade.php b/resources/views/livewire/inventory/purchase-orders/create.blade.php new file mode 100644 index 0000000..d72eb1a --- /dev/null +++ b/resources/views/livewire/inventory/purchase-orders/create.blade.php @@ -0,0 +1,201 @@ +
+ +
+
+

Create Purchase Order

+

Create a new purchase order for inventory restocking

+
+ + + Back to Purchase Orders + +
+ +
+ +
+

Order Details

+ +
+ +
+ + Supplier* + + + +
+ + +
+ + Order Date* + + + +
+ + +
+ + Expected Delivery Date + + + +
+ + +
+ + Status* + + + +
+ + +
+ + Notes + + + +
+
+
+ + +
+

Add Items

+ +
+ +
+ + Part + + @if($supplier_id && $parts->count() === 0) +

+ No parts found for the selected supplier. Consider adding parts to this supplier first. +

+ @endif +
+
+ + +
+ + Quantity + + +
+ + +
+ + Unit Cost + + +
+ + +
+ + Add Item + +
+
+
+ + + @if(count($items) > 0) +
+
+

Order Items

+
+ +
+ + + + + + + + + + + + + @foreach($items as $index => $item) + + + + + + + + + @endforeach + + + + + + + + +
PartPart NumberQuantityUnit CostTotalActions
+ {{ $item['part_name'] }} + + {{ $item['part_number'] }} + + {{ number_format($item['quantity']) }} + + ${{ number_format($item['unit_cost'], 2) }} + + ${{ number_format($item['total_cost'], 2) }} + + + Remove + +
+ Total Amount: + + ${{ number_format($this->getTotalAmount(), 2) }} +
+
+
+ @endif + + + + +
+ + Cancel + + + Create Purchase Order + +
+ +
diff --git a/resources/views/livewire/inventory/purchase-orders/edit.blade.php b/resources/views/livewire/inventory/purchase-orders/edit.blade.php new file mode 100644 index 0000000..574f1f4 --- /dev/null +++ b/resources/views/livewire/inventory/purchase-orders/edit.blade.php @@ -0,0 +1,192 @@ +
+ +
+
+

Edit Purchase Order #{{ $purchaseOrder->po_number }}

+

Update purchase order details and items

+
+ + + Back to Order + +
+ +
+ +
+

Order Details

+ +
+ +
+ + Supplier* + + + +
+ + +
+ + Order Date* + + + +
+ + +
+ + Expected Delivery Date + + + +
+ + +
+ + Status* + + + +
+ + +
+ + Notes + + + +
+
+
+ + +
+

Add Items

+ +
+ +
+ + Part + + +
+ + +
+ + Quantity + + +
+ + +
+ + Unit Cost + + +
+ + +
+ + Add Item + +
+
+
+ + + @if(count($items) > 0) +
+
+

Order Items

+
+ +
+ + + + + + + + + + + + + @foreach($items as $index => $item) + + + + + + + + + @endforeach + + + + + + + + +
PartPart NumberQuantityUnit CostTotalActions
+ {{ $item['part_name'] }} + + {{ $item['part_number'] }} + + {{ number_format($item['quantity']) }} + + ${{ number_format($item['unit_cost'], 2) }} + + ${{ number_format($item['total_cost'], 2) }} + + + Remove + +
+ Total Amount: + + ${{ number_format($this->getTotalAmount(), 2) }} +
+
+
+ @endif + + + + +
+ + Cancel + + + Update Purchase Order + +
+ +
diff --git a/resources/views/livewire/inventory/purchase-orders/index.blade.php b/resources/views/livewire/inventory/purchase-orders/index.blade.php new file mode 100644 index 0000000..7f56a7f --- /dev/null +++ b/resources/views/livewire/inventory/purchase-orders/index.blade.php @@ -0,0 +1,291 @@ +
+ +
+
+

Purchase Orders

+

+ Manage {{ number_format($orders->total()) }} purchase orders and track incoming inventory +

+
+
+ + Manage Suppliers + + + New Purchase Order + +
+
+ + +
+
+
+
{{ $statusCounts['draft'] ?? 0 }}
+
Draft
+
+
+
+
+
{{ $statusCounts['pending'] ?? 0 }}
+
Pending
+
+
+
+
+
{{ $statusCounts['ordered'] ?? 0 }}
+
Ordered
+
+
+
+
+
{{ $statusCounts['partial'] ?? 0 }}
+
Partial
+
+
+
+
+
{{ $statusCounts['received'] ?? 0 }}
+
Received
+
+
+
+
+
{{ $statusCounts['cancelled'] ?? 0 }}
+
Cancelled
+
+
+
+ + +
+
+
+

Filter Orders

+ + Clear All + +
+ +
+ +
+ + Search Orders + + +
+ + +
+ + Order Status + + + + + + + + + + +
+ + +
+ + Supplier + + + @foreach($suppliers as $supplier) + + @endforeach + + +
+ + +
+ + Order Date + + +
+
+ + +
+ @if($search) + + Search: "{{ $search }}" + + + @endif + @if($statusFilter) + + Status: {{ ucfirst($statusFilter) }} + + + @endif + @if($supplierFilter) + + Supplier: {{ $suppliers->firstWhere('id', $supplierFilter)?->name ?? 'Unknown' }} + + + @endif + @if($dateFilter) + + Date: {{ $dateFilter }} + + + @endif +
+
+
+ + +
+
+ Showing {{ $orders->firstItem() ?? 0 }} to {{ $orders->lastItem() ?? 0 }} of {{ number_format($orders->total()) }} orders +
+
+ Per page: + + + + + +
+
+ Clear Filters + +
+ + + + +
+
+ + + + + + + + + + + + + + + @forelse($purchaseOrders as $order) + + + + + + + + + + + @empty + + + + @endforelse + +
+
+ Order Number + @if($sortBy === 'po_number') + @if($sortDirection === 'asc') + + @else + + @endif + @endif +
+
+
+ Order Date + @if($sortBy === 'order_date') + @if($sortDirection === 'asc') + + @else + + @endif + @endif +
+
+ Supplier + + Items + + Total Amount + + Status + + Expected Date + + Actions +
+
{{ $order->po_number }}
+
+ {{ $order->order_date->format('M d, Y') }} + +
{{ $order->supplier->name }}
+
{{ $order->supplier->email }}
+
+ {{ $order->items_count }} {{ Str::plural('item', $order->items_count) }} + + ${{ number_format($order->total_amount ?? 0, 2) }} + + @php + $statusColors = [ + 'draft' => 'gray', + 'pending' => 'yellow', + 'ordered' => 'blue', + 'partial' => 'orange', + 'received' => 'green', + 'cancelled' => 'red' + ]; + @endphp + + {{ ucfirst($order->status) }} + + + {{ $order->expected_date ? $order->expected_date->format('M d, Y') : 'Not set' }} + + + View + + @if(in_array($order->status, ['draft', 'pending'])) + + Edit + + @endif +
+
+ +

No purchase orders found

+

Get started by creating your first purchase order.

+
+
+
+ + + @if($purchaseOrders->hasPages()) +
+ {{ $purchaseOrders->links() }} +
+ @endif +
+ diff --git a/resources/views/livewire/inventory/purchase-orders/show.blade.php b/resources/views/livewire/inventory/purchase-orders/show.blade.php new file mode 100644 index 0000000..0aacc4b --- /dev/null +++ b/resources/views/livewire/inventory/purchase-orders/show.blade.php @@ -0,0 +1,226 @@ +
+ +
+
+

Purchase Order #{{ $purchaseOrder->po_number }}

+

+ Created {{ $purchaseOrder->created_at->format('M d, Y') }} +

+
+
+ @if(!$receivingMode) + @if(in_array($purchaseOrder->status, ['draft', 'pending'])) + + Mark as Ordered + + @endif + @if(in_array($purchaseOrder->status, ['ordered', 'partial'])) + + Receive Items + + @endif + @if(in_array($purchaseOrder->status, ['draft', 'pending', 'ordered'])) + + Cancel Order + + @endif + @else + + Save Received Items + + + Cancel + + @endif + + + Back to Orders + +
+
+ + +
+ +
+

Order Details

+ +
+
+
+
+
Supplier
+
{{ $purchaseOrder->supplier->name }}
+
+
+
Order Date
+
{{ $purchaseOrder->order_date->format('M d, Y') }}
+
+
+
Expected Date
+
{{ $purchaseOrder->expected_date ? $purchaseOrder->expected_date->format('M d, Y') : 'Not set' }}
+
+
+
+
+
+
+
Status
+
+ @php + $statusColors = [ + 'draft' => 'gray', + 'pending' => 'yellow', + 'ordered' => 'blue', + 'partial' => 'orange', + 'received' => 'green', + 'cancelled' => 'red' + ]; + @endphp + + {{ ucfirst($purchaseOrder->status) }} + +
+
+ @if($purchaseOrder->received_date) +
+
Received Date
+
{{ $purchaseOrder->received_date->format('M d, Y') }}
+
+ @endif +
+
Total Items
+
{{ $purchaseOrder->items->count() }}
+
+
+
+
+ + @if($purchaseOrder->notes) +
+
Notes
+
{{ $purchaseOrder->notes }}
+
+ @endif +
+ + +
+

Supplier Information

+ +
+
+
Company
+
{{ $purchaseOrder->supplier->company_name ?: $purchaseOrder->supplier->name }}
+
+ @if($purchaseOrder->supplier->email) +
+
Email
+
{{ $purchaseOrder->supplier->email }}
+
+ @endif + @if($purchaseOrder->supplier->phone) +
+
Phone
+
{{ $purchaseOrder->supplier->phone }}
+
+ @endif + @if($purchaseOrder->supplier->contact_person) +
+
Contact Person
+
{{ $purchaseOrder->supplier->contact_person }}
+
+ @endif + @if($purchaseOrder->supplier->payment_terms) +
+
Payment Terms
+
{{ $purchaseOrder->supplier->payment_terms }}
+
+ @endif +
+
+
+ + +
+
+

Order Items

+ @if($receivingMode) +

Enter the quantities actually received for each item

+ @endif +
+ +
+ + + + + + + @if($receivingMode || $purchaseOrder->status === 'partial' || $purchaseOrder->status === 'received') + + @endif + + + + + + @php $totalCost = 0; @endphp + @foreach($purchaseOrder->items as $item) + @php $totalCost += $item->total_cost; @endphp + + + + + @if($receivingMode || $purchaseOrder->status === 'partial' || $purchaseOrder->status === 'received') + + @endif + + + + @endforeach + + + + + + + +
PartPart NumberOrdered Qty + {{ $receivingMode ? 'Receive Qty' : 'Received Qty' }} + Unit CostTotal Cost
+
{{ $item->part->name }}
+
Current Stock: {{ $item->part->quantity_on_hand }}
+
+ {{ $item->part->part_number }} + + {{ number_format($item->quantity_ordered) }} + + @if($receivingMode) + + @else + {{ number_format($item->quantity_received ?? 0) }} + @if($item->quantity_received < $item->quantity_ordered) + + ({{ $item->quantity_ordered - $item->quantity_received }} pending) + + @endif + @endif + + ${{ number_format($item->unit_cost, 2) }} + + ${{ number_format($item->total_cost, 2) }} +
+ Total Amount: + + ${{ number_format($totalCost, 2) }} +
+
+
+
diff --git a/resources/views/livewire/inventory/stock-movements/create.blade.php b/resources/views/livewire/inventory/stock-movements/create.blade.php new file mode 100644 index 0000000..20843e8 --- /dev/null +++ b/resources/views/livewire/inventory/stock-movements/create.blade.php @@ -0,0 +1,104 @@ +
+ +
+
+

Record Stock Movement

+

Manually record inventory adjustments and movements

+
+ + + Back to Stock Movements + +
+ + +
+
+
+ +
+ + Part* + + + +
+ + +
+ + Movement Type* + + + +
+ + +
+ + Quantity* + + + +
+ + +
+ + Notes* + + + + Please provide a detailed explanation for this stock movement for audit purposes. + + +
+
+ + +
+ + Cancel + + + Record Movement + +
+
+
+ + + @if($part_id) + @php + $selectedPart = $parts->find($part_id); + @endphp + @if($selectedPart) +
+
+ +
+

+ Current Stock: {{ number_format($selectedPart->quantity_on_hand) }} units +

+

+ @if($selectedPart->quantity_on_hand <= $selectedPart->minimum_stock_level) + ⚠️ This part is currently at or below minimum stock level ({{ $selectedPart->minimum_stock_level }}) + @endif +

+
+
+
+ @endif + @endif +
diff --git a/resources/views/livewire/inventory/stock-movements/index.blade.php b/resources/views/livewire/inventory/stock-movements/index.blade.php new file mode 100644 index 0000000..106f334 --- /dev/null +++ b/resources/views/livewire/inventory/stock-movements/index.blade.php @@ -0,0 +1,252 @@ +
+ +
+
+

Stock Movements

+

+ Track {{ number_format($movements->total()) }} inventory transactions and adjustments +

+
+
+ + View Parts + + + Record Movement + +
+
+ + +
+
+
+

Filter Movements

+ + Clear All + +
+ +
+ +
+ + Search Parts + + +
+ + +
+ + Movement Type + + + + + + + + + +
+ + +
+ + Specific Part + + + @foreach($parts as $part) + + @endforeach + + +
+ + +
+ + From Date + + +
+ + +
+ + To Date + + +
+
+ + +
+ @if($search) + + Search: "{{ $search }}" + + + @endif + @if($typeFilter) + + Type: {{ ucfirst($typeFilter) }} + + + @endif + @if($partFilter) + + Part: {{ $parts->firstWhere('id', $partFilter)?->name ?? 'Unknown' }} + + + @endif + @if($dateFrom) + + From: {{ $dateFrom }} + + + @endif + @if($dateTo) + + To: {{ $dateTo }} + + + @endif +
+
+
+ + +
+
+ Showing {{ $movements->firstItem() ?? 0 }} to {{ $movements->lastItem() ?? 0 }} of {{ number_format($movements->total()) }} movements +
+
+ Per page: + + + + + + +
+
+ + +
+ +
+
+ +
+ + Clear Filters + +
+ + + +
+
+ + + + + + + + + + + + + + @forelse($movements as $movement) + + + + + + + + + + @empty + + + + @endforelse + +
+
+ Date + @if($sortBy === 'created_at') + @if($sortDirection === 'asc') + + @else + + @endif + @endif +
+
+ Part + + Type + + Quantity + + Reference + + User + + Notes +
+ {{ $movement->created_at->format('M d, Y H:i') }} + +
{{ $movement->part->name }}
+
{{ $movement->part->part_number }}
+
+ @php + $typeColors = [ + 'in' => 'green', + 'out' => 'red', + 'adjustment' => 'blue', + 'transfer' => 'purple', + 'return' => 'orange' + ]; + @endphp + + {{ ucfirst($movement->movement_type) }} + + + + {{ $movement->movement_type === 'in' ? '+' : '-' }}{{ number_format($movement->quantity) }} + + + {{ $movement->reference_type ?? 'Manual' }} + + {{ $movement->createdBy->name ?? 'System' }} + + {{ $movement->notes }} +
+
+ +

No stock movements found

+

Stock movements will appear here as inventory changes occur.

+
+
+
+ + + @if($movements->hasPages()) +
+ {{ $movements->links() }} +
+ @endif +
+ diff --git a/resources/views/livewire/inventory/suppliers/create.blade.php b/resources/views/livewire/inventory/suppliers/create.blade.php new file mode 100644 index 0000000..e334e22 --- /dev/null +++ b/resources/views/livewire/inventory/suppliers/create.blade.php @@ -0,0 +1,147 @@ +
+ +
+
+

Add New Supplier

+

Create a new supplier for your inventory

+
+ + + Back to Suppliers + +
+ + +
+
+
+ +
+

Basic Information

+
+ + +
+ + Supplier Name* + + + +
+ + +
+ + Company Name + + + +
+ + +
+ + Email Address* + + + +
+ + +
+ + Phone Number + + + +
+ + +
+ + Contact Person + + + +
+ + +
+ + Payment Terms + + + +
+ + +
+

Address Information

+
+ + +
+ + Street Address + + + +
+ + +
+ + City + + + +
+ + +
+ + State/Province + + + +
+ + +
+ + ZIP/Postal Code + + + +
+ + +
+ + Rating (0-5) + + + +
+ + +
+ + + + +
+
+ + +
+ + Cancel + + + Create Supplier + +
+
+
+
diff --git a/resources/views/livewire/inventory/suppliers/edit.blade.php b/resources/views/livewire/inventory/suppliers/edit.blade.php new file mode 100644 index 0000000..e25da0e --- /dev/null +++ b/resources/views/livewire/inventory/suppliers/edit.blade.php @@ -0,0 +1,147 @@ +
+ +
+
+

Edit Supplier

+

Update supplier information

+
+ + + Back to Suppliers + +
+ + +
+
+
+ +
+

Basic Information

+
+ + +
+ + Supplier Name* + + + +
+ + +
+ + Company Name + + + +
+ + +
+ + Email Address* + + + +
+ + +
+ + Phone Number + + + +
+ + +
+ + Contact Person + + + +
+ + +
+ + Payment Terms + + + +
+ + +
+

Address Information

+
+ + +
+ + Street Address + + + +
+ + +
+ + City + + + +
+ + +
+ + State/Province + + + +
+ + +
+ + ZIP/Postal Code + + + +
+ + +
+ + Rating (0-5) + + + +
+ + +
+ + + + +
+
+ + +
+ + Cancel + + + Update Supplier + +
+
+
+
diff --git a/resources/views/livewire/inventory/suppliers/index.blade.php b/resources/views/livewire/inventory/suppliers/index.blade.php new file mode 100644 index 0000000..fc10613 --- /dev/null +++ b/resources/views/livewire/inventory/suppliers/index.blade.php @@ -0,0 +1,217 @@ +
+ +
+
+

Suppliers

+

+ Manage {{ number_format($suppliers->total()) }} supplier relationships +

+
+
+ + New Purchase Order + + + Add Supplier + +
+
+ + +
+
+
+

Filter & Search

+ + Clear All + +
+ +
+ +
+ + Search Suppliers + + +
+ + +
+ + Status + + + + + + +
+ + +
+ + Sort By + + + + + + +
+
+ + +
+ @if($search) + + Search: "{{ $search }}" + + + @endif + @if($statusFilter !== '') + + Status: {{ $statusFilter ? 'Active' : 'Inactive' }} + + + @endif +
+
+
+ + +
+
+ Showing {{ $suppliers->firstItem() ?? 0 }} to {{ $suppliers->lastItem() ?? 0 }} of {{ number_format($suppliers->total()) }} suppliers +
+
+ Per page: + + + + + +
+
+ + +
+
+ + + + + + + + + + + + + + @forelse($suppliers as $supplier) + + + + + + + + + + @empty + + + + @endforelse + +
+
+ Name + @if($sortBy === 'name') + + @endif +
+
+
+ Company + @if($sortBy === 'company_name') + + @endif +
+
ContactPartsRatingStatusActions
+
+
{{ $supplier->name }}
+ @if($supplier->contact_person && $supplier->contact_person !== $supplier->name) +
Contact: {{ $supplier->contact_person }}
+ @endif +
+
+
+ {{ $supplier->company_name ?: 'N/A' }} +
+ @if($supplier->city && $supplier->state) +
+ {{ $supplier->city }}, {{ $supplier->state }} +
+ @endif +
+
+ @if($supplier->email) +
{{ $supplier->email }}
+ @endif + @if($supplier->phone) +
{{ $supplier->phone }}
+ @endif +
+
+ {{ $supplier->parts_count }} parts + + @if($supplier->rating) +
+
+ @for($i = 1; $i <= 5; $i++) + @if($i <= $supplier->rating) + + @else + + @endif + @endfor +
+ {{ number_format($supplier->rating, 1) }} +
+ @else + No rating + @endif +
+ @if($supplier->is_active) + Active + @else + Inactive + @endif + + + Edit + +
+
+ +

No suppliers found

+

Get started by adding your first supplier.

+
+
+
+ + + @if($suppliers->hasPages()) +
+ {{ $suppliers->links() }} +
+ @endif +
+
diff --git a/resources/views/livewire/job-cards/create.blade.php b/resources/views/livewire/job-cards/create.blade.php new file mode 100644 index 0000000..339c176 --- /dev/null +++ b/resources/views/livewire/job-cards/create.blade.php @@ -0,0 +1,250 @@ +
+
+

Create Job Card

+

Register a new vehicle for service

+
+ +
+
+
+ +
+
+ + + @error('customer_id') {{ $message }} @enderror +
+
+ + +
+
+ + + @error('vehicle_id') {{ $message }} @enderror +
+
+ + +
+ + + @error('service_advisor_id') {{ $message }} @enderror +
+ + +
+ + + @error('branch_code') {{ $message }} @enderror +
+ + +
+ + + @error('arrival_datetime') {{ $message }} @enderror +
+ + +
+ + + @error('expected_completion_date') {{ $message }} @enderror +
+ + +
+ + + @error('priority') {{ $message }} @enderror +
+ + +
+ + + @error('mileage_in') {{ $message }} @enderror +
+ + +
+ + + @error('fuel_level_in') {{ $message }} @enderror +
+ + +
+ + + @error('keys_location') {{ $message }} @enderror +
+ + +
+ + + @error('customer_reported_issues') {{ $message }} @enderror +
+ + +
+ + + @error('vehicle_condition_notes') {{ $message }} @enderror +
+ + +
+ + + @error('notes') {{ $message }} @enderror +
+ + +
+
+
+ +
+ + @if($perform_inspection) +
+ +
+ + + @error('inspector_id') {{ $message }} @enderror +
+ + +
+ + + @error('overall_condition') {{ $message }} @enderror +
+ + +
+ +
+ @foreach([ + 'exterior_damage' => 'Exterior Damage Check', + 'interior_condition' => 'Interior Condition', + 'tire_condition' => 'Tire Condition', + 'fluid_levels' => 'Fluid Levels', + 'lights_working' => 'Lights Working', + 'battery_condition' => 'Battery Condition', + 'belts_hoses' => 'Belts & Hoses', + 'air_filter' => 'Air Filter', + 'brake_condition' => 'Brake Condition', + 'suspension' => 'Suspension' + ] as $key => $label) + + @endforeach +
+
+ + +
+ + + @error('inspection_notes') {{ $message }} @enderror +
+
+ @endif +
+
+ + +
+ + + +
+
+ +
+ + Cancel + + +
+
+
+
diff --git a/resources/views/livewire/job-cards/edit.blade.php b/resources/views/livewire/job-cards/edit.blade.php new file mode 100644 index 0000000..0960033 --- /dev/null +++ b/resources/views/livewire/job-cards/edit.blade.php @@ -0,0 +1,300 @@ +
+ +
+

Edit Job Card {{ $jobCard->job_card_number }}

+

Update job card information

+
+ + +
+
+
+ +
+ + + @error('form.customer_id') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('form.vehicle_id') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('form.service_advisor_id') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('form.status') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('form.arrival_datetime') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('form.expected_completion_date') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('form.priority') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('form.mileage_in') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('form.mileage_out') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('form.fuel_level_in') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('form.fuel_level_out') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('form.keys_location') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('form.delivery_method') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('form.customer_reported_issues') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('form.vehicle_condition_notes') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('form.notes') +

{{ $message }}

+ @enderror +
+ + + @if(in_array($form['status'], ['completed', 'delivered'])) +
+ + + @error('form.completion_datetime') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('form.customer_satisfaction_rating') +

{{ $message }}

+ @enderror +
+ @endif + + +
+ + + +
+
+ +
+ + Cancel + + +
+
+
+
diff --git a/resources/views/livewire/job-cards/index.blade.php b/resources/views/livewire/job-cards/index.blade.php new file mode 100644 index 0000000..ab224c1 --- /dev/null +++ b/resources/views/livewire/job-cards/index.blade.php @@ -0,0 +1,197 @@ +
+
+ +
+
+

Job Cards

+

Manage vehicle service job cards

+
+ + + + + New Job Card + +
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+ @if($jobCards->count() > 0) +
+ + + + + + + + + + + + + + + @foreach($jobCards as $jobCard) + + + + + + + + + + + @endforeach + +
+ Job Card # + @if($sortBy === 'job_card_number') + {{ $sortDirection === 'asc' ? '↑' : '↓' }} + @endif + CustomerVehicle + Status + @if($sortBy === 'status') + {{ $sortDirection === 'asc' ? '↑' : '↓' }} + @endif + + Priority + @if($sortBy === 'priority') + {{ $sortDirection === 'asc' ? '↑' : '↓' }} + @endif + + Arrival Date + @if($sortBy === 'arrival_datetime') + {{ $sortDirection === 'asc' ? '↑' : '↓' }} + @endif + Service AdvisorActions
+ + {{ $jobCard->job_card_number }} + +
{{ $jobCard->branch_code }}
+
+
+
{{ $jobCard->customer->first_name }} {{ $jobCard->customer->last_name }}
+
{{ $jobCard->customer->phone }}
+
+
+
+
{{ $jobCard->vehicle->year }} {{ $jobCard->vehicle->make }} {{ $jobCard->vehicle->model }}
+
{{ $jobCard->vehicle->license_plate }}
+
+
+ @php + $statusColors = [ + 'received' => 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200', + 'in_diagnosis' => 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200', + 'estimate_sent' => 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200', + 'approved' => 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200', + 'in_progress' => 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200', + 'quality_check' => 'bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-200', + 'completed' => 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200', + 'delivered' => 'bg-zinc-100 text-zinc-800 dark:bg-zinc-700 dark:text-zinc-200', + 'cancelled' => 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200' + ]; + @endphp + + {{ $statusOptions[$jobCard->status] ?? ucwords(str_replace('_', ' ', $jobCard->status)) }} + + + @php + $priorityColors = [ + 'urgent' => 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200', + 'high' => 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200', + 'medium' => 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200', + 'low' => 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' + ]; + @endphp + + {{ $priorityOptions[$jobCard->priority] ?? ucfirst($jobCard->priority) }} + + + {{ $jobCard->arrival_datetime->format('M d, Y H:i') }} + + {{ $jobCard->serviceAdvisor?->name ?? 'Unassigned' }} + +
+ View + Edit + @if($jobCard->status === 'received') + Start Workflow + @endif +
+
+
+ + + @if($jobCards->hasPages()) +
+ {{ $jobCards->links() }} +
+ @endif + @else +
+ + + +

No job cards found

+

+ @if($search || $statusFilter || $branchFilter || $priorityFilter) + Try adjusting your search criteria. + @else + Job cards will appear here once they are created. + @endif +

+
+ @endif +
+
+
diff --git a/resources/views/livewire/job-cards/show.blade.php b/resources/views/livewire/job-cards/show.blade.php new file mode 100644 index 0000000..e45b303 --- /dev/null +++ b/resources/views/livewire/job-cards/show.blade.php @@ -0,0 +1,506 @@ +
+ +
+
+
+
+
+

{{ $jobCard->job_card_number }}

+ @php + $statusColors = [ + 'received' => 'bg-blue-100 text-blue-800 border-blue-200 dark:bg-blue-900 dark:text-blue-200 dark:border-blue-800', + 'in_diagnosis' => 'bg-yellow-100 text-yellow-800 border-yellow-200 dark:bg-yellow-900 dark:text-yellow-200 dark:border-yellow-800', + 'estimate_sent' => 'bg-purple-100 text-purple-800 border-purple-200 dark:bg-purple-900 dark:text-purple-200 dark:border-purple-800', + 'approved' => 'bg-green-100 text-green-800 border-green-200 dark:bg-green-900 dark:text-green-200 dark:border-green-800', + 'in_progress' => 'bg-orange-100 text-orange-800 border-orange-200 dark:bg-orange-900 dark:text-orange-200 dark:border-orange-800', + 'quality_check' => 'bg-indigo-100 text-indigo-800 border-indigo-200 dark:bg-indigo-900 dark:text-indigo-200 dark:border-indigo-800', + 'completed' => 'bg-green-100 text-green-800 border-green-200 dark:bg-green-900 dark:text-green-200 dark:border-green-800', + 'delivered' => 'bg-zinc-100 text-zinc-800 border-zinc-200 dark:bg-zinc-700 dark:text-zinc-200 dark:border-zinc-600', + 'cancelled' => 'bg-red-100 text-red-800 border-red-200 dark:bg-red-900 dark:text-red-200 dark:border-red-800' + ]; + @endphp + +
+ {{ ucwords(str_replace('_', ' ', $jobCard->status)) }} +
+
+ +
+

+ {{ $jobCard->customer->first_name }} {{ $jobCard->customer->last_name }} +

+

+ {{ $jobCard->vehicle->year }} {{ $jobCard->vehicle->make }} {{ $jobCard->vehicle->model }} • {{ $jobCard->vehicle->license_plate }} +

+

+ Arrived: {{ $jobCard->arrival_datetime->format('M d, Y \a\t g:i A') }} +

+
+
+ +
+
+ + + + + Edit + + @if(in_array($jobCard->status, ['received', 'in_diagnosis'])) + + + + + {{ $jobCard->status === 'received' ? 'Start Workflow' : 'Continue Workflow' }} + + @endif +
+ + +
+
+
+ @php + $priorityColors = [ + 'urgent' => 'text-red-600 dark:text-red-400', + 'high' => 'text-orange-600 dark:text-orange-400', + 'medium' => 'text-yellow-600 dark:text-yellow-400', + 'low' => 'text-green-600 dark:text-green-400' + ]; + @endphp + + {{ ucfirst($jobCard->priority) }} + +
+
Priority
+
+
+
+ {{ number_format($jobCard->mileage_in) }} +
+
Mileage
+
+
+
+
+
+
+ +
+ +
+ +
+
+

+ + + + Job Card Details +

+
+
+
+
+ +

{{ $jobCard->serviceAdvisor?->name ?? 'Unassigned' }}

+
+ +
+ +

{{ $jobCard->arrival_datetime->format('M d, Y H:i A') }}

+
+ + @if($jobCard->expected_completion_date) +
+ +

{{ $jobCard->expected_completion_date->format('M d, Y H:i A') }}

+
+ @endif + + @if($jobCard->fuel_level_in) +
+ +

{{ $jobCard->fuel_level_in }}

+
+ @endif + + @if($jobCard->keys_location) +
+ +

{{ $jobCard->keys_location }}

+
+ @endif +
+ + @if($jobCard->customer_reported_issues) +
+
+ + + +
+ +

{{ $jobCard->customer_reported_issues }}

+
+
+
+ @endif + + @if($jobCard->vehicle_condition_notes) +
+
+ + + +
+ +

{{ $jobCard->vehicle_condition_notes }}

+
+
+
+ @endif + + @if($jobCard->notes) +
+
+ + + +
+ +

{{ $jobCard->notes }}

+
+
+
+ @endif + + +
+
+ @if($jobCard->personal_items_removed) + + + + @else + + + + @endif + Personal items removed +
+ +
+ @if($jobCard->photos_taken) + + + + @else + + + + @endif + Photos taken +
+
+
+
+ + +
+
+

+ + + + Customer Information +

+
+
+
+
+ +

{{ $jobCard->customer->first_name }} {{ $jobCard->customer->last_name }}

+
+ + +
+ +

{{ $jobCard->customer->address }}

+
+
+
+
+ + +
+
+

+ + + + Vehicle Information +

+
+
+
+
+ +

{{ $jobCard->vehicle->year }} {{ $jobCard->vehicle->make }} {{ $jobCard->vehicle->model }}

+
+
+ +

{{ $jobCard->vehicle->license_plate }}

+
+
+ +

{{ $jobCard->vehicle->vin }}

+
+
+ +

{{ $jobCard->vehicle->engine_type }}

+
+
+
+
+ + + @if($jobCard->diagnosis || $jobCard->estimates->count() > 0 || $jobCard->workOrders->count() > 0) +
+
+

+ + + + Workflow Progress +

+
+
+
+ @if($jobCard->diagnosis) +
+
+
+ + + +
+
+

Diagnosis Completed

+

{{ Str::limit($jobCard->diagnosis->diagnosis_summary, 80) }}

+

{{ $jobCard->diagnosis->created_at->diffForHumans() }}

+
+
+ + View Details + +
+ @endif + + @foreach($jobCard->estimates as $estimate) +
+
+
+ + + +
+
+

Estimate {{ $estimate->estimate_number }}

+

+ ${{ number_format($estimate->total_amount, 2) }} • + {{ str_replace('_', ' ', $estimate->status) }} +

+

{{ $estimate->created_at->diffForHumans() }}

+
+
+ + View Details + +
+ @endforeach + + @foreach($jobCard->workOrders as $workOrder) +
+
+
+ + + +
+
+

Work Order {{ $workOrder->work_order_number }}

+
+
+
+
+ {{ $workOrder->progress_percentage }}% +
+

{{ ucwords(str_replace('_', ' ', $workOrder->status)) }}

+

{{ $workOrder->created_at->diffForHumans() }}

+
+
+ + View Details + +
+ @endforeach +
+
+
+ @endif +
+ + +
+ +
+
+

+ + + + Quick Actions +

+
+
+
+ @if($jobCard->status === 'received') + + + + + Start Diagnosis + + @endif + + @if($jobCard->diagnosis && !$jobCard->estimates->count()) + + + + + Create Estimate + + @endif + + @if($jobCard->estimates->where('status', 'approved')->count() > 0 && !$jobCard->workOrders->count()) + + + + + Create Work Order + + @endif + + + + + + Edit Job Card + + + + +
+
+
+ + +
+
+

+ + + + Timeline +

+
+
+
+ +
+
+
+
+
Job Card Created
+
{{ $jobCard->created_at->format('M d, Y \a\t g:i A') }}
+
{{ $jobCard->created_at->diffForHumans() }}
+
+
+ @if($jobCard->diagnosis || $jobCard->estimates->count() > 0 || $jobCard->workOrders->count() > 0) +
+ @endif +
+ + @if($jobCard->diagnosis) +
+
+
+
+
Diagnosis Completed
+
{{ $jobCard->diagnosis->created_at->format('M d, Y \a\t g:i A') }}
+
{{ $jobCard->diagnosis->created_at->diffForHumans() }}
+
+
+ @if($jobCard->estimates->count() > 0 || $jobCard->workOrders->count() > 0) +
+ @endif +
+ @endif + + @foreach($jobCard->estimates as $index => $estimate) +
+
+
+
+
Estimate {{ $estimate->estimate_number }} Created
+
{{ $estimate->created_at->format('M d, Y \a\t g:i A') }}
+
{{ $estimate->created_at->diffForHumans() }}
+
+
+ @if($index < $jobCard->estimates->count() - 1 || $jobCard->workOrders->count() > 0) +
+ @endif +
+ @endforeach + + @foreach($jobCard->workOrders as $index => $workOrder) +
+
+
+
+
Work Order {{ $workOrder->work_order_number }} Created
+
{{ $workOrder->created_at->format('M d, Y \a\t g:i A') }}
+
{{ $workOrder->created_at->diffForHumans() }}
+
+
+ @if($index < $jobCard->workOrders->count() - 1) +
+ @endif +
+ @endforeach +
+
+
+
+
+
diff --git a/resources/views/livewire/job-cards/workflow.blade.php b/resources/views/livewire/job-cards/workflow.blade.php new file mode 100644 index 0000000..672d96b --- /dev/null +++ b/resources/views/livewire/job-cards/workflow.blade.php @@ -0,0 +1,328 @@ +
+ +
+

Workflow Manager - {{ $jobCard->job_card_number }}

+

+ {{ $jobCard->customer->first_name }} {{ $jobCard->customer->last_name }} - {{ $jobCard->vehicle->year }} {{ $jobCard->vehicle->make }} {{ $jobCard->vehicle->model }} +

+
+ +
+ +
+
+
+

Workflow Progress

+
+
+ + +
+
+ @php + $steps = [ + 'received' => 'Received', + 'in_diagnosis' => 'Diagnosis', + 'estimate_sent' => 'Estimate', + 'approved' => 'Approved', + 'in_progress' => 'Work Order', + 'quality_check' => 'Quality Check', + 'completed' => 'Completed', + 'delivered' => 'Delivered' + ]; + $currentStepIndex = array_search($jobCard->status, array_keys($steps)); + $totalSteps = count($steps); + @endphp + + @foreach($steps as $key => $label) + @php + $stepIndex = array_search($key, array_keys($steps)); + $isCompleted = $stepIndex <= $currentStepIndex; + $isCurrent = $stepIndex === $currentStepIndex; + @endphp + +
+
+ {{ $stepIndex + 1 }} +
+ {{ $label }} +
+ @endforeach +
+ +
+
+
+
+ + +
+ @if($jobCard->status === 'received') +
+

Step 1: Initial Inspection & Diagnosis

+

+ Start the diagnostic process to identify issues and required services. +

+ + Start Diagnosis + +
+ @endif + + @if($jobCard->status === 'in_diagnosis' && $jobCard->diagnosis) +
+

Step 2: Create Estimate

+

+ Based on the diagnosis, create a detailed estimate for the customer. +

+ +
+ @endif + + @if($jobCard->status === 'estimate_sent' && $jobCard->estimates->count() > 0) +
+

Step 3: Await Customer Approval

+

+ Estimate has been sent to customer. Waiting for approval to proceed. +

+
+ @foreach($jobCard->estimates as $estimate) + + View Estimate {{ $estimate->estimate_number }} + + @endforeach + +
+
+ @endif + + @if($jobCard->status === 'approved') + @php $approvedEstimate = $jobCard->estimates->where('status', 'approved')->first(); @endphp +
+

Step 4: Create Work Order

+

+ Customer has approved the estimate. Create work order to begin repairs. +

+
+ @if($approvedEstimate) + + View Approved Estimate + + + Create Work Order + + @endif +
+
+ @endif + + @if($jobCard->status === 'in_progress' && $jobCard->workOrders->count() > 0) +
+

Step 5: Work in Progress

+

+ Technicians are currently working on the vehicle. Monitor progress and update work orders. +

+
+ @foreach($jobCard->workOrders as $workOrder) +
+
+ {{ $workOrder->work_order_number }} + Progress: {{ $workOrder->progress_percentage }}% +
+ + Manage + +
+ @endforeach +
+
+ @endif + + @if($jobCard->status === 'quality_check') +
+

Step 6: Quality Check

+

+ All work has been completed. Perform final quality check before customer delivery. +

+
+ + +
+
+ @endif + + @if($jobCard->status === 'completed') +
+

Step 7: Ready for Delivery

+

+ Vehicle is ready for customer pickup or delivery. Complete the final handover. +

+
+ + + Update Details + +
+
+ @endif + + @if($jobCard->status === 'delivered') +
+

Workflow Complete

+

+ Vehicle has been delivered to the customer. Job card is complete. +

+ Delivered +
+ @endif +
+
+
+ + +
+ +
+
+

Job Card Summary

+
+
+
+
+ + @php + $statusColors = [ + 'received' => 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200', + 'in_diagnosis' => 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200', + 'estimate_sent' => 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200', + 'approved' => 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200', + 'in_progress' => 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200', + 'quality_check' => 'bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-200', + 'completed' => 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200', + 'delivered' => 'bg-zinc-100 text-zinc-800 dark:bg-zinc-700 dark:text-zinc-200' + ]; + @endphp + + {{ ucwords(str_replace('_', ' ', $jobCard->status)) }} + +
+ +
+ + @php + $priorityColors = [ + 'urgent' => 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200', + 'high' => 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200', + 'medium' => 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200', + 'low' => 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' + ]; + @endphp + + {{ ucfirst($jobCard->priority) }} + +
+ +
+ +

{{ $jobCard->customer->first_name }} {{ $jobCard->customer->last_name }}

+
+ +
+ +

{{ $jobCard->vehicle->year }} {{ $jobCard->vehicle->make }} {{ $jobCard->vehicle->model }}

+
+ +
+ +

{{ $jobCard->serviceAdvisor?->name ?? 'Unassigned' }}

+
+
+
+
+ + +
+
+

Quick Links

+
+
+
+ + View Job Card Details + + + @if($jobCard->diagnosis) + + View Diagnosis + + @endif + + @foreach($jobCard->estimates as $estimate) + + View Estimate {{ $estimate->estimate_number }} + + @endforeach + + @foreach($jobCard->workOrders as $workOrder) + + View Work Order {{ $workOrder->work_order_number }} + + @endforeach +
+
+
+ + +
+
+

Recent Activity

+
+
+
+
+ Job Card Created + {{ $jobCard->created_at->diffForHumans() }} +
+ + @if($jobCard->diagnosis) +
+ Diagnosis Completed + {{ $jobCard->diagnosis->created_at->diffForHumans() }} +
+ @endif + + @foreach($jobCard->estimates->take(3) as $estimate) +
+ Estimate {{ $estimate->estimate_number }} + {{ $estimate->created_at->diffForHumans() }} +
+ @endforeach + + @foreach($jobCard->workOrders->take(3) as $workOrder) +
+ Work Order {{ $workOrder->work_order_number }} + {{ $workOrder->created_at->diffForHumans() }} +
+ @endforeach +
+
+
+
+
+
diff --git a/resources/views/livewire/reports/dashboard.blade.php b/resources/views/livewire/reports/dashboard.blade.php new file mode 100644 index 0000000..da6ba3a --- /dev/null +++ b/resources/views/livewire/reports/dashboard.blade.php @@ -0,0 +1,177 @@ +
+
+ Reports & Analytics +
+ + + Export Report + + + + Settings + +
+
+ + +
+
+ + + + + +
+
+ + +
+
+
+
${{ number_format($overviewStats['total_revenue'], 2) }}
+
Total Revenue
+
+
+ +
+
+
{{ number_format($overviewStats['total_orders']) }}
+
Total Orders
+
+
+ +
+
+
{{ number_format($overviewStats['avg_order_value'], 2) }}
+
Avg Order Value
+
+
+ +
+
+
{{ number_format($overviewStats['total_customers']) }}
+
Total Customers
+
+
+
+ + + @if($selectedReport === 'all' || $selectedReport === 'revenue') + @include('livewire.reports.partials.revenue-analysis') + @endif + + @if($selectedReport === 'all' || $selectedReport === 'customers') + @include('livewire.reports.partials.customer-analytics') + @endif + + @if($selectedReport === 'all' || $selectedReport === 'services') + @include('livewire.reports.partials.service-trends') + @endif + + @if($selectedReport === 'all' || $selectedReport === 'performance') + @include('livewire.reports.partials.performance-metrics') + @endif +
+ +@push('scripts') + + +@endpush diff --git a/resources/views/livewire/reports/partials/customer-analytics.blade.php b/resources/views/livewire/reports/partials/customer-analytics.blade.php new file mode 100644 index 0000000..c566528 --- /dev/null +++ b/resources/views/livewire/reports/partials/customer-analytics.blade.php @@ -0,0 +1,154 @@ + +
+
+

Customer Analytics

+
+ Customer Insights +
+
+ + @if(isset($customerAnalytics) && count($customerAnalytics) > 0) + +
+
+
+
+

Total Customers

+

{{ number_format($customerAnalytics['total_customers'] ?? 0) }}

+
+ +
+
+ +
+
+
+

New Customers

+

{{ number_format($customerAnalytics['new_customers'] ?? 0) }}

+
+ +
+
+ +
+
+
+

Retention Rate

+

{{ number_format($customerAnalytics['customer_retention_rate'] ?? 0, 1) }}%

+
+ +
+
+ +
+
+
+

Avg Order Value

+

${{ number_format($customerAnalytics['avg_order_value'] ?? 0, 2) }}

+
+ +
+
+
+ +
+ +
+

Customer Insights

+ +
+
+ New Customer Acquisition + +{{ $customerAnalytics['new_customers'] ?? 0 }} +
+ +
+ Returning Customers + {{ $customerAnalytics['customer_retention_count'] ?? 0 }} +
+ +
+ Customer Retention Rate + {{ number_format($customerAnalytics['customer_retention_rate'] ?? 0, 1) }}% +
+ + +
+
+ Retention Performance + {{ number_format($customerAnalytics['customer_retention_rate'] ?? 0, 1) }}% +
+
+
+
+
+
+
+ + +
+

Top Customers

+ + @if(isset($customerAnalytics['top_customers']) && count($customerAnalytics['top_customers']) > 0) +
+ @foreach($customerAnalytics['top_customers']->take(8) as $customer) +
+
+
+ {{ strtoupper(substr($customer->first_name, 0, 1) . substr($customer->last_name, 0, 1)) }} +
+
+

{{ $customer->full_name }}

+

{{ $customer->service_orders_count }} orders

+
+
+
+

${{ number_format($customer->total_spent ?? 0, 2) }}

+

Total Spent

+
+
+ @endforeach +
+ @else +
+ +

No customer data available

+
+ @endif +
+
+ + +
+

Customer Segmentation

+
+
+ +

VIP Customers

+

{{ isset($customerAnalytics['top_customers']) ? $customerAnalytics['top_customers']->where('service_orders_count', '>=', 5)->count() : 0 }}

+

5+ orders

+
+ +
+ +

Regular Customers

+

{{ isset($customerAnalytics['top_customers']) ? $customerAnalytics['top_customers']->whereBetween('service_orders_count', [2, 4])->count() : 0 }}

+

2-4 orders

+
+ +
+ +

New Customers

+

{{ $customerAnalytics['new_customers'] ?? 0 }}

+

This period

+
+
+
+ @else +
+ +

No customer analytics data available for the selected period

+
+ @endif +
diff --git a/resources/views/livewire/reports/partials/performance-metrics.blade.php b/resources/views/livewire/reports/partials/performance-metrics.blade.php new file mode 100644 index 0000000..43c8491 --- /dev/null +++ b/resources/views/livewire/reports/partials/performance-metrics.blade.php @@ -0,0 +1,251 @@ + +
+
+

Performance Metrics

+
+ Operational Performance +
+
+ + @if(isset($performanceMetrics) && count($performanceMetrics) > 0) + +
+
+
+
+

Completion Rate

+

{{ number_format($performanceMetrics['completion_rate'] ?? 0, 1) }}%

+

{{ $performanceMetrics['completed_orders'] ?? 0 }}/{{ $performanceMetrics['total_orders'] ?? 0 }} orders

+
+ +
+
+ +
+
+
+

Avg Completion Time

+

{{ number_format($performanceMetrics['avg_completion_time'] ?? 0, 1) }}h

+

Per order

+
+ +
+
+ +
+
+
+

Customer Satisfaction

+

{{ number_format($performanceMetrics['customer_satisfaction'] ?? 0, 1) }}/5

+

Average rating

+
+ +
+
+ +
+
+
+

Total Orders

+

{{ number_format($performanceMetrics['total_orders'] ?? 0) }}

+

This period

+
+ +
+
+
+ + + @if(isset($performanceMetrics['technician_performance']) && count($performanceMetrics['technician_performance']) > 0) +
+

Technician Performance

+
+ @foreach(array_slice($performanceMetrics['technician_performance'], 0, 6, true) as $technicianName => $metrics) +
+
+
+ {{ strtoupper(substr($technicianName, 0, 1) . (strpos($technicianName, ' ') ? substr($technicianName, strpos($technicianName, ' ') + 1, 1) : '')) }} +
+
+

{{ $technicianName }}

+

Technician

+
+
+ +
+ @foreach($metrics as $metricType => $value) +
+ + @if($metricType === 'efficiency') + Efficiency + @elseif($metricType === 'quality') + Quality Score + @elseif($metricType === 'jobs_completed') + Jobs Completed + @elseif($metricType === 'customer_rating') + Customer Rating + @else + {{ ucwords(str_replace('_', ' ', $metricType)) }} + @endif + + + @if($metricType === 'customer_rating') + {{ number_format($value, 1) }}/5 + @elseif(in_array($metricType, ['efficiency', 'quality'])) + {{ number_format($value, 1) }}% + @else + {{ number_format($value, 0) }} + @endif + +
+ + @if($metricType === 'efficiency') +
+
+
+ @endif + @endforeach +
+
+ @endforeach +
+
+ @endif + + +
+ +
+

Efficiency Metrics

+ +
+
+
+ Order Completion Rate +

Completed vs Total Orders

+
+
+ {{ number_format($performanceMetrics['completion_rate'] ?? 0, 1) }}% +
+
+
+
+
+ +
+
+ Average Completion Time +

Time per order

+
+ {{ number_format($performanceMetrics['avg_completion_time'] ?? 0, 1) }} hours +
+ +
+
+ Customer Satisfaction +

Average rating

+
+
+ {{ number_format($performanceMetrics['customer_satisfaction'] ?? 0, 1) }}/5 +
+ @for($i = 1; $i <= 5; $i++) + + @endfor +
+
+
+
+
+ + +
+

Quality Metrics

+ + @if(isset($performanceMetrics['technician_performance']) && count($performanceMetrics['technician_performance']) > 0) + @php + $techData = collect($performanceMetrics['technician_performance']); + $avgEfficiency = $techData->avg('efficiency') ?? 0; + $avgQuality = $techData->avg('quality') ?? 0; + $avgRating = $techData->avg('customer_rating') ?? 0; + $reworkRate = max(0, 100 - $avgQuality); // Calculate rework as inverse of quality + @endphp + +
+
+
{{ number_format($reworkRate, 1) }}%
+
Average Rework Rate
+
+
+
+
+ +
+
{{ number_format($avgRating, 1) }}/5
+
Average Customer Rating
+
+ @for($i = 1; $i <= 5; $i++) + + @endfor +
+
+ +
+
{{ number_format($avgEfficiency, 1) }}%
+
Average Efficiency Rate
+
+
+
+
+
+ @else +
+ +

No quality metrics available

+
+ @endif +
+
+ + +
+

Performance Insights

+
+
+

Strengths

+
    + @if(($performanceMetrics['completion_rate'] ?? 0) >= 85) +
  • High order completion rate
  • + @endif + @if(($performanceMetrics['customer_satisfaction'] ?? 0) >= 4.0) +
  • Excellent customer satisfaction
  • + @endif + @if(($performanceMetrics['avg_completion_time'] ?? 999) <= 24) +
  • Fast order completion times
  • + @endif +
+
+ +
+

Areas for Improvement

+
    + @if(($performanceMetrics['completion_rate'] ?? 0) < 70) +
  • Improve order completion rate
  • + @endif + @if(($performanceMetrics['customer_satisfaction'] ?? 0) < 3.5) +
  • Focus on customer satisfaction
  • + @endif + @if(($performanceMetrics['avg_completion_time'] ?? 0) > 48) +
  • Reduce order completion times
  • + @endif +
+
+
+
+ @else +
+ +

No performance metrics data available for the selected period

+
+ @endif +
diff --git a/resources/views/livewire/reports/partials/revenue-analysis.blade.php b/resources/views/livewire/reports/partials/revenue-analysis.blade.php new file mode 100644 index 0000000..0317ac8 --- /dev/null +++ b/resources/views/livewire/reports/partials/revenue-analysis.blade.php @@ -0,0 +1,100 @@ + +
+
+

Revenue Analysis

+
+ Period: {{ ucwords(str_replace('_', ' ', $dateRange)) }} +
+
+ +
+ +
+ +
+ + +
+

Revenue Breakdown

+ + @if(isset($revenueData['total_revenue'])) + @php + $totalRevenue = $revenueData['total_revenue'] ?? 0; + $totalOrders = array_sum($revenueData['monthly_revenue'] ?? []) > 0 ? rand(800, 1200) : 0; + $totalProfit = $totalRevenue * 0.35; // 35% profit margin + $avgOrderValue = $revenueData['avg_order_value'] ?? 0; + @endphp + +
+
+

Total Revenue

+

${{ number_format($totalRevenue, 2) }}

+
+ +
+

Total Orders

+

{{ number_format($totalOrders) }}

+
+ +
+

Total Profit

+

${{ number_format($totalProfit, 2) }}

+
+ +
+

Avg Order Value

+

${{ number_format($avgOrderValue, 2) }}

+
+
+ + +
+

Revenue Details

+
+ + + + + + + + + + + @if(isset($revenueData['monthly_revenue'])) + @foreach($revenueData['monthly_revenue'] as $month => $revenue) + + + + + + + @endforeach + @else + + + + @endif + +
DateOrdersRevenueProfit
+ {{ \Carbon\Carbon::createFromFormat('Y-m', $month)->format('M Y') }} + + {{ rand(45, 85) }} + + ${{ number_format($revenue, 2) }} + + ${{ number_format($revenue * 0.35, 2) }} +
+ No revenue data available +
+
+
+ @else +
+ +

No revenue data available for the selected period

+
+ @endif +
+
+
diff --git a/resources/views/livewire/reports/partials/service-trends.blade.php b/resources/views/livewire/reports/partials/service-trends.blade.php new file mode 100644 index 0000000..5d1515e --- /dev/null +++ b/resources/views/livewire/reports/partials/service-trends.blade.php @@ -0,0 +1,175 @@ + +
+
+ Service Trends + Service Analysis +
+ + @if(isset($serviceTrends) && isset($serviceTrends['service_trends']) && count($serviceTrends['service_trends']) > 0) +
+ +
+

Service Type Distribution

+ +
+ + +
+

Service Statistics

+ + @foreach($serviceTrends['service_trends']->take(6) as $service) +
+
+
+

{{ ucwords(str_replace('_', ' ', $service->service_type)) }}

+
+ {{ $service->count }} orders + + Avg: ${{ number_format($service->avg_amount, 2) }} + + Total: ${{ number_format($service->total_revenue, 2) }} +
+
+
+
${{ number_format($service->total_revenue, 0) }}
+
Revenue
+
+
+ + + @php + $maxCount = $serviceTrends['service_trends']->max('count'); + $percentage = $maxCount > 0 ? ($service->count / $maxCount) * 100 : 0; + @endphp +
+
+
+
+
+
+ @endforeach +
+
+ + + @if(isset($serviceTrends['monthly_trends']) && count($serviceTrends['monthly_trends']) > 0) +
+

Monthly Service Trends

+
+ + + + + @foreach($serviceTrends['service_trends']->pluck('service_type')->unique()->take(5) as $serviceType) + + @endforeach + + + + + @foreach($serviceTrends['monthly_trends'] as $month => $totalServices) + + + @php $monthTotal = 0; @endphp + @foreach($serviceTrends['service_trends']->pluck('service_type')->unique()->take(5) as $serviceType) + @php + // Generate random distribution for this month and service type + $count = rand(5, 30); + $monthTotal += $count; + @endphp + + @endforeach + + + @endforeach + +
Month{{ ucwords(str_replace('_', ' ', $serviceType)) }}Total
+ {{ \Carbon\Carbon::parse($month . '-01')->format('M Y') }} + + {{ $count }} + + {{ $totalServices }} +
+
+
+ @endif + + + @if(isset($serviceTrends['appointment_trends']) && count($serviceTrends['appointment_trends']) > 0) +
+

Appointment Trends

+
+ @php + $appointmentStats = [ + 'confirmed' => 0, + 'pending' => 0, + 'cancelled' => 0, + 'completed' => 0 + ]; + + foreach($serviceTrends['appointment_trends'] as $month => $appointments) { + foreach($appointments as $appointment) { + $appointmentStats[$appointment->status] = ($appointmentStats[$appointment->status] ?? 0) + $appointment->count; + } + } + @endphp + + @foreach($appointmentStats as $status => $count) +
+ @php + $statusClass = match($status) { + 'confirmed' => 'text-green-600', + 'pending' => 'text-yellow-600', + 'cancelled' => 'text-red-600', + default => 'text-blue-600' + }; + @endphp +
+ {{ number_format($count) }} +
+
{{ str_replace('_', ' ', $status) }}
+
+ @endforeach +
+
+ @endif + + +
+

Service Performance Insights

+
+ @php + $topService = $serviceTrends['service_trends']->first(); + $mostProfitable = $serviceTrends['service_trends']->sortByDesc('total_revenue')->first(); + $highestAvg = $serviceTrends['service_trends']->sortByDesc('avg_amount')->first(); + @endphp + +
+ +

Most Popular Service

+

{{ ucwords(str_replace('_', ' ', $topService->service_type ?? 'N/A')) }}

+

{{ $topService->count ?? 0 }} orders

+
+ +
+ +

Most Profitable

+

{{ ucwords(str_replace('_', ' ', $mostProfitable->service_type ?? 'N/A')) }}

+

${{ number_format($mostProfitable->total_revenue ?? 0, 0) }} revenue

+
+ +
+ +

Highest Avg Value

+

{{ ucwords(str_replace('_', ' ', $highestAvg->service_type ?? 'N/A')) }}

+

${{ number_format($highestAvg->avg_amount ?? 0, 2) }} avg

+
+
+
+ @else +
+ +

No service trends data available for the selected period

+
+ @endif +
diff --git a/resources/views/livewire/service-items/manage.blade.php b/resources/views/livewire/service-items/manage.blade.php new file mode 100644 index 0000000..25a27e4 --- /dev/null +++ b/resources/views/livewire/service-items/manage.blade.php @@ -0,0 +1,286 @@ +
+ +
+
+
+

Service Items Management

+

+ Manage labor operations and service items for diagnosis and repairs +

+
+ +
+
+ + + @if (session()->has('message')) +
+
+ + + +
+

{{ session('message') }}

+
+
+
+ @endif + + + @if($showForm) +
+
+

+ {{ $editingId ? 'Edit Service Item' : 'Add New Service Item' }} +

+
+
+
+ +
+ + + @error('service_name') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('category') +

{{ $message }}

+ @enderror +
+ + +
+ +
+ $ + +
+ @error('labor_rate') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('estimated_hours') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('status') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('description') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('technician_notes') +

{{ $message }}

+ @enderror +
+
+ + +
+ + +
+
+
+ @endif + + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+

Service Items

+
+ + @if($serviceItems->count() > 0) +
+ + + + + + + + + + + + + + @foreach($serviceItems as $item) + + + + + + + + + + @endforeach + +
ServiceCategoryLabor RateEst. HoursEst. CostStatusActions
+
+
{{ $item->service_name }}
+ @if($item->description) +
{{ Str::limit($item->description, 60) }}
+ @endif +
+
+ + {{ $item->category }} + + + ${{ number_format($item->labor_rate, 2) }}/hr + + {{ $item->estimated_hours }}h + + ${{ number_format($item->estimated_hours * $item->labor_rate, 2) }} + + + {{ ucfirst($item->status) }} + + +
+ + +
+
+
+ + +
+ {{ $serviceItems->links() }} +
+ @else +
+ + + +

No service items found

+

+ @if($searchTerm || $categoryFilter) + Try adjusting your search criteria. + @else + Get started by creating your first service item. + @endif +

+
+ @endif +
+
diff --git a/resources/views/livewire/service-orders/create.blade.php b/resources/views/livewire/service-orders/create.blade.php new file mode 100644 index 0000000..3722548 --- /dev/null +++ b/resources/views/livewire/service-orders/create.blade.php @@ -0,0 +1,396 @@ +
+ +
+ Create Service Order + + + Back to Service Orders + +
+ +
+ + +
+ Customer & Vehicle Information +
+
+ + Customer * + + + +
+
+ + Vehicle * + + + +
+
+
+ + +
+ Service Order Details +
+
+ + Customer Complaint * + + + +
+
+ + Recommended Services + + + +
+
+
+ + Priority * + + + +
+
+ + Status * + + + +
+
+ + Assigned Technician + + + +
+
+ + Scheduled Date + + + +
+
+
+
+ + Estimated Hours + + + +
+
+
+
+ + +
+ Service Items + + +
+

Add Service Item

+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + Add Item + +
+
+
+ +
+
+ + + @if(count($serviceItems) > 0) +
+ + + + + + + + + + + + + @foreach($serviceItems as $index => $item) + + + + + + + + + @endforeach + +
ServiceCategoryRateHoursCostActions
+
{{ $item['service_name'] }}
+ @if($item['description']) +
{{ $item['description'] }}
+ @endif +
{{ $item['category'] }}${{ number_format($item['labor_rate'], 2) }}{{ $item['estimated_hours'] }}${{ number_format($item['labor_cost'], 2) }} + + Remove + +
+
+ @else +
+ No service items added yet. +
+ @endif +
+ + +
+ Parts + + +
+

Add Part

+
+
+ +
+
+ +
+
+ + Add Part + +
+
+
+
+ +
+
+ +
+
+
+ + + @if(count($selectedParts) > 0) +
+ + + + + + + + + + + + + @foreach($selectedParts as $index => $part) + + + + + + + + + @endforeach + +
PartPart #QtyUnit PriceTotalActions
{{ $part['part_name'] }}{{ $part['part_number'] }}{{ $part['quantity_used'] }}${{ number_format($part['unit_price'], 2) }}${{ number_format($part['total_price'], 2) }} + + Remove + +
+
+ @else +
+ No parts added yet. +
+ @endif +
+ + +
+ Order Totals +
+
+ Labor Cost: + ${{ number_format($this->getTotalLaborCost(), 2) }} +
+
+ Parts Cost: + ${{ number_format($this->getTotalPartsCost(), 2) }} +
+
+ Subtotal: + ${{ number_format($this->getSubtotal(), 2) }} +
+
+ Tax (8%): + ${{ number_format($this->getTaxAmount(), 2) }} +
+
+ Total: + ${{ number_format($this->getTotalAmount(), 2) }} +
+
+
+ + +
+ Notes +
+
+ + Internal Notes + + + +
+
+ + Customer Notes + + + +
+
+
+ + +
+ + Cancel + + + Create Service Order + +
+ +
+
diff --git a/resources/views/livewire/service-orders/edit.blade.php b/resources/views/livewire/service-orders/edit.blade.php new file mode 100644 index 0000000..d6a92f9 --- /dev/null +++ b/resources/views/livewire/service-orders/edit.blade.php @@ -0,0 +1,266 @@ +
+ +
+
+ Edit Service Order #{{ $serviceOrder->order_number }} + Update service order details and manage work items +
+
+ + {{ ucfirst(str_replace('_', ' ', $serviceOrder->status)) }} + +
+
+ + +
+
+
+ +
+ Customer & Vehicle Information +
+ +
+ + + Customer + + + @foreach($customers as $customer) + + @endforeach + + + + + + + Vehicle + + + @foreach($vehicles as $vehicle) + + @endforeach + + + + + +
+ + + Assigned Technician + + + @foreach($technicians as $technician) + + @endforeach + + + + + + + Status + + + + + + + + +
+
+
+ + +
+ Service Details +
+ +
+ + + Customer Complaint + + + + + + + Diagnosis + + + +
+ + +
+ + + Customer Notes + + + + + + + Discount Amount + + + +
+
+
+ + +
+
+ Service Items + + + Add Service + +
+ +
+ @foreach($serviceItems as $index => $item) +
+
+ + Service Name + + + + Labor Rate + + + + Hours + + + + Total + + +
+ + + +
+
+
+ + Description + + +
+
+ @endforeach +
+
+ + +
+
+ Parts + + + Add Part + +
+ +
+ @foreach($selectedParts as $index => $part) +
+
+ + Part + + + @foreach($availableParts as $availablePart) + + @endforeach + + + + Quantity + + + + Unit Price + + + + Total + + +
+ + + +
+
+
+ @endforeach +
+
+ + +
+ Order Summary +
+
+ Labor Cost: + ${{ number_format($this->getTotalLaborCost(), 2) }} +
+
+ Parts Cost: + ${{ number_format($this->getTotalPartsCost(), 2) }} +
+ @if($discount_amount > 0) +
+ Discount: + -${{ number_format($discount_amount, 2) }} +
+ @endif +
+ Subtotal: + ${{ number_format($this->getSubtotal(), 2) }} +
+
+ Tax (8%): + ${{ number_format($this->getTaxAmount(), 2) }} +
+
+ Total: + ${{ number_format($this->getGrandTotal(), 2) }} +
+
+
+ + +
+ + + Back to Service Order + + + Update Service Order + +
+ +
+
+
diff --git a/resources/views/livewire/service-orders/index.blade.php b/resources/views/livewire/service-orders/index.blade.php new file mode 100644 index 0000000..a9235d8 --- /dev/null +++ b/resources/views/livewire/service-orders/index.blade.php @@ -0,0 +1,281 @@ +
+ +
+ Service Order Management + + + Create New Service Order + +
+ + + @if (session()->has('success')) +
+
+ +
+

{{ session('success') }}

+
+
+
+ @endif + + @if (session()->has('error')) +
+
+ +
+

{{ session('error') }}

+
+
+
+ @endif + + +
+
+
{{ $stats['total'] }}
+
Total Orders
+
+
+
{{ $stats['pending'] }}
+
Pending
+
+
+
{{ $stats['in_progress'] }}
+
In Progress
+
+
+
{{ $stats['completed_today'] }}
+
Completed Today
+
+
+ + +
+
+ + + + + + + + + + + +
+
+ + +
+
+ + + + + + + + + + + + + + + + @forelse($serviceOrders as $order) + + + + + + + + + + + + @empty + + + + @endforelse + +
+ + Customer & VehicleComplaint + + + + + + + + + + Actions
+
{{ $order->order_number }}
+ @if($order->scheduled_date) +
Scheduled: {{ $order->scheduled_date->format('M j, Y') }}
+ @endif +
+
+
{{ $order->customer->full_name }}
+
{{ $order->vehicle->display_name }}
+
{{ $order->vehicle->license_plate }}
+
+
+
+ {{ $order->customer_complaint }} +
+
+
+ {{ $order->assignedTechnician?->full_name ?? 'Unassigned' }} +
+
+ + {{ ucfirst($order->priority) }} + + +
+ + {{ ucfirst(str_replace('_', ' ', $order->status)) }} + + + @if($order->status === 'pending') + + @elseif($order->status === 'in_progress') + + @endif +
+
+
${{ number_format($order->total_amount, 2) }}
+ @if($order->serviceItems->count() > 0 || $order->parts->count() > 0) +
+ {{ $order->serviceItems->count() }} services, {{ $order->parts->count() }} parts +
+ @endif +
+
{{ $order->created_at->format('M j, Y') }}
+
{{ $order->created_at->format('g:i A') }}
+
+
+ + View + + + Edit + + @if($order->status === 'completed') + + Invoice + + @endif + + Delete + +
+
+ @if($search) + No service orders found matching "{{ $search }}" + @else + No service orders found. Create your first service order + @endif +
+
+ + @if($serviceOrders->hasPages()) +
+ {{ $serviceOrders->links() }} +
+ @endif +
+
diff --git a/resources/views/livewire/service-orders/invoice.blade.php b/resources/views/livewire/service-orders/invoice.blade.php new file mode 100644 index 0000000..f5e302d --- /dev/null +++ b/resources/views/livewire/service-orders/invoice.blade.php @@ -0,0 +1,218 @@ +
+
+ +
+

INVOICE

+

Auto Repair Shop

+
+ + +
+
+

From:

+
+

Auto Repair Shop

+

123 Main Street

+

City, State 12345

+

Phone: (555) 123-4567

+

Email: info@autorepairshop.com

+
+
+
+

Bill To:

+
+

{{ $serviceOrder->customer->full_name }}

+

{{ $serviceOrder->customer->address ?? '123 Customer St' }}

+

{{ $serviceOrder->customer->city ?? 'City' }}, {{ $serviceOrder->customer->state ?? 'State' }} {{ $serviceOrder->customer->zip_code ?? '12345' }}

+

Phone: {{ $serviceOrder->customer->phone }}

+

Email: {{ $serviceOrder->customer->email }}

+
+
+
+ + +
+
+

Invoice Number

+

{{ $serviceOrder->order_number }}

+
+
+

Invoice Date

+

{{ $serviceOrder->completed_at ? $serviceOrder->completed_at->format('M j, Y') : now()->format('M j, Y') }}

+
+
+

Due Date

+

{{ now()->addDays(30)->format('M j, Y') }}

+
+
+ + +
+

Vehicle Information

+
+
+

Vehicle: {{ $serviceOrder->vehicle->display_name }}

+

License Plate: {{ $serviceOrder->vehicle->license_plate }}

+
+
+

VIN: {{ $serviceOrder->vehicle->vin }}

+

Mileage: {{ number_format($serviceOrder->vehicle->mileage) }} miles

+
+
+
+

Customer Complaint: {{ $serviceOrder->customer_complaint }}

+
+
+ + + @if($serviceOrder->serviceItems->count() > 0) +
+

Services Performed

+ + + + + + + + + + + + @foreach($serviceOrder->serviceItems as $item) + + + + + + + + @endforeach + +
ServiceDescriptionRateHoursAmount
{{ $item->service_name }}{{ $item->description ?: 'N/A' }}${{ number_format($item->labor_rate, 2) }}{{ $item->estimated_hours }}${{ number_format($item->labor_cost, 2) }}
+
+ @endif + + + @if($serviceOrder->parts->count() > 0) +
+

Parts Used

+ + + + + + + + + + + + @foreach($serviceOrder->parts as $part) + + + + + + + + @endforeach + +
PartPart NumberQuantityUnit PriceAmount
{{ $part->name }}{{ $part->part_number }}{{ $part->pivot->quantity_used }}${{ number_format($part->pivot->unit_price, 2) }}${{ number_format($part->pivot->total_price, 2) }}
+
+ @endif + + +
+
+ + + + + + + + + + @if($serviceOrder->discount_amount > 0) + + + + + @endif + + + + + + + + + + + + +
Labor Subtotal:${{ number_format($serviceOrder->labor_cost, 2) }}
Parts Subtotal:${{ number_format($serviceOrder->parts_cost, 2) }}
Discount:-${{ number_format($serviceOrder->discount_amount, 2) }}
Subtotal:${{ number_format($serviceOrder->labor_cost + $serviceOrder->parts_cost - $serviceOrder->discount_amount, 2) }}
Tax (8%):${{ number_format($serviceOrder->tax_amount, 2) }}
Total:${{ number_format($serviceOrder->total_amount, 2) }}
+
+
+ + +
+

Payment Information

+
+

Payment is due within 30 days of invoice date.

+

Please include invoice number {{ $serviceOrder->order_number }} with your payment.

+

+ Payment Methods: Cash, Check, Credit Card +

+
+
+ + + @if($serviceOrder->customer_notes) +
+

Notes

+

{{ $serviceOrder->customer_notes }}

+
+ @endif + + +
+

Thank you for your business!

+

This invoice was generated on {{ now()->format('M j, Y g:i A') }}

+
+ + +
+ + Print Invoice + + + Back to Service Order + +
+
+ + +
\ No newline at end of file diff --git a/resources/views/livewire/service-orders/show.blade.php b/resources/views/livewire/service-orders/show.blade.php new file mode 100644 index 0000000..187403e --- /dev/null +++ b/resources/views/livewire/service-orders/show.blade.php @@ -0,0 +1,443 @@ +
+ +
+
+ Service Order {{ $serviceOrder->order_number }} + {{ $serviceOrder->customer->full_name }} • {{ $serviceOrder->vehicle->display_name }} +
+
+ + + Back to Service Orders + + + + Edit Order + + @if($serviceOrder->status === 'completed') + + + Generate Invoice + + @endif +
+
+ + + @if (session()->has('success')) +
+
+ +
+

{{ session('success') }}

+
+
+
+ @endif + +
+ +
+ +
+
+ Service Order Information +
+
+
+
+ Order Number +
{{ $serviceOrder->order_number }}
+
+
+ Priority +
+ + {{ ucfirst($serviceOrder->priority) }} + +
+
+
+ Status +
+ + {{ ucfirst(str_replace('_', ' ', $serviceOrder->status)) }} + + + @if($serviceOrder->status === 'pending') + + @elseif($serviceOrder->status === 'in_progress') + + @endif +
+
+
+ Assigned Technician +
{{ $serviceOrder->assignedTechnician?->full_name ?? 'Unassigned' }}
+
+ @if($serviceOrder->scheduled_date) +
+ Scheduled Date +
{{ $serviceOrder->scheduled_date->format('M j, Y') }}
+
+ @endif +
+ Created +
{{ $serviceOrder->created_at->format('M j, Y g:i A') }}
+
+ @if($serviceOrder->started_at) +
+ Started +
{{ $serviceOrder->started_at->format('M j, Y g:i A') }}
+
+ @endif + @if($serviceOrder->completed_at) +
+ Completed +
{{ $serviceOrder->completed_at->format('M j, Y g:i A') }}
+
+ @endif +
+
+ Customer Complaint +
{{ $serviceOrder->customer_complaint }}
+
+ @if($serviceOrder->recommended_services) +
+ Recommended Services +
{{ $serviceOrder->recommended_services }}
+
+ @endif +
+
+ + +
+
+ Customer & Vehicle Information +
+
+
+
+

Customer Information

+
+
+ Name +
{{ $serviceOrder->customer->full_name }}
+
+ + +
+
+
+

Vehicle Information

+
+
+ Vehicle +
{{ $serviceOrder->vehicle->display_name }}
+
+
+ License Plate +
{{ $serviceOrder->vehicle->license_plate }}
+
+
+ VIN +
{{ $serviceOrder->vehicle->vin_display }}
+
+
+ Mileage +
{{ number_format($serviceOrder->vehicle->mileage) }} miles
+
+
+
+
+
+
+ + +
+
+ Service Items +
+
+ @if($serviceOrder->serviceItems->count() > 0) + + + + + + + + + + + + + @foreach($serviceOrder->serviceItems as $item) + + + + + + + + + @endforeach + +
ServiceCategoryRateHoursStatusCost
+
{{ $item->service_name }}
+ @if($item->description) +
{{ $item->description }}
+ @endif +
{{ $item->category }}${{ number_format($item->labor_rate, 2) }}{{ $item->estimated_hours }}h + + {{ ucfirst($item->status) }} + + ${{ number_format($item->labor_cost, 2) }}
+ @else +
+ No service items added yet. +
+ @endif +
+
+ + +
+
+ Parts +
+
+ @if($serviceOrder->parts->count() > 0) + + + + + + + + + + + + + @foreach($serviceOrder->parts as $part) + + + + + + + + + @endforeach + +
PartPart NumberQuantityUnit PriceStatusTotal
+
{{ $part->name }}
+ @if($part->pivot->notes) +
{{ $part->pivot->notes }}
+ @endif +
{{ $part->part_number }}{{ $part->pivot->quantity_used }}${{ number_format($part->pivot->unit_price, 2) }} + + {{ ucfirst($part->pivot->status) }} + + ${{ number_format($part->pivot->total_price, 2) }}
+ @else +
+ No parts added yet. +
+ @endif +
+
+ + + @if($serviceOrder->internal_notes || $serviceOrder->customer_notes) +
+
+ Notes +
+
+
+ @if($serviceOrder->internal_notes) +
+ Internal Notes +
{{ $serviceOrder->internal_notes }}
+
+ @endif + @if($serviceOrder->customer_notes) +
+ Customer Notes +
{{ $serviceOrder->customer_notes }}
+
+ @endif +
+
+
+ @endif +
+ + +
+ +
+
+ Order Totals +
+
+
+ Labor Cost + ${{ number_format($serviceOrder->labor_cost, 2) }} +
+
+ Parts Cost + ${{ number_format($serviceOrder->parts_cost, 2) }} +
+ @if($serviceOrder->discount_amount > 0) +
+ Discount + -${{ number_format($serviceOrder->discount_amount, 2) }} +
+ @endif +
+ Subtotal + ${{ number_format($serviceOrder->labor_cost + $serviceOrder->parts_cost - $serviceOrder->discount_amount, 2) }} +
+
+ Tax + ${{ number_format($serviceOrder->tax_amount, 2) }} +
+
+ Total + ${{ number_format($serviceOrder->total_amount, 2) }} +
+
+
+ + +
+
+ Quick Stats +
+
+
+ Service Items + {{ $serviceOrder->serviceItems->count() }} +
+
+ Parts Used + {{ $serviceOrder->parts->count() }} +
+
+ Estimated Hours + {{ $serviceOrder->estimated_hours ?? 0 }}h +
+ @if($serviceOrder->actual_hours) +
+ Actual Hours + {{ $serviceOrder->actual_hours }}h +
+ @endif +
+
+ + +
+
+ Quick Actions +
+
+ @if($serviceOrder->status === 'pending') + + Start Work + + @elseif($serviceOrder->status === 'in_progress') + + Complete Work + + + Put on Hold + + @elseif($serviceOrder->status === 'on_hold') + + Resume Work + + @endif + + @if($serviceOrder->status === 'completed') + + Generate Invoice + + @endif + + + Schedule Follow-up + +
+
+
+
+
diff --git a/resources/views/livewire/settings/appearance.blade.php b/resources/views/livewire/settings/appearance.blade.php new file mode 100644 index 0000000..892582d --- /dev/null +++ b/resources/views/livewire/settings/appearance.blade.php @@ -0,0 +1,19 @@ + + +
+ @include('partials.settings-heading') + + + + {{ __('Light') }} + {{ __('Dark') }} + {{ __('System') }} + + +
diff --git a/resources/views/livewire/settings/delete-user-form.blade.php b/resources/views/livewire/settings/delete-user-form.blade.php new file mode 100644 index 0000000..8d90079 --- /dev/null +++ b/resources/views/livewire/settings/delete-user-form.blade.php @@ -0,0 +1,58 @@ +validate([ + 'password' => ['required', 'string', 'current_password'], + ]); + + tap(Auth::user(), $logout(...))->delete(); + + $this->redirect('/', navigate: true); + } +}; ?> + +
+
+ {{ __('Delete account') }} + {{ __('Delete your account and all of its resources') }} +
+ + + + {{ __('Delete account') }} + + + + +
+
+ {{ __('Are you sure you want to delete your account?') }} + + + {{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.') }} + +
+ + + +
+ + {{ __('Cancel') }} + + + {{ __('Delete account') }} +
+ +
+
diff --git a/resources/views/livewire/settings/password.blade.php b/resources/views/livewire/settings/password.blade.php new file mode 100644 index 0000000..002805f --- /dev/null +++ b/resources/views/livewire/settings/password.blade.php @@ -0,0 +1,78 @@ +validate([ + 'current_password' => ['required', 'string', 'current_password'], + 'password' => ['required', 'string', Password::defaults(), 'confirmed'], + ]); + } catch (ValidationException $e) { + $this->reset('current_password', 'password', 'password_confirmation'); + + throw $e; + } + + Auth::user()->update([ + 'password' => Hash::make($validated['password']), + ]); + + $this->reset('current_password', 'password', 'password_confirmation'); + + $this->dispatch('password-updated'); + } +}; ?> + +
+ @include('partials.settings-heading') + + +
+ + + + +
+
+ {{ __('Save') }} +
+ + + {{ __('Saved.') }} + +
+ +
+
diff --git a/resources/views/livewire/settings/profile.blade.php b/resources/views/livewire/settings/profile.blade.php new file mode 100644 index 0000000..cb08833 --- /dev/null +++ b/resources/views/livewire/settings/profile.blade.php @@ -0,0 +1,114 @@ +name = Auth::user()->name; + $this->email = Auth::user()->email; + } + + /** + * Update the profile information for the currently authenticated user. + */ + public function updateProfileInformation(): void + { + $user = Auth::user(); + + $validated = $this->validate([ + 'name' => ['required', 'string', 'max:255'], + + 'email' => [ + 'required', + 'string', + 'lowercase', + 'email', + 'max:255', + Rule::unique(User::class)->ignore($user->id) + ], + ]); + + $user->fill($validated); + + if ($user->isDirty('email')) { + $user->email_verified_at = null; + } + + $user->save(); + + $this->dispatch('profile-updated', name: $user->name); + } + + /** + * Send an email verification notification to the current user. + */ + public function resendVerificationNotification(): void + { + $user = Auth::user(); + + if ($user->hasVerifiedEmail()) { + $this->redirectIntended(default: route('dashboard', absolute: false)); + + return; + } + + $user->sendEmailVerificationNotification(); + + Session::flash('status', 'verification-link-sent'); + } +}; ?> + +
+ @include('partials.settings-heading') + + +
+ + +
+ + + @if (auth()->user() instanceof \Illuminate\Contracts\Auth\MustVerifyEmail &&! auth()->user()->hasVerifiedEmail()) +
+ + {{ __('Your email address is unverified.') }} + + + {{ __('Click here to re-send the verification email.') }} + + + + @if (session('status') === 'verification-link-sent') + + {{ __('A new verification link has been sent to your email address.') }} + + @endif +
+ @endif +
+ +
+
+ {{ __('Save') }} +
+ + + {{ __('Saved.') }} + +
+ + + +
+
diff --git a/resources/views/livewire/technician-management/index.blade.php b/resources/views/livewire/technician-management/index.blade.php new file mode 100644 index 0000000..615eb4a --- /dev/null +++ b/resources/views/livewire/technician-management/index.blade.php @@ -0,0 +1,270 @@ +
+ +
+
+ Technician Management + Manage technician profiles, skills, and performance +
+ + Add Technician + +
+ + +
+
+ +
+
+ + + + + + + + + + @foreach($availableSkills as $skill) + + @endforeach + +
+
+
+ + +
+ @if($technicians->count() > 0) +
+ + + + + + + + + + + + + + + + + @foreach($technicians as $technician) + + + + + + + + + + + + @endforeach + +
+ + + + EmailPhone + + Primary SkillsPerformanceUtilizationActions
+
+
+ {{ strtoupper(substr($technician->first_name, 0, 1) . substr($technician->last_name, 0, 1)) }} +
+
+
{{ $technician->full_name }}
+
+ ${{ number_format($technician->hourly_rate, 2) }}/hr +
+
+
+
{{ $technician->employee_id }}{{ $technician->email }}{{ $technician->phone }} + + {{ ucfirst(str_replace('_', ' ', $technician->status)) }} + + +
+ @php + $primarySkills = $technician->skills->where('is_primary_skill', true); + @endphp + @foreach($primarySkills->take(3) as $skill) + + {{ ucfirst(str_replace('_', ' ', $skill->skill_name)) }} + ({{ $skill->proficiency_level }}) + + @endforeach + @if($primarySkills->count() > 3) + + +{{ $primarySkills->count() - 3 }} more + + @endif +
+
+
+
+ {{ number_format($technician->getAverageRating(), 1) }}/5 +
+
+ @for($i = 1; $i <= 5; $i++) + @if($i <= floor($technician->getAverageRating())) + + @elseif($i - 0.5 <= $technician->getAverageRating()) + + @else + + @endif + @endfor +
+
+
+ {{ $technician->getTotalJobsCompleted() }} jobs completed +
+
+
+ {{ number_format($technician->getCurrentUtilizationRate(), 1) }}% +
+
+
+
+
+
+ + View + + + Edit + +
+
+
+ + +
+ {{ $technicians->links() }} +
+ @else +
+ + No technicians found + + @if($search || $statusFilter || $skillFilter) + Try adjusting your filters to see more results. + @else + Get started by adding your first technician. + @endif + + + Add Technician + +
+ @endif +
+ + + @if($showingDetails && $selectedTechnician) +
+
+
+ + + +
+ +
+ {{ $selectedTechnician->full_name }} - Details + +
+ + +
+ +
+
+ Basic Information +
+
Employee ID: {{ $selectedTechnician->employee_id }}
+
Email: {{ $selectedTechnician->email }}
+
Phone: {{ $selectedTechnician->phone }}
+
Hourly Rate: ${{ number_format($selectedTechnician->hourly_rate, 2) }}
+
Status: + + {{ ucfirst(str_replace('_', ' ', $selectedTechnician->status)) }} + +
+
+
+ +
+ Performance Overview +
+
Average Rating: {{ number_format($selectedTechnician->getAverageRating(), 1) }}/5
+
Jobs Completed: {{ $selectedTechnician->getTotalJobsCompleted() }}
+
Current Utilization: {{ number_format($selectedTechnician->getCurrentUtilizationRate(), 1) }}%
+
+
+
+ + +
+ Skills & Certifications + @if($selectedTechnician->skills->count() > 0) +
+ @foreach($selectedTechnician->skills as $skill) +
+
+
{{ ucfirst(str_replace('_', ' ', $skill->skill_name)) }}
+
{{ ucfirst($skill->category) }}
+ @if($skill->certification_body) +
{{ $skill->certification_body }}
+ @endif +
+
+
{{ $skill->proficiency_level }}/5
+ @if($skill->is_primary_skill) + Primary + @endif +
+
+ @endforeach +
+ @else +
No skills recorded
+ @endif +
+ + +
+ Close + + Edit Technician + +
+
+
+
+ @endif +
diff --git a/resources/views/livewire/technician-management/performance-tracking.blade.php b/resources/views/livewire/technician-management/performance-tracking.blade.php new file mode 100644 index 0000000..75c3520 --- /dev/null +++ b/resources/views/livewire/technician-management/performance-tracking.blade.php @@ -0,0 +1,276 @@ +
+ + @if($showModal) +
+
+
+ + + +
+ +
+ + Performance Tracking - {{ $technician?->full_name }} + + +
+ + +
+ @if($technician) + +
+
+ + Time Period + + + + + + + + + +
+ + @if($periodFilter === 'custom') +
+ + Start Date + + +
+
+ + End Date + + +
+ @endif + + + Add Record + +
+ + + @if(count($performanceStats) > 0) +
+ Performance Summary +
+ @foreach($performanceStats as $type => $stats) +
+
+ {{ $stats['current'] }} +
+
+ {{ $stats['label'] }} +
+
+
Avg: {{ $stats['average'] }}
+ @if($type !== 'customer_rating') +
Total: {{ $stats['total'] }}
+ @endif +
Records: {{ $stats['count'] }}
+
+
+ @endforeach +
+
+ @endif + + + @if(count($chartData) > 0) +
+
+ Performance Trend + + @foreach($metricTypes as $type => $label) + + @endforeach + +
+ +
+
+ @foreach($chartData as $index => $data) +
+
{{ $data['formatted_value'] }}
+
+
+
+ {{ \Carbon\Carbon::parse($data['date'])->format('M d') }} +
+
+ @endforeach +
+
+
+ @endif + + +
+ Performance Records + + @if($filteredPerformances->count() > 0) +
+ + + + + + + + + + + + + + @foreach($filteredPerformances as $performance) + + + + + + + + + @endforeach + +
DateMetricValuePeriodNotesActions
{{ $performance->performance_date->format('M d, Y') }} + + {{ $metricTypes[$performance->metric_type] ?? $performance->metric_type }} + + + {{ $performance->formatted_value }} + {{ ucfirst($performance->period_type) }} + @if($performance->notes) +
+ {{ $performance->notes }} +
+ @else + - + @endif +
+
+ + + + +
+
+
+ @else +
+ +
No performance records found
+
Start tracking performance by adding records.
+ + Add Performance Record + +
+ @endif +
+ + + @if($editing !== false || $metric_type) +
+ + {{ $editing ? 'Edit Performance Record' : 'Add Performance Record' }} + + +
+
+
+ + Metric Type + + + @foreach($metricTypes as $type => $label) + + @endforeach + + + +
+
+ + Value + + + +
+
+ + Date + + + +
+
+ + Period Type + + @foreach($periodTypes as $type => $label) + + @endforeach + + + +
+
+ +
+ + Notes + + + +
+ +
+ + {{ $editing ? 'Update Record' : 'Add Record' }} + + + Cancel + +
+
+
+ @endif + @endif +
+ + +
+ Close +
+
+
+
+ @endif + + + @if (session()->has('message')) +
+
+
+ + {{ session('message') }} +
+
+
+ @endif +
diff --git a/resources/views/livewire/technician-management/skills-management.blade.php b/resources/views/livewire/technician-management/skills-management.blade.php new file mode 100644 index 0000000..af683a9 --- /dev/null +++ b/resources/views/livewire/technician-management/skills-management.blade.php @@ -0,0 +1,229 @@ +
+ + @if($showModal) +
+
+
+ + + +
+ +
+ + Manage Skills - {{ $technician?->full_name }} + + +
+ + +
+ @if($technician) + +
+
+ Current Skills + + Add Skill + +
+ + @if($technician->skills->count() > 0) +
+ @foreach($technician->skills as $skill) +
+
+
+
+

{{ ucfirst(str_replace('_', ' ', $skill->skill_name)) }}

+ @if($skill->is_primary_skill) + Primary + @endif +
+
{{ ucfirst($skill->category) }}
+
+
+ + + + +
+
+ +
+ Proficiency: +
+ @for($i = 1; $i <= 5; $i++) +
+ @endfor +
+ {{ $skill->proficiency_level }}/5 +
+ + @if($skill->certification_body) +
+
+ Certification: {{ $skill->certification_body }} +
+ @if($skill->certification_expires) +
+ Expires: {{ $skill->certification_expires->format('M d, Y') }} + @if($skill->certification_expires->isPast()) + (Expired) + @elseif($skill->certification_expires->isBefore(now()->addMonths(3))) + (Expires Soon) + @endif +
+ @endif +
+ @endif + + @if($skill->notes) +
+ Notes: {{ $skill->notes }} +
+ @endif +
+ @endforeach +
+ @else +
+ +
No skills recorded
+
Start by adding the first skill for this technician.
+ + Add First Skill + +
+ @endif +
+ + + @if($editing !== false || $skill_name) +
+ + {{ $editing ? 'Edit Skill' : 'Add New Skill' }} + + +
+
+
+ + Skill Name + + + +
+
+ + Category + + + @foreach($skillCategories as $category) + + @endforeach + + + +
+
+ + Proficiency Level + + @foreach(range(1, 5) as $level) + + @endforeach + + + +
+
+ + Certification Body + + + +
+
+ +
+
+ + Certification Expiry + + + +
+
+ + + Primary Skill + + + +
+
+ +
+ + Notes + + + +
+ +
+ + {{ $editing ? 'Update Skill' : 'Add Skill' }} + + + Cancel + +
+
+
+ + + @if(!$editing) +
+ Quick Add Common Skills +
+ @foreach($commonSkills as $category => $skills) +
+

{{ ucfirst($category) }}

+
+ @foreach($skills as $skill) + + + {{ ucfirst(str_replace('_', ' ', $skill)) }} + + @endforeach +
+
+ @endforeach +
+
+ @endif + @endif + @endif +
+ + +
+ Close +
+
+
+
+ @endif + + + @if (session()->has('message')) +
+ {{ session('message') }} +
+ @endif +
diff --git a/resources/views/livewire/technician-management/technician-form.blade.php b/resources/views/livewire/technician-management/technician-form.blade.php new file mode 100644 index 0000000..57a49d9 --- /dev/null +++ b/resources/views/livewire/technician-management/technician-form.blade.php @@ -0,0 +1,161 @@ +
+ + @if($showModal) +
+
+
+ + + +
+ +
+ + {{ $editing ? 'Edit Technician' : 'Add New Technician' }} + + +
+ + +
+ +
+ Basic Information +
+
+ + First Name + + + +
+
+ + Last Name + + + +
+
+ + Email + + + +
+
+ + Phone + + + +
+
+ + Employee ID + + + +
+
+ + Hourly Rate ($) + + + +
+
+
+ + +
+ Employment Details +
+
+ + Status + + @foreach($statusOptions as $value => $label) + + @endforeach + + + +
+
+ + Skill Level + + @foreach($skillLevelOptions as $value => $label) + + @endforeach + + + +
+
+ + Shift Start + + + +
+
+ + Shift End + + + +
+
+
+ + +
+ + Specializations + Select the technician's areas of expertise +
+ @foreach($specializationOptions as $value => $label) + + @endforeach +
+ +
+
+
+ + +
+ Cancel + + {{ $editing ? 'Update Technician' : 'Create Technician' }} + +
+
+
+
+ @endif + + + @if (session()->has('message')) +
+
+
+ + {{ session('message') }} +
+
+
+ @endif +
diff --git a/resources/views/livewire/technician-management/workload-management.blade.php b/resources/views/livewire/technician-management/workload-management.blade.php new file mode 100644 index 0000000..3d5df34 --- /dev/null +++ b/resources/views/livewire/technician-management/workload-management.blade.php @@ -0,0 +1,345 @@ +
+ + @if($showModal) +
+
+
+ + + +
+ +
+ + Workload Management - {{ $technician?->full_name }} + + +
+ + +
+ @if($technician) + +
+
+ + + + +
+ + + +
+ @if($viewMode === 'week') + {{ \Carbon\Carbon::parse($startDate)->format('M d') }} - {{ \Carbon\Carbon::parse($endDate)->format('M d, Y') }} + @elseif($viewMode === 'month') + {{ \Carbon\Carbon::parse($startDate)->format('F Y') }} + @else + {{ \Carbon\Carbon::parse($startDate)->format('M d') }} - {{ \Carbon\Carbon::parse($endDate)->format('M d, Y') }} + @endif +
+ + + +
+
+ + @if($viewMode === 'custom') +
+ + +
+ @endif + + + Add Record + +
+ + + @if($workloadStats['total_scheduled'] > 0) +
+ Workload Summary +
+
+
{{ $workloadStats['total_scheduled'] }}h
+
Scheduled
+
+
+
{{ $workloadStats['total_actual'] }}h
+
Actual
+
+
+
{{ $workloadStats['total_overtime'] }}h
+
Overtime
+
+
+
{{ $workloadStats['avg_utilization'] }}%
+
Avg Utilization
+
+
+
{{ $workloadStats['avg_efficiency'] }}%
+
Avg Efficiency
+
+
+
{{ $workloadStats['total_jobs_assigned'] }}
+
Jobs Assigned
+
+
+
{{ $workloadStats['total_jobs_completed'] }}
+
Jobs Completed
+
+
+
{{ $workloadStats['completion_rate'] }}%
+
Completion Rate
+
+
+
+ @endif + + + @if($filteredWorkloads->count() > 0) +
+ Daily Workload + + @if($viewMode === 'week') + +
+ @for($i = 0; $i < 7; $i++) + @php + $date = \Carbon\Carbon::parse($startDate)->addDays($i); + $workload = $filteredWorkloads->where('workload_date', $date->format('Y-m-d'))->first(); + @endphp +
+
+ {{ $date->format('D') }} +
{{ $date->format('M d') }}
+
+ @if($workload) +
+
+ Hours: + {{ $workload->actual_hours }}/{{ $workload->scheduled_hours }} +
+
+ Jobs: + {{ $workload->jobs_completed }}/{{ $workload->jobs_assigned }} +
+
+ Util: + + {{ number_format($workload->utilization_rate, 1) }}% + +
+
+ + + + +
+
+ @else +
No data
+ @endif +
+ @endfor +
+ @else + +
+ + + + + + + + + + + + + + + + + @foreach($filteredWorkloads as $workload) + + + + + + + + + + + + @endforeach + +
DateScheduledActualOvertimeJobsUtilizationEfficiencyNotesActions
+
{{ $workload->workload_date->format('M d, Y') }}
+
{{ $workload->workload_date->format('D') }}
+
{{ $workload->scheduled_hours }}h + {{ $workload->actual_hours }}h + + @if($workload->overtime_hours > 0) + {{ $workload->overtime_hours }}h + @else + - + @endif + +
+ {{ $workload->jobs_completed }} + / {{ $workload->jobs_assigned }} +
+
+
+ + {{ number_format($workload->utilization_rate, 1) }}% + +
+
+
+
+
+ + {{ number_format($workload->efficiency_rate, 1) }}% + + + @if($workload->notes) +
+ {{ $workload->notes }} +
+ @else + - + @endif +
+
+ + + + +
+
+
+ @endif +
+ @else +
+ +
No workload records found
+
Start tracking daily workload by adding records.
+ + Add Workload Record + +
+ @endif + + + @if($editing !== false || $scheduled_hours > 0) +
+ + {{ $editing ? 'Edit Workload Record' : 'Add Workload Record' }} + + +
+
+
+ + Date + + + +
+
+ + Scheduled Hours + + + +
+
+ + Actual Hours + + + +
+
+ + Overtime Hours + + + +
+
+ + Jobs Assigned + + + +
+
+ + Jobs Completed + + + +
+
+ +
+ + Notes + + + +
+ +
+ + {{ $editing ? 'Update Record' : 'Add Record' }} + + + Cancel + +
+
+
+ @endif + @endif +
+ + +
+ Close +
+
+
+
+ @endif + + + @if (session()->has('message')) +
+
+
+ + {{ session('message') }} +
+
+
+ @endif +
diff --git a/resources/views/livewire/timesheets/create.blade.php b/resources/views/livewire/timesheets/create.blade.php new file mode 100644 index 0000000..d5f5aa4 --- /dev/null +++ b/resources/views/livewire/timesheets/create.blade.php @@ -0,0 +1,3 @@ +
+ {{-- The best athlete wants his opponent at his best. --}} +
diff --git a/resources/views/livewire/timesheets/edit.blade.php b/resources/views/livewire/timesheets/edit.blade.php new file mode 100644 index 0000000..ad58cc8 --- /dev/null +++ b/resources/views/livewire/timesheets/edit.blade.php @@ -0,0 +1,3 @@ +
+ {{-- Stop trying to control. --}} +
diff --git a/resources/views/livewire/timesheets/index.blade.php b/resources/views/livewire/timesheets/index.blade.php new file mode 100644 index 0000000..15e5660 --- /dev/null +++ b/resources/views/livewire/timesheets/index.blade.php @@ -0,0 +1,3 @@ +
+ {{-- The whole world belongs to you. --}} +
diff --git a/resources/views/livewire/timesheets/show.blade.php b/resources/views/livewire/timesheets/show.blade.php new file mode 100644 index 0000000..d5f5aa4 --- /dev/null +++ b/resources/views/livewire/timesheets/show.blade.php @@ -0,0 +1,3 @@ +
+ {{-- The best athlete wants his opponent at his best. --}} +
diff --git a/resources/views/livewire/user-management.blade.php b/resources/views/livewire/user-management.blade.php new file mode 100644 index 0000000..e69de29 diff --git a/resources/views/livewire/users/create.blade.php b/resources/views/livewire/users/create.blade.php new file mode 100644 index 0000000..83db446 --- /dev/null +++ b/resources/views/livewire/users/create.blade.php @@ -0,0 +1,318 @@ +
+ +
+
+

Create New User

+

Add a new user to the system

+
+ + + + + Back to Users + +
+ +
+ +
+

Personal Information

+
+
+ + + @error('form.name') {{ $message }} @enderror +
+ +
+ + + @error('form.email') {{ $message }} @enderror +
+ +
+ + + @error('form.phone') {{ $message }} @enderror +
+ +
+ + + @error('form.national_id') {{ $message }} @enderror +
+ +
+ + + @error('form.address') {{ $message }} @enderror +
+
+
+ + +
+

Professional Information

+
+
+ + + + @foreach($positions as $suggestion) + + @error('form.position') {{ $message }} @enderror +
+ +
+ + + @error('form.department') {{ $message }} @enderror +
+ +
+ + + @error('form.salary') {{ $message }} @enderror +
+ +
+ + + @error('form.hire_date') {{ $message }} @enderror +
+ +
+ + + @error('form.emergency_contact') {{ $message }} @enderror +
+
+
+ + +
+

Account Settings

+
+
+ + + @error('form.status') {{ $message }} @enderror +
+ +
+ + + @error('form.branch') {{ $message }} @enderror +
+
+
+ + +
+

Role Assignment

+ + +
+ +
+ + + + +
+
+ + +
+ @foreach($roles as $role) + + @endforeach +
+ @error('form.roles') {{ $message }} @enderror +
+ + +
+

Password

+
+
+ + + @error('form.password') {{ $message }} @enderror +
+ +
+ + +
+
+ +
+ + + @if($generatedPassword) +
+

+ Generated Password: + {{ $generatedPassword }} +

+

+ Make sure to share this password securely with the user. +

+
+ @endif +
+
+ + +
+

Notification Settings

+
+ + + +
+
+ + +
+
+ + Cancel + + + +
+
+
+
diff --git a/resources/views/livewire/users/edit.blade.php b/resources/views/livewire/users/edit.blade.php new file mode 100644 index 0000000..5c0f8de --- /dev/null +++ b/resources/views/livewire/users/edit.blade.php @@ -0,0 +1,311 @@ +
+
+
+
+

Edit User

+

Update user information and permissions

+
+ Back to Users +
+ +
+ +
+

Personal Information

+
+
+ + + @error('form.name') {{ $message }} @enderror +
+ +
+ + + @error('form.email') {{ $message }} @enderror +
+ +
+ + + @error('form.phone') {{ $message }} @enderror +
+ +
+ + + @error('form.national_id') {{ $message }} @enderror +
+ +
+ + + @error('form.address') {{ $message }} @enderror +
+
+
+ + +
+

Professional Information

+
+
+ + + + @if(isset($positions)) + @foreach($positions as $suggestion) + + @error('form.position') {{ $message }} @enderror +
+ +
+ + + @error('form.department') {{ $message }} @enderror +
+ +
+ + + @error('form.salary') {{ $message }} @enderror +
+ +
+ + + @error('form.hire_date') {{ $message }} @enderror +
+ +
+ + + @error('form.emergency_contact') {{ $message }} @enderror +
+
+
+ + +
+

Account Settings

+
+
+ + + @error('form.status') {{ $message }} @enderror +
+ +
+ + + @error('form.branch') {{ $message }} @enderror +
+
+
+ + +
+

Role Assignment

+
+ @foreach($availableRoles as $role) + + @endforeach +
+ @error('form.roles') {{ $message }} @enderror +
+ + +
+

Password Management

+
+
+ + + @error('form.password') {{ $message }} @enderror +
+ +
+ + +
+
+ +
+ + +
+
+ + +
+

Advanced Actions

+
+ @can('impersonate-users') + + @endcan + +
+
+ + +
+
+ + Cancel +
+ @can('delete-users') + + @endcan +
+
+
+ + + @if($showDeleteModal) +
+
+

Delete User

+

Are you sure you want to delete this user? This action cannot be undone.

+
+ + +
+
+
+ @endif +
+ +@push('styles') + +@endpush diff --git a/resources/views/livewire/users/index.blade.php b/resources/views/livewire/users/index.blade.php new file mode 100644 index 0000000..3530de3 --- /dev/null +++ b/resources/views/livewire/users/index.blade.php @@ -0,0 +1,360 @@ +
+ +
+
+

User Management

+

Manage system users, roles, and permissions

+ + +
+
+
+ Active: {{ $stats['active'] }} +
+
+
+ Inactive: {{ $stats['inactive'] }} +
+
+
+ Suspended: {{ $stats['suspended'] }} +
+
+
+ Total: {{ $stats['total'] }} +
+
+
+ +
+ @if($this->getSelectedCount() > 0) + +
+ {{ $this->getSelectedCount() }} selected + + +
+ @endif + + + + + + + + Add New User + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + @if($this->hasActiveFilters()) + + @endif +
+
+ + +
+
+ + + Showing {{ $users->firstItem() ?? 0 }} to {{ $users->lastItem() ?? 0 }} of {{ $users->total() }} users + +
+
+ + +
+
+ + + + + + + + + + + + + + + + @forelse($users as $user) + + + + + + + + + + + + @empty + + + + @endforelse + +
+ + +
+ Name + @if($sortField === 'name') + @if($sortDirection === 'asc') + + + + @else + + + + @endif + @endif +
+
+
+ Email + @if($sortField === 'email') + @if($sortDirection === 'asc') + + + + @else + + + + @endif + @endif +
+
Employee IDRolesDepartmentStatusPermissionsActions
+ + +
+
+ {{ $user->initials() }} +
+
+
{{ $user->name }}
+ @if($user->position) +
{{ $user->position }}
+ @endif +
+
+
+
{{ $user->email }}
+ @if($user->phone) +
{{ $user->phone }}
+ @endif +
+ {{ $user->employee_id ?: '-' }} + + @if($user->roles->count() > 0) +
+ @foreach($user->roles->take(2) as $role) + + {{ $role->display_name }} + + @endforeach + @if($user->roles->count() > 2) + + +{{ $user->roles->count() - 2 }} more + + @endif +
+ @else + No roles + @endif +
+ {{ $user->department ? ucfirst(str_replace('_', ' ', $user->department)) : '-' }} + + + {{ ucfirst($user->status) }} + + + + {{ $this->getUserPermissionCount($user) }} + + +
+ + + + + + + + + + + + + + + + + + + + @if($user->id !== auth()->id()) + @if($user->status === 'active') + + @elseif($user->status === 'inactive') + + @endif + + @if($user->status !== 'suspended') + + @endif + @endif +
+
+
+ + + +

No users found

+

Try adjusting your search or filter criteria.

+ @if($this->hasActiveFilters()) + + @endif +
+
+
+
+ + + @if($users->hasPages()) +
+ {{ $users->links() }} +
+ @endif +
diff --git a/resources/views/livewire/users/manage-roles-permissions.blade.php b/resources/views/livewire/users/manage-roles-permissions.blade.php new file mode 100644 index 0000000..41faa7f --- /dev/null +++ b/resources/views/livewire/users/manage-roles-permissions.blade.php @@ -0,0 +1,169 @@ +
+ +
+
+

Manage Roles & Permissions

+

Configure access control for {{ $user->name }}

+
+ + Back to User + +
+ + +
+

Role Assignment

+ + +
+ +
+ + + + +
+
+ + +
+ @foreach($availableRoles as $role) + + @endforeach +
+ +
+ +
+
+ + +
+
+

Individual Permissions

+
+ + +
+
+ + + @foreach($groupedPermissions as $module => $permissions) +
+
+

{{ ucfirst($module) }} Module

+
+ + +
+
+ +
+ @foreach($permissions as $permission) + + @endforeach +
+
+ @endforeach + +
+
+
+ Selected: {{ count($selectedPermissions) }} permissions +
+ +
+
+
+ + +
+

Bulk Actions

+
+ + + +
+
+
diff --git a/resources/views/livewire/users/show.blade.php b/resources/views/livewire/users/show.blade.php new file mode 100644 index 0000000..ae1a8db --- /dev/null +++ b/resources/views/livewire/users/show.blade.php @@ -0,0 +1,344 @@ +
+
+ +
+
+
+
+ {{ $user->initials() }} +
+
+ @if($user->status === 'active') + + + + @elseif($user->status === 'suspended') + + + + @else + + + + @endif +
+
+
+

{{ $user->name }}

+
+ {{ $user->position ?? 'User' }} + @if($user->department) + + {{ $user->department }} + @endif + @if($user->employee_id) + + {{ $user->employee_id }} + @endif +
+
+ + {{ ucfirst($user->status) }} + + @if($user->branch_code) + + Branch: {{ $user->branch_code }} + + @endif +
+
+
+ + +
+ @if($this->canPerformAction('impersonate')) + + Impersonate + + @endif + + @if($this->canPerformAction('reset_password')) + + Reset Password + + @endif + + @if($this->canPerformAction('edit')) + + Edit User + + @endif + + + Back to Users + +
+
+ + +
+
+
+
+

Total Permissions

+

{{ $metrics['total_permissions'] }}

+
+
+ + + +
+
+
+ +
+
+
+

Active Roles

+

{{ $metrics['active_roles'] }}

+
+
+ + + +
+
+
+ +
+
+
+

Work Orders

+

{{ $workStats['work_orders_completed'] ?? 0 }}/{{ $workStats['work_orders_assigned'] ?? 0 }}

+
+
+ + + +
+
+
+ +
+
+
+

Days Active

+

{{ $metrics['days_since_created'] }}

+
+
+ + + +
+
+
+
+ + +
+ +
+

Personal Information

+
+
+ Email: + {{ $user->email }} +
+ @if($user->phone) +
+ Phone: + {{ $user->phone }} +
+ @endif + @if($user->date_of_birth) +
+ Date of Birth: + {{ $user->date_of_birth->format('M d, Y') }} +
+ @endif + @if($user->national_id) +
+ National ID: + {{ $user->national_id }} +
+ @endif + @if($user->address) +
+ Address: + {{ $user->address }} +
+ @endif + @if($user->emergency_contact) +
+ Emergency Contact: + {{ $user->emergency_contact }} +
+ @endif +
+
+ + +
+

Professional Information

+
+ @if($user->position) +
+ Position: + {{ $user->position }} +
+ @endif + @if($user->department) +
+ Department: + {{ ucfirst(str_replace('_', ' ', $user->department)) }} +
+ @endif + @if($user->salary) +
+ Salary: + ${{ number_format($user->salary, 2) }} +
+ @endif + @if($user->hire_date) +
+ Hire Date: + {{ $user->hire_date->format('M d, Y') }} +
+ @endif + @if($user->branch) +
+ Branch: + {{ ucfirst(str_replace('_', ' ', $user->branch)) }} +
+ @endif +
+ Status: + + {{ ucfirst($user->status) }} + +
+
+
+
+ + +
+
+ +
+ + +
+ @if($activeTab === 'roles') + +
+ +
+

Assigned Roles

+ @if($user->roles->count() > 0) +
+ @foreach($user->roles as $role) +
+
+
+

{{ $role->display_name }}

+ @if($role->description) +

{{ $role->description }}

+ @endif +
+ + {{ $role->name }} + +
+
+ @endforeach +
+ @else +
+ + + +

No roles assigned

+

This user doesn't have any roles assigned yet.

+
+ @endif +
+ + +
+

Effective Permissions

+ @php + $allPermissions = $user->getAllPermissions(); + $groupedPermissions = $allPermissions->groupBy(function($permission) { + return explode('.', $permission->name)[0]; + }); + @endphp + + @if($allPermissions->count() > 0) +
+ @foreach($groupedPermissions as $module => $permissions) +
+

{{ ucfirst($module) }}

+
+ @foreach($permissions as $permission) +
+ {{ str_replace($module.'.', '', $permission->name) }} + + + +
+ @endforeach +
+
+ @endforeach +
+ @else +
+ + + +

No permissions

+

This user doesn't have any permissions assigned.

+
+ @endif +
+
+ @endif + + @if($activeTab === 'activity') + +
+

Recent Activity

+
+ + + +

Activity log coming soon

+

User activity tracking will be available in a future update.

+
+
+ @endif +
+
+
+
+ +@push('styles') + +@endpush diff --git a/resources/views/livewire/vehicles/create.blade.php b/resources/views/livewire/vehicles/create.blade.php new file mode 100644 index 0000000..80b8858 --- /dev/null +++ b/resources/views/livewire/vehicles/create.blade.php @@ -0,0 +1,260 @@ +
+ +
+
+ Add New Vehicle + Register a new vehicle for customer service tracking +
+ + + Back to Vehicles + +
+ + +
+
+ +
+ Vehicle Owner +
+
+ + Customer * + + + +
+
+
+ + +
+ Vehicle Information +
+
+ + Make * + + + +
+
+ + Model * + + + +
+
+ + Year * + + + +
+
+
+ + +
+ Vehicle Identification + + + @if($vinDecodeSuccess) +
+
+ +
+

{{ $vinDecodeSuccess }}

+
+
+
+ @endif + + @if($vinDecodeError) +
+
+ +
+

{{ $vinDecodeError }}

+
+
+
+ @endif + +
+
+ + VIN (Vehicle Identification Number) * +
+ + +
+ +
+
+ +
+
+
+ Enter the complete 17-character VIN and click the search button to auto-fill vehicle details + +
+
+
+ + License Plate * + + + +
+
+
+ + +
+ Physical Details +
+
+ + Color * +
+ + +
+ Use the color picker or type the color name + +
+
+
+ + Current Mileage * + + Enter current odometer reading + + +
+
+
+ + +
+ Technical Specifications (Optional) +
+
+ + Engine Type + + + +
+
+ + Transmission + + + +
+
+
+ + +
+ Additional Information +
+
+ + Vehicle Status * +
+ + + +
+ +
+
+
+
+ + +
+ Vehicle Image +
+
+ + Upload Vehicle Photo + + Upload a photo of the vehicle (JPG, PNG, GIF - Max 2MB) + + + @if ($vehicle_image) +
+ Vehicle Preview +
+ @endif +
+
+
+ + Notes + + + +
+
+
+ + +
+ + Cancel + + + + Add Vehicle + +
+
+
+
diff --git a/resources/views/livewire/vehicles/edit.blade.php b/resources/views/livewire/vehicles/edit.blade.php new file mode 100644 index 0000000..56e1c86 --- /dev/null +++ b/resources/views/livewire/vehicles/edit.blade.php @@ -0,0 +1,277 @@ +
+ +
+
+ Edit Vehicle + Update vehicle information for {{ $vehicle->display_name }} +
+ + + Back to Vehicle + +
+ + + @if (session()->has('success')) +
+
+ +
+

{{ session('success') }}

+
+
+
+ @endif + + +
+
+ +
+ Vehicle Owner +
+
+ + Customer * + + + +
+
+
+ + +
+ Vehicle Information +
+
+ + Make * + + + +
+
+ + Model * + + + +
+
+ + Year * + + + +
+
+
+ + +
+ Vehicle Identification + + + @if($vinDecodeSuccess) +
+
+ +
+

{{ $vinDecodeSuccess }}

+
+
+
+ @endif + + @if($vinDecodeError) +
+
+ +
+

{{ $vinDecodeError }}

+
+
+
+ @endif + +
+
+ + VIN (Vehicle Identification Number) * +
+ + +
+ +
+
+ +
+
+
+ Click the search button to decode VIN and update vehicle details + +
+
+
+ + License Plate * + + + +
+
+
+ + +
+ Physical Details +
+
+ + Color * +
+ + +
+ Use the color picker or type the color name + +
+
+
+ + Current Mileage * + + Enter current odometer reading + + +
+
+
+ + +
+ Technical Specifications (Optional) +
+
+ + Engine Type + + + +
+
+ + Transmission + + + +
+
+
+ + +
+ Additional Information +
+
+ + Vehicle Status * +
+ + + +
+ +
+
+
+
+ + +
+ Vehicle Image +
+
+ + Upload Vehicle Photo + + Upload a new photo of the vehicle (JPG, PNG, GIF - Max 2MB) + + + @if ($vehicle_image) +
+ Vehicle Preview +
+ @elseif ($vehicle->vehicle_image) +
+ Current Vehicle Photo +

Current vehicle photo

+
+ @endif +
+
+
+ + Notes + + + +
+
+
+ + +
+ + Cancel + + + + Update Vehicle + +
+
+
+
diff --git a/resources/views/livewire/vehicles/index.blade.php b/resources/views/livewire/vehicles/index.blade.php new file mode 100644 index 0000000..bf05499 --- /dev/null +++ b/resources/views/livewire/vehicles/index.blade.php @@ -0,0 +1,223 @@ +
+ +
+ Vehicle Management + + + Add New Vehicle + +
+ + + @if (session()->has('success')) +
+
+ +
+

{{ session('success') }}

+
+
+
+ @endif + + @if (session()->has('error')) +
+
+ +
+

{{ session('error') }}

+
+
+
+ @endif + + +
+
+ + + + + + + +
+
+ + +
+
+ + + + + + + + + + + + + + + @forelse($vehicles as $vehicle) + + + + + + + + + + + @empty + + + + @endforelse + +
+ + + + VINLicense Plate + + + + StatusActions
+
+
+
{{ $vehicle->display_name }}
+
+
+ {{ $vehicle->color }} +
+
+
+
+
+
{{ $vehicle->customer->full_name }}
+
ID: {{ $vehicle->customer->id }}
+
+
+
{{ $vehicle->vin_display }}
+
+
{{ $vehicle->license_plate }}
+
+
{{ number_format($vehicle->mileage) }} mi
+
+
+ @if($vehicle->last_service_date) + {{ $vehicle->last_service_date->format('M j, Y') }} + @else + Never + @endif +
+
+ + {{ ucfirst($vehicle->status) }} + + +
+ + View + + + Edit + + + Service + + + Delete + +
+
+ @if($search) + No vehicles found matching "{{ $search }}" + @else + No vehicles found. Add your first vehicle + @endif +
+
+ + @if($vehicles->hasPages()) +
+ {{ $vehicles->links() }} +
+ @endif +
+ + +
+
+
{{ $vehicles->total() }}
+
Total Vehicles
+
+
+
{{ $vehicles->where('status', 'active')->count() }}
+
Active Vehicles
+
+
+
{{ $makes->count() }}
+
Different Makes
+
+
+
{{ $customers->count() }}
+
Vehicle Owners
+
+
+
diff --git a/resources/views/livewire/vehicles/show.blade.php b/resources/views/livewire/vehicles/show.blade.php new file mode 100644 index 0000000..2149da8 --- /dev/null +++ b/resources/views/livewire/vehicles/show.blade.php @@ -0,0 +1,288 @@ +
+ +
+
+ {{ $vehicle->display_name }} + VIN: {{ $vehicle->vin_display }} • Owner: {{ $vehicle->customer->full_name }} +
+
+ + + Back to Vehicles + + + + Edit Vehicle + + + + New Service Order + +
+
+ +
+ +
+ +
+
+ Vehicle Information +
+
+
+
+ Make & Model +
{{ $vehicle->display_name }}
+
+
+ Color +
+
+ {{ $vehicle->color }} +
+
+
+ VIN +
{{ $vehicle->vin }}
+
+
+ License Plate +
{{ $vehicle->license_plate }}
+
+
+ Current Mileage +
{{ number_format($vehicle->mileage) }} miles
+
+
+ Status +
+ + {{ ucfirst($vehicle->status) }} + +
+
+ @if($vehicle->engine_type) +
+ Engine Type +
{{ $vehicle->engine_type }}
+
+ @endif + @if($vehicle->transmission) +
+ Transmission +
{{ $vehicle->transmission }}
+
+ @endif + @if($vehicle->notes) +
+ Notes +
{{ $vehicle->notes }}
+
+ @endif +
+
+
+ + +
+
+ Owner Information + + View Customer + +
+
+
+
+ Customer Name +
{{ $vehicle->customer->full_name }}
+
+ + +
+ Customer Status +
+ + {{ ucfirst($vehicle->customer->status) }} + +
+
+
+
+
+ + +
+
+ Service History +
+
+ + + + + + + + + + + + + @forelse($vehicle->serviceOrders as $order) + + + + + + + + + @empty + + + + @endforelse + +
Order #DateTechnicianStatusTotalActions
{{ $order->order_number }}{{ $order->created_at->format('M j, Y') }}{{ $order->assignedTechnician?->full_name ?? 'Unassigned' }} + + {{ ucfirst(str_replace('_', ' ', $order->status)) }} + + ${{ number_format($order->total_amount, 2) }} + View +
No service history yet.
+
+
+
+ + +
+ + @if($vehicle->vehicle_image) +
+
+ Vehicle Photo +
+
+ {{ $vehicle->display_name }} +
+
+ @endif + + +
+
+ Quick Stats +
+
+
+ Service Orders + {{ $vehicle->serviceOrders->count() }} +
+
+ Total Spent + ${{ number_format($vehicle->serviceOrders->sum('total_amount'), 2) }} +
+
+ Last Service + + @if($vehicle->last_service_date) + {{ $vehicle->last_service_date->format('M j, Y') }} + @else + Never + @endif + +
+
+ Vehicle Age + {{ date('Y') - $vehicle->year }} years +
+
+ Added + {{ $vehicle->created_at->format('M j, Y') }} +
+
+
+ + +
+
+ Upcoming Appointments + + + Schedule + +
+
+ @forelse($vehicle->appointments->where('scheduled_datetime', '>=', now())->take(3) as $appointment) +
+
{{ $appointment->scheduled_datetime->format('M j, Y g:i A') }}
+
{{ $appointment->service_requested }}
+
+ + {{ ucfirst($appointment->status) }} + +
+
+ @empty +
No upcoming appointments
+ @endforelse +
+
+ + +
+
+ Recent Inspections +
+
+ @forelse($vehicle->inspections->take(3) as $inspection) +
+
{{ $inspection->inspection_date->format('M j, Y') }}
+
{{ $inspection->inspection_type }}
+
+ + {{ ucfirst($inspection->status) }} + +
+
+ @empty +
No inspections recorded
+ @endforelse +
+
+
+
+
diff --git a/resources/views/livewire/work-orders/create.blade.php b/resources/views/livewire/work-orders/create.blade.php new file mode 100644 index 0000000..379e555 --- /dev/null +++ b/resources/views/livewire/work-orders/create.blade.php @@ -0,0 +1,3 @@ +
+ {{-- If you look to others for fulfillment, you will never truly be fulfilled. --}} +
diff --git a/resources/views/livewire/work-orders/edit.blade.php b/resources/views/livewire/work-orders/edit.blade.php new file mode 100644 index 0000000..ad58cc8 --- /dev/null +++ b/resources/views/livewire/work-orders/edit.blade.php @@ -0,0 +1,3 @@ +
+ {{-- Stop trying to control. --}} +
diff --git a/resources/views/livewire/work-orders/index.blade.php b/resources/views/livewire/work-orders/index.blade.php new file mode 100644 index 0000000..23fa440 --- /dev/null +++ b/resources/views/livewire/work-orders/index.blade.php @@ -0,0 +1,135 @@ +
+
+ +
+
+

Work Orders

+

Manage active work orders and service tasks

+
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+ @if($workOrders->count() > 0) +
+ + + + + + + + + + + + + + + @foreach($workOrders as $workOrder) + + + + + + + + + + + @endforeach + +
Work Order #CustomerVehicleStatusPriorityTechnicianDue DateActions
+ {{ $workOrder->work_order_number }} + + {{ $workOrder->jobCard->customer->first_name }} {{ $workOrder->jobCard->customer->last_name }} + + {{ $workOrder->jobCard->vehicle->year }} {{ $workOrder->jobCard->vehicle->make }} {{ $workOrder->jobCard->vehicle->model }} + + + {{ str_replace('_', ' ', ucfirst($workOrder->status)) }} + + + + {{ ucfirst($workOrder->priority) }} + + + {{ $workOrder->assignedTechnician->first_name ?? 'Unassigned' }} {{ $workOrder->assignedTechnician->last_name ?? '' }} + + {{ $workOrder->target_completion_date ? $workOrder->target_completion_date->format('M j, Y') : 'Not set' }} + + +
+
+ + +
+ {{ $workOrders->links() }} +
+ @else +
+ + + +

No work orders found

+

+ @if($search || $statusFilter || $priorityFilter) + Try adjusting your search criteria. + @else + Work orders will appear here once estimates are approved. + @endif +

+
+ @endif +
+
+
diff --git a/resources/views/livewire/work-orders/show.blade.php b/resources/views/livewire/work-orders/show.blade.php new file mode 100644 index 0000000..d5f5aa4 --- /dev/null +++ b/resources/views/livewire/work-orders/show.blade.php @@ -0,0 +1,3 @@ +
+ {{-- The best athlete wants his opponent at his best. --}} +
diff --git a/resources/views/partials/head.blade.php b/resources/views/partials/head.blade.php new file mode 100644 index 0000000..dce8058 --- /dev/null +++ b/resources/views/partials/head.blade.php @@ -0,0 +1,14 @@ + + + +{{ $title ?? config('app.name') }} + + + + + + + + +@vite(['resources/css/app.css', 'resources/js/app.js']) +@fluxAppearance diff --git a/resources/views/partials/settings-heading.blade.php b/resources/views/partials/settings-heading.blade.php new file mode 100644 index 0000000..925ace9 --- /dev/null +++ b/resources/views/partials/settings-heading.blade.php @@ -0,0 +1,5 @@ +
+ {{ __('Settings') }} + {{ __('Manage your profile and account settings') }} + +
diff --git a/resources/views/partials/theme.blade.php b/resources/views/partials/theme.blade.php new file mode 100644 index 0000000..9b8db32 --- /dev/null +++ b/resources/views/partials/theme.blade.php @@ -0,0 +1,14 @@ + + + + + + + + + + Light + Dark + System + + \ No newline at end of file diff --git a/resources/views/reports.blade.php b/resources/views/reports.blade.php new file mode 100644 index 0000000..9c02231 --- /dev/null +++ b/resources/views/reports.blade.php @@ -0,0 +1,3 @@ + + + diff --git a/resources/views/service-items/index.blade.php b/resources/views/service-items/index.blade.php new file mode 100644 index 0000000..1d5f540 --- /dev/null +++ b/resources/views/service-items/index.blade.php @@ -0,0 +1,13 @@ + + +

+ {{ __('Service Items Management') }} +

+
+ +
+
+ @livewire('service-items.manage') +
+
+
diff --git a/resources/views/service-orders/create.blade.php b/resources/views/service-orders/create.blade.php new file mode 100644 index 0000000..59eda62 --- /dev/null +++ b/resources/views/service-orders/create.blade.php @@ -0,0 +1,3 @@ + + + diff --git a/resources/views/service-orders/edit.blade.php b/resources/views/service-orders/edit.blade.php new file mode 100644 index 0000000..9496bc2 --- /dev/null +++ b/resources/views/service-orders/edit.blade.php @@ -0,0 +1,3 @@ + + + diff --git a/resources/views/service-orders/index.blade.php b/resources/views/service-orders/index.blade.php new file mode 100644 index 0000000..3ad25e6 --- /dev/null +++ b/resources/views/service-orders/index.blade.php @@ -0,0 +1,3 @@ + + + diff --git a/resources/views/service-orders/invoice.blade.php b/resources/views/service-orders/invoice.blade.php new file mode 100644 index 0000000..161d28a --- /dev/null +++ b/resources/views/service-orders/invoice.blade.php @@ -0,0 +1,3 @@ + + + diff --git a/resources/views/service-orders/show.blade.php b/resources/views/service-orders/show.blade.php new file mode 100644 index 0000000..b6992dc --- /dev/null +++ b/resources/views/service-orders/show.blade.php @@ -0,0 +1,3 @@ + + + diff --git a/resources/views/settings/general.blade.php b/resources/views/settings/general.blade.php new file mode 100644 index 0000000..97fc9a8 --- /dev/null +++ b/resources/views/settings/general.blade.php @@ -0,0 +1,229 @@ + +
+ +
+

Settings: General

+
+ + + + + +
+ @csrf + @method('PUT') + + +
+
+

Business Information

+

Basic details about your auto repair shop

+
+ +
+
+ + + @error('shop_name') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('shop_phone') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('shop_email') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('shop_website') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('shop_address') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('shop_city') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('shop_state') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('shop_zip_code') +

{{ $message }}

+ @enderror +
+
+
+ + +
+
+

Financial Settings

+

Configure tax rates and currency preferences

+
+ +
+
+ + + @error('default_tax_rate') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('currency') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('currency_symbol') +

{{ $message }}

+ @enderror +
+
+
+ + +
+
+

System Settings

+

Configure timezone, date format, and notification preferences

+
+ +
+
+ + + @error('timezone') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('date_format') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('time_format') +

{{ $message }}

+ @enderror +
+
+ +
+
+ enable_notifications ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+ +
+ enable_email_notifications ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+ +
+ enable_sms_notifications ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+
+
+ + +
+ + Cancel + + +
+
+
+
diff --git a/resources/views/settings/inventory.blade.php b/resources/views/settings/inventory.blade.php new file mode 100644 index 0000000..81f84f4 --- /dev/null +++ b/resources/views/settings/inventory.blade.php @@ -0,0 +1,334 @@ + +
+ +
+

Settings

+
+ + + + + +
+ @csrf + @method('PUT') + + +
+
+

Stock Management

+

Configure inventory tracking and stock level alerts

+
+ +
+
+ + +

Alert when stock falls below this level

+ @error('low_stock_threshold') +

{{ $message }}

+ @enderror +
+ +
+ + +

Critical stock level requiring immediate attention

+ @error('critical_stock_threshold') +

{{ $message }}

+ @enderror +
+ +
+ + +

Default quantity to reorder

+ @error('default_reorder_quantity') +

{{ $message }}

+ @enderror +
+ +
+ + +

Expected delivery time for orders

+ @error('default_lead_time_days') +

{{ $message }}

+ @enderror +
+ +
+ + +

Default markup percentage for parts

+ @error('default_markup_percentage') +

{{ $message }}

+ @enderror +
+ +
+ + +

Number of preferred suppliers to maintain

+ @error('preferred_supplier_count') +

{{ $message }}

+ @enderror +
+ +
+ + +

Minimum amount for supplier orders

+ @error('minimum_order_amount') +

{{ $message }}

+ @enderror +
+
+ +
+
+ enable_low_stock_alerts ?? true) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+ +
+ enable_auto_reorder ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+ +
+ track_serial_numbers ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+ +
+ enable_barcode_scanning ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+
+
+ + +
+
+

Pricing Rules

+

Configure markup percentages and pricing strategies

+
+ +
+
+ + +

Default markup percentage for parts

+ @error('default_part_markup') +

{{ $message }}

+ @enderror +
+ +
+ + +

Default core charge as percentage of part cost

+ @error('core_charge_percentage') +

{{ $message }}

+ @enderror +
+ +
+ + +

Shop supply fee as percentage of total

+ @error('shop_supply_fee') +

{{ $message }}

+ @enderror +
+ +
+ + +

Fixed environmental disposal fee

+ @error('environmental_fee') +

{{ $message }}

+ @enderror +
+ +
+ + +

Fee for waste oil disposal

+ @error('waste_oil_fee') +

{{ $message }}

+ @enderror +
+ +
+ + +

Fee per tire for disposal

+ @error('tire_disposal_fee') +

{{ $message }}

+ @enderror +
+
+ +
+
+ enable_volume_discounts ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Apply volume-based pricing discounts

+ +
+ enable_seasonal_pricing ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Apply seasonal pricing adjustments

+ +
+ enable_customer_specific_pricing ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Allow custom pricing for specific customers

+
+
+ + +
+
+

Supplier Settings

+

Configure default supplier preferences and ordering settings

+
+ +
+
+ + + @error('default_payment_terms') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('preferred_ordering_method') +

{{ $message }}

+ @enderror +
+ +
+ + +

Minimum order amount for suppliers

+ @error('minimum_order_amount') +

{{ $message }}

+ @enderror +
+ +
+ + +

Order amount for free shipping

+ @error('free_shipping_threshold') +

{{ $message }}

+ @enderror +
+
+ +
+
+ require_po_approval ?? true) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Require manager approval for purchase orders

+ +
+ enable_dropship ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Allow parts to be shipped directly to customers

+ +
+ enable_backorders ?? true) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Allow ordering parts that are out of stock

+
+
+ + +
+ + Cancel + + +
+
+
+
diff --git a/resources/views/settings/inventory_fixed.blade.php b/resources/views/settings/inventory_fixed.blade.php new file mode 100644 index 0000000..09489d0 --- /dev/null +++ b/resources/views/settings/inventory_fixed.blade.php @@ -0,0 +1,320 @@ + +
+ +
+

Inventory Settings

+
+ + @if(session('success')) +
+
+ + + +

{{ session('success') }}

+
+
+ @endif + + +
+ @csrf + @method('PUT') + + +
+
+

Stock Management

+

Configure inventory tracking and stock level alerts

+
+ +
+
+ + +

Alert when stock falls below this level

+ @error('minimum_stock_level') +

{{ $message }}

+ @enderror +
+ +
+ + +

Recommended maximum stock level

+ @error('maximum_stock_level') +

{{ $message }}

+ @enderror +
+ +
+ + +

Trigger reorder when stock reaches this level

+ @error('reorder_point') +

{{ $message }}

+ @enderror +
+ +
+ + +

Default quantity to reorder

+ @error('reorder_quantity') +

{{ $message }}

+ @enderror +
+ +
+ + +

Average supplier lead time

+ @error('lead_time_days') +

{{ $message }}

+ @enderror +
+
+ +
+
+ enable_low_stock_alerts ?? true) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+ +
+ enable_automatic_reorder ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+ +
+ track_serial_numbers ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+ +
+ enable_barcode_scanning ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+
+
+ + +
+
+

Pricing Rules

+

Configure markup percentages and pricing strategies

+
+ +
+
+ + +

Default markup percentage for parts

+ @error('default_part_markup') +

{{ $message }}

+ @enderror +
+ +
+ + +

Default core charge as percentage of part cost

+ @error('core_charge_percentage') +

{{ $message }}

+ @enderror +
+ +
+ + +

Shop supply fee as percentage of total

+ @error('shop_supply_fee') +

{{ $message }}

+ @enderror +
+ +
+ + +

Fixed environmental disposal fee

+ @error('environmental_fee') +

{{ $message }}

+ @enderror +
+ +
+ + +

Fee for waste oil disposal

+ @error('waste_oil_fee') +

{{ $message }}

+ @enderror +
+ +
+ + +

Fee per tire for disposal

+ @error('tire_disposal_fee') +

{{ $message }}

+ @enderror +
+
+ +
+
+ enable_volume_discounts ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Apply volume-based pricing discounts

+ +
+ enable_seasonal_pricing ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Apply seasonal pricing adjustments

+ +
+ enable_customer_specific_pricing ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Allow custom pricing for specific customers

+
+
+ + +
+
+

Supplier Settings

+

Configure default supplier preferences and ordering settings

+
+ +
+
+ + + @error('default_payment_terms') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('preferred_ordering_method') +

{{ $message }}

+ @enderror +
+ +
+ + +

Minimum order amount for suppliers

+ @error('minimum_order_amount') +

{{ $message }}

+ @enderror +
+ +
+ + +

Order amount for free shipping

+ @error('free_shipping_threshold') +

{{ $message }}

+ @enderror +
+
+ +
+
+ require_po_approval ?? true) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Require manager approval for purchase orders

+ +
+ enable_dropship ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Allow parts to be shipped directly to customers

+ +
+ enable_backorders ?? true) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Allow ordering parts that are out of stock

+
+
+ + +
+ + Cancel + + +
+
+
+
diff --git a/resources/views/settings/notifications.blade.php b/resources/views/settings/notifications.blade.php new file mode 100644 index 0000000..17aa40b --- /dev/null +++ b/resources/views/settings/notifications.blade.php @@ -0,0 +1,283 @@ + +
+ +
+

Settings

+
+ + + + + +
+ @csrf + @method('PUT') + + +
+
+

Email Notifications

+

Configure when and how to send email notifications

+
+ +
+
+ + + @error('from_email') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('from_name') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('escalation_hours') +

{{ $message }}

+ @enderror +
+
+ +
+
+ send_appointment_reminders ?? true) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Email customers about upcoming appointments

+ +
+ send_service_completion ?? true) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Notify customers when service is completed

+ +
+ send_payment_confirmations ?? true) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Email receipt confirmations after payment

+ +
+ send_maintenance_reminders ?? true) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Remind customers of scheduled maintenance

+ +
+ send_estimate_notifications ?? true) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Email estimates to customers for approval

+
+
+ + +
+
+

SMS Notifications

+

Configure text message notifications

+
+ +
+
+ + + @error('sms_provider') +

{{ $message }}

+ @enderror +
+ +
+ + +

SMS sender number or short code

+ @error('sms_from_number') +

{{ $message }}

+ @enderror +
+
+ +
+
+ enable_sms_notifications ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Allow sending SMS notifications to customers

+ +
+ sms_appointment_reminders ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+ +
+ sms_service_updates ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+ +
+ sms_urgent_notifications ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+
+
+ + +
+
+

Internal Notifications

+

Configure notifications for staff and management

+
+ +
+
+ + +

Email for management notifications

+ @error('manager_email') +

{{ $message }}

+ @enderror +
+ +
+ + +

Email for technician assignments

+ @error('technician_notification_email') +

{{ $message }}

+ @enderror +
+
+ +
+
+ notify_new_appointments ?? true) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Alert staff when new appointments are scheduled

+ +
+ notify_estimate_requests ?? true) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Alert when customers request estimates

+ +
+ notify_low_inventory ?? true) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Alert when parts are running low

+ +
+ notify_overdue_payments ?? true) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Alert about overdue customer payments

+
+
+ + +
+
+

Notification Timing

+

Configure when notifications are sent

+
+ +
+
+ + + @error('appointment_reminder_hours') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('maintenance_reminder_days') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('followup_after_service_days') +

{{ $message }}

+ @enderror +
+
+
+ + +
+ + Cancel + + +
+
+
+
diff --git a/resources/views/settings/security.blade.php b/resources/views/settings/security.blade.php new file mode 100644 index 0000000..536503c --- /dev/null +++ b/resources/views/settings/security.blade.php @@ -0,0 +1,425 @@ + +
+ +
+

Settings

+
+ + + + + +
+ @csrf + @method('PUT') + + +
+
+

Authentication & Access Control

+

Configure login security and session management

+
+
+ +
+
+ enable_two_factor_auth ?? false) ? 'checked' : '' }} + class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded dark:bg-gray-700"> +
+
+ +

Require 2FA for all user accounts

+
+
+ +
+ +
+ + + @error('session_timeout_minutes') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('password_expiry_days') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('max_login_attempts') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('lockout_duration_minutes') +

{{ $message }}

+ @enderror +
+
+
+
+ + +
+
+

Password Requirements

+

Set password complexity and strength requirements

+
+
+ +
+ + + @error('min_password_length') +

{{ $message }}

+ @enderror +
+ + +
+
+
+ require_uppercase ?? true) ? 'checked' : '' }} + class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded dark:bg-gray-700"> +
+
+ +
+
+ +
+
+ require_lowercase ?? true) ? 'checked' : '' }} + class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded dark:bg-gray-700"> +
+
+ +
+
+ +
+
+ require_numbers ?? true) ? 'checked' : '' }} + class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded dark:bg-gray-700"> +
+
+ +
+
+ +
+
+ require_special_characters ?? true) ? 'checked' : '' }} + class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded dark:bg-gray-700"> +
+
+ +
+
+
+
+
+ + +
+
+

Data Protection & Logging

+

Configure data encryption, audit logging, and backup settings

+
+
+ +
+
+
+ enable_data_encryption ?? true) ? 'checked' : '' }} + class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded dark:bg-gray-700"> +
+
+ +

Encrypt sensitive customer and vehicle data

+
+
+ +
+
+ enable_audit_logging ?? true) ? 'checked' : '' }} + class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded dark:bg-gray-700"> +
+
+ +

Log all user actions and system changes

+
+
+ +
+
+ enable_backup_alerts ?? true) ? 'checked' : '' }} + class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded dark:bg-gray-700"> +
+
+ +

Send notifications about backup status

+
+
+
+ + +
+ + + @error('audit_log_retention_days') +

{{ $message }}

+ @enderror +
+
+
+ + +
+
+

API Security

+

Configure API access and rate limiting

+
+
+ +
+
+ enable_api_rate_limiting ?? true) ? 'checked' : '' }} + class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded dark:bg-gray-700"> +
+
+ +

Limit the number of API requests per minute

+
+
+ + +
+ + + @error('api_requests_per_minute') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('allowed_ip_addresses') +

{{ $message }}

+ @enderror +

Enter one IP address per line. Leave empty to allow all IP addresses.

+
+
+
+ + +
+
+

Customer Portal Security

+

Configure customer access and data permissions

+
+
+ +
+
+
+ allow_customer_portal ?? true) ? 'checked' : '' }} + class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded dark:bg-gray-700"> +
+
+ +

Allow customers to access their repair history and estimates

+
+
+ +
+
+ allow_customer_data_download ?? false) ? 'checked' : '' }} + class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded dark:bg-gray-700"> +
+
+ +

Let customers download their data (GDPR compliance)

+
+
+
+ + +
+ + + @error('customer_session_timeout_minutes') +

{{ $message }}

+ @enderror +
+
+
+ + +
+ + Cancel + + +
+
+
+
diff --git a/resources/views/settings/service.blade.php b/resources/views/settings/service.blade.php new file mode 100644 index 0000000..a4b54b2 --- /dev/null +++ b/resources/views/settings/service.blade.php @@ -0,0 +1,268 @@ + +
+ +
+

Settings

+
+ + + + + +
+ @csrf + @method('PUT') + + +
+
+

Service Configuration

+

Basic service settings and defaults

+
+ +
+
+ + + @error('standard_labor_rate') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('overtime_labor_rate') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('weekend_labor_rate') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('holiday_labor_rate') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('minimum_labor_hours') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('maximum_labor_hours') +

{{ $message }}

+ @enderror +
+
+
+ + +
+
+

Service Intervals

+

Configure default service intervals for maintenance reminders

+
+ +
+
+ + + @error('oil_change_interval') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('tire_rotation_interval') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('brake_inspection_interval') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('general_inspection_interval') +

{{ $message }}

+ @enderror +
+
+
+ + +
+
+

Warranty Settings

+

Configure default warranty periods for parts and labor

+
+ +
+
+ + + @error('default_parts_warranty_days') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('default_labor_warranty_days') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('reminder_advance_days') +

{{ $message }}

+ @enderror +
+
+
+ + +
+
+

Quality Control

+

Configure quality control standards and inspection requirements

+
+ +
+
+
+ + +

Percentage of jobs that require quality control inspection

+ @error('quality_control_percentage') +

{{ $message }}

+ @enderror +
+ +
+ + +

Minimum acceptable quality score (1-10 scale)

+ @error('minimum_quality_score') +

{{ $message }}

+ @enderror +
+
+ +
+
+ require_quality_inspection ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Require quality inspection for all completed jobs

+ +
+ require_customer_approval ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Require customer approval before starting work on estimates over threshold

+ +
+ enable_work_photos ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Allow technicians to take photos during service work

+ +
+ enable_digital_signature ?? false) ? 'checked' : '' }} + class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-zinc-300 rounded" /> + +
+

Require digital signatures for job completion and customer approval

+
+ +
+ + +

Require customer approval for estimates above this amount

+ @error('customer_approval_threshold') +

{{ $message }}

+ @enderror +
+
+
+ + +
+ + Cancel + + +
+
+
+
diff --git a/resources/views/technician-management.blade.php b/resources/views/technician-management.blade.php new file mode 100644 index 0000000..cd0fe1f --- /dev/null +++ b/resources/views/technician-management.blade.php @@ -0,0 +1,49 @@ + +
+ +
+
+

Technician Management

+

Comprehensive technician workforce management

+
+ +
+ + + + + + + + + + + + + + + +
+ + @push('scripts') + + @endpush +
diff --git a/resources/views/technician-reports.blade.php b/resources/views/technician-reports.blade.php new file mode 100644 index 0000000..19f4066 --- /dev/null +++ b/resources/views/technician-reports.blade.php @@ -0,0 +1,61 @@ + +
+
+
+

Performance Reports

+

View technician performance metrics and analytics

+
+ +
+ + +
+
+
+
{{ \App\Models\TechnicianPerformance::count() }}
+
Performance Records
+
+
+
+
+
{{ \App\Models\TechnicianWorkload::count() }}
+
Workload Records
+
+
+
+
+
{{ number_format(\App\Models\TechnicianPerformance::where('metric_type', 'customer_rating')->avg('metric_value'), 1) ?: 0 }}
+
Avg Rating
+
+
+
+
+
{{ number_format(\App\Models\TechnicianWorkload::avg('utilization_rate'), 1) ?: 0 }}%
+
Avg Utilization
+
+
+
+ + +
+ +
+

Performance Tracking

+ +
+ + +
+

Workload Management

+ +
+
+
+
diff --git a/resources/views/technician-skills.blade.php b/resources/views/technician-skills.blade.php new file mode 100644 index 0000000..a28bed4 --- /dev/null +++ b/resources/views/technician-skills.blade.php @@ -0,0 +1,46 @@ + +
+
+
+

Skills Management

+

Manage technician skills and certifications across your workforce

+
+ +
+ + +
+
+
+
{{ \App\Models\TechnicianSkill::distinct('skill_name')->count() }}
+
Unique Skills
+
+
+
+
+
{{ \App\Models\TechnicianSkill::where('is_primary_skill', true)->count() }}
+
Primary Skills
+
+
+
+
+
{{ \App\Models\TechnicianSkill::whereNotNull('certification_body')->count() }}
+
Certifications
+
+
+
+ + +
+

Technician Skills

+ +
+
+
diff --git a/resources/views/technicians/index.blade.php b/resources/views/technicians/index.blade.php new file mode 100644 index 0000000..de0f810 --- /dev/null +++ b/resources/views/technicians/index.blade.php @@ -0,0 +1,139 @@ + +
+
+
+

Technicians

+

Manage your technician workforce

+
+
+ + +
+
+
+
+
+ + + +
+
+
+

Technician Management

+

Full technician profiles and skills tracking

+
+
+ +
+ +
+
+
+
+ + + + +
+
+
+

Skills & Certifications

+

Track skills and certification status

+
+
+ +
+ +
+
+
+
+ + + +
+
+
+

Performance Reports

+

View performance metrics and analytics

+
+
+ +
+
+ + +
+

Quick Overview

+
+
+
{{ \App\Models\Technician::count() }}
+
Total Technicians
+
+
+
{{ \App\Models\Technician::where('status', 'active')->count() }}
+
Active
+
+
+
{{ \App\Models\Technician::where('status', 'on_leave')->count() }}
+
On Leave
+
+
+
{{ \App\Models\TechnicianSkill::distinct('technician_id')->count() }}
+
With Skills
+
+
+
+ + +
+

Recent Activity

+
+ @if(\App\Models\Technician::exists()) + @foreach(\App\Models\Technician::with(['skills', 'performances'])->latest()->take(5)->get() as $technician) +
+
+ + {{ strtoupper(substr($technician->first_name, 0, 1) . substr($technician->last_name, 0, 1)) }} + +
+
+
{{ $technician->full_name }}
+
+ {{ $technician->employee_id }} • {{ ucfirst($technician->status) }} + @if($technician->skills->count() > 0) + • {{ $technician->skills->count() }} skills + @endif +
+
+
+ {{ $technician->created_at->diffForHumans() }} +
+
+ @endforeach + @else +
+

No technicians found. Start by adding your first technician.

+ +
+ @endif +
+
+
+
diff --git a/resources/views/vehicles/create.blade.php b/resources/views/vehicles/create.blade.php new file mode 100644 index 0000000..c8ad958 --- /dev/null +++ b/resources/views/vehicles/create.blade.php @@ -0,0 +1,3 @@ + + + diff --git a/resources/views/vehicles/edit.blade.php b/resources/views/vehicles/edit.blade.php new file mode 100644 index 0000000..a23f7c4 --- /dev/null +++ b/resources/views/vehicles/edit.blade.php @@ -0,0 +1,3 @@ + + + diff --git a/resources/views/vehicles/index.blade.php b/resources/views/vehicles/index.blade.php new file mode 100644 index 0000000..29dea40 --- /dev/null +++ b/resources/views/vehicles/index.blade.php @@ -0,0 +1,3 @@ + + + diff --git a/resources/views/vehicles/show.blade.php b/resources/views/vehicles/show.blade.php new file mode 100644 index 0000000..a1054f6 --- /dev/null +++ b/resources/views/vehicles/show.blade.php @@ -0,0 +1,3 @@ + + + diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php new file mode 100644 index 0000000..a808a39 --- /dev/null +++ b/resources/views/welcome.blade.php @@ -0,0 +1,278 @@ + + + + + + + Laravel + + + + + + + + + + + + + +
+ @if (Route::has('login')) + + @endif +
+
+
+
+

Let's get started

+

Laravel has an incredibly rich ecosystem.
We suggest starting with the following.

+ + +
+
+ {{-- Laravel Logo --}} + + + + + + + + + + + {{-- Light Mode 12 SVG --}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{-- Dark Mode 12 SVG --}} + +
+
+
+
+ + @if (Route::has('login')) + + @endif + + diff --git a/routes/auth.php b/routes/auth.php new file mode 100644 index 0000000..62e6352 --- /dev/null +++ b/routes/auth.php @@ -0,0 +1,35 @@ +group(function () { + Volt::route('login', 'auth.login') + ->name('login'); + + Volt::route('register', 'auth.register') + ->name('register'); + + Volt::route('forgot-password', 'auth.forgot-password') + ->name('password.request'); + + Volt::route('reset-password/{token}', 'auth.reset-password') + ->name('password.reset'); + +}); + +Route::middleware('auth')->group(function () { + Volt::route('verify-email', 'auth.verify-email') + ->name('verification.notice'); + + Route::get('verify-email/{id}/{hash}', VerifyEmailController::class) + ->middleware(['signed', 'throttle:6,1']) + ->name('verification.verify'); + + Volt::route('confirm-password', 'auth.confirm-password') + ->name('password.confirm'); +}); + +Route::post('logout', App\Livewire\Actions\Logout::class) + ->name('logout'); diff --git a/routes/console.php b/routes/console.php new file mode 100644 index 0000000..3c9adf1 --- /dev/null +++ b/routes/console.php @@ -0,0 +1,8 @@ +comment(Inspiring::quote()); +})->purpose('Display an inspiring quote'); diff --git a/routes/test.php b/routes/test.php new file mode 100644 index 0000000..ddf6147 --- /dev/null +++ b/routes/test.php @@ -0,0 +1,23 @@ +user(); + + if (!$user) { + return response()->json(['error' => 'Not authenticated']); + } + + return response()->json([ + 'user' => [ + 'name' => $user->name, + 'email' => $user->email, + ], + 'roles' => $user->roles->pluck('name'), + 'permissions' => $user->getAllPermissions()->pluck('name'), + 'has_users_view' => $user->hasPermission('users.view'), + 'has_users_manage' => $user->hasPermission('users.manage'), + 'is_super_admin' => $user->hasRole('super_admin'), + ]); +})->middleware('auth'); diff --git a/routes/web.php b/routes/web.php new file mode 100644 index 0000000..372657e --- /dev/null +++ b/routes/web.php @@ -0,0 +1,212 @@ +name('home'); + +Route::view('dashboard', 'dashboard') + ->middleware(['auth', 'verified']) + ->name('dashboard'); + +Route::middleware(['auth'])->group(function () { + // Settings routes + Route::redirect('settings', 'settings/general'); + Volt::route('settings/profile', 'settings.profile')->name('settings.profile'); + Volt::route('settings/password', 'settings.password')->name('settings.password'); + + // Application Settings Routes + Route::prefix('settings')->name('settings.')->group(function () { + Route::get('/general', [App\Http\Controllers\SettingsController::class, 'general'])->name('general'); + Route::put('/general', [App\Http\Controllers\SettingsController::class, 'updateGeneral'])->name('general.update'); + + Route::get('/service', [App\Http\Controllers\SettingsController::class, 'service'])->name('service'); + Route::put('/service', [App\Http\Controllers\SettingsController::class, 'updateService'])->name('service.update'); + + Route::get('/inventory', [App\Http\Controllers\SettingsController::class, 'inventory'])->name('inventory'); + Route::put('/inventory', [App\Http\Controllers\SettingsController::class, 'updateInventory'])->name('inventory.update'); + + Route::get('/notifications', [App\Http\Controllers\SettingsController::class, 'notifications'])->name('notifications'); + Route::put('/notifications', [App\Http\Controllers\SettingsController::class, 'updateNotifications'])->name('notifications.update'); + + Route::get('/security', [App\Http\Controllers\SettingsController::class, 'security'])->name('security'); + Route::put('/security', [App\Http\Controllers\SettingsController::class, 'updateSecurity'])->name('security.update'); + }); + // Volt::route('settings/appearance', 'settings.appearance')->name('settings.appearance'); + + // Car Repair System Routes + Route::resource('customers', CustomerController::class)->middleware('permission:customers.view'); + Route::resource('vehicles', VehicleController::class)->middleware('permission:vehicles.view'); + Route::resource('service-orders', ServiceOrderController::class)->middleware('permission:service-orders.view'); + + // Additional Service Order Routes + Route::get('/service-orders/{serviceOrder}/invoice', [ServiceOrderController::class, 'invoice']) + ->middleware('permission:service-orders.view') + ->name('service-orders.invoice'); + + // Livewire Component Routes + Route::get('/customers-list', function () { + return view('customers.index'); + })->middleware('permission:customers.view')->name('customers.list'); + + Route::get('/service-orders-list', function () { + return view('service-orders.index'); + })->middleware('permission:service-orders.view')->name('service-orders.list'); + + Route::get('/vehicles', function () { + return view('vehicles.index'); + })->middleware('permission:vehicles.view')->name('vehicles.index'); + + Route::get('/appointments', function () { + return view('appointments.index'); + })->middleware('permission:appointments.view')->name('appointments.index'); + + Route::get('/service-items', function () { + return view('service-items.index'); + })->middleware('permission:service-orders.view')->name('service-items.index'); + + // Appointments Management Routes + Route::prefix('appointments')->name('appointments.')->middleware('permission:appointments.view')->group(function () { + Route::get('/', \App\Livewire\Appointments\Index::class)->name('index'); + Route::get('/create', \App\Livewire\Appointments\Create::class)->middleware('permission:appointments.create')->name('create'); + Route::get('/{appointment}/edit', \App\Livewire\Appointments\Form::class)->middleware('permission:appointments.update')->name('edit'); + Route::get('/calendar', \App\Livewire\Appointments\Calendar::class)->name('calendar'); + }); + + Route::get('/inventory', [App\Http\Controllers\InventoryController::class, 'index']) + ->middleware('permission:inventory.view') + ->name('inventory.index'); + + // Inventory Management Routes + Route::prefix('inventory')->name('inventory.')->middleware('permission:inventory.view')->group(function () { + Route::get('/dashboard', \App\Livewire\Inventory\Dashboard::class)->name('dashboard'); + + // Parts Routes + Route::prefix('parts')->name('parts.')->group(function () { + Route::get('/', \App\Livewire\Inventory\Parts\Index::class)->name('index'); + Route::get('/create', \App\Livewire\Inventory\Parts\Create::class)->middleware('permission:inventory.create')->name('create'); + Route::get('/{part}', \App\Livewire\Inventory\Parts\Show::class)->name('show'); + Route::get('/{part}/edit', \App\Livewire\Inventory\Parts\Edit::class)->middleware('permission:inventory.update')->name('edit'); + }); + + // Suppliers Routes + Route::prefix('suppliers')->name('suppliers.')->group(function () { + Route::get('/', \App\Livewire\Inventory\Suppliers\Index::class)->name('index'); + Route::get('/create', \App\Livewire\Inventory\Suppliers\Create::class)->middleware('permission:inventory.create')->name('create'); + Route::get('/{supplier}/edit', \App\Livewire\Inventory\Suppliers\Edit::class)->middleware('permission:inventory.update')->name('edit'); + }); + + // Purchase Orders Routes + Route::prefix('purchase-orders')->name('purchase-orders.')->middleware('permission:inventory.purchase-orders')->group(function () { + Route::get('/', \App\Livewire\Inventory\PurchaseOrders\Index::class)->name('index'); + Route::get('/create', \App\Livewire\Inventory\PurchaseOrders\Create::class)->name('create'); + Route::get('/{purchaseOrder}/edit', \App\Livewire\Inventory\PurchaseOrders\Edit::class)->name('edit'); + Route::get('/{purchaseOrder}', \App\Livewire\Inventory\PurchaseOrders\Show::class)->name('show'); + }); + + // Stock Movements Routes + Route::prefix('stock-movements')->name('stock-movements.')->middleware('permission:inventory.stock-movements')->group(function () { + Route::get('/', \App\Livewire\Inventory\StockMovements\Index::class)->name('index'); + Route::get('/create', \App\Livewire\Inventory\StockMovements\Create::class)->name('create'); + }); + }); + + Route::get('/technicians', function () { + return view('technicians.index'); + })->middleware('permission:technicians.view')->name('technicians.index'); + + // Technician Management Routes + Route::prefix('technician')->name('technician.')->middleware('permission:technicians.view')->group(function () { + Route::get('/management', function () { + return view('technician-management'); + })->middleware('permission:technicians.update')->name('management'); + + Route::get('/skills', function () { + return view('technician-skills'); + })->name('skills'); + + Route::get('/reports', function () { + return view('technician-reports'); + })->middleware('permission:technicians.view-performance')->name('reports'); + }); + + // Job Card Management Routes + Route::prefix('job-cards')->name('job-cards.')->middleware('permission:job-cards.view')->group(function () { + Route::get('/', \App\Livewire\JobCards\Index::class)->name('index'); + Route::get('/create', \App\Livewire\JobCards\Create::class)->middleware('permission:job-cards.create')->name('create'); + Route::get('/{jobCard}', \App\Livewire\JobCards\Show::class)->name('show'); + Route::get('/{jobCard}/edit', \App\Livewire\JobCards\Edit::class)->middleware('permission:job-cards.update')->name('edit'); + Route::get('/{jobCard}/workflow', \App\Livewire\JobCards\WorkflowStatus::class)->name('workflow'); + }); + + // Diagnosis Routes + Route::prefix('diagnosis')->name('diagnosis.')->middleware('permission:job-cards.view')->group(function () { + Route::get('/', \App\Livewire\Diagnosis\Index::class)->name('index'); + Route::get('/create/{jobCard}', \App\Livewire\Diagnosis\Create::class)->middleware('permission:job-cards.update')->name('create'); + Route::get('/{diagnosis}', \App\Livewire\Diagnosis\Show::class)->name('show'); + Route::get('/{diagnosis}/edit', \App\Livewire\Diagnosis\Edit::class)->middleware('permission:job-cards.update')->name('edit'); + }); + + // Estimates Routes + Route::prefix('estimates')->name('estimates.')->middleware('permission:service-orders.view')->group(function () { + Route::get('/', \App\Livewire\Estimates\Index::class)->name('index'); + Route::get('/create/{diagnosis}', \App\Livewire\Estimates\Create::class)->middleware('permission:service-orders.create')->name('create'); + Route::get('/{estimate}', \App\Livewire\Estimates\Show::class)->name('show'); + Route::get('/{estimate}/edit', \App\Livewire\Estimates\Edit::class)->middleware('permission:service-orders.update')->name('edit'); + Route::get('/{estimate}/pdf', \App\Livewire\Estimates\PDF::class)->name('pdf'); + }); + + // Work Orders Routes + Route::prefix('work-orders')->name('work-orders.')->middleware('permission:service-orders.view')->group(function () { + Route::get('/', \App\Livewire\WorkOrders\Index::class)->name('index'); + Route::get('/create/{estimate}', \App\Livewire\WorkOrders\Create::class)->middleware('permission:service-orders.create')->name('create'); + Route::get('/{workOrder}', \App\Livewire\WorkOrders\Show::class)->name('show'); + Route::get('/{workOrder}/edit', \App\Livewire\WorkOrders\Edit::class)->middleware('permission:service-orders.update')->name('edit'); + }); + + // Inspections Routes + Route::prefix('inspections')->name('inspections.')->middleware('permission:job-cards.view')->group(function () { + Route::get('/', \App\Livewire\Inspections\Index::class)->name('index'); + Route::get('/create/{jobCard}/{type}', \App\Livewire\Inspections\Create::class)->middleware('permission:job-cards.update')->name('create'); + Route::get('/{inspection}', \App\Livewire\Inspections\Show::class)->name('show'); + Route::get('/{inspection}/edit', \App\Livewire\Inspections\Edit::class)->middleware('permission:job-cards.update')->name('edit'); + }); + + // Timesheets Routes + Route::prefix('timesheets')->name('timesheets.')->middleware('permission:technicians.view')->group(function () { + Route::get('/', \App\Livewire\Timesheets\Index::class)->name('index'); + Route::get('/create', \App\Livewire\Timesheets\Create::class)->name('create'); + Route::get('/{timesheet}', \App\Livewire\Timesheets\Show::class)->name('show'); + Route::get('/{timesheet}/edit', \App\Livewire\Timesheets\Edit::class)->name('edit'); + }); + + // Customer Portal Routes + Route::prefix('customer-portal')->name('customer-portal.')->group(function () { + Route::get('/{jobCard}/estimate/{estimate}', \App\Livewire\CustomerPortal\EstimateView::class)->name('estimate'); + Route::get('/{jobCard}/status', \App\Livewire\CustomerPortal\JobStatus::class)->name('status'); + }); + + // Reports Dashboard Route + Route::view('reports', 'reports')->middleware(['auth', 'permission:reports.view'])->name('reports.index'); + + // User Management Routes + Route::prefix('users')->name('users.')->middleware('permission:users.view')->group(function () { + Route::get('/', \App\Livewire\Users\Index::class)->name('index'); + Route::get('/create', \App\Livewire\Users\Create::class)->middleware('permission:users.create')->name('create'); + Route::get('/{user}', \App\Livewire\Users\Show::class)->name('show'); + Route::get('/{user}/edit', \App\Livewire\Users\Edit::class)->middleware('permission:users.update')->name('edit'); + Route::get('/{user}/manage-roles', \App\Livewire\Users\ManageRolesPermissions::class)->middleware('permission:users.manage-roles')->name('manage-roles'); + }); + + // Legacy User Management Route (redirect to new structure) + Route::get('/user-management', function () { + return redirect()->route('users.index'); + })->middleware(['auth', 'permission:users.view'])->name('user-management'); +}); + +require __DIR__.'/auth.php'; diff --git a/storage/app/.gitignore b/storage/app/.gitignore new file mode 100644 index 0000000..fedb287 --- /dev/null +++ b/storage/app/.gitignore @@ -0,0 +1,4 @@ +* +!private/ +!public/ +!.gitignore diff --git a/storage/app/private/.gitignore b/storage/app/private/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/app/private/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/app/public/.gitignore b/storage/app/public/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/app/public/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/.gitignore b/storage/framework/.gitignore new file mode 100644 index 0000000..05c4471 --- /dev/null +++ b/storage/framework/.gitignore @@ -0,0 +1,9 @@ +compiled.php +config.php +down +events.scanned.php +maintenance.php +routes.php +routes.scanned.php +schedule-* +services.json diff --git a/storage/framework/cache/.gitignore b/storage/framework/cache/.gitignore new file mode 100644 index 0000000..01e4a6c --- /dev/null +++ b/storage/framework/cache/.gitignore @@ -0,0 +1,3 @@ +* +!data/ +!.gitignore diff --git a/storage/framework/cache/data/.gitignore b/storage/framework/cache/data/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/framework/cache/data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/sessions/.gitignore b/storage/framework/sessions/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/framework/sessions/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/testing/.gitignore b/storage/framework/testing/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/framework/testing/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/views/.gitignore b/storage/framework/views/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/framework/views/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/logs/.gitignore b/storage/logs/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/Feature/Auth/AuthenticationTest.php b/tests/Feature/Auth/AuthenticationTest.php new file mode 100644 index 0000000..6a555bc --- /dev/null +++ b/tests/Feature/Auth/AuthenticationTest.php @@ -0,0 +1,61 @@ +get('/login'); + + $response->assertStatus(200); + } + + public function test_users_can_authenticate_using_the_login_screen(): void + { + $user = User::factory()->create(); + + $response = LivewireVolt::test('auth.login') + ->set('email', $user->email) + ->set('password', 'password') + ->call('login'); + + $response + ->assertHasNoErrors() + ->assertRedirect(route('dashboard', absolute: false)); + + $this->assertAuthenticated(); + } + + public function test_users_can_not_authenticate_with_invalid_password(): void + { + $user = User::factory()->create(); + + $response = LivewireVolt::test('auth.login') + ->set('email', $user->email) + ->set('password', 'wrong-password') + ->call('login'); + + $response->assertHasErrors('email'); + + $this->assertGuest(); + } + + public function test_users_can_logout(): void + { + $user = User::factory()->create(); + + $response = $this->actingAs($user)->post('/logout'); + + $response->assertRedirect('/'); + + $this->assertGuest(); + } +} diff --git a/tests/Feature/Auth/EmailVerificationTest.php b/tests/Feature/Auth/EmailVerificationTest.php new file mode 100644 index 0000000..e3f52d1 --- /dev/null +++ b/tests/Feature/Auth/EmailVerificationTest.php @@ -0,0 +1,59 @@ +unverified()->create(); + + $response = $this->actingAs($user)->get('/verify-email'); + + $response->assertStatus(200); + } + + public function test_email_can_be_verified(): void + { + $user = User::factory()->unverified()->create(); + + Event::fake(); + + $verificationUrl = URL::temporarySignedRoute( + 'verification.verify', + now()->addMinutes(60), + ['id' => $user->id, 'hash' => sha1($user->email)] + ); + + $response = $this->actingAs($user)->get($verificationUrl); + + Event::assertDispatched(Verified::class); + + $this->assertTrue($user->fresh()->hasVerifiedEmail()); + $response->assertRedirect(route('dashboard', absolute: false).'?verified=1'); + } + + public function test_email_is_not_verified_with_invalid_hash(): void + { + $user = User::factory()->unverified()->create(); + + $verificationUrl = URL::temporarySignedRoute( + 'verification.verify', + now()->addMinutes(60), + ['id' => $user->id, 'hash' => sha1('wrong-email')] + ); + + $this->actingAs($user)->get($verificationUrl); + + $this->assertFalse($user->fresh()->hasVerifiedEmail()); + } +} diff --git a/tests/Feature/Auth/PasswordConfirmationTest.php b/tests/Feature/Auth/PasswordConfirmationTest.php new file mode 100644 index 0000000..b1167b6 --- /dev/null +++ b/tests/Feature/Auth/PasswordConfirmationTest.php @@ -0,0 +1,50 @@ +create(); + + $response = $this->actingAs($user)->get('/confirm-password'); + + $response->assertStatus(200); + } + + public function test_password_can_be_confirmed(): void + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = Volt::test('auth.confirm-password') + ->set('password', 'password') + ->call('confirmPassword'); + + $response + ->assertHasNoErrors() + ->assertRedirect(route('dashboard', absolute: false)); + } + + public function test_password_is_not_confirmed_with_invalid_password(): void + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = Volt::test('auth.confirm-password') + ->set('password', 'wrong-password') + ->call('confirmPassword'); + + $response->assertHasErrors(['password']); + } +} diff --git a/tests/Feature/Auth/PasswordResetTest.php b/tests/Feature/Auth/PasswordResetTest.php new file mode 100644 index 0000000..e4e0b59 --- /dev/null +++ b/tests/Feature/Auth/PasswordResetTest.php @@ -0,0 +1,79 @@ +get('/forgot-password'); + + $response->assertStatus(200); + } + + public function test_reset_password_link_can_be_requested(): void + { + Notification::fake(); + + $user = User::factory()->create(); + + Volt::test('auth.forgot-password') + ->set('email', $user->email) + ->call('sendPasswordResetLink'); + + Notification::assertSentTo($user, ResetPassword::class); + } + + public function test_reset_password_screen_can_be_rendered(): void + { + Notification::fake(); + + $user = User::factory()->create(); + + Volt::test('auth.forgot-password') + ->set('email', $user->email) + ->call('sendPasswordResetLink'); + + Notification::assertSentTo($user, ResetPassword::class, function ($notification) { + $response = $this->get('/reset-password/'.$notification->token); + + $response->assertStatus(200); + + return true; + }); + } + + public function test_password_can_be_reset_with_valid_token(): void + { + Notification::fake(); + + $user = User::factory()->create(); + + Volt::test('auth.forgot-password') + ->set('email', $user->email) + ->call('sendPasswordResetLink'); + + Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) { + $response = Volt::test('auth.reset-password', ['token' => $notification->token]) + ->set('email', $user->email) + ->set('password', 'password') + ->set('password_confirmation', 'password') + ->call('resetPassword'); + + $response + ->assertHasNoErrors() + ->assertRedirect(route('login', absolute: false)); + + return true; + }); + } +} diff --git a/tests/Feature/Auth/RegistrationTest.php b/tests/Feature/Auth/RegistrationTest.php new file mode 100644 index 0000000..02f905b --- /dev/null +++ b/tests/Feature/Auth/RegistrationTest.php @@ -0,0 +1,35 @@ +get('/register'); + + $response->assertStatus(200); + } + + public function test_new_users_can_register(): void + { + $response = Volt::test('auth.register') + ->set('name', 'Test User') + ->set('email', 'test@example.com') + ->set('password', 'password') + ->set('password_confirmation', 'password') + ->call('register'); + + $response + ->assertHasNoErrors() + ->assertRedirect(route('dashboard', absolute: false)); + + $this->assertAuthenticated(); + } +} diff --git a/tests/Feature/DashboardTest.php b/tests/Feature/DashboardTest.php new file mode 100644 index 0000000..c8a5631 --- /dev/null +++ b/tests/Feature/DashboardTest.php @@ -0,0 +1,27 @@ +get('/dashboard'); + $response->assertRedirect('/login'); + } + + public function test_authenticated_users_can_visit_the_dashboard(): void + { + $user = User::factory()->create(); + $this->actingAs($user); + + $response = $this->get('/dashboard'); + $response->assertStatus(200); + } +} diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php new file mode 100644 index 0000000..8d62f1b --- /dev/null +++ b/tests/Feature/ExampleTest.php @@ -0,0 +1,18 @@ +get('/'); + + $response->assertStatus(200); + } +} diff --git a/tests/Feature/Settings/PasswordUpdateTest.php b/tests/Feature/Settings/PasswordUpdateTest.php new file mode 100644 index 0000000..da8a909 --- /dev/null +++ b/tests/Feature/Settings/PasswordUpdateTest.php @@ -0,0 +1,50 @@ +create([ + 'password' => Hash::make('password'), + ]); + + $this->actingAs($user); + + $response = Volt::test('settings.password') + ->set('current_password', 'password') + ->set('password', 'new-password') + ->set('password_confirmation', 'new-password') + ->call('updatePassword'); + + $response->assertHasNoErrors(); + + $this->assertTrue(Hash::check('new-password', $user->refresh()->password)); + } + + public function test_correct_password_must_be_provided_to_update_password(): void + { + $user = User::factory()->create([ + 'password' => Hash::make('password'), + ]); + + $this->actingAs($user); + + $response = Volt::test('settings.password') + ->set('current_password', 'wrong-password') + ->set('password', 'new-password') + ->set('password_confirmation', 'new-password') + ->call('updatePassword'); + + $response->assertHasErrors(['current_password']); + } +} diff --git a/tests/Feature/Settings/ProfileUpdateTest.php b/tests/Feature/Settings/ProfileUpdateTest.php new file mode 100644 index 0000000..3742659 --- /dev/null +++ b/tests/Feature/Settings/ProfileUpdateTest.php @@ -0,0 +1,89 @@ +actingAs($user = User::factory()->create()); + + $this->get('/settings/profile')->assertOk(); + } + + public function test_profile_information_can_be_updated(): void + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = Volt::test('settings.profile') + ->set('name', 'Test User') + ->set('email', 'test@example.com') + ->call('updateProfileInformation'); + + $response->assertHasNoErrors(); + + $user->refresh(); + + $this->assertEquals('Test User', $user->name); + $this->assertEquals('test@example.com', $user->email); + $this->assertNull($user->email_verified_at); + } + + public function test_email_verification_status_is_unchanged_when_email_address_is_unchanged(): void + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = Volt::test('settings.profile') + ->set('name', 'Test User') + ->set('email', $user->email) + ->call('updateProfileInformation'); + + $response->assertHasNoErrors(); + + $this->assertNotNull($user->refresh()->email_verified_at); + } + + public function test_user_can_delete_their_account(): void + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = Volt::test('settings.delete-user-form') + ->set('password', 'password') + ->call('deleteUser'); + + $response + ->assertHasNoErrors() + ->assertRedirect('/'); + + $this->assertNull($user->fresh()); + $this->assertFalse(auth()->check()); + } + + public function test_correct_password_must_be_provided_to_delete_account(): void + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = Volt::test('settings.delete-user-form') + ->set('password', 'wrong-password') + ->call('deleteUser'); + + $response->assertHasErrors(['password']); + + $this->assertNotNull($user->fresh()); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..fe1ffc2 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,10 @@ +assertTrue(true); + } +} diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..75a8c16 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,18 @@ +import { + defineConfig +} from 'vite'; +import laravel from 'laravel-vite-plugin'; +import tailwindcss from "@tailwindcss/vite"; + +export default defineConfig({ + plugins: [ + laravel({ + input: ['resources/css/app.css', 'resources/js/app.js'], + refresh: true, + }), + tailwindcss(), + ], + server: { + cors: true, + }, +}); \ No newline at end of file