From 63e0958b66a5fd52ba363545a5e1e3247c3279db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Pocrnji=C4=8D?= Date: Sun, 2 Nov 2025 12:31:01 +0100 Subject: [PATCH] Dev branch --- .../Commands/RefreshMaterializedViews.php | 57 + app/Console/Kernel.php | 13 +- .../Controllers/Admin/PackageController.php | 14 + app/Http/Controllers/ClientCaseContoller.php | 648 +---- app/Http/Controllers/ClientController.php | 178 +- app/Http/Controllers/PhoneViewController.php | 8 +- app/Http/Controllers/ReportController.php | 379 +++ app/Providers/ReportServiceProvider.php | 36 + app/Reports/ActionsDecisionsCountReport.php | 53 + app/Reports/ActiveContractsReport.php | 78 + app/Reports/ActivitiesPerPeriodReport.php | 95 + app/Reports/BaseEloquentReport.php | 33 + app/Reports/Contracts/Report.php | 54 + app/Reports/DecisionsCountReport.php | 51 + app/Reports/FieldJobsCompletedReport.php | 60 + app/Reports/ReportRegistry.php | 29 + app/Reports/SegmentActivityCountsReport.php | 54 + .../Documents/DocumentStreamService.php | 221 ++ app/Services/ReferenceDataCache.php | 68 + bootstrap/providers.php | 1 + components.json | 21 + composer.json | 6 +- composer.lock | 958 ++++++- config/reports.php | 10 + ...5_01_15_000000_add_performance_indexes.php | 143 + package-lock.json | 2292 +++++++---------- package.json | 26 +- postcss.config.js | 11 +- resources/css/app.css | 265 +- resources/js/Components/AddressCreateForm.vue | 228 -- resources/js/Components/AddressUpdateForm.vue | 197 -- resources/js/Components/ApplicationMark.vue | 2 +- resources/js/Components/BasicTable.vue | 49 +- resources/js/Components/Breadcrumbs.vue | 6 +- resources/js/Components/ConfirmDialog.vue | 33 +- resources/js/Components/CurrencyInput.vue | 5 +- resources/js/Components/CusTabs.vue | 8 +- .../Components/DataTable/ActionMenuItem.vue | 54 + .../js/Components/DataTable/ColumnFilter.vue | 134 + .../js/Components/DataTable/DataTable.vue | 884 +++++++ .../Components/DataTable/DataTableClient.vue | 101 +- .../Components/DataTable/DataTableServer.vue | 98 +- .../Components/DataTable/DataTableToolbar.vue | 318 +++ .../js/Components/DataTable/StatusBadge.vue | 170 ++ .../js/Components/DataTable/TableActions.vue | 55 + resources/js/Components/DatePicker.vue | 141 + resources/js/Components/DateRangePicker.vue | 231 ++ resources/js/Components/DialogModal.vue | 101 +- .../Components/Dialogs/ConfirmationDialog.vue | 111 + .../js/Components/Dialogs/CreateDialog.vue | 100 + .../js/Components/Dialogs/DeleteDialog.vue | 96 + .../js/Components/Dialogs/UpdateDialog.vue | 100 + .../js/Components/Dialogs/WarningDialog.vue | 104 + .../js/Components/DocumentEditDialog.vue | 25 +- .../js/Components/DocumentUploadDialog.vue | 29 +- resources/js/Components/DocumentsTable.vue | 351 ++- resources/js/Components/DragDropList.vue | 20 +- resources/js/Components/Dropdown.vue | 182 +- resources/js/Components/EmailCreateForm.vue | 168 -- resources/js/Components/EmptyState.vue | 124 + resources/js/Components/Modal.vue | 16 +- resources/js/Components/Pagination.vue | 509 ++-- .../PersonInfo/AddressCreateForm.vue | 283 ++ .../PersonInfo/AddressUpdateForm.vue | 217 ++ .../Components/PersonInfo/EmailCreateForm.vue | 234 ++ .../{ => PersonInfo}/EmailUpdateForm.vue | 0 .../PersonInfo/PersonInfoAddressesTab.vue | 85 + .../PersonInfo/PersonInfoEmailsTab.vue | 97 + .../Components/PersonInfo/PersonInfoGrid.vue | 455 ++++ .../PersonInfo/PersonInfoPersonTab.vue | 93 + .../PersonInfo/PersonInfoPhonesTab.vue | 98 + .../PersonInfo/PersonInfoSmsDialog.vue | 480 ++++ .../PersonInfo/PersonInfoTrrTab.vue | 116 + .../PersonInfo/PersonUpdateForm.vue | 183 ++ .../Components/PersonInfo/PhoneCreateForm.vue | 229 ++ .../Components/PersonInfo/PhoneUpdateForm.vue | 254 ++ .../Components/PersonInfo/TrrCreateForm.vue | 324 +++ .../{ => PersonInfo}/TrrUpdateForm.vue | 0 resources/js/Components/PersonInfoGrid.vue | 1065 ++------ resources/js/Components/PersonUpdateForm.vue | 139 - resources/js/Components/PhoneCreateForm.vue | 254 +- resources/js/Components/PhoneUpdateForm.vue | 17 - .../js/Components/Skeleton/SkeletonCard.vue | 47 + .../js/Components/Skeleton/SkeletonInline.vue | 49 + .../js/Components/Skeleton/SkeletonList.vue | 32 + .../js/Components/Skeleton/SkeletonTable.vue | 44 + .../js/Components/Toast/ToastContainer.vue | 70 + resources/js/Components/TrrCreateForm.vue | 177 -- resources/js/Components/ui/badge/Badge.vue | 15 + resources/js/Components/ui/badge/index.js | 23 + .../ui/button-group/ButtonGroup.vue | 22 + .../ui/button-group/ButtonGroupSeparator.vue | 28 + .../ui/button-group/ButtonGroupText.vue | 29 + .../js/Components/ui/button-group/index.js | 22 + resources/js/Components/ui/button/Button.vue | 24 + resources/js/Components/ui/button/index.js | 36 + .../js/Components/ui/calendar/Calendar.vue | 95 + .../Components/ui/calendar/CalendarCell.vue | 30 + .../ui/calendar/CalendarCellTrigger.vue | 42 + .../Components/ui/calendar/CalendarGrid.vue | 24 + .../ui/calendar/CalendarGridBody.vue | 14 + .../ui/calendar/CalendarGridHead.vue | 15 + .../ui/calendar/CalendarGridRow.vue | 21 + .../ui/calendar/CalendarHeadCell.vue | 29 + .../Components/ui/calendar/CalendarHeader.vue | 26 + .../ui/calendar/CalendarHeading.vue | 29 + .../ui/calendar/CalendarNextButton.vue | 35 + .../ui/calendar/CalendarPrevButton.vue | 35 + resources/js/Components/ui/calendar/index.js | 12 + .../js/Components/ui/checkbox/Checkbox.vue | 42 + resources/js/Components/ui/checkbox/index.js | 1 + resources/js/Components/ui/dialog/Dialog.vue | 18 + .../js/Components/ui/dialog/DialogClose.vue | 14 + .../js/Components/ui/dialog/DialogContent.vue | 58 + .../ui/dialog/DialogDescription.vue | 24 + .../js/Components/ui/dialog/DialogFooter.vue | 20 + .../js/Components/ui/dialog/DialogHeader.vue | 15 + .../ui/dialog/DialogScrollContent.vue | 71 + .../js/Components/ui/dialog/DialogTitle.vue | 26 + .../js/Components/ui/dialog/DialogTrigger.vue | 14 + resources/js/Components/ui/dialog/index.js | 9 + .../ui/dropdown-menu/DropdownMenu.vue | 19 + .../DropdownMenuCheckboxItem.vue | 43 + .../ui/dropdown-menu/DropdownMenuContent.vue | 61 + .../ui/dropdown-menu/DropdownMenuGroup.vue | 14 + .../ui/dropdown-menu/DropdownMenuItem.vue | 33 + .../ui/dropdown-menu/DropdownMenuLabel.vue | 27 + .../dropdown-menu/DropdownMenuRadioGroup.vue | 18 + .../dropdown-menu/DropdownMenuRadioItem.vue | 44 + .../dropdown-menu/DropdownMenuSeparator.vue | 20 + .../ui/dropdown-menu/DropdownMenuShortcut.vue | 13 + .../ui/dropdown-menu/DropdownMenuSub.vue | 17 + .../dropdown-menu/DropdownMenuSubContent.vue | 55 + .../dropdown-menu/DropdownMenuSubTrigger.vue | 33 + .../ui/dropdown-menu/DropdownMenuTrigger.vue | 17 + .../js/Components/ui/dropdown-menu/index.js | 16 + .../js/Components/ui/form/FormControl.vue | 18 + .../js/Components/ui/form/FormDescription.vue | 19 + resources/js/Components/ui/form/FormItem.vue | 19 + resources/js/Components/ui/form/FormLabel.vue | 23 + .../js/Components/ui/form/FormMessage.vue | 16 + resources/js/Components/ui/form/index.js | 11 + .../js/Components/ui/form/injectionKeys.js | 1 + .../js/Components/ui/form/useFormField.js | 30 + resources/js/Components/ui/input/Input.vue | 29 + resources/js/Components/ui/input/index.js | 1 + resources/js/Components/ui/label/Label.vue | 29 + resources/js/Components/ui/label/index.js | 1 + .../js/Components/ui/popover/Popover.vue | 18 + .../Components/ui/popover/PopoverContent.vue | 62 + .../Components/ui/popover/PopoverTrigger.vue | 14 + resources/js/Components/ui/popover/index.js | 4 + .../ui/range-calendar/RangeCalendar.vue | 103 + .../ui/range-calendar/RangeCalendarCell.vue | 30 + .../RangeCalendarCellTrigger.vue | 44 + .../ui/range-calendar/RangeCalendarGrid.vue | 24 + .../range-calendar/RangeCalendarGridBody.vue | 14 + .../range-calendar/RangeCalendarGridHead.vue | 14 + .../range-calendar/RangeCalendarGridRow.vue | 24 + .../range-calendar/RangeCalendarHeadCell.vue | 29 + .../ui/range-calendar/RangeCalendarHeader.vue | 26 + .../range-calendar/RangeCalendarHeading.vue | 29 + .../RangeCalendarNextButton.vue | 35 + .../RangeCalendarPrevButton.vue | 35 + .../js/Components/ui/range-calendar/index.js | 12 + resources/js/Components/ui/select/Select.vue | 26 + .../js/Components/ui/select/SelectContent.vue | 80 + .../js/Components/ui/select/SelectGroup.vue | 19 + .../js/Components/ui/select/SelectItem.vue | 46 + .../Components/ui/select/SelectItemText.vue | 14 + .../js/Components/ui/select/SelectLabel.vue | 17 + .../ui/select/SelectScrollDownButton.vue | 29 + .../ui/select/SelectScrollUpButton.vue | 29 + .../Components/ui/select/SelectSeparator.vue | 20 + .../js/Components/ui/select/SelectTrigger.vue | 35 + .../js/Components/ui/select/SelectValue.vue | 15 + resources/js/Components/ui/select/index.js | 11 + .../js/Components/ui/separator/Separator.vue | 28 + resources/js/Components/ui/separator/index.js | 1 + resources/js/Components/ui/sonner/Sonner.vue | 45 + resources/js/Components/ui/sonner/index.js | 1 + resources/js/Components/ui/table/Table.vue | 15 + .../js/Components/ui/table/TableBody.vue | 13 + .../js/Components/ui/table/TableCaption.vue | 13 + .../js/Components/ui/table/TableCell.vue | 20 + .../js/Components/ui/table/TableEmpty.vue | 31 + .../js/Components/ui/table/TableFooter.vue | 17 + .../js/Components/ui/table/TableHead.vue | 20 + .../js/Components/ui/table/TableHeader.vue | 13 + resources/js/Components/ui/table/TableRow.vue | 20 + resources/js/Components/ui/table/index.js | 9 + resources/js/Components/ui/tabs/Tabs.vue | 23 + .../js/Components/ui/tabs/TabsContent.vue | 29 + resources/js/Components/ui/tabs/TabsList.vue | 28 + .../js/Components/ui/tabs/TabsTrigger.vue | 33 + resources/js/Components/ui/tabs/index.js | 4 + .../js/Components/ui/textarea/Textarea.vue | 29 + resources/js/Components/ui/textarea/index.js | 1 + resources/js/Layouts/AdminLayout.vue | 121 +- resources/js/Layouts/AppLayout.vue | 156 +- resources/js/Layouts/AppPhoneLayout.vue | 198 +- .../js/Layouts/Partials/GlobalSearch.vue | 66 +- .../js/Pages/Admin/MailProfiles/Index.vue | 62 +- resources/js/Pages/Admin/Packages/Index.vue | 455 +++- .../js/Pages/Admin/SmsProfiles/Index.vue | 25 +- resources/js/Pages/Auth/Login.vue | 165 +- resources/js/Pages/Cases/Index.vue | 19 +- .../Pages/Cases/Partials/ActivityDrawer.vue | 286 +- .../js/Pages/Cases/Partials/ActivityTable.vue | 408 ++- .../Cases/Partials/CaseObjectCreateDialog.vue | 95 +- .../Cases/Partials/CaseObjectsDialog.vue | 4 +- .../Pages/Cases/Partials/ContractDrawer.vue | 316 ++- .../js/Pages/Cases/Partials/ContractTable.vue | 1491 +++++------ .../js/Pages/Cases/Partials/PaymentDialog.vue | 90 +- resources/js/Pages/Cases/Show.vue | 162 +- resources/js/Pages/Client/Contracts.vue | 203 +- resources/js/Pages/Client/Index.vue | 472 ++-- .../Pages/Client/Partials/FormCreateCase.vue | 426 +-- resources/js/Pages/Client/Show.vue | 87 +- resources/js/Pages/Dashboard.vue | 140 +- resources/js/Pages/Reports/Index.vue | 29 + resources/js/Pages/Reports/Show.vue | 312 +++ .../Pages/Settings/ContractConfigs/Index.vue | 49 +- .../js/Pages/Settings/FieldJob/Index.vue | 57 +- .../Pages/Settings/Partials/ActionTable.vue | 65 +- .../Pages/Settings/Partials/DecisionTable.vue | 67 +- .../js/Pages/Settings/Segments/Index.vue | 55 +- .../js/Pages/Settings/Workflow/Index.vue | 18 +- resources/js/Pages/Testing/Index.vue | 8 +- resources/js/Pages/Welcome.vue | 34 +- resources/js/Utilities/Icons.js | 22 +- resources/js/app.js | 3 + resources/js/lib/utils.js | 6 + resources/views/reports/pdf/table.blade.php | 38 + routes/web.php | 12 + tailwind.config.js | 27 +- tests/Feature/Reports/NewReportsTest.php | 202 ++ tests/Feature/Reports/ReportsExportTest.php | 50 + tests/Feature/Reports/ReportsIndexTest.php | 22 + tests/Feature/Reports/ReportsShowTest.php | 37 + vite.config.js | 3 + 241 files changed, 17686 insertions(+), 7327 deletions(-) create mode 100644 app/Console/Commands/RefreshMaterializedViews.php create mode 100644 app/Http/Controllers/ReportController.php create mode 100644 app/Providers/ReportServiceProvider.php create mode 100644 app/Reports/ActionsDecisionsCountReport.php create mode 100644 app/Reports/ActiveContractsReport.php create mode 100644 app/Reports/ActivitiesPerPeriodReport.php create mode 100644 app/Reports/BaseEloquentReport.php create mode 100644 app/Reports/Contracts/Report.php create mode 100644 app/Reports/DecisionsCountReport.php create mode 100644 app/Reports/FieldJobsCompletedReport.php create mode 100644 app/Reports/ReportRegistry.php create mode 100644 app/Reports/SegmentActivityCountsReport.php create mode 100644 app/Services/Documents/DocumentStreamService.php create mode 100644 app/Services/ReferenceDataCache.php create mode 100644 components.json create mode 100644 config/reports.php create mode 100644 database/migrations/2025_01_15_000000_add_performance_indexes.php delete mode 100644 resources/js/Components/AddressCreateForm.vue delete mode 100644 resources/js/Components/AddressUpdateForm.vue create mode 100644 resources/js/Components/DataTable/ActionMenuItem.vue create mode 100644 resources/js/Components/DataTable/ColumnFilter.vue create mode 100644 resources/js/Components/DataTable/DataTable.vue create mode 100644 resources/js/Components/DataTable/DataTableToolbar.vue create mode 100644 resources/js/Components/DataTable/StatusBadge.vue create mode 100644 resources/js/Components/DataTable/TableActions.vue create mode 100644 resources/js/Components/DatePicker.vue create mode 100644 resources/js/Components/DateRangePicker.vue create mode 100644 resources/js/Components/Dialogs/ConfirmationDialog.vue create mode 100644 resources/js/Components/Dialogs/CreateDialog.vue create mode 100644 resources/js/Components/Dialogs/DeleteDialog.vue create mode 100644 resources/js/Components/Dialogs/UpdateDialog.vue create mode 100644 resources/js/Components/Dialogs/WarningDialog.vue delete mode 100644 resources/js/Components/EmailCreateForm.vue create mode 100644 resources/js/Components/EmptyState.vue create mode 100644 resources/js/Components/PersonInfo/AddressCreateForm.vue create mode 100644 resources/js/Components/PersonInfo/AddressUpdateForm.vue create mode 100644 resources/js/Components/PersonInfo/EmailCreateForm.vue rename resources/js/Components/{ => PersonInfo}/EmailUpdateForm.vue (100%) create mode 100644 resources/js/Components/PersonInfo/PersonInfoAddressesTab.vue create mode 100644 resources/js/Components/PersonInfo/PersonInfoEmailsTab.vue create mode 100644 resources/js/Components/PersonInfo/PersonInfoGrid.vue create mode 100644 resources/js/Components/PersonInfo/PersonInfoPersonTab.vue create mode 100644 resources/js/Components/PersonInfo/PersonInfoPhonesTab.vue create mode 100644 resources/js/Components/PersonInfo/PersonInfoSmsDialog.vue create mode 100644 resources/js/Components/PersonInfo/PersonInfoTrrTab.vue create mode 100644 resources/js/Components/PersonInfo/PersonUpdateForm.vue create mode 100644 resources/js/Components/PersonInfo/PhoneCreateForm.vue create mode 100644 resources/js/Components/PersonInfo/PhoneUpdateForm.vue create mode 100644 resources/js/Components/PersonInfo/TrrCreateForm.vue rename resources/js/Components/{ => PersonInfo}/TrrUpdateForm.vue (100%) delete mode 100644 resources/js/Components/PersonUpdateForm.vue delete mode 100644 resources/js/Components/PhoneUpdateForm.vue create mode 100644 resources/js/Components/Skeleton/SkeletonCard.vue create mode 100644 resources/js/Components/Skeleton/SkeletonInline.vue create mode 100644 resources/js/Components/Skeleton/SkeletonList.vue create mode 100644 resources/js/Components/Skeleton/SkeletonTable.vue create mode 100644 resources/js/Components/Toast/ToastContainer.vue delete mode 100644 resources/js/Components/TrrCreateForm.vue create mode 100644 resources/js/Components/ui/badge/Badge.vue create mode 100644 resources/js/Components/ui/badge/index.js create mode 100644 resources/js/Components/ui/button-group/ButtonGroup.vue create mode 100644 resources/js/Components/ui/button-group/ButtonGroupSeparator.vue create mode 100644 resources/js/Components/ui/button-group/ButtonGroupText.vue create mode 100644 resources/js/Components/ui/button-group/index.js create mode 100644 resources/js/Components/ui/button/Button.vue create mode 100644 resources/js/Components/ui/button/index.js create mode 100644 resources/js/Components/ui/calendar/Calendar.vue create mode 100644 resources/js/Components/ui/calendar/CalendarCell.vue create mode 100644 resources/js/Components/ui/calendar/CalendarCellTrigger.vue create mode 100644 resources/js/Components/ui/calendar/CalendarGrid.vue create mode 100644 resources/js/Components/ui/calendar/CalendarGridBody.vue create mode 100644 resources/js/Components/ui/calendar/CalendarGridHead.vue create mode 100644 resources/js/Components/ui/calendar/CalendarGridRow.vue create mode 100644 resources/js/Components/ui/calendar/CalendarHeadCell.vue create mode 100644 resources/js/Components/ui/calendar/CalendarHeader.vue create mode 100644 resources/js/Components/ui/calendar/CalendarHeading.vue create mode 100644 resources/js/Components/ui/calendar/CalendarNextButton.vue create mode 100644 resources/js/Components/ui/calendar/CalendarPrevButton.vue create mode 100644 resources/js/Components/ui/calendar/index.js create mode 100644 resources/js/Components/ui/checkbox/Checkbox.vue create mode 100644 resources/js/Components/ui/checkbox/index.js create mode 100644 resources/js/Components/ui/dialog/Dialog.vue create mode 100644 resources/js/Components/ui/dialog/DialogClose.vue create mode 100644 resources/js/Components/ui/dialog/DialogContent.vue create mode 100644 resources/js/Components/ui/dialog/DialogDescription.vue create mode 100644 resources/js/Components/ui/dialog/DialogFooter.vue create mode 100644 resources/js/Components/ui/dialog/DialogHeader.vue create mode 100644 resources/js/Components/ui/dialog/DialogScrollContent.vue create mode 100644 resources/js/Components/ui/dialog/DialogTitle.vue create mode 100644 resources/js/Components/ui/dialog/DialogTrigger.vue create mode 100644 resources/js/Components/ui/dialog/index.js create mode 100644 resources/js/Components/ui/dropdown-menu/DropdownMenu.vue create mode 100644 resources/js/Components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue create mode 100644 resources/js/Components/ui/dropdown-menu/DropdownMenuContent.vue create mode 100644 resources/js/Components/ui/dropdown-menu/DropdownMenuGroup.vue create mode 100644 resources/js/Components/ui/dropdown-menu/DropdownMenuItem.vue create mode 100644 resources/js/Components/ui/dropdown-menu/DropdownMenuLabel.vue create mode 100644 resources/js/Components/ui/dropdown-menu/DropdownMenuRadioGroup.vue create mode 100644 resources/js/Components/ui/dropdown-menu/DropdownMenuRadioItem.vue create mode 100644 resources/js/Components/ui/dropdown-menu/DropdownMenuSeparator.vue create mode 100644 resources/js/Components/ui/dropdown-menu/DropdownMenuShortcut.vue create mode 100644 resources/js/Components/ui/dropdown-menu/DropdownMenuSub.vue create mode 100644 resources/js/Components/ui/dropdown-menu/DropdownMenuSubContent.vue create mode 100644 resources/js/Components/ui/dropdown-menu/DropdownMenuSubTrigger.vue create mode 100644 resources/js/Components/ui/dropdown-menu/DropdownMenuTrigger.vue create mode 100644 resources/js/Components/ui/dropdown-menu/index.js create mode 100644 resources/js/Components/ui/form/FormControl.vue create mode 100644 resources/js/Components/ui/form/FormDescription.vue create mode 100644 resources/js/Components/ui/form/FormItem.vue create mode 100644 resources/js/Components/ui/form/FormLabel.vue create mode 100644 resources/js/Components/ui/form/FormMessage.vue create mode 100644 resources/js/Components/ui/form/index.js create mode 100644 resources/js/Components/ui/form/injectionKeys.js create mode 100644 resources/js/Components/ui/form/useFormField.js create mode 100644 resources/js/Components/ui/input/Input.vue create mode 100644 resources/js/Components/ui/input/index.js create mode 100644 resources/js/Components/ui/label/Label.vue create mode 100644 resources/js/Components/ui/label/index.js create mode 100644 resources/js/Components/ui/popover/Popover.vue create mode 100644 resources/js/Components/ui/popover/PopoverContent.vue create mode 100644 resources/js/Components/ui/popover/PopoverTrigger.vue create mode 100644 resources/js/Components/ui/popover/index.js create mode 100644 resources/js/Components/ui/range-calendar/RangeCalendar.vue create mode 100644 resources/js/Components/ui/range-calendar/RangeCalendarCell.vue create mode 100644 resources/js/Components/ui/range-calendar/RangeCalendarCellTrigger.vue create mode 100644 resources/js/Components/ui/range-calendar/RangeCalendarGrid.vue create mode 100644 resources/js/Components/ui/range-calendar/RangeCalendarGridBody.vue create mode 100644 resources/js/Components/ui/range-calendar/RangeCalendarGridHead.vue create mode 100644 resources/js/Components/ui/range-calendar/RangeCalendarGridRow.vue create mode 100644 resources/js/Components/ui/range-calendar/RangeCalendarHeadCell.vue create mode 100644 resources/js/Components/ui/range-calendar/RangeCalendarHeader.vue create mode 100644 resources/js/Components/ui/range-calendar/RangeCalendarHeading.vue create mode 100644 resources/js/Components/ui/range-calendar/RangeCalendarNextButton.vue create mode 100644 resources/js/Components/ui/range-calendar/RangeCalendarPrevButton.vue create mode 100644 resources/js/Components/ui/range-calendar/index.js create mode 100644 resources/js/Components/ui/select/Select.vue create mode 100644 resources/js/Components/ui/select/SelectContent.vue create mode 100644 resources/js/Components/ui/select/SelectGroup.vue create mode 100644 resources/js/Components/ui/select/SelectItem.vue create mode 100644 resources/js/Components/ui/select/SelectItemText.vue create mode 100644 resources/js/Components/ui/select/SelectLabel.vue create mode 100644 resources/js/Components/ui/select/SelectScrollDownButton.vue create mode 100644 resources/js/Components/ui/select/SelectScrollUpButton.vue create mode 100644 resources/js/Components/ui/select/SelectSeparator.vue create mode 100644 resources/js/Components/ui/select/SelectTrigger.vue create mode 100644 resources/js/Components/ui/select/SelectValue.vue create mode 100644 resources/js/Components/ui/select/index.js create mode 100644 resources/js/Components/ui/separator/Separator.vue create mode 100644 resources/js/Components/ui/separator/index.js create mode 100644 resources/js/Components/ui/sonner/Sonner.vue create mode 100644 resources/js/Components/ui/sonner/index.js create mode 100644 resources/js/Components/ui/table/Table.vue create mode 100644 resources/js/Components/ui/table/TableBody.vue create mode 100644 resources/js/Components/ui/table/TableCaption.vue create mode 100644 resources/js/Components/ui/table/TableCell.vue create mode 100644 resources/js/Components/ui/table/TableEmpty.vue create mode 100644 resources/js/Components/ui/table/TableFooter.vue create mode 100644 resources/js/Components/ui/table/TableHead.vue create mode 100644 resources/js/Components/ui/table/TableHeader.vue create mode 100644 resources/js/Components/ui/table/TableRow.vue create mode 100644 resources/js/Components/ui/table/index.js create mode 100644 resources/js/Components/ui/tabs/Tabs.vue create mode 100644 resources/js/Components/ui/tabs/TabsContent.vue create mode 100644 resources/js/Components/ui/tabs/TabsList.vue create mode 100644 resources/js/Components/ui/tabs/TabsTrigger.vue create mode 100644 resources/js/Components/ui/tabs/index.js create mode 100644 resources/js/Components/ui/textarea/Textarea.vue create mode 100644 resources/js/Components/ui/textarea/index.js create mode 100644 resources/js/Pages/Reports/Index.vue create mode 100644 resources/js/Pages/Reports/Show.vue create mode 100644 resources/js/lib/utils.js create mode 100644 resources/views/reports/pdf/table.blade.php create mode 100644 tests/Feature/Reports/NewReportsTest.php create mode 100644 tests/Feature/Reports/ReportsExportTest.php create mode 100644 tests/Feature/Reports/ReportsIndexTest.php create mode 100644 tests/Feature/Reports/ReportsShowTest.php diff --git a/app/Console/Commands/RefreshMaterializedViews.php b/app/Console/Commands/RefreshMaterializedViews.php new file mode 100644 index 0000000..0312a9c --- /dev/null +++ b/app/Console/Commands/RefreshMaterializedViews.php @@ -0,0 +1,57 @@ +info('No materialized views configured.'); + + return self::SUCCESS; + } + + $concurrently = $this->option('concurrently') ? ' CONCURRENTLY' : ''; + + foreach ($views as $view) { + $name = trim((string) $view); + if ($name === '') { + continue; + } + $sql = 'REFRESH MATERIALIZED VIEW'.$concurrently.' '.DB::getPdo()->quote($name); + // PDO::quote wraps with single quotes; for identifiers we need double quotes or no quotes. + // Use a safe fallback: wrap with " if not already quoted + $safe = 'REFRESH MATERIALIZED VIEW'.$concurrently.' "'.str_replace('"', '""', $name).'"'; + try { + DB::statement($safe); + $this->info("Refreshed: {$name}"); + } catch (\Throwable $e) { + $this->error("Failed to refresh {$name}: ".$e->getMessage()); + } + } + + return self::SUCCESS; + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 86e68a0..3846641 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -15,11 +15,22 @@ protected function schedule(Schedule $schedule): void // Optionally prune old previews daily if (config('files.enable_preview_prune', true)) { $days = (int) config('files.preview_retention_days', 90); - if ($days < 1) { $days = 90; } + if ($days < 1) { + $days = 90; + } $schedule->command('documents:prune-previews', [ '--days' => $days, ])->dailyAt('02:00'); } + + // Optional: refresh configured materialized views for reporting + $views = (array) config('reports.materialized_views', []); + if (! empty($views)) { + $time = (string) (config('reports.refresh_time', '03:00') ?: '03:00'); + $schedule->command('reports:refresh-mviews', [ + '--concurrently' => true, + ])->dailyAt($time); + } } /** diff --git a/app/Http/Controllers/Admin/PackageController.php b/app/Http/Controllers/Admin/PackageController.php index 4f676a6..768c301 100644 --- a/app/Http/Controllers/Admin/PackageController.php +++ b/app/Http/Controllers/Admin/PackageController.php @@ -280,6 +280,20 @@ public function cancel(Package $package): RedirectResponse return back()->with('success', 'Package canceled'); } + public function destroy(Package $package): RedirectResponse + { + // Allow deletion only for drafts (not yet dispatched) + if ($package->status !== Package::STATUS_DRAFT) { + return back()->with('error', 'Package not in a deletable state.'); + } + + // Remove items first to avoid FK issues + $package->items()->delete(); + $package->delete(); + + return back()->with('success', 'Package deleted'); + } + /** * List contracts for a given segment and include selected phone per person. */ diff --git a/app/Http/Controllers/ClientCaseContoller.php b/app/Http/Controllers/ClientCaseContoller.php index 1476525..361dd52 100644 --- a/app/Http/Controllers/ClientCaseContoller.php +++ b/app/Http/Controllers/ClientCaseContoller.php @@ -7,6 +7,8 @@ use App\Models\ClientCase; use App\Models\Contract; use App\Models\Document; +use App\Services\Documents\DocumentStreamService; +use App\Services\ReferenceDataCache; use App\Services\Sms\SmsService; use Exception; use Illuminate\Database\QueryException; @@ -16,45 +18,45 @@ class ClientCaseContoller extends Controller { + public function __construct( + protected ReferenceDataCache $referenceCache, + protected DocumentStreamService $documentStream + ) {} + /** * Display a listing of the resource. */ public function index(ClientCase $clientCase, Request $request) { + $search = $request->input('search'); + $query = $clientCase::query() - ->with(['person.client', 'client.person']) - ->where('active', 1) - ->when($request->input('search'), function ($que, $search) { - $que->whereHas('person', function ($q) use ($search) { - $q->where('full_name', 'ilike', '%'.$search.'%'); - }); + ->select('client_cases.*') + ->when($search, function ($que) use ($search) { + $que->join('person', 'person.id', '=', 'client_cases.person_id') + ->where('person.full_name', 'ilike', '%'.$search.'%') + ->groupBy('client_cases.id'); }) + ->where('client_cases.active', 1) + // Use LEFT JOINs for aggregated data to avoid subqueries + ->leftJoin('contracts', function ($join) { + $join->on('contracts.client_case_id', '=', 'client_cases.id') + ->whereNull('contracts.deleted_at'); + }) + ->leftJoin('contract_segment', function ($join) { + $join->on('contract_segment.contract_id', '=', 'contracts.id') + ->where('contract_segment.active', true); + }) + ->leftJoin('accounts', 'accounts.contract_id', '=', 'contracts.id') + ->groupBy('client_cases.id') ->addSelect([ // Count of active contracts (a contract is considered active if it has an active pivot in contract_segment) - 'active_contracts_count' => \DB::query() - ->from('contracts') - ->selectRaw('COUNT(*)') - ->whereColumn('contracts.client_case_id', 'client_cases.id') - ->whereNull('contracts.deleted_at') - ->whereExists(function ($q) { - $q->from('contract_segment') - ->whereColumn('contract_segment.contract_id', 'contracts.id') - ->where('contract_segment.active', true); - }), + \DB::raw('COUNT(DISTINCT CASE WHEN contract_segment.id IS NOT NULL THEN contracts.id END) as active_contracts_count'), // Sum of balances for accounts of active contracts - 'active_contracts_balance_sum' => \DB::query() - ->from('contracts') - ->join('accounts', 'accounts.contract_id', '=', 'contracts.id') - ->selectRaw('COALESCE(SUM(accounts.balance_amount), 0)') - ->whereColumn('contracts.client_case_id', 'client_cases.id') - ->whereNull('contracts.deleted_at') - ->whereExists(function ($q) { - $q->from('contract_segment') - ->whereColumn('contract_segment.contract_id', 'contracts.id') - ->where('contract_segment.active', true); - }), + \DB::raw('COALESCE(SUM(CASE WHEN contract_segment.id IS NOT NULL THEN accounts.balance_amount END), 0) as active_contracts_balance_sum'), ]) - ->orderByDesc('created_at'); + ->with(['person.client', 'client.person']) + ->orderByDesc('client_cases.created_at'); return Inertia::render('Cases/Index', [ 'client_cases' => $query @@ -609,188 +611,7 @@ public function viewDocument(ClientCase $clientCase, Document $document, Request abort(404); } - // Optional: add authz checks here (e.g., policies) - $disk = $document->disk ?: 'public'; - // Normalize relative path (handle legacy 'public/' or 'public\\' prefixes and backslashes on Windows) - $relPath = $document->path ?? ''; - $relPath = str_replace('\\', '/', $relPath); // unify slashes - $relPath = ltrim($relPath, '/'); - if (str_starts_with($relPath, 'public/')) { - $relPath = substr($relPath, 7); - } - - // If a preview exists (e.g., PDF generated for doc/docx), stream that - $previewDisk = config('files.preview_disk', 'public'); - if ($document->preview_path && Storage::disk($previewDisk)->exists($document->preview_path)) { - $stream = Storage::disk($previewDisk)->readStream($document->preview_path); - if ($stream === false) { - abort(404); - } - - return response()->stream(function () use ($stream) { - fpassthru($stream); - }, 200, [ - 'Content-Type' => $document->preview_mime ?: 'application/pdf', - 'Content-Disposition' => 'inline; filename="'.addslashes(($document->original_name ?: $document->file_name).'.pdf').'"', - 'Cache-Control' => 'private, max-age=0, no-cache', - 'Pragma' => 'no-cache', - ]); - } - - // If it's a DOC/DOCX and no preview yet, queue generation and show 202 Accepted - $ext = strtolower(pathinfo($document->original_name ?: $document->file_name, PATHINFO_EXTENSION)); - if (in_array($ext, ['doc', 'docx'])) { - \App\Jobs\GenerateDocumentPreview::dispatch($document->id); - - return response('Preview is being generated. Please try again shortly.', 202); - } - - // Try multiple path candidates to account for legacy prefixes - $candidates = []; - $candidates[] = $relPath; - // also try raw original (normalized slashes, trimmed) - $raw = $document->path ? ltrim(str_replace('\\', '/', $document->path), '/') : null; - if ($raw && $raw !== $relPath) { - $candidates[] = $raw; - } - // if path accidentally contains 'storage/' prefix (public symlink), strip it - if (str_starts_with($relPath, 'storage/')) { - $candidates[] = substr($relPath, 8); - } - if ($raw && str_starts_with($raw, 'storage/')) { - $candidates[] = substr($raw, 8); - } - - $existsOnDisk = false; - foreach ($candidates as $cand) { - if (Storage::disk($disk)->exists($cand)) { - $existsOnDisk = true; - $relPath = $cand; - break; - } - } - - if (! $existsOnDisk) { - // Fallback: some legacy files may live directly under public/, attempt to stream from there - $publicFull = public_path($relPath); - $real = @realpath($publicFull); - $publicRoot = @realpath(public_path()); - $realN = $real ? str_replace('\\', '/', $real) : null; - $rootN = $publicRoot ? str_replace('\\', '/', $publicRoot) : null; - if ($realN && $rootN && str_starts_with($realN, $rootN) && is_file($real)) { - logger()->info('Document view fallback: serving from public path', [ - 'document_id' => $document->id, - 'path' => $realN, - ]); - $fp = @fopen($real, 'rb'); - if ($fp === false) { - abort(404); - } - - return response()->stream(function () use ($fp) { - fpassthru($fp); - }, 200, [ - 'Content-Type' => $document->mime_type ?: 'application/octet-stream', - 'Content-Disposition' => 'inline; filename="'.addslashes((($document->name ?: pathinfo($document->original_name ?: $document->file_name, PATHINFO_FILENAME)).'.'.strtolower(pathinfo($document->original_name ?: $document->file_name, PATHINFO_EXTENSION)))).'"', - 'Cache-Control' => 'private, max-age=0, no-cache', - 'Pragma' => 'no-cache', - ]); - } - - logger()->warning('Document view 404: file missing on disk and public fallback failed', [ - 'document_id' => $document->id, - 'document_uuid' => $document->uuid, - 'disk' => $disk, - 'path' => $document->path, - 'normalizedCandidates' => $candidates, - 'public_candidate' => $publicFull, - ]); - abort(404); - } - - $stream = Storage::disk($disk)->readStream($relPath); - if ($stream === false) { - logger()->warning('Document view: readStream failed, attempting fallbacks', [ - 'document_id' => $document->id, - 'disk' => $disk, - 'relPath' => $relPath, - ]); - - $headers = [ - 'Content-Type' => $document->mime_type ?: 'application/octet-stream', - 'Content-Disposition' => 'inline; filename="'.addslashes((($document->name ?: pathinfo($document->original_name ?: $document->file_name, PATHINFO_FILENAME)).'.'.strtolower(pathinfo($document->original_name ?: $document->file_name, PATHINFO_EXTENSION)))).'"', - 'Cache-Control' => 'private, max-age=0, no-cache', - 'Pragma' => 'no-cache', - ]; - - // Fallback 1: get() the bytes directly - try { - $bytes = Storage::disk($disk)->get($relPath); - } catch (\Throwable $e) { - $bytes = null; - } - if (! is_null($bytes) && $bytes !== false) { - return response($bytes, 200, $headers); - } - - // Fallback 2: open via absolute path (local driver) - $abs = null; - try { - if (method_exists(Storage::disk($disk), 'path')) { - $abs = Storage::disk($disk)->path($relPath); - } - } catch (\Throwable $e) { - $abs = null; - } - if ($abs && is_file($abs)) { - $fp = @fopen($abs, 'rb'); - if ($fp !== false) { - logger()->info('Document view fallback: serving from absolute storage path', [ - 'document_id' => $document->id, - 'abs' => str_replace('\\\\', '/', (string) realpath($abs)), - ]); - - return response()->stream(function () use ($fp) { - fpassthru($fp); - }, 200, $headers); - } - } - - // Fallback 3: serve from public path if available - $publicFull = public_path($relPath); - $real = @realpath($publicFull); - $publicRoot = @realpath(public_path()); - $realN = $real ? str_replace('\\\\', '/', $real) : null; - $rootN = $publicRoot ? str_replace('\\\\', '/', $publicRoot) : null; - if ($realN && $rootN && str_starts_with($realN, $rootN) && is_file($real)) { - logger()->info('Document view fallback: serving from public path (post-readStream failure)', [ - 'document_id' => $document->id, - 'path' => $realN, - ]); - $fp = @fopen($real, 'rb'); - if ($fp !== false) { - return response()->stream(function () use ($fp) { - fpassthru($fp); - }, 200, $headers); - } - } - - logger()->warning('Document view 404: all fallbacks failed after readStream failure', [ - 'document_id' => $document->id, - 'disk' => $disk, - 'relPath' => $relPath, - ]); - abort(404); - } - - return response()->stream(function () use ($stream) { - fpassthru($stream); - }, 200, [ - 'Content-Type' => $document->mime_type ?: 'application/octet-stream', - 'Content-Disposition' => 'inline; filename="'.addslashes((($document->name ?: pathinfo($document->original_name ?: $document->file_name, PATHINFO_FILENAME)).'.'.strtolower(pathinfo($document->original_name ?: $document->file_name, PATHINFO_EXTENSION)))).'"', - 'Cache-Control' => 'private, max-age=0, no-cache', - 'Pragma' => 'no-cache', - ]); + return $this->documentStream->stream($document, inline: true); } public function downloadDocument(ClientCase $clientCase, Document $document, Request $request) @@ -814,163 +635,8 @@ public function downloadDocument(ClientCase $clientCase, Document $document, Req ]); abort(404); } - $disk = $document->disk ?: 'public'; - // Normalize relative path for Windows and legacy prefixes - $relPath = $document->path ?? ''; - $relPath = str_replace('\\', '/', $relPath); - $relPath = ltrim($relPath, '/'); - if (str_starts_with($relPath, 'public/')) { - $relPath = substr($relPath, 7); - } - $candidates = []; - $candidates[] = $relPath; - $raw = $document->path ? ltrim(str_replace('\\', '/', $document->path), '/') : null; - if ($raw && $raw !== $relPath) { - $candidates[] = $raw; - } - if (str_starts_with($relPath, 'storage/')) { - $candidates[] = substr($relPath, 8); - } - if ($raw && str_starts_with($raw, 'storage/')) { - $candidates[] = substr($raw, 8); - } - - $existsOnDisk = false; - foreach ($candidates as $cand) { - if (Storage::disk($disk)->exists($cand)) { - $existsOnDisk = true; - $relPath = $cand; - break; - } - } - - if (! $existsOnDisk) { - // Fallback to public/ direct path if present - $publicFull = public_path($relPath); - $real = @realpath($publicFull); - $publicRoot = @realpath(public_path()); - $realN = $real ? str_replace('\\', '/', $real) : null; - $rootN = $publicRoot ? str_replace('\\', '/', $publicRoot) : null; - if ($realN && $rootN && str_starts_with($realN, $rootN) && is_file($real)) { - logger()->info('Document download fallback: serving from public path', [ - 'document_id' => $document->id, - 'path' => $realN, - ]); - $nameBase = $document->name ?: pathinfo($document->original_name ?: $document->file_name, PATHINFO_FILENAME); - $ext = strtolower(pathinfo($document->original_name ?: $document->file_name, PATHINFO_EXTENSION)); - $name = $ext ? ($nameBase.'.'.$ext) : $nameBase; - $fp = @fopen($real, 'rb'); - if ($fp === false) { - abort(404); - } - - return response()->stream(function () use ($fp) { - fpassthru($fp); - }, 200, [ - 'Content-Type' => $document->mime_type ?: 'application/octet-stream', - 'Content-Disposition' => 'attachment; filename="'.addslashes($name).'"', - 'Cache-Control' => 'private, max-age=0, no-cache', - 'Pragma' => 'no-cache', - ]); - } - - logger()->warning('Document download 404: file missing on disk and public fallback failed', [ - 'document_id' => $document->id, - 'document_uuid' => $document->uuid, - 'disk' => $disk, - 'path' => $document->path, - 'normalizedCandidates' => $candidates, - 'public_candidate' => $publicFull, - ]); - abort(404); - } - $nameBase = $document->name ?: pathinfo($document->original_name ?: $document->file_name, PATHINFO_FILENAME); - $ext = strtolower(pathinfo($document->original_name ?: $document->file_name, PATHINFO_EXTENSION)); - $name = $ext ? ($nameBase.'.'.$ext) : $nameBase; - $stream = Storage::disk($disk)->readStream($relPath); - if ($stream === false) { - logger()->warning('Document download: readStream failed, attempting fallbacks', [ - 'document_id' => $document->id, - 'disk' => $disk, - 'relPath' => $relPath, - ]); - - $headers = [ - 'Content-Type' => $document->mime_type ?: 'application/octet-stream', - 'Content-Disposition' => 'attachment; filename="'.addslashes($name).'"', - 'Cache-Control' => 'private, max-age=0, no-cache', - 'Pragma' => 'no-cache', - ]; - - // Fallback 1: get() the bytes directly - try { - $bytes = Storage::disk($disk)->get($relPath); - } catch (\Throwable $e) { - $bytes = null; - } - if (! is_null($bytes) && $bytes !== false) { - return response($bytes, 200, $headers); - } - - // Fallback 2: open via absolute storage path - $abs = null; - try { - if (method_exists(Storage::disk($disk), 'path')) { - $abs = Storage::disk($disk)->path($relPath); - } - } catch (\Throwable $e) { - $abs = null; - } - if ($abs && is_file($abs)) { - $fp = @fopen($abs, 'rb'); - if ($fp !== false) { - logger()->info('Document download fallback: serving from absolute storage path', [ - 'document_id' => $document->id, - 'abs' => str_replace('\\\\', '/', (string) realpath($abs)), - ]); - - return response()->stream(function () use ($fp) { - fpassthru($fp); - }, 200, $headers); - } - } - - // Fallback 3: serve from public path if available - $publicFull = public_path($relPath); - $real = @realpath($publicFull); - $publicRoot = @realpath(public_path()); - $realN = $real ? str_replace('\\\\', '/', $real) : null; - $rootN = $publicRoot ? str_replace('\\\\', '/', $publicRoot) : null; - if ($realN && $rootN && str_starts_with($realN, $rootN) && is_file($real)) { - logger()->info('Document download fallback: serving from public path (post-readStream failure)', [ - 'document_id' => $document->id, - 'path' => $realN, - ]); - $fp = @fopen($real, 'rb'); - if ($fp !== false) { - return response()->stream(function () use ($fp) { - fpassthru($fp); - }, 200, $headers); - } - } - - logger()->warning('Document download 404: all fallbacks failed after readStream failure', [ - 'document_id' => $document->id, - 'disk' => $disk, - 'relPath' => $relPath, - ]); - abort(404); - } - - return response()->stream(function () use ($stream) { - fpassthru($stream); - }, 200, [ - 'Content-Type' => $document->mime_type ?: 'application/octet-stream', - 'Content-Disposition' => 'attachment; filename="'.addslashes($name).'"', - 'Cache-Control' => 'private, max-age=0, no-cache', - 'Pragma' => 'no-cache', - ]); + return $this->documentStream->stream($document, inline: false); } /** @@ -984,8 +650,7 @@ public function viewContractDocument(Contract $contract, Document $document, Req abort(404); } - // Reuse the existing logic by delegating to a small helper - return $this->streamDocumentForDisk($document, inline: true); + return $this->documentStream->stream($document, inline: true); } /** @@ -998,138 +663,7 @@ public function downloadContractDocument(Contract $contract, Document $document, abort(404); } - return $this->streamDocumentForDisk($document, inline: false); - } - - /** - * Internal helper to stream a document either inline or as attachment with all Windows/public fallbacks. - */ - protected function streamDocumentForDisk(Document $document, bool $inline = true) - { - $disk = $document->disk ?: 'public'; - $relPath = $document->path ?? ''; - $relPath = str_replace('\\', '/', $relPath); - $relPath = ltrim($relPath, '/'); - if (str_starts_with($relPath, 'public/')) { - $relPath = substr($relPath, 7); - } - - // Previews for DOC/DOCX - $ext = strtolower(pathinfo($document->original_name ?: $document->file_name, PATHINFO_EXTENSION)); - $previewDisk = config('files.preview_disk', 'public'); - if ($inline && in_array($ext, ['doc', 'docx'])) { - if ($document->preview_path && Storage::disk($previewDisk)->exists($document->preview_path)) { - $stream = Storage::disk($previewDisk)->readStream($document->preview_path); - if ($stream !== false) { - $previewNameBase = $document->name ?: pathinfo($document->original_name ?: $document->file_name, PATHINFO_FILENAME); - - return response()->stream(function () use ($stream) { - fpassthru($stream); - }, 200, [ - 'Content-Type' => $document->preview_mime ?: 'application/pdf', - 'Content-Disposition' => 'inline; filename="'.addslashes($previewNameBase.'.pdf').'"', - 'Cache-Control' => 'private, max-age=0, no-cache', - 'Pragma' => 'no-cache', - ]); - } - } - \App\Jobs\GenerateDocumentPreview::dispatch($document->id); - - return response('Preview is being generated. Please try again shortly.', 202); - } - - // Try storage candidates - $candidates = [$relPath]; - $raw = $document->path ? ltrim(str_replace('\\', '/', $document->path), '/') : null; - if ($raw && $raw !== $relPath) { - $candidates[] = $raw; - } - if (str_starts_with($relPath, 'storage/')) { - $candidates[] = substr($relPath, 8); - } - if ($raw && str_starts_with($raw, 'storage/')) { - $candidates[] = substr($raw, 8); - } - - $found = null; - foreach ($candidates as $cand) { - if (Storage::disk($disk)->exists($cand)) { - $found = $cand; - break; - } - } - - $headers = [ - 'Content-Type' => $document->mime_type ?: 'application/octet-stream', - 'Content-Disposition' => ($inline ? 'inline' : 'attachment').'; filename="'.addslashes($document->original_name ?: $document->file_name).'"', - 'Cache-Control' => 'private, max-age=0, no-cache', - 'Pragma' => 'no-cache', - ]; - - if (! $found) { - // public/ fallback - $publicFull = public_path($relPath); - $real = @realpath($publicFull); - $publicRoot = @realpath(public_path()); - $realN = $real ? str_replace('\\\\', '/', $real) : null; - $rootN = $publicRoot ? str_replace('\\\\', '/', $publicRoot) : null; - if ($realN && $rootN && str_starts_with($realN, $rootN) && is_file($real)) { - $fp = @fopen($real, 'rb'); - if ($fp !== false) { - return response()->stream(function () use ($fp) { - fpassthru($fp); - }, 200, $headers); - } - } - abort(404); - } - - $stream = Storage::disk($disk)->readStream($found); - if ($stream !== false) { - return response()->stream(function () use ($stream) { - fpassthru($stream); - }, 200, $headers); - } - - // Fallbacks on readStream failure - try { - $bytes = Storage::disk($disk)->get($found); - if (! is_null($bytes) && $bytes !== false) { - return response($bytes, 200, $headers); - } - } catch (\Throwable $e) { - } - - $abs = null; - try { - if (method_exists(Storage::disk($disk), 'path')) { - $abs = Storage::disk($disk)->path($found); - } - } catch (\Throwable $e) { - $abs = null; - } - if ($abs && is_file($abs)) { - $fp = @fopen($abs, 'rb'); - if ($fp !== false) { - return response()->stream(function () use ($fp) { - fpassthru($fp); - }, 200, $headers); - } - } - - // public/ again as last try - $publicFull = public_path($found); - $real = @realpath($publicFull); - if ($real && is_file($real)) { - $fp = @fopen($real, 'rb'); - if ($fp !== false) { - return response()->stream(function () use ($fp) { - fpassthru($fp); - }, 200, $headers); - } - } - - abort(404); + return $this->documentStream->stream($document, inline: false); } /** @@ -1142,8 +676,8 @@ public function show(ClientCase $clientCase) ])->where('active', 1)->findOrFail($clientCase->id); $types = [ - 'address_types' => \App\Models\Person\AddressType::all(), - 'phone_types' => \App\Models\Person\PhoneType::all(), + 'address_types' => $this->referenceCache->getAddressTypes(), + 'phone_types' => $this->referenceCache->getPhoneTypes(), ]; // $active = false; @@ -1210,10 +744,10 @@ public function show(ClientCase $clientCase) }); } - // NOTE: If a case has an extremely large number of contracts this can still be heavy. - // Consider pagination or deferred (Inertia lazy) loading. For now, hard-cap to 500 to prevent - // pathological memory / header growth. Frontend can request more via future endpoint. - $contracts = $contractsQuery->limit(500)->get(); + // Use pagination for contracts to avoid loading too many at once + // Default to 50 per page, but allow frontend to request more + $perPage = request()->integer('contracts_per_page', 50); + $contracts = $contractsQuery->paginate($perPage, ['*'], 'contracts_page')->withQueryString(); // TEMP DEBUG: log what balances are being sent to Inertia (remove once issue resolved) try { @@ -1234,49 +768,19 @@ public function show(ClientCase $clientCase) // swallow } + // Prepare contract reference and UUID maps from paginated contracts + $contractItems = $contracts instanceof \Illuminate\Contracts\Pagination\LengthAwarePaginator + ? $contracts->items() + : $contracts->all(); + $contractRefMap = []; - foreach ($contracts as $c) { + $contractUuidMap = []; + foreach ($contractItems as $c) { $contractRefMap[$c->id] = $c->reference; + $contractUuidMap[$c->id] = $c->uuid; } - - // Merge client case and contract documents into a single array and include contract reference when applicable - $contractIds = $contracts->pluck('id'); - // Include 'uuid' so frontend can build document routes (was causing missing 'document' param error) - // IMPORTANT: If there are no contracts for this case we must NOT return all contract documents from other cases. - if ($contractIds->isEmpty()) { - $contractDocs = collect(); - } else { - $contractDocs = Document::query() - ->select(['id', 'uuid', 'documentable_id', 'documentable_type', 'name', 'file_name', 'original_name', 'extension', 'mime_type', 'size', 'created_at', 'is_public']) - ->where('documentable_type', Contract::class) - ->whereIn('documentable_id', $contractIds) - ->orderByDesc('created_at') - ->limit(300) // cap to prevent excessive payload; add pagination later if needed - ->get() - ->map(function ($d) use ($contractRefMap) { - $arr = method_exists($d, 'toArray') ? $d->toArray() : (array) $d; - $arr['contract_reference'] = $contractRefMap[$d->documentable_id] ?? null; - $arr['contract_uuid'] = optional(Contract::withTrashed()->find($d->documentable_id))->uuid; - - return $arr; - }); - } - - $caseDocs = $case->documents() - ->select(['id', 'uuid', 'documentable_id', 'documentable_type', 'name', 'file_name', 'original_name', 'extension', 'mime_type', 'size', 'created_at', 'is_public']) - ->orderByDesc('created_at') - ->limit(200) - ->get() - ->map(function ($d) use ($case) { - $arr = method_exists($d, 'toArray') ? $d->toArray() : (array) $d; - $arr['client_case_uuid'] = $case->uuid; - - return $arr; - }); - $mergedDocs = $caseDocs - ->concat($contractDocs) - ->sortByDesc('created_at') - ->values(); + + $contractIds = collect($contractItems)->pluck('id'); // Resolve current segment for display when filtered $currentSegment = null; @@ -1284,10 +788,55 @@ public function show(ClientCase $clientCase) $currentSegment = \App\Models\Segment::query()->select('id', 'name')->find($segmentId); } + // Load initial batch of documents (limit to reduce payload size) + $contractDocs = collect(); + if ($contractIds->isNotEmpty()) { + // Build UUID map for all contracts (including trashed) to avoid N+1 queries + $allContractUuids = Contract::withTrashed() + ->whereIn('id', $contractIds->all()) + ->pluck('uuid', 'id') + ->toArray(); + + // Merge with contracts already loaded + $contractUuidMap = array_merge($contractUuidMap, $allContractUuids); + + $contractDocs = Document::query() + ->select(['id', 'uuid', 'documentable_id', 'documentable_type', 'name', 'file_name', 'original_name', 'extension', 'mime_type', 'size', 'created_at', 'is_public']) + ->where('documentable_type', Contract::class) + ->whereIn('documentable_id', $contractIds->all()) + ->orderByDesc('created_at') + ->limit(50) // Initial batch - frontend can request more via separate endpoint if needed + ->get() + ->map(function ($d) use ($contractRefMap, $contractUuidMap) { + $arr = $d->toArray(); + $arr['contract_reference'] = $contractRefMap[$d->documentable_id] ?? null; + $arr['contract_uuid'] = $contractUuidMap[$d->documentable_id] ?? null; + return $arr; + }); + } + + $caseDocs = $case->documents() + ->select(['id', 'uuid', 'documentable_id', 'documentable_type', 'name', 'file_name', 'original_name', 'extension', 'mime_type', 'size', 'created_at', 'is_public']) + ->orderByDesc('created_at') + ->limit(50) // Initial batch + ->get() + ->map(function ($d) use ($case) { + $arr = $d->toArray(); + $arr['client_case_uuid'] = $case->uuid; + return $arr; + }); + + $mergedDocs = $caseDocs + ->concat($contractDocs) + ->sortByDesc('created_at') + ->values(); + return Inertia::render('Cases/Show', [ 'client' => $case->client()->with('person', fn ($q) => $q->with(['addresses', 'phones', 'emails', 'bankAccounts', 'client']))->firstOrFail(), 'client_case' => $case, - 'contracts' => $contracts, + 'contracts' => $contracts, // Now paginated + 'documents' => $mergedDocs, + ])->with([ // Active document templates for contracts (latest version per slug) 'contract_doc_templates' => \App\Models\DocumentTemplate::query() ->where('active', true) @@ -1326,9 +875,8 @@ function ($p) { }); } ), - 'documents' => $mergedDocs, - 'contract_types' => \App\Models\ContractType::whereNull('deleted_at')->get(), - 'account_types' => \App\Models\AccountType::all(), + 'contract_types' => $this->referenceCache->getContractTypes(), + 'account_types' => $this->referenceCache->getAccountTypes(), // Include decisions with auto-mail metadata and the linked email template entity_types for UI logic 'actions' => \App\Models\Action::query() ->with([ diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index 0db4353..d1261e2 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -3,57 +3,51 @@ namespace App\Http\Controllers; use App\Models\Client; +use App\Services\ReferenceDataCache; use DB; use Illuminate\Http\Request; use Inertia\Inertia; class ClientController extends Controller { + public function __construct(protected ReferenceDataCache $referenceCache) {} + public function index(Client $client, Request $request) { + $search = $request->input('search'); + $query = $client::query() - ->with('person') - ->when($request->input('search'), function ($que, $search) { - $que->whereHas('person', function ($q) use ($search) { - $q->where('full_name', 'ilike', '%'.$search.'%'); - }); + ->select('clients.*') + ->when($search, function ($que) use ($search) { + $que->join('person', 'person.id', '=', 'clients.person_id') + ->where('person.full_name', 'ilike', '%'.$search.'%') + ->groupBy('clients.id'); }) - ->where('active', 1) + ->where('clients.active', 1) + // Use LEFT JOINs for aggregated data to avoid subqueries + ->leftJoin('client_cases', 'client_cases.client_id', '=', 'clients.id') + ->leftJoin('contracts', function ($join) { + $join->on('contracts.client_case_id', '=', 'client_cases.id') + ->whereNull('contracts.deleted_at'); + }) + ->leftJoin('contract_segment', function ($join) { + $join->on('contract_segment.contract_id', '=', 'contracts.id') + ->where('contract_segment.active', true); + }) + ->leftJoin('accounts', 'accounts.contract_id', '=', 'contracts.id') + ->groupBy('clients.id') ->addSelect([ // Number of client cases for this client that have at least one active contract - 'cases_with_active_contracts_count' => DB::query() - ->from('client_cases') - ->join('contracts', 'contracts.client_case_id', '=', 'client_cases.id') - ->selectRaw('COUNT(DISTINCT client_cases.id)') - ->whereColumn('client_cases.client_id', 'clients.id') - ->whereNull('contracts.deleted_at') - ->whereExists(function ($q) { - $q->from('contract_segment') - ->whereColumn('contract_segment.contract_id', 'contracts.id') - ->where('contract_segment.active', true); - }), - // Sum of account balances for active contracts that belong to this client's cases - 'active_contracts_balance_sum' => DB::query() - ->from('contracts') - ->join('accounts', 'accounts.contract_id', '=', 'contracts.id') - ->selectRaw('COALESCE(SUM(accounts.balance_amount), 0)') - ->whereExists(function ($q) { - $q->from('client_cases') - ->whereColumn('client_cases.id', 'contracts.client_case_id') - ->whereColumn('client_cases.client_id', 'clients.id'); - }) - ->whereNull('contracts.deleted_at') - ->whereExists(function ($q) { - $q->from('contract_segment') - ->whereColumn('contract_segment.contract_id', 'contracts.id') - ->where('contract_segment.active', true); - }), + DB::raw('COUNT(DISTINCT CASE WHEN contract_segment.id IS NOT NULL THEN client_cases.id END) as cases_with_active_contracts_count'), + // Sum of account balances for active contracts + DB::raw('COALESCE(SUM(CASE WHEN contract_segment.id IS NOT NULL THEN accounts.balance_amount END), 0) as active_contracts_balance_sum'), ]) - ->orderByDesc('created_at'); + ->with('person') + ->orderByDesc('clients.created_at'); return Inertia::render('Client/Index', [ 'clients' => $query - ->paginate($request->integer('perPage', 15)) + ->paginate($request->integer('per_page', 15)) ->withQueryString(), 'filters' => $request->only(['search']), ]); @@ -67,44 +61,37 @@ public function show(Client $client, Request $request) ->findOrFail($client->id); $types = [ - 'address_types' => \App\Models\Person\AddressType::all(), - 'phone_types' => \App\Models\Person\PhoneType::all(), + 'address_types' => $this->referenceCache->getAddressTypes(), + 'phone_types' => $this->referenceCache->getPhoneTypes(), ]; return Inertia::render('Client/Show', [ 'client' => $data, 'client_cases' => $data->clientCases() - ->with(['person', 'client.person']) - ->when($request->input('search'), fn ($que, $search) => $que->whereHas( - 'person', - fn ($q) => $q->where('full_name', 'ilike', '%'.$search.'%') - )) + ->select('client_cases.*') + ->when($request->input('search'), function ($que, $search) { + $que->join('person', 'person.id', '=', 'client_cases.person_id') + ->where('person.full_name', 'ilike', '%'.$search.'%') + ->groupBy('client_cases.id'); + }) + ->leftJoin('contracts', function ($join) { + $join->on('contracts.client_case_id', '=', 'client_cases.id') + ->whereNull('contracts.deleted_at'); + }) + ->leftJoin('contract_segment', function ($join) { + $join->on('contract_segment.contract_id', '=', 'contracts.id') + ->where('contract_segment.active', true); + }) + ->leftJoin('accounts', 'accounts.contract_id', '=', 'contracts.id') + ->groupBy('client_cases.id') ->addSelect([ - 'active_contracts_count' => \DB::query() - ->from('contracts') - ->selectRaw('COUNT(*)') - ->whereColumn('contracts.client_case_id', 'client_cases.id') - ->whereNull('contracts.deleted_at') - ->whereExists(function ($q) { - $q->from('contract_segment') - ->whereColumn('contract_segment.contract_id', 'contracts.id') - ->where('contract_segment.active', true); - }), - 'active_contracts_balance_sum' => \DB::query() - ->from('contracts') - ->join('accounts', 'accounts.contract_id', '=', 'contracts.id') - ->selectRaw('COALESCE(SUM(accounts.balance_amount), 0)') - ->whereColumn('contracts.client_case_id', 'client_cases.id') - ->whereNull('contracts.deleted_at') - ->whereExists(function ($q) { - $q->from('contract_segment') - ->whereColumn('contract_segment.contract_id', 'contracts.id') - ->where('contract_segment.active', true); - }), + \DB::raw('COUNT(DISTINCT CASE WHEN contract_segment.id IS NOT NULL THEN contracts.id END) as active_contracts_count'), + \DB::raw('COALESCE(SUM(CASE WHEN contract_segment.id IS NOT NULL THEN accounts.balance_amount END), 0) as active_contracts_balance_sum'), ]) - ->where('active', 1) - ->orderByDesc('created_at') - ->paginate($request->integer('perPage', 15)) + ->with(['person', 'client.person']) + ->where('client_cases.active', 1) + ->orderByDesc('client_cases.created_at') + ->paginate($request->integer('per_page', 15)) ->withQueryString(), 'types' => $types, 'filters' => $request->only(['search']), @@ -121,8 +108,31 @@ public function contracts(Client $client, Request $request) $segmentId = $request->input('segment'); $contractsQuery = \App\Models\Contract::query() - ->whereHas('clientCase', function ($q) use ($client) { - $q->where('client_id', $client->id); + ->select(['contracts.id', 'contracts.uuid', 'contracts.reference', 'contracts.start_date', 'contracts.client_case_id']) + ->join('client_cases', 'client_cases.id', '=', 'contracts.client_case_id') + ->where('client_cases.client_id', $client->id) + ->whereNull('contracts.deleted_at') + ->when($from || $to, function ($q) use ($from, $to) { + if (! empty($from)) { + $q->whereDate('contracts.start_date', '>=', $from); + } + if (! empty($to)) { + $q->whereDate('contracts.start_date', '<=', $to); + } + }) + ->when($search, function ($q) use ($search) { + $q->leftJoin('person', 'person.id', '=', 'client_cases.person_id') + ->where(function ($inner) use ($search) { + $inner->where('contracts.reference', 'ilike', '%'.$search.'%') + ->orWhere('person.full_name', 'ilike', '%'.$search.'%'); + }); + }) + ->when($segmentId, function ($q) use ($segmentId) { + $q->join('contract_segment', function ($join) use ($segmentId) { + $join->on('contract_segment.contract_id', '=', 'contracts.id') + ->where('contract_segment.segment_id', $segmentId) + ->where('contract_segment.active', true); + }); }) ->with([ 'clientCase:id,uuid,person_id', @@ -132,42 +142,18 @@ public function contracts(Client $client, Request $request) }, 'account:id,accounts.contract_id,balance_amount', ]) - ->select(['id', 'uuid', 'reference', 'start_date', 'client_case_id']) - ->whereNull('deleted_at') - ->when($from || $to, function ($q) use ($from, $to) { - if (! empty($from)) { - $q->whereDate('start_date', '>=', $from); - } - if (! empty($to)) { - $q->whereDate('start_date', '<=', $to); - } - }) - ->when($search, function ($q) use ($search) { - $q->where(function ($inner) use ($search) { - $inner->where('reference', 'ilike', '%'.$search.'%') - ->orWhereHas('clientCase.person', function ($p) use ($search) { - $p->where('full_name', 'ilike', '%'.$search.'%'); - }); - }); - }) - ->when($segmentId, function ($q) use ($segmentId) { - $q->whereHas('segments', function ($s) use ($segmentId) { - $s->where('segments.id', $segmentId) - ->where('contract_segment.active', true); - }); - }) - ->orderByDesc('start_date'); + ->orderByDesc('contracts.start_date'); $segments = \App\Models\Segment::orderBy('name')->get(['id', 'name']); $types = [ - 'address_types' => \App\Models\Person\AddressType::all(), - 'phone_types' => \App\Models\Person\PhoneType::all(), + 'address_types' => $this->referenceCache->getAddressTypes(), + 'phone_types' => $this->referenceCache->getPhoneTypes(), ]; return Inertia::render('Client/Contracts', [ 'client' => $data, - 'contracts' => $contractsQuery->paginate($request->integer('perPage', 20))->withQueryString(), + 'contracts' => $contractsQuery->paginate($request->integer('per_page', 20))->withQueryString(), 'filters' => $request->only(['from', 'to', 'search', 'segment']), 'segments' => $segments, 'types' => $types, diff --git a/app/Http/Controllers/PhoneViewController.php b/app/Http/Controllers/PhoneViewController.php index 062a095..26f2b7f 100644 --- a/app/Http/Controllers/PhoneViewController.php +++ b/app/Http/Controllers/PhoneViewController.php @@ -3,11 +3,13 @@ namespace App\Http\Controllers; use App\Models\FieldJob; +use App\Services\ReferenceDataCache; use Illuminate\Http\Request; use Inertia\Inertia; class PhoneViewController extends Controller { + public function __construct(protected ReferenceDataCache $referenceCache) {} public function index(Request $request) { $userId = $request->user()->id; @@ -168,8 +170,8 @@ public function showCase(\App\Models\ClientCase $clientCase, Request $request) // Provide minimal types for PersonInfoGrid $types = [ - 'address_types' => \App\Models\Person\AddressType::all(), - 'phone_types' => \App\Models\Person\PhoneType::all(), + 'address_types' => $this->referenceCache->getAddressTypes(), + 'phone_types' => $this->referenceCache->getPhoneTypes(), ]; // Case activities (compact for phone): latest 20 with relations @@ -235,7 +237,7 @@ function ($q) { 'contracts' => $contracts, 'documents' => $documents, 'types' => $types, - 'account_types' => \App\Models\AccountType::all(), + 'account_types' => $this->referenceCache->getAccountTypes(), // Provide decisions (filtered by segment) with linked email template metadata (entity_types, allow_attachments) 'actions' => $actions, 'activities' => $activities, diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php new file mode 100644 index 0000000..7236a18 --- /dev/null +++ b/app/Http/Controllers/ReportController.php @@ -0,0 +1,379 @@ +registry->all()) + ->map(fn ($r) => [ + 'slug' => $r->slug(), + 'name' => $r->name(), + 'description' => $r->description(), + ]) + ->values(); + + return Inertia::render('Reports/Index', [ + 'reports' => $reports, + ]); + } + + public function show(string $slug, Request $request) + { + $report = $this->registry->findBySlug($slug); + abort_if(! $report, 404); + $report->authorize($request); + + // Accept filters & pagination from query and return initial data for server-driven table + $filters = $this->validateFilters($report->inputs(), $request); + \Log::info('Report filters', ['filters' => $filters, 'request' => $request->all()]); + $perPage = (int) ($request->integer('per_page') ?: 25); + $paginator = $report->paginate($filters, $perPage); + + $rows = collect($paginator->items()) + ->map(fn ($row) => $this->normalizeRow($row)) + ->values(); + + return Inertia::render('Reports/Show', [ + 'slug' => $report->slug(), + 'name' => $report->name(), + 'description' => $report->description(), + 'inputs' => $report->inputs(), + 'columns' => $report->columns(), + 'rows' => $rows, + 'meta' => [ + 'total' => $paginator->total(), + 'current_page' => $paginator->currentPage(), + 'per_page' => $paginator->perPage(), + 'last_page' => $paginator->lastPage(), + ], + 'query' => array_filter($filters, fn ($v) => $v !== null && $v !== ''), + ]); + } + + public function data(string $slug, Request $request) + { + $report = $this->registry->findBySlug($slug); + abort_if(! $report, 404); + $report->authorize($request); + + $filters = $this->validateFilters($report->inputs(), $request); + $perPage = (int) ($request->integer('per_page') ?: 25); + + $paginator = $report->paginate($filters, $perPage); + + $rows = collect($paginator->items()) + ->map(fn ($row) => $this->normalizeRow($row)) + ->values(); + + return response()->json([ + 'data' => $rows, + 'total' => $paginator->total(), + 'current_page' => $paginator->currentPage(), + 'last_page' => $paginator->lastPage(), + ]); + } + + public function export(string $slug, Request $request) + { + $report = $this->registry->findBySlug($slug); + abort_if(! $report, 404); + $report->authorize($request); + + $filters = $this->validateFilters($report->inputs(), $request); + $format = strtolower((string) $request->get('format', 'csv')); + + $rows = $report->query($filters)->get()->map(fn ($row) => $this->normalizeRow($row)); + $columns = $report->columns(); + $filename = $report->slug().'-'.now()->format('Ymd_His'); + + if ($format === 'pdf') { + $pdf = \Barryvdh\DomPDF\Facade\Pdf::loadView('reports.pdf.table', [ + 'name' => $report->name(), + 'columns' => $columns, + 'rows' => $rows, + ]); + + return $pdf->download($filename.'.pdf'); + } + + if ($format === 'xlsx') { + $keys = array_map(fn ($c) => $c['key'], $columns); + $headings = array_map(fn ($c) => $c['label'] ?? $c['key'], $columns); + + // Convert values for correct Excel rendering (dates, numbers, text) + $array = $this->prepareXlsxArray($rows, $keys); + + // Build base column formats: text for contracts, EU datetime for *_at; numbers are formatted per-cell in AfterSheet + $columnFormats = []; + $textColumns = []; + $dateColumns = []; + foreach ($keys as $i => $key) { + $letter = $this->excelColumnLetter($i + 1); + if ($key === 'contract_reference') { + $columnFormats[$letter] = '@'; + $textColumns[] = $letter; + + continue; + } + if (str_ends_with($key, '_at')) { + $columnFormats[$letter] = 'dd.mm.yyyy hh:mm'; + $dateColumns[] = $letter; + + continue; + } + } + + // Anonymous export with custom value binder to force text where needed + $export = new class($array, $headings, $columnFormats, $textColumns, $dateColumns) extends \Maatwebsite\Excel\DefaultValueBinder implements \Maatwebsite\Excel\Concerns\FromArray, \Maatwebsite\Excel\Concerns\ShouldAutoSize, \Maatwebsite\Excel\Concerns\WithColumnFormatting, \Maatwebsite\Excel\Concerns\WithCustomValueBinder, \Maatwebsite\Excel\Concerns\WithEvents, \Maatwebsite\Excel\Concerns\WithHeadings + { + public function __construct(private array $array, private array $headings, private array $formats, private array $textColumns, private array $dateColumns) {} + + public function array(): array + { + return $this->array; + } + + public function headings(): array + { + return $this->headings; + } + + public function columnFormats(): array + { + return $this->formats; + } + + public function bindValue(\PhpOffice\PhpSpreadsheet\Cell\Cell $cell, $value): bool + { + $col = preg_replace('/\d+/', '', $cell->getCoordinate()); // e.g., B from B2 + // Force text for configured columns or very long digit-only strings (>15) + if (in_array($col, $this->textColumns, true) || (is_string($value) && ctype_digit($value) && strlen($value) > 15)) { + $cell->setValueExplicit((string) $value, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING); + + return true; + } + + return parent::bindValue($cell, $value); + } + + public function registerEvents(): array + { + return [ + \Maatwebsite\Excel\Events\AfterSheet::class => function (\Maatwebsite\Excel\Events\AfterSheet $event) { + $sheet = $event->sheet->getDelegate(); + // Data starts at row 2 (row 1 is headings) + $rowIndex = 2; + foreach ($this->array as $row) { + foreach (array_values($row) as $i => $val) { + $colLetter = $this->colLetter($i + 1); + if (in_array($colLetter, $this->textColumns, true) || in_array($colLetter, $this->dateColumns, true)) { + continue; // already handled via columnFormats or binder + } + $coord = $colLetter.$rowIndex; + $fmt = null; + if (is_int($val)) { + // Integer: thousands separator, no decimals + $fmt = '#,##0'; + } elseif (is_float($val)) { + // Float: show decimals only if fractional part exists + $fmt = (floor($val) != $val) ? '#,##0.00' : '#,##0'; + } + if ($fmt) { + $sheet->getStyle($coord)->getNumberFormat()->setFormatCode($fmt); + } + } + $rowIndex++; + } + }, + ]; + } + + private function colLetter(int $index): string + { + $letter = ''; + while ($index > 0) { + $mod = ($index - 1) % 26; + $letter = chr(65 + $mod).$letter; + $index = intdiv($index - $mod, 26) - 1; + } + + return $letter; + } + }; + + return \Maatwebsite\Excel\Facades\Excel::download($export, $filename.'.xlsx'); + } + + // Default CSV export + $keys = array_map(fn ($c) => $c['key'], $columns); + $headings = array_map(fn ($c) => $c['label'] ?? $c['key'], $columns); + + $csv = fopen('php://temp', 'r+'); + fputcsv($csv, $headings); + foreach ($rows as $r) { + $line = collect($keys)->map(fn ($k) => data_get($r, $k))->toArray(); + fputcsv($csv, $line); + } + rewind($csv); + $content = stream_get_contents($csv) ?: ''; + fclose($csv); + + return response($content, 200, [ + 'Content-Type' => 'text/csv', + 'Content-Disposition' => 'attachment; filename="'.$filename.'.csv"', + ]); + } + + /** + * Lightweight users lookup for filters: id + name, optional search and limit. + */ + public function users(Request $request) + { + $search = trim((string) $request->get('search', '')); + $limit = (int) ($request->integer('limit') ?: 10); + + $q = \App\Models\User::query()->orderBy('name'); + if ($search !== '') { + $like = '%'.mb_strtolower($search).'%'; + $q->where(function ($qq) use ($like) { + $qq->whereRaw('LOWER(name) LIKE ?', [$like]) + ->orWhereRaw('LOWER(email) LIKE ?', [$like]); + }); + } + + $users = $q->limit(max(1, min(50, $limit)))->get(['id', 'name']); + + return response()->json($users); + } + + /** + * Lightweight clients lookup for filters: uuid + name (person full_name), optional search and limit. + */ + public function clients(Request $request) + { + $clients = \App\Models\Client::query() + ->with('person:id,full_name') + ->get() + ->map(fn($c) => [ + 'id' => $c->uuid, + 'name' => $c->person->full_name ?? 'Unknown' + ]) + ->sortBy('name') + ->values(); + + return response()->json($clients); + } + + /** + * Build validation rules based on inputs descriptor and validate. + * + * @param array> $inputs + * @return array + */ + protected function validateFilters(array $inputs, Request $request): array + { + $rules = []; + foreach ($inputs as $inp) { + $key = $inp['key']; + $type = $inp['type'] ?? 'string'; + $nullable = ($inp['nullable'] ?? true) ? 'nullable' : 'required'; + $rules[$key] = match ($type) { + 'date' => [$nullable, 'date'], + 'integer' => [$nullable, 'integer'], + 'select:user' => [$nullable, 'integer', 'exists:users,id'], + 'select:client' => [$nullable, 'string', 'exists:clients,uuid'], + default => [$nullable, 'string'], + }; + } + + return $request->validate($rules); + } + + /** + * Ensure derived export/display fields exist on row objects. + */ + protected function normalizeRow(object $row): object + { + if (isset($row->contract) && ! isset($row->contract_reference)) { + $row->contract_reference = $row->contract->reference ?? null; + } + if (isset($row->assignedUser) && ! isset($row->assigned_user_name)) { + $row->assigned_user_name = $row->assignedUser->name ?? null; + } + + return $row; + } + + /** + * Convert rows for XLSX export: dates to Excel serial numbers, numbers to numeric, contract refs to text. + * + * @param iterable $rows + * @param array $keys + * @return array> + */ + protected function prepareXlsxArray(iterable $rows, array $keys): array + { + $out = []; + foreach ($rows as $r) { + $line = []; + foreach ($keys as $k) { + $v = data_get($r, $k); + if ($k === 'contract_reference') { + $line[] = (string) $v; + + continue; + } + if (str_ends_with($k, '_at')) { + if (empty($v)) { + $line[] = null; + } else { + try { + $dt = \Carbon\Carbon::parse($v); + $line[] = \PhpOffice\PhpSpreadsheet\Shared\Date::dateTimeToExcel($dt); + } catch (\Throwable $e) { + $line[] = (string) $v; + } + } + + continue; + } + if (is_int($v) || is_float($v)) { + $line[] = $v; + } elseif (is_numeric($v) && is_string($v)) { + // cast numeric-like strings unless they are identifiers that we want as text + $line[] = (strpos($k, 'id') !== false) ? (int) $v : ($v + 0); + } else { + $line[] = $v; + } + } + $out[] = $line; + } + + return $out; + } + + /** + * Convert 1-based index to Excel column letter. + */ + protected function excelColumnLetter(int $index): string + { + $letter = ''; + while ($index > 0) { + $mod = ($index - 1) % 26; + $letter = chr(65 + $mod).$letter; + $index = intdiv($index - $mod, 26) - 1; + } + + return $letter; + } +} diff --git a/app/Providers/ReportServiceProvider.php b/app/Providers/ReportServiceProvider.php new file mode 100644 index 0000000..fbc9d88 --- /dev/null +++ b/app/Providers/ReportServiceProvider.php @@ -0,0 +1,36 @@ +app->singleton(ReportRegistry::class, function () { + $registry = new ReportRegistry; + // Register built-in reports here + $registry->register(new FieldJobsCompletedReport); + $registry->register(new SegmentActivityCountsReport); + $registry->register(new ActionsDecisionsCountReport); + $registry->register(new ActivitiesPerPeriodReport); + $registry->register(new DecisionsCountReport); + $registry->register(new ActiveContractsReport); + + return $registry; + }); + } + + public function boot(): void + { + // + } +} diff --git a/app/Reports/ActionsDecisionsCountReport.php b/app/Reports/ActionsDecisionsCountReport.php new file mode 100644 index 0000000..70efda3 --- /dev/null +++ b/app/Reports/ActionsDecisionsCountReport.php @@ -0,0 +1,53 @@ + 'from', 'type' => 'date', 'label' => 'Od', 'nullable' => true], + ['key' => 'to', 'type' => 'date', 'label' => 'Do', 'nullable' => true], + ]; + } + + public function columns(): array + { + return [ + ['key' => 'action_name', 'label' => 'Dejanje'], + ['key' => 'decision_name', 'label' => 'Odločitev'], + ['key' => 'activities_count', 'label' => 'Št. aktivnosti'], + ]; + } + + public function query(array $filters): Builder + { + return Activity::query() + ->leftJoin('actions', 'activities.action_id', '=', 'actions.id') + ->leftJoin('decisions', 'activities.decision_id', '=', 'decisions.id') + ->when(! empty($filters['from']), fn ($q) => $q->whereDate('activities.created_at', '>=', $filters['from'])) + ->when(! empty($filters['to']), fn ($q) => $q->whereDate('activities.created_at', '<=', $filters['to'])) + ->groupBy('actions.name', 'decisions.name') + ->selectRaw("COALESCE(actions.name, '—') as action_name, COALESCE(decisions.name, '—') as decision_name, COUNT(*) as activities_count"); + } +} diff --git a/app/Reports/ActiveContractsReport.php b/app/Reports/ActiveContractsReport.php new file mode 100644 index 0000000..9623dcc --- /dev/null +++ b/app/Reports/ActiveContractsReport.php @@ -0,0 +1,78 @@ + 'client_uuid', 'type' => 'select:client', 'label' => 'Stranka', 'nullable' => true], + ]; + } + + public function columns(): array + { + return [ + ['key' => 'contract_reference', 'label' => 'Pogodba'], + ['key' => 'client_name', 'label' => 'Stranka'], + ['key' => 'person_name', 'label' => 'Zadeva (oseba)'], + ['key' => 'start_date', 'label' => 'Začetek'], + ['key' => 'end_date', 'label' => 'Konec'], + ['key' => 'balance_amount', 'label' => 'Saldo'], + ]; + } + + public function query(array $filters): Builder + { + $asOf = now()->toDateString(); + + return Contract::query() + ->join('client_cases', 'contracts.client_case_id', '=', 'client_cases.id') + ->leftJoin('clients', 'client_cases.client_id', '=', 'clients.id') + ->leftJoin('person as client_people', 'clients.person_id', '=', 'client_people.id') + ->leftJoin('person as subject_people', 'client_cases.person_id', '=', 'subject_people.id') + ->leftJoin('accounts', 'contracts.id', '=', 'accounts.contract_id') + ->when(! empty($filters['client_uuid']), fn ($q) => $q->where('clients.uuid', $filters['client_uuid'])) + // Active as of date: start_date <= as_of (or null) AND (end_date is null OR end_date >= as_of) + ->where(function ($q) use ($asOf) { + $q->whereNull('contracts.start_date') + ->orWhereDate('contracts.start_date', '<=', $asOf); + }) + ->where(function ($q) use ($asOf) { + $q->whereNull('contracts.end_date') + ->orWhereDate('contracts.end_date', '>=', $asOf); + }) + ->select([ + 'contracts.id', + 'contracts.start_date', + 'contracts.end_date', + ]) + ->addSelect([ + \DB::raw('contracts.reference as contract_reference'), + \DB::raw('client_people.full_name as client_name'), + \DB::raw('subject_people.full_name as person_name'), + \DB::raw('CAST(accounts.balance_amount AS FLOAT) as balance_amount'), + ]) + ->orderBy('contracts.start_date', 'asc'); + } +} diff --git a/app/Reports/ActivitiesPerPeriodReport.php b/app/Reports/ActivitiesPerPeriodReport.php new file mode 100644 index 0000000..5d3ec08 --- /dev/null +++ b/app/Reports/ActivitiesPerPeriodReport.php @@ -0,0 +1,95 @@ + 'from', 'type' => 'date', 'label' => 'Od', 'nullable' => true], + ['key' => 'to', 'type' => 'date', 'label' => 'Do', 'nullable' => true], + ['key' => 'period', 'type' => 'string', 'label' => 'Obdobje (day|week|month)', 'default' => 'day'], + ]; + } + + public function columns(): array + { + return [ + ['key' => 'period', 'label' => 'Obdobje'], + ['key' => 'activities_count', 'label' => 'Št. aktivnosti'], + ]; + } + + public function query(array $filters): Builder + { + $periodRaw = $filters['period'] ?? 'day'; + $period = in_array($periodRaw, ['day', 'week', 'month'], true) ? $periodRaw : 'day'; + $driver = DB::getDriverName(); + + // Build database-compatible period expressions + if ($driver === 'sqlite') { + if ($period === 'day') { + // Use string slice to avoid timezone conversion differences in SQLite + $selectExpr = DB::raw('SUBSTR(activities.created_at, 1, 10) as period'); + $groupExpr = DB::raw('SUBSTR(activities.created_at, 1, 10)'); + $orderExpr = DB::raw('SUBSTR(activities.created_at, 1, 10)'); + } elseif ($period === 'month') { + $selectExpr = DB::raw("strftime('%Y-%m-01', activities.created_at) as period"); + $groupExpr = DB::raw("strftime('%Y-%m-01', activities.created_at)"); + $orderExpr = DB::raw("strftime('%Y-%m-01', activities.created_at)"); + } else { // week + $selectExpr = DB::raw("strftime('%Y-%W', activities.created_at) as period"); + $groupExpr = DB::raw("strftime('%Y-%W', activities.created_at)"); + $orderExpr = DB::raw("strftime('%Y-%W', activities.created_at)"); + } + } elseif ($driver === 'mysql') { + if ($period === 'day') { + $selectExpr = DB::raw('DATE(activities.created_at) as period'); + $groupExpr = DB::raw('DATE(activities.created_at)'); + $orderExpr = DB::raw('DATE(activities.created_at)'); + } elseif ($period === 'month') { + $selectExpr = DB::raw("DATE_FORMAT(activities.created_at, '%Y-%m-01') as period"); + $groupExpr = DB::raw("DATE_FORMAT(activities.created_at, '%Y-%m-01')"); + $orderExpr = DB::raw("DATE_FORMAT(activities.created_at, '%Y-%m-01')"); + } else { // week + // ISO week-year-week number for grouping; adequate for summary grouping + $selectExpr = DB::raw("DATE_FORMAT(activities.created_at, '%x-%v') as period"); + $groupExpr = DB::raw("DATE_FORMAT(activities.created_at, '%x-%v')"); + $orderExpr = DB::raw("DATE_FORMAT(activities.created_at, '%x-%v')"); + } + } else { // postgres and others supporting date_trunc + $selectExpr = DB::raw("date_trunc('".$period."', activities.created_at) as period"); + $groupExpr = DB::raw("date_trunc('".$period."', activities.created_at)"); + $orderExpr = DB::raw("date_trunc('".$period."', activities.created_at)"); + } + + return Activity::query() + ->when(! empty($filters['from']), fn ($q) => $q->whereDate('activities.created_at', '>=', $filters['from'])) + ->when(! empty($filters['to']), fn ($q) => $q->whereDate('activities.created_at', '<=', $filters['to'])) + ->groupBy($groupExpr) + ->orderBy($orderExpr) + ->select($selectExpr) + ->selectRaw('COUNT(*) as activities_count'); + } +} diff --git a/app/Reports/BaseEloquentReport.php b/app/Reports/BaseEloquentReport.php new file mode 100644 index 0000000..fbe8cee --- /dev/null +++ b/app/Reports/BaseEloquentReport.php @@ -0,0 +1,33 @@ + $filters + */ + public function paginate(array $filters, int $perPage = 25): LengthAwarePaginator + { + /** @var EloquentBuilder|QueryBuilder $query */ + $query = $this->query($filters); + + return $query->paginate($perPage); + } +} diff --git a/app/Reports/Contracts/Report.php b/app/Reports/Contracts/Report.php new file mode 100644 index 0000000..60bf1e3 --- /dev/null +++ b/app/Reports/Contracts/Report.php @@ -0,0 +1,54 @@ + 'from', 'type' => 'date', 'label' => 'Od', 'default' => today()] + * + * @return array> + */ + public function inputs(): array; + + /** + * Return column definitions for the table and exports. + * Example: [ ['key' => 'id', 'label' => '#'], ['key' => 'user', 'label' => 'Uporabnik'] ] + * + * @return array> + */ + public function columns(): array; + + /** + * Build the data source query for the report based on validated filters. + * Should return an Eloquent or Query builder. + * + * @param array $filters + * @return EloquentBuilder|QueryBuilder + */ + public function query(array $filters); + + /** + * Optional per-report authorization logic. + */ + public function authorize(Request $request): void; + + /** + * Execute the report and return a paginator for UI. + * + * @param array $filters + */ + public function paginate(array $filters, int $perPage = 25): LengthAwarePaginator; +} diff --git a/app/Reports/DecisionsCountReport.php b/app/Reports/DecisionsCountReport.php new file mode 100644 index 0000000..2fb5dc8 --- /dev/null +++ b/app/Reports/DecisionsCountReport.php @@ -0,0 +1,51 @@ + 'from', 'type' => 'date', 'label' => 'Od', 'nullable' => true], + ['key' => 'to', 'type' => 'date', 'label' => 'Do', 'nullable' => true], + ]; + } + + public function columns(): array + { + return [ + ['key' => 'decision_name', 'label' => 'Odločitev'], + ['key' => 'activities_count', 'label' => 'Št. aktivnosti'], + ]; + } + + public function query(array $filters): Builder + { + return Activity::query() + ->leftJoin('decisions', 'activities.decision_id', '=', 'decisions.id') + ->when(!empty($filters['from']), fn ($q) => $q->whereDate('activities.created_at', '>=', $filters['from'])) + ->when(!empty($filters['to']), fn ($q) => $q->whereDate('activities.created_at', '<=', $filters['to'])) + ->groupBy('decisions.name') + ->selectRaw("COALESCE(decisions.name, '—') as decision_name, COUNT(*) as activities_count"); + } +} diff --git a/app/Reports/FieldJobsCompletedReport.php b/app/Reports/FieldJobsCompletedReport.php new file mode 100644 index 0000000..395aa88 --- /dev/null +++ b/app/Reports/FieldJobsCompletedReport.php @@ -0,0 +1,60 @@ + 'from', 'type' => 'date', 'label' => 'Od', 'default' => now()->startOfMonth()->toDateString()], + ['key' => 'to', 'type' => 'date', 'label' => 'Do', 'default' => now()->toDateString()], + ['key' => 'user_id', 'type' => 'select:user', 'label' => 'Uporabnik', 'default' => null], + ]; + } + + public function columns(): array + { + return [ + ['key' => 'id', 'label' => '#'], + ['key' => 'contract_reference', 'label' => 'Pogodba'], + ['key' => 'assigned_user_name', 'label' => 'Terenski'], + ['key' => 'completed_at', 'label' => 'Zaključeno'], + ['key' => 'notes', 'label' => 'Opombe'], + ]; + } + + /** + * @param array $filters + */ + public function query(array $filters): EloquentBuilder + { + $from = isset($filters['from']) ? now()->parse($filters['from'])->startOfDay() : now()->startOfMonth(); + $to = isset($filters['to']) ? now()->parse($filters['to'])->endOfDay() : now()->endOfDay(); + + return FieldJob::query() + ->whereNull('cancelled_at') + ->whereBetween('completed_at', [$from, $to]) + ->when(! empty($filters['user_id']), fn ($q) => $q->where('assigned_user_id', $filters['user_id'])) + ->with(['assignedUser:id,name', 'contract:id,reference']) + ->select(['id', 'assigned_user_id', 'contract_id', 'completed_at', 'notes']); + } +} diff --git a/app/Reports/ReportRegistry.php b/app/Reports/ReportRegistry.php new file mode 100644 index 0000000..d63ca5f --- /dev/null +++ b/app/Reports/ReportRegistry.php @@ -0,0 +1,29 @@ + */ + protected array $reports = []; + + public function register(Report $report): void + { + $this->reports[$report->slug()] = $report; + } + + /** + * @return array + */ + public function all(): array + { + return $this->reports; + } + + public function findBySlug(string $slug): ?Report + { + return $this->reports[$slug] ?? null; + } +} diff --git a/app/Reports/SegmentActivityCountsReport.php b/app/Reports/SegmentActivityCountsReport.php new file mode 100644 index 0000000..f530af1 --- /dev/null +++ b/app/Reports/SegmentActivityCountsReport.php @@ -0,0 +1,54 @@ + 'from', 'type' => 'date', 'label' => 'Od', 'nullable' => true], + ['key' => 'to', 'type' => 'date', 'label' => 'Do', 'nullable' => true], + ]; + } + + public function columns(): array + { + return [ + ['key' => 'segment_name', 'label' => 'Segment'], + ['key' => 'activities_count', 'label' => 'Št. aktivnosti'], + ]; + } + + public function query(array $filters): Builder + { + $q = Activity::query() + ->join('actions', 'activities.action_id', '=', 'actions.id') + ->leftJoin('segments', 'actions.segment_id', '=', 'segments.id') + ->when(! empty($filters['from']), fn ($qq) => $qq->whereDate('activities.created_at', '>=', $filters['from'])) + ->when(! empty($filters['to']), fn ($qq) => $qq->whereDate('activities.created_at', '<=', $filters['to'])) + ->groupBy('segments.name') + ->selectRaw("COALESCE(segments.name, 'Brez segmenta') as segment_name, COUNT(*) as activities_count"); + + return $q; + } +} diff --git a/app/Services/Documents/DocumentStreamService.php b/app/Services/Documents/DocumentStreamService.php new file mode 100644 index 0000000..ab53465 --- /dev/null +++ b/app/Services/Documents/DocumentStreamService.php @@ -0,0 +1,221 @@ +disk ?: 'public'; + $relPath = $this->normalizePath($document->path ?? ''); + + // Handle DOC/DOCX previews for inline viewing + if ($inline) { + $previewResponse = $this->tryPreview($document); + if ($previewResponse) { + return $previewResponse; + } + } + + // Try to find the file using multiple path candidates + $found = $this->findFile($disk, $relPath); + + if (! $found) { + // Try public/ fallback + $found = $this->tryPublicFallback($relPath); + if (! $found) { + abort(404, 'Document file not found'); + } + } + + $headers = $this->buildHeaders($document, $inline); + + // Try streaming first + $stream = Storage::disk($disk)->readStream($found); + if ($stream !== false) { + return response()->stream(function () use ($stream) { + fpassthru($stream); + }, 200, $headers); + } + + // Fallbacks on readStream failure + return $this->fallbackStream($disk, $found, $document, $relPath, $headers); + } + + /** + * Normalize path for Windows and legacy prefixes. + */ + protected function normalizePath(string $path): string + { + $path = str_replace('\\', '/', $path); + $path = ltrim($path, '/'); + if (str_starts_with($path, 'public/')) { + $path = substr($path, 7); + } + + return $path; + } + + /** + * Build path candidates to try. + */ + protected function buildPathCandidates(string $relPath, ?string $documentPath): array + { + $candidates = [$relPath]; + $raw = $documentPath ? ltrim(str_replace('\\', '/', $documentPath), '/') : null; + + if ($raw && $raw !== $relPath) { + $candidates[] = $raw; + } + if (str_starts_with($relPath, 'storage/')) { + $candidates[] = substr($relPath, 8); + } + if ($raw && str_starts_with($raw, 'storage/')) { + $candidates[] = substr($raw, 8); + } + + return array_unique($candidates); + } + + /** + * Try to find file using path candidates. + */ + protected function findFile(string $disk, string $relPath, ?string $documentPath = null): ?string + { + $candidates = $this->buildPathCandidates($relPath, $documentPath); + + foreach ($candidates as $cand) { + if (Storage::disk($disk)->exists($cand)) { + return $cand; + } + } + + return null; + } + + /** + * Try public/ fallback path. + */ + protected function tryPublicFallback(string $relPath): ?string + { + $publicFull = public_path($relPath); + $real = @realpath($publicFull); + $publicRoot = @realpath(public_path()); + $realN = $real ? str_replace('\\\\', '/', $real) : null; + $rootN = $publicRoot ? str_replace('\\\\', '/', $publicRoot) : null; + + if ($realN && $rootN && str_starts_with($realN, $rootN) && is_file($real)) { + return $real; + } + + return null; + } + + /** + * Try to stream preview for DOC/DOCX files. + */ + protected function tryPreview(Document $document): StreamedResponse|Response|null + { + $ext = strtolower(pathinfo($document->original_name ?: $document->file_name, PATHINFO_EXTENSION)); + if (! in_array($ext, ['doc', 'docx'])) { + return null; + } + + $previewDisk = config('files.preview_disk', 'public'); + if ($document->preview_path && Storage::disk($previewDisk)->exists($document->preview_path)) { + $stream = Storage::disk($previewDisk)->readStream($document->preview_path); + if ($stream !== false) { + $previewNameBase = $document->name ?: pathinfo($document->original_name ?: $document->file_name, PATHINFO_FILENAME); + + return response()->stream(function () use ($stream) { + fpassthru($stream); + }, 200, [ + 'Content-Type' => $document->preview_mime ?: 'application/pdf', + 'Content-Disposition' => 'inline; filename="'.addslashes($previewNameBase.'.pdf').'"', + 'Cache-Control' => 'private, max-age=0, no-cache', + 'Pragma' => 'no-cache', + ]); + } + } + + // Queue preview generation if not available + \App\Jobs\GenerateDocumentPreview::dispatch($document->id); + + return response('Preview is being generated. Please try again shortly.', 202); + } + + /** + * Build response headers. + */ + protected function buildHeaders(Document $document, bool $inline): array + { + $nameBase = $document->name ?: pathinfo($document->original_name ?: $document->file_name, PATHINFO_FILENAME); + $ext = strtolower(pathinfo($document->original_name ?: $document->file_name, PATHINFO_EXTENSION)); + $name = $ext ? ($nameBase.'.'.$ext) : $nameBase; + + return [ + 'Content-Type' => $document->mime_type ?: 'application/octet-stream', + 'Content-Disposition' => ($inline ? 'inline' : 'attachment').'; filename="'.addslashes($name).'"', + 'Cache-Control' => 'private, max-age=0, no-cache', + 'Pragma' => 'no-cache', + ]; + } + + /** + * Fallback streaming methods when readStream fails. + */ + protected function fallbackStream(string $disk, string $found, Document $document, string $relPath, array $headers): StreamedResponse|Response + { + // Fallback 1: get() the bytes directly + try { + $bytes = Storage::disk($disk)->get($found); + if (! is_null($bytes) && $bytes !== false) { + return response($bytes, 200, $headers); + } + } catch (\Throwable $e) { + // Continue to next fallback + } + + // Fallback 2: open via absolute storage path + $abs = null; + try { + if (method_exists(Storage::disk($disk), 'path')) { + $abs = Storage::disk($disk)->path($found); + } + } catch (\Throwable $e) { + $abs = null; + } + + if ($abs && is_file($abs)) { + $fp = @fopen($abs, 'rb'); + if ($fp !== false) { + return response()->stream(function () use ($fp) { + fpassthru($fp); + }, 200, $headers); + } + } + + // Fallback 3: serve from public path if available + $publicFull = public_path($found); + $real = @realpath($publicFull); + if ($real && is_file($real)) { + $fp = @fopen($real, 'rb'); + if ($fp !== false) { + return response()->stream(function () use ($fp) { + fpassthru($fp); + }, 200, $headers); + } + } + + abort(404, 'Document file could not be streamed'); + } +} + diff --git a/app/Services/ReferenceDataCache.php b/app/Services/ReferenceDataCache.php new file mode 100644 index 0000000..cf916aa --- /dev/null +++ b/app/Services/ReferenceDataCache.php @@ -0,0 +1,68 @@ + AddressType::all()); + } + + public function getPhoneTypes() + { + return Cache::remember('reference_data:phone_types', self::TTL, fn () => PhoneType::all()); + } + + public function getAccountTypes() + { + return Cache::remember('reference_data:account_types', self::TTL, fn () => AccountType::all()); + } + + public function getContractTypes() + { + return Cache::remember('reference_data:contract_types', self::TTL, fn () => ContractType::whereNull('deleted_at')->get()); + } + + /** + * Clear all reference data cache. + */ + public function clearAll(): void + { + Cache::forget('reference_data:address_types'); + Cache::forget('reference_data:phone_types'); + Cache::forget('reference_data:account_types'); + Cache::forget('reference_data:contract_types'); + } + + /** + * Clear specific reference data cache. + */ + public function clear(string $type): void + { + Cache::forget("reference_data:{$type}"); + } + + /** + * Get all types as an array for convenience. + */ + public function getAllTypes(): array + { + return [ + 'address_types' => $this->getAddressTypes(), + 'phone_types' => $this->getPhoneTypes(), + 'account_types' => $this->getAccountTypes(), + 'contract_types' => $this->getContractTypes(), + ]; + } +} + + diff --git a/bootstrap/providers.php b/bootstrap/providers.php index 67d2d5f..460af4e 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -5,4 +5,5 @@ App\Providers\AuthServiceProvider::class, App\Providers\FortifyServiceProvider::class, App\Providers\JetstreamServiceProvider::class, + App\Providers\ReportServiceProvider::class, ]; diff --git a/components.json b/components.json new file mode 100644 index 0000000..51dcdc6 --- /dev/null +++ b/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://shadcn-vue.com/schema.json", + "style": "new-york", + "typescript": false, + "tailwind": { + "config": "tailwind.config.js", + "css": "resources/css/app.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "composables": "@/composables" + }, + "registries": {} +} diff --git a/composer.json b/composer.json index 0bee2c3..78878ab 100644 --- a/composer.json +++ b/composer.json @@ -5,9 +5,9 @@ "keywords": ["laravel", "framework"], "license": "MIT", "require": { - "tijsverkoyen/css-to-inline-styles": "^2.2", "php": "^8.2", "arielmejiadev/larapex-charts": "^2.1", + "barryvdh/laravel-dompdf": "^3.1", "diglactic/laravel-breadcrumbs": "^10.0", "http-interop/http-factory-guzzle": "^1.2", "inertiajs/inertia-laravel": "^2.0", @@ -16,9 +16,11 @@ "laravel/sanctum": "^4.0", "laravel/scout": "^10.11", "laravel/tinker": "^2.9", + "maatwebsite/excel": "^3.1", "meilisearch/meilisearch-php": "^1.11", "robertboes/inertia-breadcrumbs": "dev-laravel-12", - "tightenco/ziggy": "^2.0" + "tightenco/ziggy": "^2.0", + "tijsverkoyen/css-to-inline-styles": "^2.2" }, "require-dev": { "fakerphp/faker": "^1.23", diff --git a/composer.lock b/composer.lock index c640d1c..2238565 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "51fd57123c1b9f51c24f28e04a692ec4", + "content-hash": "d28e6760b713feea1c4ad6058f96287a", "packages": [ { "name": "arielmejiadev/larapex-charts", @@ -113,6 +113,83 @@ }, "time": "2024-10-01T13:55:55+00:00" }, + { + "name": "barryvdh/laravel-dompdf", + "version": "v3.1.1", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-dompdf.git", + "reference": "8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d", + "reference": "8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d", + "shasum": "" + }, + "require": { + "dompdf/dompdf": "^3.0", + "illuminate/support": "^9|^10|^11|^12", + "php": "^8.1" + }, + "require-dev": { + "larastan/larastan": "^2.7|^3.0", + "orchestra/testbench": "^7|^8|^9|^10", + "phpro/grumphp": "^2.5", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "PDF": "Barryvdh\\DomPDF\\Facade\\Pdf", + "Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf" + }, + "providers": [ + "Barryvdh\\DomPDF\\ServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Barryvdh\\DomPDF\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "A DOMPDF Wrapper for Laravel", + "keywords": [ + "dompdf", + "laravel", + "pdf" + ], + "support": { + "issues": "https://github.com/barryvdh/laravel-dompdf/issues", + "source": "https://github.com/barryvdh/laravel-dompdf/tree/v3.1.1" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2025-02-13T15:07:54+00:00" + }, { "name": "brick/math", "version": "0.12.3", @@ -242,6 +319,162 @@ ], "time": "2024-02-09T16:56:22+00:00" }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" + }, { "name": "dasprid/enum", "version": "1.0.6", @@ -605,6 +838,161 @@ ], "time": "2024-02-05T11:56:58+00:00" }, + { + "name": "dompdf/dompdf", + "version": "v3.1.4", + "source": { + "type": "git", + "url": "https://github.com/dompdf/dompdf.git", + "reference": "db712c90c5b9868df3600e64e68da62e78a34623" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/db712c90c5b9868df3600e64e68da62e78a34623", + "reference": "db712c90c5b9868df3600e64e68da62e78a34623", + "shasum": "" + }, + "require": { + "dompdf/php-font-lib": "^1.0.0", + "dompdf/php-svg-lib": "^1.0.0", + "ext-dom": "*", + "ext-mbstring": "*", + "masterminds/html5": "^2.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "ext-gd": "*", + "ext-json": "*", + "ext-zip": "*", + "mockery/mockery": "^1.3", + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0" + }, + "suggest": { + "ext-gd": "Needed to process images", + "ext-gmagick": "Improves image processing performance", + "ext-imagick": "Improves image processing performance", + "ext-zlib": "Needed for pdf stream compression" + }, + "type": "library", + "autoload": { + "psr-4": { + "Dompdf\\": "src/" + }, + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "The Dompdf Community", + "homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md" + } + ], + "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", + "homepage": "https://github.com/dompdf/dompdf", + "support": { + "issues": "https://github.com/dompdf/dompdf/issues", + "source": "https://github.com/dompdf/dompdf/tree/v3.1.4" + }, + "time": "2025-10-29T12:43:30+00:00" + }, + { + "name": "dompdf/php-font-lib", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-font-lib.git", + "reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d", + "reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3 || ^4 || ^5 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "FontLib\\": "src/FontLib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "The FontLib Community", + "homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse, export and make subsets of different types of font files.", + "homepage": "https://github.com/dompdf/php-font-lib", + "support": { + "issues": "https://github.com/dompdf/php-font-lib/issues", + "source": "https://github.com/dompdf/php-font-lib/tree/1.0.1" + }, + "time": "2024-12-02T14:37:59+00:00" + }, + { + "name": "dompdf/php-svg-lib", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-svg-lib.git", + "reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/eb045e518185298eb6ff8d80d0d0c6b17aecd9af", + "reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0", + "sabberworm/php-css-parser": "^8.4" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Svg\\": "src/Svg" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "The SvgLib Community", + "homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse and export to PDF SVG files.", + "homepage": "https://github.com/dompdf/php-svg-lib", + "support": { + "issues": "https://github.com/dompdf/php-svg-lib/issues", + "source": "https://github.com/dompdf/php-svg-lib/tree/1.0.0" + }, + "time": "2024-04-29T13:26:35+00:00" + }, { "name": "dragonmantank/cron-expression", "version": "v3.4.0", @@ -737,6 +1125,67 @@ ], "time": "2025-03-06T22:45:56+00:00" }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.19.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "b287d2a16aceffbf6e0295559b39662612b77fcf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/b287d2a16aceffbf6e0295559b39662612b77fcf", + "reference": "b287d2a16aceffbf6e0295559b39662612b77fcf", + "shasum": "" + }, + "require": { + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" + }, + "require-dev": { + "cerdic/css-tidy": "^1.7 || ^2.0", + "simpletest/simpletest": "dev-master" + }, + "suggest": { + "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", + "ext-bcmath": "Used for unit conversion and imagecrash protection", + "ext-iconv": "Converts text to and from non-UTF-8 encodings", + "ext-tidy": "Used for pretty-printing HTML" + }, + "type": "library", + "autoload": { + "files": [ + "library/HTMLPurifier.composer.php" + ], + "psr-0": { + "HTMLPurifier": "library/" + }, + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "support": { + "issues": "https://github.com/ezyang/htmlpurifier/issues", + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.19.0" + }, + "time": "2025-10-17T16:34:55+00:00" + }, { "name": "facade/ignition-contracts", "version": "1.0.2", @@ -2695,6 +3144,339 @@ ], "time": "2024-12-08T08:18:47+00:00" }, + { + "name": "maatwebsite/excel", + "version": "3.1.67", + "source": { + "type": "git", + "url": "https://github.com/SpartnerNL/Laravel-Excel.git", + "reference": "e508e34a502a3acc3329b464dad257378a7edb4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/e508e34a502a3acc3329b464dad257378a7edb4d", + "reference": "e508e34a502a3acc3329b464dad257378a7edb4d", + "shasum": "" + }, + "require": { + "composer/semver": "^3.3", + "ext-json": "*", + "illuminate/support": "5.8.*||^6.0||^7.0||^8.0||^9.0||^10.0||^11.0||^12.0", + "php": "^7.0||^8.0", + "phpoffice/phpspreadsheet": "^1.30.0", + "psr/simple-cache": "^1.0||^2.0||^3.0" + }, + "require-dev": { + "laravel/scout": "^7.0||^8.0||^9.0||^10.0", + "orchestra/testbench": "^6.0||^7.0||^8.0||^9.0||^10.0", + "predis/predis": "^1.1" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Excel": "Maatwebsite\\Excel\\Facades\\Excel" + }, + "providers": [ + "Maatwebsite\\Excel\\ExcelServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Maatwebsite\\Excel\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Patrick Brouwers", + "email": "patrick@spartner.nl" + } + ], + "description": "Supercharged Excel exports and imports in Laravel", + "keywords": [ + "PHPExcel", + "batch", + "csv", + "excel", + "export", + "import", + "laravel", + "php", + "phpspreadsheet" + ], + "support": { + "issues": "https://github.com/SpartnerNL/Laravel-Excel/issues", + "source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.67" + }, + "funding": [ + { + "url": "https://laravel-excel.com/commercial-support", + "type": "custom" + }, + { + "url": "https://github.com/patrickbrouwers", + "type": "github" + } + ], + "time": "2025-08-26T09:13:16+00:00" + }, + { + "name": "maennchen/zipstream-php", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/9712d8fa4cdf9240380b01eb4be55ad8dcf71416", + "reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-zlib": "*", + "php-64bit": "^8.3" + }, + "require-dev": { + "brianium/paratest": "^7.7", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.16", + "guzzlehttp/guzzle": "^7.5", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^12.0", + "vimeo/psalm": "^6.0" + }, + "suggest": { + "guzzlehttp/psr7": "^2.4", + "psr/http-message": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + } + ], + "time": "2025-07-17T11:15:13+00:00" + }, + { + "name": "markbaker/complex", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" + }, + "time": "2022-12-06T16:21:08+00:00" + }, + { + "name": "markbaker/matrix", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" + }, + "time": "2022-12-02T22:17:43+00:00" + }, + { + "name": "masterminds/html5", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "fcf91eb64359852f00d921887b219479b4f21251" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", + "reference": "fcf91eb64359852f00d921887b219479b4f21251", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" + }, + "time": "2025-07-25T09:04:22+00:00" + }, { "name": "meilisearch/meilisearch-php", "version": "v1.13.0", @@ -3475,6 +4257,112 @@ }, "time": "2024-10-02T11:20:13+00:00" }, + { + "name": "phpoffice/phpspreadsheet", + "version": "1.30.1", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "fa8257a579ec623473eabfe49731de5967306c4c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fa8257a579ec623473eabfe49731de5967306c4c", + "reference": "fa8257a579ec623473eabfe49731de5967306c4c", + "shasum": "" + }, + "require": { + "composer/pcre": "^1||^2||^3", + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "ezyang/htmlpurifier": "^4.15", + "maennchen/zipstream-php": "^2.1 || ^3.0", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": ">=7.4.0 <8.5.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-main", + "dompdf/dompdf": "^1.0 || ^2.0 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.2", + "mitoteam/jpgraph": "^10.3", + "mpdf/mpdf": "^8.1.1", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.7", + "tecnickcom/tcpdf": "^6.5" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "ext-intl": "PHP Internationalization Functions", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.1" + }, + "time": "2025-10-26T16:01:04+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.3", @@ -4442,6 +5330,72 @@ ], "time": "2025-02-28T15:16:05+00:00" }, + { + "name": "sabberworm/php-css-parser", + "version": "v8.9.0", + "source": { + "type": "git", + "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", + "reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/d8e916507b88e389e26d4ab03c904a082aa66bb9", + "reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": "^5.6.20 || ^7.0.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + }, + "require-dev": { + "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.41", + "rawr/cross-data-providers": "^2.0.0" + }, + "suggest": { + "ext-mbstring": "for parsing UTF-8 CSS" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Sabberworm\\CSS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Raphael Schweikert" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Jake Hotson", + "email": "jake.github@qzdesign.co.uk" + } + ], + "description": "Parser for CSS Files written in PHP", + "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", + "keywords": [ + "css", + "parser", + "stylesheet" + ], + "support": { + "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.9.0" + }, + "time": "2025-07-11T13:20:48+00:00" + }, { "name": "spatie/laravel-package-tools", "version": "1.92.0", @@ -10335,6 +11289,6 @@ "platform": { "php": "^8.2" }, - "platform-dev": {}, + "platform-dev": [], "plugin-api-version": "2.6.0" } diff --git a/config/reports.php b/config/reports.php new file mode 100644 index 0000000..f40d01a --- /dev/null +++ b/config/reports.php @@ -0,0 +1,10 @@ + [ + // e.g., 'mv_activities_daily', 'mv_segment_activity_counts' + ], + // Time for scheduled refresh (24h format HH:MM) + 'refresh_time' => '03:00', +]; diff --git a/database/migrations/2025_01_15_000000_add_performance_indexes.php b/database/migrations/2025_01_15_000000_add_performance_indexes.php new file mode 100644 index 0000000..389d00e --- /dev/null +++ b/database/migrations/2025_01_15_000000_add_performance_indexes.php @@ -0,0 +1,143 @@ +indexExists('contracts', 'contracts_client_case_id_active_deleted_at_index')) { + $table->index(['client_case_id', 'active', 'deleted_at'], 'contracts_client_case_id_active_deleted_at_index'); + } + if (! $this->indexExists('contracts', 'contracts_start_date_end_date_index')) { + $table->index(['start_date', 'end_date'], 'contracts_start_date_end_date_index'); + } + }); + + // Contract segment pivot table indexes + Schema::table('contract_segment', function (Blueprint $table) { + if (! $this->indexExists('contract_segment', 'contract_segment_contract_id_active_index')) { + $table->index(['contract_id', 'active'], 'contract_segment_contract_id_active_index'); + } + if (! $this->indexExists('contract_segment', 'contract_segment_segment_id_active_index')) { + $table->index(['segment_id', 'active'], 'contract_segment_segment_id_active_index'); + } + }); + + // Activities table indexes + Schema::table('activities', function (Blueprint $table) { + if (! $this->indexExists('activities', 'activities_client_case_id_created_at_index')) { + $table->index(['client_case_id', 'created_at'], 'activities_client_case_id_created_at_index'); + } + if (! $this->indexExists('activities', 'activities_contract_id_created_at_index')) { + $table->index(['contract_id', 'created_at'], 'activities_contract_id_created_at_index'); + } + }); + + // Client cases table indexes + Schema::table('client_cases', function (Blueprint $table) { + if (! $this->indexExists('client_cases', 'client_cases_client_id_active_index')) { + $table->index(['client_id', 'active'], 'client_cases_client_id_active_index'); + } + if (! $this->indexExists('client_cases', 'client_cases_person_id_active_index')) { + $table->index(['person_id', 'active'], 'client_cases_person_id_active_index'); + } + }); + + // Documents table indexes for polymorphic relations + Schema::table('documents', function (Blueprint $table) { + if (! $this->indexExists('documents', 'documents_documentable_type_documentable_id_index')) { + $table->index(['documentable_type', 'documentable_id'], 'documents_documentable_type_documentable_id_index'); + } + if (! $this->indexExists('documents', 'documents_created_at_index')) { + $table->index(['created_at'], 'documents_created_at_index'); + } + }); + + // Field jobs indexes + Schema::table('field_jobs', function (Blueprint $table) { + if (! $this->indexExists('field_jobs', 'field_jobs_assigned_user_id_index')) { + $table->index(['assigned_user_id'], 'field_jobs_assigned_user_id_index'); + } + if (! $this->indexExists('field_jobs', 'field_jobs_contract_id_index')) { + $table->index(['contract_id'], 'field_jobs_contract_id_index'); + } + if (! $this->indexExists('field_jobs', 'field_jobs_completed_at_index')) { + $table->index(['completed_at'], 'field_jobs_completed_at_index'); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('contracts', function (Blueprint $table) { + $table->dropIndex('contracts_client_case_id_active_deleted_at_index'); + $table->dropIndex('contracts_start_date_end_date_index'); + }); + + Schema::table('contract_segment', function (Blueprint $table) { + $table->dropIndex('contract_segment_contract_id_active_index'); + $table->dropIndex('contract_segment_segment_id_active_index'); + }); + + Schema::table('activities', function (Blueprint $table) { + $table->dropIndex('activities_client_case_id_created_at_index'); + $table->dropIndex('activities_contract_id_created_at_index'); + }); + + Schema::table('client_cases', function (Blueprint $table) { + $table->dropIndex('client_cases_client_id_active_index'); + $table->dropIndex('client_cases_person_id_active_index'); + }); + + Schema::table('documents', function (Blueprint $table) { + $table->dropIndex('documents_documentable_type_documentable_id_index'); + $table->dropIndex('documents_created_at_index'); + }); + + Schema::table('field_jobs', function (Blueprint $table) { + $table->dropIndex('field_jobs_assigned_user_id_index'); + $table->dropIndex('field_jobs_contract_id_index'); + $table->dropIndex('field_jobs_completed_at_index'); + }); + } + + /** + * Check if an index exists on a table. + */ + protected function indexExists(string $table, string $index): bool + { + $connection = Schema::getConnection(); + $driver = $connection->getDriverName(); + + if ($driver === 'pgsql') { + // PostgreSQL uses pg_indexes system catalog + $result = $connection->select( + "SELECT COUNT(*) as count FROM pg_indexes + WHERE schemaname = 'public' AND tablename = ? AND indexname = ?", + [$table, $index] + ); + } else { + // MySQL/MariaDB uses information_schema.statistics + $databaseName = $connection->getDatabaseName(); + $result = $connection->select( + "SELECT COUNT(*) as count FROM information_schema.statistics + WHERE table_schema = ? AND table_name = ? AND index_name = ?", + [$databaseName, $table, $index] + ); + } + + return $result[0]->count > 0; + } +}; + diff --git a/package-lock.json b/package-lock.json index b5b52a8..fee49e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,34 +13,44 @@ "@headlessui/vue": "^1.7.23", "@heroicons/vue": "^2.1.5", "@internationalized/date": "^3.9.0", + "@vee-validate/zod": "^4.15.1", "@vuepic/vue-datepicker": "^11.0.2", + "@vueuse/core": "^14.0.0", "apexcharts": "^4.0.0", - "flowbite": "^2.5.2", - "flowbite-vue": "^0.1.6", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "date-fns": "^4.1.0", "lodash": "^4.17.21", + "lucide-vue-next": "^0.552.0", "material-design-icons-iconfont": "^6.7.0", "preline": "^2.7.0", "quill": "^1.3.7", - "reka-ui": "^2.5.1", + "reka-ui": "^2.6.0", + "tailwind-merge": "^3.3.1", + "tailwindcss-animate": "^1.0.7", "tailwindcss-inner-border": "^0.2.0", "v-calendar": "^3.1.2", + "vee-validate": "^4.15.1", "vue-currency-input": "^3.2.1", "vue-multiselect": "^3.1.0", "vue-search-input": "^1.1.16", + "vue-sonner": "^2.0.9", "vue3-apexcharts": "^1.7.0", - "vuedraggable": "^4.1.0" + "vuedraggable": "^4.1.0", + "zod": "^3.25.76" }, "devDependencies": { "@inertiajs/vue3": "2.0", "@mdi/js": "^7.4.47", "@tailwindcss/forms": "^0.5.7", + "@tailwindcss/postcss": "^4.1.16", "@tailwindcss/typography": "^0.5.10", "@vitejs/plugin-vue": "^6.0.1", "autoprefixer": "^10.4.16", "axios": "^1.7.4", "laravel-vite-plugin": "^2.0.1", "postcss": "^8.4.32", - "tailwindcss": "^3.4.0", + "tailwindcss": "^4.1.16", "vite": "^7.1.7", "vue": "^3.3.13" } @@ -49,6 +59,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -563,15 +574,6 @@ "@floating-ui/utils": "^0.2.10" } }, - "node_modules/@floating-ui/dom": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.1.1.tgz", - "integrity": "sha512-TpIO93+DIujg3g7SykEAGZMDtbJRrmnYRCNYSjJlvIbGhBjRSNTLVbNeDQBrzy9qDgUbiWdc7KA0uZHZ2tJmiw==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.1.0" - } - }, "node_modules/@floating-ui/utils": { "version": "0.2.10", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", @@ -760,37 +762,33 @@ "@swc/helpers": "^0.5.0" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -806,6 +804,7 @@ "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -819,51 +818,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -881,52 +835,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", - "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.78.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", - "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.52.3", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz", @@ -1313,6 +1221,347 @@ "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" } }, + "node_modules/@tailwindcss/node": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.16.tgz", + "integrity": "sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.19", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.16" + } + }, + "node_modules/@tailwindcss/node/node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.16.tgz", + "integrity": "sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.16", + "@tailwindcss/oxide-darwin-arm64": "4.1.16", + "@tailwindcss/oxide-darwin-x64": "4.1.16", + "@tailwindcss/oxide-freebsd-x64": "4.1.16", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.16", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.16", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.16", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.16", + "@tailwindcss/oxide-linux-x64-musl": "4.1.16", + "@tailwindcss/oxide-wasm32-wasi": "4.1.16", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.16", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.16" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.16.tgz", + "integrity": "sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.16.tgz", + "integrity": "sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.16.tgz", + "integrity": "sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.16.tgz", + "integrity": "sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.16.tgz", + "integrity": "sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.16.tgz", + "integrity": "sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.16.tgz", + "integrity": "sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.16.tgz", + "integrity": "sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.16.tgz", + "integrity": "sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.16.tgz", + "integrity": "sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.0.7", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.5.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.5.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.0.7", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@tybys/wasm-util": "^0.10.1" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "inBundle": true, + "license": "0BSD", + "optional": true + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.16.tgz", + "integrity": "sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.16.tgz", + "integrity": "sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.16.tgz", + "integrity": "sha512-Qn3SFGPXYQMKR/UtqS+dqvPrzEeBZHrFA92maT4zijCVggdsXnDBMsPFJo1eArX3J+O+Gi+8pV4PkqjLCNBk3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.16", + "@tailwindcss/oxide": "4.1.16", + "postcss": "^8.4.41", + "tailwindcss": "4.1.16" + } + }, "node_modules/@tailwindcss/typography": { "version": "0.5.19", "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", @@ -1356,6 +1605,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, "license": "MIT" }, "node_modules/@types/lodash": { @@ -1370,17 +1620,24 @@ "integrity": "sha512-cNw5iH8JkMkb3QkCoe7DaZiawbDQEUX8t7iuQaRTyLOyQCR2h+ibBD4GJt7p5yhUHrlOeL7ZtbxNHeipqNsBzQ==", "license": "MIT" }, - "node_modules/@types/resolve": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", - "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", "license": "MIT" }, - "node_modules/@types/web-bluetooth": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", - "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", - "license": "MIT" + "node_modules/@vee-validate/zod": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@vee-validate/zod/-/zod-4.15.1.tgz", + "integrity": "sha512-329Z4TDBE5Vx0FdbA8S4eR9iGCFFUNGbxjpQ20ff5b5wGueScjocUIx9JHPa79LTG06RnlUR4XogQsjN4tecKA==", + "license": "MIT", + "dependencies": { + "type-fest": "^4.8.3", + "vee-validate": "4.15.1" + }, + "peerDependencies": { + "zod": "^3.24.0" + } }, "node_modules/@vitejs/plugin-vue": { "version": "6.0.1", @@ -1449,6 +1706,39 @@ "@vue/shared": "3.5.22" } }, + "node_modules/@vue/devtools-api": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz", + "integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.7" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz", + "integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.7", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz", + "integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, "node_modules/@vue/reactivity": { "version": "3.5.22", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.22.tgz", @@ -1515,102 +1805,49 @@ } }, "node_modules/@vueuse/core": { - "version": "12.5.0", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.5.0.tgz", - "integrity": "sha512-GVyH1iYqNANwcahAx8JBm6awaNgvR/SwZ1fjr10b8l1HIgDp82ngNbfzJUgOgWEoxjL+URAggnlilAEXwCOZtg==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.0.0.tgz", + "integrity": "sha512-d6tKRWkZE8IQElX2aHBxXOMD478fHIYV+Dzm2y9Ag122ICBpNKtGICiXKOhWU3L1kKdttDD9dCMS4bGP3jhCTQ==", "license": "MIT", "dependencies": { - "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "12.5.0", - "@vueuse/shared": "12.5.0", - "vue": "^3.5.13" + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "14.0.0", + "@vueuse/shared": "14.0.0" }, "funding": { "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/core/node_modules/@vueuse/shared": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.0.0.tgz", + "integrity": "sha512-mTCA0uczBgurRlwVaQHfG0Ja7UdGe4g9mwffiJmvLiTtp1G4AQyIjej6si/k8c8pUwTfVpNufck+23gXptPAkw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" } }, "node_modules/@vueuse/metadata": { - "version": "12.5.0", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.5.0.tgz", - "integrity": "sha512-Ui7Lo2a7AxrMAXRF+fAp9QsXuwTeeZ8fIB9wsLHqzq9MQk+2gMYE2IGJW48VMJ8ecvCB3z3GsGLKLbSasQ5Qlg==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.0.0.tgz", + "integrity": "sha512-6yoGqbJcMldVCevkFiHDBTB1V5Hq+G/haPlGIuaFZHpXC0HADB0EN1ryQAAceiW+ryS3niUwvdFbGiqHqBrfVA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" } }, - "node_modules/@vueuse/shared": { - "version": "12.5.0", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.5.0.tgz", - "integrity": "sha512-vMpcL1lStUU6O+kdj6YdHDixh0odjPAUM15uJ9f7MY781jcYkIwFA4iv2EfoIPO6vBmvutI1HxxAwmf0cx5ISQ==", - "license": "MIT", - "dependencies": { - "vue": "^3.5.13" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/@yr/monotone-cubic-spline": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==", "license": "MIT" }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "license": "MIT" - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/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/apexcharts": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-4.7.0.tgz", @@ -1625,12 +1862,6 @@ "@yr/monotone-cubic-spline": "^1.0.3" } }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "license": "MIT" - }, "node_modules/aria-hidden": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", @@ -1700,12 +1931,6 @@ "proxy-from-env": "^1.1.0" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, "node_modules/baseline-browser-mapping": { "version": "2.8.10", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.10.tgz", @@ -1716,37 +1941,13 @@ "baseline-browser-mapping": "dist/cli.js" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "node_modules/birpc": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.6.1.tgz", + "integrity": "sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ==", "license": "MIT", - "engines": { - "node": ">=8" - }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" + "url": "https://github.com/sponsors/antfu" } }, "node_modules/browserslist": { @@ -1830,15 +2031,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001746", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001746.tgz", @@ -1860,48 +2052,18 @@ ], "license": "CC-BY-4.0" }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "license": "MIT", + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" + "clsx": "^2.1.1" }, "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "url": "https://polar.sh/cva" } }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/classnames": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", - "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", - "license": "MIT" - }, "node_modules/clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -1911,24 +2073,15 @@ "node": ">=0.8" } }, - "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==", + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, "engines": { - "node": ">=7.0.0" + "node": ">=6" } }, - "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", @@ -1942,33 +2095,26 @@ "node": ">= 0.8" } }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "is-what": "^5.2.0" }, "engines": { - "node": ">= 8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" } }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -2013,15 +2159,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -2072,17 +2209,15 @@ "node": ">=0.4.0" } }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "license": "Apache-2.0" - }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "license": "MIT" + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } }, "node_modules/dunder-proto": { "version": "1.0.1", @@ -2098,12 +2233,6 @@ "node": ">= 0.4" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" - }, "node_modules/electron-to-chromium": { "version": "1.5.228", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.228.tgz", @@ -2111,11 +2240,19 @@ "dev": true, "license": "ISC" }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } }, "node_modules/entities": { "version": "4.5.0", @@ -2256,43 +2393,6 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -2311,82 +2411,6 @@ } } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/floating-vue": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/floating-vue/-/floating-vue-5.2.2.tgz", - "integrity": "sha512-afW+h2CFafo+7Y9Lvw/xsqjaQlKLdJV7h1fCHfcYQ1C4SVMlu7OAekqWgu5d4SgvkBVU0pVpLlVsrSTBURFRkg==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "~1.1.1", - "vue-resize": "^2.0.0-alpha.1" - }, - "peerDependencies": { - "@nuxt/kit": "^3.2.0", - "vue": "^3.2.0" - }, - "peerDependenciesMeta": { - "@nuxt/kit": { - "optional": true - } - } - }, - "node_modules/flowbite": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/flowbite/-/flowbite-2.5.2.tgz", - "integrity": "sha512-kwFD3n8/YW4EG8GlY3Od9IoKND97kitO+/ejISHSqpn3vw2i5K/+ZI8Jm2V+KC4fGdnfi0XZ+TzYqQb4Q1LshA==", - "license": "MIT", - "dependencies": { - "@popperjs/core": "^2.9.3", - "flowbite-datepicker": "^1.3.0", - "mini-svg-data-uri": "^1.4.3" - } - }, - "node_modules/flowbite-datepicker": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/flowbite-datepicker/-/flowbite-datepicker-1.3.2.tgz", - "integrity": "sha512-6Nfm0MCVX3mpaR7YSCjmEO2GO8CDt6CX8ZpQnGdeu03WUCWtEPQ/uy0PUiNtIJjJZWnX0Cm3H55MOhbD1g+E/g==", - "license": "MIT", - "dependencies": { - "@rollup/plugin-node-resolve": "^15.2.3", - "flowbite": "^2.0.0" - } - }, - "node_modules/flowbite-vue": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/flowbite-vue/-/flowbite-vue-0.1.9.tgz", - "integrity": "sha512-0fLA2k8a+U/cFoSoAR7Cs1yvSpNSudB709/hiCVImdbYuhj2lIupbdL3rblULQt+lwYnLwuVb5R/UE9GMGxY4w==", - "license": "MIT", - "dependencies": { - "@vueuse/core": "12.5.0", - "classnames": "2.5.1", - "floating-vue": "^5.2.2", - "flowbite": "2.5.2", - "lodash-es": "^4.17.21", - "nanoid": "5.0.9", - "tailwind-merge": "2.6.0", - "tailwindcss": "^3" - }, - "engines": { - "node": ">=18.x", - "npm": ">=10.x" - }, - "peerDependencies": { - "tailwindcss": "^3", - "vue": "^3.4.x" - } - }, "node_modules/follow-redirects": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", @@ -2408,22 +2432,6 @@ } } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/form-data": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", @@ -2459,6 +2467,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -2524,38 +2533,6 @@ "node": ">= 0.4" } }, - "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -2568,6 +2545,13 @@ "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==", + "dev": true, + "license": "ISC" + }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -2619,6 +2603,12 @@ "node": ">= 0.4" } }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, "node_modules/is-arguments": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", @@ -2635,33 +2625,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-date-object": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", @@ -2678,51 +2641,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "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/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "license": "MIT" - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -2741,32 +2659,26 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "license": "MIT", + "engines": { + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "url": "https://github.com/sponsors/mesqueeb" } }, "node_modules/jiti": { "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -2791,23 +2703,266 @@ "vite": "^7.0.0" } }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "license": "MIT", + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, "engines": { - "node": ">=14" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/antonk52" + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "license": "MIT" + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "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.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "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.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "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.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "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.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "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.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "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.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "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.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "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.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "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.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "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", @@ -2815,17 +2970,14 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" + "node_modules/lucide-vue-next": { + "version": "0.552.0", + "resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-0.552.0.tgz", + "integrity": "sha512-xbP3UBwNkGoCl1ezW/zsGHtyJ1rOowPmSpUG1f7V38YvZBlGiV3BxL+4mu9C9i0EjfJ6ca066FpnB3VK+HN92g==", + "license": "ISC", + "peerDependencies": { + "vue": ">=3.0.1" + } }, "node_modules/magic-string": { "version": "0.30.19", @@ -2851,40 +3003,6 @@ "node": ">= 0.4" } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/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/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -2912,63 +3030,17 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "dev": true, "license": "MIT", "bin": { "mini-svg-data-uri": "cli.js" } }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/nanoid": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.9.tgz", - "integrity": "sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^18 || >=20" - } + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" }, "node_modules/node-releases": { "version": "2.0.21", @@ -2977,15 +3049,6 @@ "dev": true, "license": "MIT" }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/normalize-range": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", @@ -2996,24 +3059,6 @@ "node": ">=0.10.0" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -3058,49 +3103,18 @@ "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", "license": "MIT" }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.0" - }, "node_modules/parchment": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz", "integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==", "license": "BSD-3-Clause" }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", "license": "MIT" }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -3111,6 +3125,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -3119,24 +3134,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -3165,128 +3162,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-js": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", - "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "peerDependencies": { - "postcss": "^8.4.21" - } - }, - "node_modules/postcss-load-config": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", - "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "lilconfig": "^3.1.1" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "jiti": ">=1.21.0", - "postcss": ">=8.0.9", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - }, - "postcss": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/postcss-nested": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", - "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.1.1" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-nested/node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-selector-parser": { "version": "6.0.10", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", @@ -3305,6 +3180,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, "license": "MIT" }, "node_modules/postcss/node_modules/nanoid": { @@ -3357,26 +3233,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/quill": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/quill/-/quill-1.3.7.tgz", @@ -3411,39 +3267,6 @@ "node": ">=0.10" } }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "license": "MIT", - "dependencies": { - "pify": "^2.3.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/readdirp/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/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -3465,9 +3288,9 @@ } }, "node_modules/reka-ui": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.5.1.tgz", - "integrity": "sha512-QJGB3q21wQ1Kw28HhhNDpjfFe8qpePX1gK4FTBRd68XTh9aEnhR5bTJnlV0jxi8FBPh0xivZBeNFUc3jiGx7mQ==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.6.0.tgz", + "integrity": "sha512-NrGMKrABD97l890mFS3TNUzB0BLUfbL3hh0NjcJRIUSUljb288bx3Mzo31nOyUcdiiW0HqFGXJwyCBh9cWgb0w==", "license": "MIT", "dependencies": { "@floating-ui/dom": "^1.6.13", @@ -3495,41 +3318,53 @@ "@floating-ui/utils": "^0.2.10" } }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "node_modules/reka-ui/node_modules/@vueuse/core": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz", + "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "node_modules/reka-ui/node_modules/@vueuse/metadata": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz", + "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/sponsors/antfu" } }, + "node_modules/reka-ui/node_modules/@vueuse/shared": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz", + "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", + "license": "MIT", + "dependencies": { + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, "node_modules/rollup": { "version": "4.52.3", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.3.tgz", "integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -3567,29 +3402,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -3622,27 +3434,6 @@ "node": ">= 0.4" } }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -3719,18 +3510,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/sortablejs": { "version": "1.14.0", "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz", @@ -3746,140 +3525,31 @@ "node": ">=0.10.0" } }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/superjson": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.5.tgz", + "integrity": "sha512-zWPTX96LVsA/eVYnqOM2+ofcdPqdS1dAF1LN4TS2/MWuUpfitd9ctTa87wt4xrYnZnkLtS69xpBdSxVBP5Rm6w==", "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "copy-anything": "^4" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "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/string-width-cjs/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/string-width-cjs/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/string-width-cjs/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/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "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/strip-ansi-cjs/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/sucrase": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", - "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "glob": "^10.3.10", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=16" } }, "node_modules/tailwind-merge": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", - "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", + "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==", "license": "MIT", "funding": { "type": "github", @@ -3887,40 +3557,18 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.18", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz", - "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz", + "integrity": "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==", + "license": "MIT" + }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", "license": "MIT", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.6.0", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.21.7", - "lilconfig": "^3.1.3", - "micromatch": "^4.0.8", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.47", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", - "postcss-nested": "^6.2.0", - "postcss-selector-parser": "^6.1.2", - "resolve": "^1.22.8", - "sucrase": "^3.35.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" } }, "node_modules/tailwindcss-inner-border": { @@ -3936,38 +3584,18 @@ "tailwindcss": ">=3" } }, - "node_modules/tailwindcss/node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, "engines": { - "node": ">=4" - } - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" + "node": ">=6" }, - "engines": { - "node": ">=0.8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/tinyglobby": { @@ -3987,30 +3615,24 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "license": "Apache-2.0" - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -4046,6 +3668,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, "license": "MIT" }, "node_modules/v-calendar": { @@ -4091,6 +3714,19 @@ "date-fns": "2.x" } }, + "node_modules/vee-validate": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.15.1.tgz", + "integrity": "sha512-DkFsiTwEKau8VIxyZBGdO6tOudD+QoUBPuHj3e6QFqmbfCRj1ArmYWue9lEp6jLSWBIw4XPlDLjFIZNLdRAMSg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.5.2", + "type-fest": "^4.8.3" + }, + "peerDependencies": { + "vue": "^3.4.26" + } + }, "node_modules/vite": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.7.tgz", @@ -4230,15 +3866,6 @@ "npm": ">= 6.14.15" } }, - "node_modules/vue-resize": { - "version": "2.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-2.0.0-alpha.1.tgz", - "integrity": "sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg==", - "license": "MIT", - "peerDependencies": { - "vue": "^3.0.0" - } - }, "node_modules/vue-screen-utils": { "version": "1.0.0-beta.13", "resolved": "https://registry.npmjs.org/vue-screen-utils/-/vue-screen-utils-1.0.0-beta.13.tgz", @@ -4254,6 +3881,28 @@ "integrity": "sha512-cQ4XVNLOKywZr7XAYqvhxhTuY/Fka3USfVOw1FAtD6KHzVzZ6+/wGQw8ukm5wWsbJVh2FNOUsmnn+PA174g+eA==", "license": "MIT" }, + "node_modules/vue-sonner": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/vue-sonner/-/vue-sonner-2.0.9.tgz", + "integrity": "sha512-i6BokNlNDL93fpzNxN/LZSn6D6MzlO+i3qXt6iVZne3x1k7R46d5HlFB4P8tYydhgqOrRbIZEsnRd3kG7qGXyw==", + "license": "MIT", + "peerDependencies": { + "@nuxt/kit": "^4.0.3", + "@nuxt/schema": "^4.0.3", + "nuxt": "^4.0.3" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + }, + "@nuxt/schema": { + "optional": true + }, + "nuxt": { + "optional": true + } + } + }, "node_modules/vue3-apexcharts": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/vue3-apexcharts/-/vue3-apexcharts-1.8.0.tgz", @@ -4276,110 +3925,13 @@ "vue": "^3.0.1" } }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "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/wrap-ansi-cjs/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/wrap-ansi-cjs/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/wrap-ansi-cjs/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/wrap-ansi-cjs/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/wrap-ansi-cjs/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" + "url": "https://github.com/sponsors/colinhacks" } } } diff --git a/package.json b/package.json index dc8fe0c..23b2619 100644 --- a/package.json +++ b/package.json @@ -9,13 +9,14 @@ "@inertiajs/vue3": "2.0", "@mdi/js": "^7.4.47", "@tailwindcss/forms": "^0.5.7", + "@tailwindcss/postcss": "^4.1.16", "@tailwindcss/typography": "^0.5.10", - "@vitejs/plugin-vue": "^6.0.1", + "@vitejs/plugin-vue": "^6.0.1", "autoprefixer": "^10.4.16", "axios": "^1.7.4", - "laravel-vite-plugin": "^2.0.1", + "laravel-vite-plugin": "^2.0.1", "postcss": "^8.4.32", - "tailwindcss": "^3.4.0", + "tailwindcss": "^4.1.16", "vite": "^7.1.7", "vue": "^3.3.13" }, @@ -25,24 +26,33 @@ "@fortawesome/free-regular-svg-icons": "^6.6.0", "@fortawesome/free-solid-svg-icons": "^6.6.0", "@fortawesome/vue-fontawesome": "^3.0.8", - "quill": "^1.3.7", "@headlessui/vue": "^1.7.23", "@heroicons/vue": "^2.1.5", "@internationalized/date": "^3.9.0", + "@vee-validate/zod": "^4.15.1", "@vuepic/vue-datepicker": "^11.0.2", + "@vueuse/core": "^14.0.0", "apexcharts": "^4.0.0", - "flowbite": "^2.5.2", - "flowbite-vue": "^0.1.6", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "date-fns": "^4.1.0", "lodash": "^4.17.21", + "lucide-vue-next": "^0.552.0", "material-design-icons-iconfont": "^6.7.0", "preline": "^2.7.0", - "reka-ui": "^2.5.1", + "quill": "^1.3.7", + "reka-ui": "^2.6.0", + "tailwind-merge": "^3.3.1", + "tailwindcss-animate": "^1.0.7", "tailwindcss-inner-border": "^0.2.0", "v-calendar": "^3.1.2", + "vee-validate": "^4.15.1", + "vue-currency-input": "^3.2.1", "vue-multiselect": "^3.1.0", "vue-search-input": "^1.1.16", + "vue-sonner": "^2.0.9", "vue3-apexcharts": "^1.7.0", "vuedraggable": "^4.1.0", - "vue-currency-input": "^3.2.1" + "zod": "^3.25.76" } } diff --git a/postcss.config.js b/postcss.config.js index 49c0612..f02fc56 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,6 +1,9 @@ +import tailwindcss from '@tailwindcss/postcss'; +import autoprefixer from 'autoprefixer'; + export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, + plugins: [ + tailwindcss(), + autoprefixer(), + ], }; diff --git a/resources/css/app.css b/resources/css/app.css index 013c66e..d7c4ede 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -1,10 +1,142 @@ -@import '/node_modules/floating-vue/dist/style.css'; -@import '/node_modules/vue-search-input/dist/styles.css'; -@import '/node_modules/vue-multiselect/dist/vue-multiselect.min.css'; -@tailwind base; -@tailwind components; -@tailwind utilities; +@import "tailwindcss"; +@plugin "tailwindcss-animate"; + +@custom-variant dark (&:is(.dark *)); + +@theme { + /* Disable dark mode */ + --default-transition-duration: 150ms; + --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + + /* Font Family */ + --font-family-sans: 'Figtree', ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + + /* Primary brand colors */ + --color-primary-50: #eef2ff; + --color-primary-100: #e0e7ff; + --color-primary-200: #c7d2fe; + --color-primary-300: #a5b4fc; + --color-primary-400: #818cf8; + --color-primary-500: #6366f1; + --color-primary-600: #4f46e5; + --color-primary-700: #4338ca; + --color-primary-800: #3730a3; + --color-primary-900: #312e81; + --color-primary-950: #1e1b4b; + + /* Semantic colors - Success */ + --color-success-50: #f0fdf4; + --color-success-100: #dcfce7; + --color-success-200: #bbf7d0; + --color-success-300: #86efac; + --color-success-400: #4ade80; + --color-success-500: #22c55e; + --color-success-600: #16a34a; + --color-success-700: #15803d; + --color-success-800: #166534; + --color-success-900: #14532d; + + /* Semantic colors - Warning */ + --color-warning-50: #fffbeb; + --color-warning-100: #fef3c7; + --color-warning-200: #fde68a; + --color-warning-300: #fcd34d; + --color-warning-400: #fbbf24; + --color-warning-500: #f59e0b; + --color-warning-600: #d97706; + --color-warning-700: #b45309; + --color-warning-800: #92400e; + --color-warning-900: #78350f; + + /* Semantic colors - Error */ + --color-error-50: #fef2f2; + --color-error-100: #fee2e2; + --color-error-200: #fecaca; + --color-error-300: #fca5a5; + --color-error-400: #f87171; + --color-error-500: #ef4444; + --color-error-600: #dc2626; + --color-error-700: #b91c1c; + --color-error-800: #991b1b; + --color-error-900: #7f1d1d; + + /* Semantic colors - Info */ + --color-info-50: #eff6ff; + --color-info-100: #dbeafe; + --color-info-200: #bfdbfe; + --color-info-300: #93c5fd; + --color-info-400: #60a5fa; + --color-info-500: #3b82f6; + --color-info-600: #2563eb; + --color-info-700: #1d4ed8; + --color-info-800: #1e40af; + --color-info-900: #1e3a8a; + + /* Neutral grays */ + --color-neutral-50: #f9fafb; + --color-neutral-100: #f3f4f6; + --color-neutral-200: #e5e7eb; + --color-neutral-300: #d1d5db; + --color-neutral-400: #9ca3af; + --color-neutral-500: #6b7280; + --color-neutral-600: #4b5563; + --color-neutral-700: #374151; + --color-neutral-800: #1f2937; + --color-neutral-900: #111827; + + /* Spacing scale */ + --spacing-18: 4.5rem; + --spacing-88: 22rem; + --spacing-112: 28rem; + --spacing-128: 32rem; + + /* Border radius */ + --radius-4xl: 2rem; + + /* Box shadows */ + --shadow-soft: 0 2px 15px -3px rgba(0, 0, 0, 0.07), 0 10px 20px -2px rgba(0, 0, 0, 0.04); + --shadow-medium: 0 4px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + --shadow-strong: 0 10px 40px -10px rgba(0, 0, 0, 0.2); + + /* Animations */ + --animate-fade-in: fade-in 0.2s ease-in-out; + --animate-slide-up: slide-up 0.3s ease-out; + --animate-slide-down: slide-down 0.3s ease-out; + --animate-shimmer: shimmer 2s infinite linear; + + @keyframes fade-in { + from { opacity: 0; } + to { opacity: 1; } + } + + @keyframes slide-up { + from { + transform: translateY(10px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } + } + + @keyframes slide-down { + from { + transform: translateY(-10px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } + } + + @keyframes shimmer { + from { background-position: -1000px 0; } + to { background-position: 1000px 0; } + } +} [x-cloak] { display: none; @@ -12,3 +144,124 @@ [x-cloak] { /* Ensure dropdowns/menus render above dialog overlays when appended to body */ .multiselect__content-wrapper { z-index: 2147483647 !important; } + +/* stylelint-disable-next-line at-rule-no-unknown */ +/* @theme is a valid Tailwind CSS v4 at-rule */ +@theme inline { + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.129 0.042 264.695); + --card: oklch(1 0 0); + --card-foreground: oklch(0.129 0.042 264.695); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.129 0.042 264.695); + --primary: oklch(0.208 0.042 265.755); + --primary-foreground: oklch(0.984 0.003 247.858); + --secondary: oklch(0.968 0.007 247.896); + --secondary-foreground: oklch(0.208 0.042 265.755); + --muted: oklch(0.968 0.007 247.896); + --muted-foreground: oklch(0.554 0.046 257.417); + --accent: oklch(0.968 0.007 247.896); + --accent-foreground: oklch(0.208 0.042 265.755); + --destructive: oklch(0.577 0.245 27.325); + --destructive-foreground: oklch(0.577 0.245 27.325); + --border: oklch(0.929 0.013 255.508); + --input: oklch(0.929 0.013 255.508); + --ring: oklch(0.704 0.04 256.788); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.984 0.003 247.858); + --sidebar-foreground: oklch(0.129 0.042 264.695); + --sidebar-primary: oklch(0.208 0.042 265.755); + --sidebar-primary-foreground: oklch(0.984 0.003 247.858); + --sidebar-accent: oklch(0.968 0.007 247.896); + --sidebar-accent-foreground: oklch(0.208 0.042 265.755); + --sidebar-border: oklch(0.929 0.013 255.508); + --sidebar-ring: oklch(0.704 0.04 256.788); +} + +.dark { + --background: oklch(0.129 0.042 264.695); + --foreground: oklch(0.984 0.003 247.858); + --card: oklch(0.129 0.042 264.695); + --card-foreground: oklch(0.984 0.003 247.858); + --popover: oklch(0.129 0.042 264.695); + --popover-foreground: oklch(0.984 0.003 247.858); + --primary: oklch(0.984 0.003 247.858); + --primary-foreground: oklch(0.208 0.042 265.755); + --secondary: oklch(0.279 0.041 260.031); + --secondary-foreground: oklch(0.984 0.003 247.858); + --muted: oklch(0.279 0.041 260.031); + --muted-foreground: oklch(0.704 0.04 256.788); + --accent: oklch(0.279 0.041 260.031); + --accent-foreground: oklch(0.984 0.003 247.858); + --destructive: oklch(0.396 0.141 25.723); + --destructive-foreground: oklch(0.637 0.237 25.331); + --border: oklch(0.279 0.041 260.031); + --input: oklch(0.279 0.041 260.031); + --ring: oklch(0.446 0.043 257.281); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.208 0.042 265.755); + --sidebar-foreground: oklch(0.984 0.003 247.858); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.984 0.003 247.858); + --sidebar-accent: oklch(0.279 0.041 260.031); + --sidebar-accent-foreground: oklch(0.984 0.003 247.858); + --sidebar-border: oklch(0.279 0.041 260.031); + --sidebar-ring: oklch(0.446 0.043 257.281); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/resources/js/Components/AddressCreateForm.vue b/resources/js/Components/AddressCreateForm.vue deleted file mode 100644 index bb39718..0000000 --- a/resources/js/Components/AddressCreateForm.vue +++ /dev/null @@ -1,228 +0,0 @@ - - - diff --git a/resources/js/Components/AddressUpdateForm.vue b/resources/js/Components/AddressUpdateForm.vue deleted file mode 100644 index b26481e..0000000 --- a/resources/js/Components/AddressUpdateForm.vue +++ /dev/null @@ -1,197 +0,0 @@ - - - - - diff --git a/resources/js/Components/ApplicationMark.vue b/resources/js/Components/ApplicationMark.vue index 3933bd0..e04fdd0 100644 --- a/resources/js/Components/ApplicationMark.vue +++ b/resources/js/Components/ApplicationMark.vue @@ -1,3 +1,3 @@ diff --git a/resources/js/Components/BasicTable.vue b/resources/js/Components/BasicTable.vue index facced9..e1399bb 100644 --- a/resources/js/Components/BasicTable.vue +++ b/resources/js/Components/BasicTable.vue @@ -1,5 +1,5 @@ diff --git a/resources/js/Components/CurrencyInput.vue b/resources/js/Components/CurrencyInput.vue index 2362c82..e0caaa3 100644 --- a/resources/js/Components/CurrencyInput.vue +++ b/resources/js/Components/CurrencyInput.vue @@ -1,6 +1,8 @@ \ No newline at end of file + diff --git a/resources/js/Components/DataTable/ActionMenuItem.vue b/resources/js/Components/DataTable/ActionMenuItem.vue new file mode 100644 index 0000000..656cfb7 --- /dev/null +++ b/resources/js/Components/DataTable/ActionMenuItem.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/resources/js/Components/DataTable/ColumnFilter.vue b/resources/js/Components/DataTable/ColumnFilter.vue new file mode 100644 index 0000000..f5c2308 --- /dev/null +++ b/resources/js/Components/DataTable/ColumnFilter.vue @@ -0,0 +1,134 @@ + + + + + + diff --git a/resources/js/Components/DataTable/DataTable.vue b/resources/js/Components/DataTable/DataTable.vue new file mode 100644 index 0000000..d18d089 --- /dev/null +++ b/resources/js/Components/DataTable/DataTable.vue @@ -0,0 +1,884 @@ + + + + diff --git a/resources/js/Components/DataTable/DataTableClient.vue b/resources/js/Components/DataTable/DataTableClient.vue index 455950b..37bdfdd 100644 --- a/resources/js/Components/DataTable/DataTableClient.vue +++ b/resources/js/Components/DataTable/DataTableClient.vue @@ -1,13 +1,15 @@ + + + + diff --git a/resources/js/Components/DataTable/StatusBadge.vue b/resources/js/Components/DataTable/StatusBadge.vue new file mode 100644 index 0000000..7893196 --- /dev/null +++ b/resources/js/Components/DataTable/StatusBadge.vue @@ -0,0 +1,170 @@ + + + + + diff --git a/resources/js/Components/DataTable/TableActions.vue b/resources/js/Components/DataTable/TableActions.vue new file mode 100644 index 0000000..5a8dc2f --- /dev/null +++ b/resources/js/Components/DataTable/TableActions.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/resources/js/Components/DatePicker.vue b/resources/js/Components/DatePicker.vue new file mode 100644 index 0000000..dc21b03 --- /dev/null +++ b/resources/js/Components/DatePicker.vue @@ -0,0 +1,141 @@ + + + + diff --git a/resources/js/Components/DateRangePicker.vue b/resources/js/Components/DateRangePicker.vue new file mode 100644 index 0000000..aed8635 --- /dev/null +++ b/resources/js/Components/DateRangePicker.vue @@ -0,0 +1,231 @@ + + + diff --git a/resources/js/Components/DialogModal.vue b/resources/js/Components/DialogModal.vue index 4f3a8ed..455114f 100644 --- a/resources/js/Components/DialogModal.vue +++ b/resources/js/Components/DialogModal.vue @@ -1,47 +1,76 @@ diff --git a/resources/js/Components/Dialogs/ConfirmationDialog.vue b/resources/js/Components/Dialogs/ConfirmationDialog.vue new file mode 100644 index 0000000..7324f8b --- /dev/null +++ b/resources/js/Components/Dialogs/ConfirmationDialog.vue @@ -0,0 +1,111 @@ + + + + diff --git a/resources/js/Components/Dialogs/CreateDialog.vue b/resources/js/Components/Dialogs/CreateDialog.vue new file mode 100644 index 0000000..e176fe6 --- /dev/null +++ b/resources/js/Components/Dialogs/CreateDialog.vue @@ -0,0 +1,100 @@ + + + + diff --git a/resources/js/Components/Dialogs/DeleteDialog.vue b/resources/js/Components/Dialogs/DeleteDialog.vue new file mode 100644 index 0000000..1ba869b --- /dev/null +++ b/resources/js/Components/Dialogs/DeleteDialog.vue @@ -0,0 +1,96 @@ + + + + diff --git a/resources/js/Components/Dialogs/UpdateDialog.vue b/resources/js/Components/Dialogs/UpdateDialog.vue new file mode 100644 index 0000000..cd5d577 --- /dev/null +++ b/resources/js/Components/Dialogs/UpdateDialog.vue @@ -0,0 +1,100 @@ + + + + diff --git a/resources/js/Components/Dialogs/WarningDialog.vue b/resources/js/Components/Dialogs/WarningDialog.vue new file mode 100644 index 0000000..8efd074 --- /dev/null +++ b/resources/js/Components/Dialogs/WarningDialog.vue @@ -0,0 +1,104 @@ + + + + diff --git a/resources/js/Components/DocumentEditDialog.vue b/resources/js/Components/DocumentEditDialog.vue index c1767f0..3facc50 100644 --- a/resources/js/Components/DocumentEditDialog.vue +++ b/resources/js/Components/DocumentEditDialog.vue @@ -1,5 +1,5 @@ \ No newline at end of file diff --git a/resources/js/Components/DocumentUploadDialog.vue b/resources/js/Components/DocumentUploadDialog.vue index 9d78599..223a879 100644 --- a/resources/js/Components/DocumentUploadDialog.vue +++ b/resources/js/Components/DocumentUploadDialog.vue @@ -1,5 +1,5 @@ \ No newline at end of file diff --git a/resources/js/Components/DocumentsTable.vue b/resources/js/Components/DocumentsTable.vue index 33e1740..3832267 100644 --- a/resources/js/Components/DocumentsTable.vue +++ b/resources/js/Components/DocumentsTable.vue @@ -1,13 +1,4 @@ diff --git a/resources/js/Components/PersonUpdateForm.vue b/resources/js/Components/PersonUpdateForm.vue deleted file mode 100644 index 1705db0..0000000 --- a/resources/js/Components/PersonUpdateForm.vue +++ /dev/null @@ -1,139 +0,0 @@ - - \ No newline at end of file diff --git a/resources/js/Components/PhoneCreateForm.vue b/resources/js/Components/PhoneCreateForm.vue index cda7bae..673a251 100644 --- a/resources/js/Components/PhoneCreateForm.vue +++ b/resources/js/Components/PhoneCreateForm.vue @@ -1,11 +1,9 @@ diff --git a/resources/js/Components/PhoneUpdateForm.vue b/resources/js/Components/PhoneUpdateForm.vue deleted file mode 100644 index 741c7c7..0000000 --- a/resources/js/Components/PhoneUpdateForm.vue +++ /dev/null @@ -1,17 +0,0 @@ - - - - - \ No newline at end of file diff --git a/resources/js/Components/Skeleton/SkeletonCard.vue b/resources/js/Components/Skeleton/SkeletonCard.vue new file mode 100644 index 0000000..b870bda --- /dev/null +++ b/resources/js/Components/Skeleton/SkeletonCard.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/resources/js/Components/Skeleton/SkeletonInline.vue b/resources/js/Components/Skeleton/SkeletonInline.vue new file mode 100644 index 0000000..c16fec2 --- /dev/null +++ b/resources/js/Components/Skeleton/SkeletonInline.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/resources/js/Components/Skeleton/SkeletonList.vue b/resources/js/Components/Skeleton/SkeletonList.vue new file mode 100644 index 0000000..c128270 --- /dev/null +++ b/resources/js/Components/Skeleton/SkeletonList.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/resources/js/Components/Skeleton/SkeletonTable.vue b/resources/js/Components/Skeleton/SkeletonTable.vue new file mode 100644 index 0000000..94de988 --- /dev/null +++ b/resources/js/Components/Skeleton/SkeletonTable.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/resources/js/Components/Toast/ToastContainer.vue b/resources/js/Components/Toast/ToastContainer.vue new file mode 100644 index 0000000..bfb3825 --- /dev/null +++ b/resources/js/Components/Toast/ToastContainer.vue @@ -0,0 +1,70 @@ + + + diff --git a/resources/js/Components/TrrCreateForm.vue b/resources/js/Components/TrrCreateForm.vue deleted file mode 100644 index 8a89376..0000000 --- a/resources/js/Components/TrrCreateForm.vue +++ /dev/null @@ -1,177 +0,0 @@ - - - diff --git a/resources/js/Components/ui/badge/Badge.vue b/resources/js/Components/ui/badge/Badge.vue new file mode 100644 index 0000000..b439355 --- /dev/null +++ b/resources/js/Components/ui/badge/Badge.vue @@ -0,0 +1,15 @@ + + + diff --git a/resources/js/Components/ui/badge/index.js b/resources/js/Components/ui/badge/index.js new file mode 100644 index 0000000..4c549e6 --- /dev/null +++ b/resources/js/Components/ui/badge/index.js @@ -0,0 +1,23 @@ +import { cva } from "class-variance-authority"; + +export { default as Badge } from "./Badge.vue"; + +export const badgeVariants = cva( + "inline-flex gap-1 items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); diff --git a/resources/js/Components/ui/button-group/ButtonGroup.vue b/resources/js/Components/ui/button-group/ButtonGroup.vue new file mode 100644 index 0000000..21b93d3 --- /dev/null +++ b/resources/js/Components/ui/button-group/ButtonGroup.vue @@ -0,0 +1,22 @@ + + + diff --git a/resources/js/Components/ui/button-group/ButtonGroupSeparator.vue b/resources/js/Components/ui/button-group/ButtonGroupSeparator.vue new file mode 100644 index 0000000..4fdcdc4 --- /dev/null +++ b/resources/js/Components/ui/button-group/ButtonGroupSeparator.vue @@ -0,0 +1,28 @@ + + + diff --git a/resources/js/Components/ui/button-group/ButtonGroupText.vue b/resources/js/Components/ui/button-group/ButtonGroupText.vue new file mode 100644 index 0000000..489cfe7 --- /dev/null +++ b/resources/js/Components/ui/button-group/ButtonGroupText.vue @@ -0,0 +1,29 @@ + + + diff --git a/resources/js/Components/ui/button-group/index.js b/resources/js/Components/ui/button-group/index.js new file mode 100644 index 0000000..eb73e31 --- /dev/null +++ b/resources/js/Components/ui/button-group/index.js @@ -0,0 +1,22 @@ +import { cva } from "class-variance-authority"; + +export { default as ButtonGroup } from "./ButtonGroup.vue"; +export { default as ButtonGroupSeparator } from "./ButtonGroupSeparator.vue"; +export { default as ButtonGroupText } from "./ButtonGroupText.vue"; + +export const buttonGroupVariants = cva( + "flex w-fit items-stretch [&>*:focus-visible]:z-10 [&>*:focus-visible]:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2", + { + variants: { + orientation: { + horizontal: + "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none", + vertical: + "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none", + }, + }, + defaultVariants: { + orientation: "horizontal", + }, + }, +); diff --git a/resources/js/Components/ui/button/Button.vue b/resources/js/Components/ui/button/Button.vue new file mode 100644 index 0000000..8abe477 --- /dev/null +++ b/resources/js/Components/ui/button/Button.vue @@ -0,0 +1,24 @@ + + + diff --git a/resources/js/Components/ui/button/index.js b/resources/js/Components/ui/button/index.js new file mode 100644 index 0000000..b5acafa --- /dev/null +++ b/resources/js/Components/ui/button/index.js @@ -0,0 +1,36 @@ +import { cva } from "class-variance-authority"; + +export { default as Button } from "./Button.vue"; + +export const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + { + variants: { + variant: { + default: + "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", + destructive: + "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", + secondary: + "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", + ghost: + "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2 has-[>svg]:px-3", + sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + icon: "size-9", + "icon-sm": "size-8", + "icon-lg": "size-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); diff --git a/resources/js/Components/ui/calendar/Calendar.vue b/resources/js/Components/ui/calendar/Calendar.vue new file mode 100644 index 0000000..f3a7d82 --- /dev/null +++ b/resources/js/Components/ui/calendar/Calendar.vue @@ -0,0 +1,95 @@ + + + diff --git a/resources/js/Components/ui/calendar/CalendarCell.vue b/resources/js/Components/ui/calendar/CalendarCell.vue new file mode 100644 index 0000000..939a3e6 --- /dev/null +++ b/resources/js/Components/ui/calendar/CalendarCell.vue @@ -0,0 +1,30 @@ + + + diff --git a/resources/js/Components/ui/calendar/CalendarCellTrigger.vue b/resources/js/Components/ui/calendar/CalendarCellTrigger.vue new file mode 100644 index 0000000..e52d8f6 --- /dev/null +++ b/resources/js/Components/ui/calendar/CalendarCellTrigger.vue @@ -0,0 +1,42 @@ + + + diff --git a/resources/js/Components/ui/calendar/CalendarGrid.vue b/resources/js/Components/ui/calendar/CalendarGrid.vue new file mode 100644 index 0000000..7765028 --- /dev/null +++ b/resources/js/Components/ui/calendar/CalendarGrid.vue @@ -0,0 +1,24 @@ + + + diff --git a/resources/js/Components/ui/calendar/CalendarGridBody.vue b/resources/js/Components/ui/calendar/CalendarGridBody.vue new file mode 100644 index 0000000..ee55203 --- /dev/null +++ b/resources/js/Components/ui/calendar/CalendarGridBody.vue @@ -0,0 +1,14 @@ + + + diff --git a/resources/js/Components/ui/calendar/CalendarGridHead.vue b/resources/js/Components/ui/calendar/CalendarGridHead.vue new file mode 100644 index 0000000..c16093c --- /dev/null +++ b/resources/js/Components/ui/calendar/CalendarGridHead.vue @@ -0,0 +1,15 @@ + + + diff --git a/resources/js/Components/ui/calendar/CalendarGridRow.vue b/resources/js/Components/ui/calendar/CalendarGridRow.vue new file mode 100644 index 0000000..07af044 --- /dev/null +++ b/resources/js/Components/ui/calendar/CalendarGridRow.vue @@ -0,0 +1,21 @@ + + + diff --git a/resources/js/Components/ui/calendar/CalendarHeadCell.vue b/resources/js/Components/ui/calendar/CalendarHeadCell.vue new file mode 100644 index 0000000..fade3e0 --- /dev/null +++ b/resources/js/Components/ui/calendar/CalendarHeadCell.vue @@ -0,0 +1,29 @@ + + + diff --git a/resources/js/Components/ui/calendar/CalendarHeader.vue b/resources/js/Components/ui/calendar/CalendarHeader.vue new file mode 100644 index 0000000..3d7b286 --- /dev/null +++ b/resources/js/Components/ui/calendar/CalendarHeader.vue @@ -0,0 +1,26 @@ + + + diff --git a/resources/js/Components/ui/calendar/CalendarHeading.vue b/resources/js/Components/ui/calendar/CalendarHeading.vue new file mode 100644 index 0000000..581a1c6 --- /dev/null +++ b/resources/js/Components/ui/calendar/CalendarHeading.vue @@ -0,0 +1,29 @@ + + + diff --git a/resources/js/Components/ui/calendar/CalendarNextButton.vue b/resources/js/Components/ui/calendar/CalendarNextButton.vue new file mode 100644 index 0000000..d26a634 --- /dev/null +++ b/resources/js/Components/ui/calendar/CalendarNextButton.vue @@ -0,0 +1,35 @@ + + + diff --git a/resources/js/Components/ui/calendar/CalendarPrevButton.vue b/resources/js/Components/ui/calendar/CalendarPrevButton.vue new file mode 100644 index 0000000..5853186 --- /dev/null +++ b/resources/js/Components/ui/calendar/CalendarPrevButton.vue @@ -0,0 +1,35 @@ + + + diff --git a/resources/js/Components/ui/calendar/index.js b/resources/js/Components/ui/calendar/index.js new file mode 100644 index 0000000..f5e9b7e --- /dev/null +++ b/resources/js/Components/ui/calendar/index.js @@ -0,0 +1,12 @@ +export { default as Calendar } from "./Calendar.vue"; +export { default as CalendarCell } from "./CalendarCell.vue"; +export { default as CalendarCellTrigger } from "./CalendarCellTrigger.vue"; +export { default as CalendarGrid } from "./CalendarGrid.vue"; +export { default as CalendarGridBody } from "./CalendarGridBody.vue"; +export { default as CalendarGridHead } from "./CalendarGridHead.vue"; +export { default as CalendarGridRow } from "./CalendarGridRow.vue"; +export { default as CalendarHeadCell } from "./CalendarHeadCell.vue"; +export { default as CalendarHeader } from "./CalendarHeader.vue"; +export { default as CalendarHeading } from "./CalendarHeading.vue"; +export { default as CalendarNextButton } from "./CalendarNextButton.vue"; +export { default as CalendarPrevButton } from "./CalendarPrevButton.vue"; diff --git a/resources/js/Components/ui/checkbox/Checkbox.vue b/resources/js/Components/ui/checkbox/Checkbox.vue new file mode 100644 index 0000000..0d9a1a3 --- /dev/null +++ b/resources/js/Components/ui/checkbox/Checkbox.vue @@ -0,0 +1,42 @@ + + + diff --git a/resources/js/Components/ui/checkbox/index.js b/resources/js/Components/ui/checkbox/index.js new file mode 100644 index 0000000..75be342 --- /dev/null +++ b/resources/js/Components/ui/checkbox/index.js @@ -0,0 +1 @@ +export { default as Checkbox } from "./Checkbox.vue"; diff --git a/resources/js/Components/ui/dialog/Dialog.vue b/resources/js/Components/ui/dialog/Dialog.vue new file mode 100644 index 0000000..509afc1 --- /dev/null +++ b/resources/js/Components/ui/dialog/Dialog.vue @@ -0,0 +1,18 @@ + + + diff --git a/resources/js/Components/ui/dialog/DialogClose.vue b/resources/js/Components/ui/dialog/DialogClose.vue new file mode 100644 index 0000000..c90b9bb --- /dev/null +++ b/resources/js/Components/ui/dialog/DialogClose.vue @@ -0,0 +1,14 @@ + + + diff --git a/resources/js/Components/ui/dialog/DialogContent.vue b/resources/js/Components/ui/dialog/DialogContent.vue new file mode 100644 index 0000000..9c54b7e --- /dev/null +++ b/resources/js/Components/ui/dialog/DialogContent.vue @@ -0,0 +1,58 @@ + + + diff --git a/resources/js/Components/ui/dialog/DialogDescription.vue b/resources/js/Components/ui/dialog/DialogDescription.vue new file mode 100644 index 0000000..544211e --- /dev/null +++ b/resources/js/Components/ui/dialog/DialogDescription.vue @@ -0,0 +1,24 @@ + + + diff --git a/resources/js/Components/ui/dialog/DialogFooter.vue b/resources/js/Components/ui/dialog/DialogFooter.vue new file mode 100644 index 0000000..f623c10 --- /dev/null +++ b/resources/js/Components/ui/dialog/DialogFooter.vue @@ -0,0 +1,20 @@ + + + diff --git a/resources/js/Components/ui/dialog/DialogHeader.vue b/resources/js/Components/ui/dialog/DialogHeader.vue new file mode 100644 index 0000000..e745386 --- /dev/null +++ b/resources/js/Components/ui/dialog/DialogHeader.vue @@ -0,0 +1,15 @@ + + + diff --git a/resources/js/Components/ui/dialog/DialogScrollContent.vue b/resources/js/Components/ui/dialog/DialogScrollContent.vue new file mode 100644 index 0000000..95cbf42 --- /dev/null +++ b/resources/js/Components/ui/dialog/DialogScrollContent.vue @@ -0,0 +1,71 @@ + + + diff --git a/resources/js/Components/ui/dialog/DialogTitle.vue b/resources/js/Components/ui/dialog/DialogTitle.vue new file mode 100644 index 0000000..6093ccf --- /dev/null +++ b/resources/js/Components/ui/dialog/DialogTitle.vue @@ -0,0 +1,26 @@ + + + diff --git a/resources/js/Components/ui/dialog/DialogTrigger.vue b/resources/js/Components/ui/dialog/DialogTrigger.vue new file mode 100644 index 0000000..c7e102b --- /dev/null +++ b/resources/js/Components/ui/dialog/DialogTrigger.vue @@ -0,0 +1,14 @@ + + + diff --git a/resources/js/Components/ui/dialog/index.js b/resources/js/Components/ui/dialog/index.js new file mode 100644 index 0000000..e2b3a15 --- /dev/null +++ b/resources/js/Components/ui/dialog/index.js @@ -0,0 +1,9 @@ +export { default as Dialog } from "./Dialog.vue"; +export { default as DialogClose } from "./DialogClose.vue"; +export { default as DialogContent } from "./DialogContent.vue"; +export { default as DialogDescription } from "./DialogDescription.vue"; +export { default as DialogFooter } from "./DialogFooter.vue"; +export { default as DialogHeader } from "./DialogHeader.vue"; +export { default as DialogScrollContent } from "./DialogScrollContent.vue"; +export { default as DialogTitle } from "./DialogTitle.vue"; +export { default as DialogTrigger } from "./DialogTrigger.vue"; diff --git a/resources/js/Components/ui/dropdown-menu/DropdownMenu.vue b/resources/js/Components/ui/dropdown-menu/DropdownMenu.vue new file mode 100644 index 0000000..8a1429e --- /dev/null +++ b/resources/js/Components/ui/dropdown-menu/DropdownMenu.vue @@ -0,0 +1,19 @@ + + + diff --git a/resources/js/Components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue b/resources/js/Components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue new file mode 100644 index 0000000..f8871e9 --- /dev/null +++ b/resources/js/Components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue @@ -0,0 +1,43 @@ + + + diff --git a/resources/js/Components/ui/dropdown-menu/DropdownMenuContent.vue b/resources/js/Components/ui/dropdown-menu/DropdownMenuContent.vue new file mode 100644 index 0000000..72dc0b0 --- /dev/null +++ b/resources/js/Components/ui/dropdown-menu/DropdownMenuContent.vue @@ -0,0 +1,61 @@ + + + diff --git a/resources/js/Components/ui/dropdown-menu/DropdownMenuGroup.vue b/resources/js/Components/ui/dropdown-menu/DropdownMenuGroup.vue new file mode 100644 index 0000000..8b11981 --- /dev/null +++ b/resources/js/Components/ui/dropdown-menu/DropdownMenuGroup.vue @@ -0,0 +1,14 @@ + + + diff --git a/resources/js/Components/ui/dropdown-menu/DropdownMenuItem.vue b/resources/js/Components/ui/dropdown-menu/DropdownMenuItem.vue new file mode 100644 index 0000000..5f0d0ce --- /dev/null +++ b/resources/js/Components/ui/dropdown-menu/DropdownMenuItem.vue @@ -0,0 +1,33 @@ + + + diff --git a/resources/js/Components/ui/dropdown-menu/DropdownMenuLabel.vue b/resources/js/Components/ui/dropdown-menu/DropdownMenuLabel.vue new file mode 100644 index 0000000..cf82678 --- /dev/null +++ b/resources/js/Components/ui/dropdown-menu/DropdownMenuLabel.vue @@ -0,0 +1,27 @@ + + + diff --git a/resources/js/Components/ui/dropdown-menu/DropdownMenuRadioGroup.vue b/resources/js/Components/ui/dropdown-menu/DropdownMenuRadioGroup.vue new file mode 100644 index 0000000..1f605ed --- /dev/null +++ b/resources/js/Components/ui/dropdown-menu/DropdownMenuRadioGroup.vue @@ -0,0 +1,18 @@ + + + diff --git a/resources/js/Components/ui/dropdown-menu/DropdownMenuRadioItem.vue b/resources/js/Components/ui/dropdown-menu/DropdownMenuRadioItem.vue new file mode 100644 index 0000000..1348800 --- /dev/null +++ b/resources/js/Components/ui/dropdown-menu/DropdownMenuRadioItem.vue @@ -0,0 +1,44 @@ + + + diff --git a/resources/js/Components/ui/dropdown-menu/DropdownMenuSeparator.vue b/resources/js/Components/ui/dropdown-menu/DropdownMenuSeparator.vue new file mode 100644 index 0000000..abc40a0 --- /dev/null +++ b/resources/js/Components/ui/dropdown-menu/DropdownMenuSeparator.vue @@ -0,0 +1,20 @@ + + + diff --git a/resources/js/Components/ui/dropdown-menu/DropdownMenuShortcut.vue b/resources/js/Components/ui/dropdown-menu/DropdownMenuShortcut.vue new file mode 100644 index 0000000..b4ee962 --- /dev/null +++ b/resources/js/Components/ui/dropdown-menu/DropdownMenuShortcut.vue @@ -0,0 +1,13 @@ + + + diff --git a/resources/js/Components/ui/dropdown-menu/DropdownMenuSub.vue b/resources/js/Components/ui/dropdown-menu/DropdownMenuSub.vue new file mode 100644 index 0000000..43ccfd0 --- /dev/null +++ b/resources/js/Components/ui/dropdown-menu/DropdownMenuSub.vue @@ -0,0 +1,17 @@ + + + diff --git a/resources/js/Components/ui/dropdown-menu/DropdownMenuSubContent.vue b/resources/js/Components/ui/dropdown-menu/DropdownMenuSubContent.vue new file mode 100644 index 0000000..c75a82c --- /dev/null +++ b/resources/js/Components/ui/dropdown-menu/DropdownMenuSubContent.vue @@ -0,0 +1,55 @@ + + + diff --git a/resources/js/Components/ui/dropdown-menu/DropdownMenuSubTrigger.vue b/resources/js/Components/ui/dropdown-menu/DropdownMenuSubTrigger.vue new file mode 100644 index 0000000..b2e22d0 --- /dev/null +++ b/resources/js/Components/ui/dropdown-menu/DropdownMenuSubTrigger.vue @@ -0,0 +1,33 @@ + + + diff --git a/resources/js/Components/ui/dropdown-menu/DropdownMenuTrigger.vue b/resources/js/Components/ui/dropdown-menu/DropdownMenuTrigger.vue new file mode 100644 index 0000000..278d55e --- /dev/null +++ b/resources/js/Components/ui/dropdown-menu/DropdownMenuTrigger.vue @@ -0,0 +1,17 @@ + + + diff --git a/resources/js/Components/ui/dropdown-menu/index.js b/resources/js/Components/ui/dropdown-menu/index.js new file mode 100644 index 0000000..9e57848 --- /dev/null +++ b/resources/js/Components/ui/dropdown-menu/index.js @@ -0,0 +1,16 @@ +export { default as DropdownMenu } from "./DropdownMenu.vue"; + +export { default as DropdownMenuCheckboxItem } from "./DropdownMenuCheckboxItem.vue"; +export { default as DropdownMenuContent } from "./DropdownMenuContent.vue"; +export { default as DropdownMenuGroup } from "./DropdownMenuGroup.vue"; +export { default as DropdownMenuItem } from "./DropdownMenuItem.vue"; +export { default as DropdownMenuLabel } from "./DropdownMenuLabel.vue"; +export { default as DropdownMenuRadioGroup } from "./DropdownMenuRadioGroup.vue"; +export { default as DropdownMenuRadioItem } from "./DropdownMenuRadioItem.vue"; +export { default as DropdownMenuSeparator } from "./DropdownMenuSeparator.vue"; +export { default as DropdownMenuShortcut } from "./DropdownMenuShortcut.vue"; +export { default as DropdownMenuSub } from "./DropdownMenuSub.vue"; +export { default as DropdownMenuSubContent } from "./DropdownMenuSubContent.vue"; +export { default as DropdownMenuSubTrigger } from "./DropdownMenuSubTrigger.vue"; +export { default as DropdownMenuTrigger } from "./DropdownMenuTrigger.vue"; +export { DropdownMenuPortal } from "reka-ui"; diff --git a/resources/js/Components/ui/form/FormControl.vue b/resources/js/Components/ui/form/FormControl.vue new file mode 100644 index 0000000..ca8c783 --- /dev/null +++ b/resources/js/Components/ui/form/FormControl.vue @@ -0,0 +1,18 @@ + + + diff --git a/resources/js/Components/ui/form/FormDescription.vue b/resources/js/Components/ui/form/FormDescription.vue new file mode 100644 index 0000000..7dd1ace --- /dev/null +++ b/resources/js/Components/ui/form/FormDescription.vue @@ -0,0 +1,19 @@ + + + diff --git a/resources/js/Components/ui/form/FormItem.vue b/resources/js/Components/ui/form/FormItem.vue new file mode 100644 index 0000000..54fe9f0 --- /dev/null +++ b/resources/js/Components/ui/form/FormItem.vue @@ -0,0 +1,19 @@ + + + diff --git a/resources/js/Components/ui/form/FormLabel.vue b/resources/js/Components/ui/form/FormLabel.vue new file mode 100644 index 0000000..8f98472 --- /dev/null +++ b/resources/js/Components/ui/form/FormLabel.vue @@ -0,0 +1,23 @@ + + + diff --git a/resources/js/Components/ui/form/FormMessage.vue b/resources/js/Components/ui/form/FormMessage.vue new file mode 100644 index 0000000..4294cf0 --- /dev/null +++ b/resources/js/Components/ui/form/FormMessage.vue @@ -0,0 +1,16 @@ + + + diff --git a/resources/js/Components/ui/form/index.js b/resources/js/Components/ui/form/index.js new file mode 100644 index 0000000..7749894 --- /dev/null +++ b/resources/js/Components/ui/form/index.js @@ -0,0 +1,11 @@ +export { default as FormControl } from "./FormControl.vue"; +export { default as FormDescription } from "./FormDescription.vue"; +export { default as FormItem } from "./FormItem.vue"; +export { default as FormLabel } from "./FormLabel.vue"; +export { default as FormMessage } from "./FormMessage.vue"; +export { FORM_ITEM_INJECTION_KEY } from "./injectionKeys"; +export { + Form, + Field as FormField, + FieldArray as FormFieldArray, +} from "vee-validate"; diff --git a/resources/js/Components/ui/form/injectionKeys.js b/resources/js/Components/ui/form/injectionKeys.js new file mode 100644 index 0000000..533eabd --- /dev/null +++ b/resources/js/Components/ui/form/injectionKeys.js @@ -0,0 +1 @@ +export const FORM_ITEM_INJECTION_KEY = Symbol(); diff --git a/resources/js/Components/ui/form/useFormField.js b/resources/js/Components/ui/form/useFormField.js new file mode 100644 index 0000000..9c5a764 --- /dev/null +++ b/resources/js/Components/ui/form/useFormField.js @@ -0,0 +1,30 @@ +import { FieldContextKey } from "vee-validate"; +import { computed, inject } from "vue"; +import { FORM_ITEM_INJECTION_KEY } from "./injectionKeys"; + +export function useFormField() { + const fieldContext = inject(FieldContextKey); + const fieldItemContext = inject(FORM_ITEM_INJECTION_KEY); + + if (!fieldContext) + throw new Error("useFormField should be used within "); + + const { name, errorMessage: error, meta } = fieldContext; + const id = fieldItemContext; + + const fieldState = { + valid: computed(() => meta.valid), + isDirty: computed(() => meta.dirty), + isTouched: computed(() => meta.touched), + error, + }; + + return { + id, + name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + }; +} diff --git a/resources/js/Components/ui/input/Input.vue b/resources/js/Components/ui/input/Input.vue new file mode 100644 index 0000000..fa84341 --- /dev/null +++ b/resources/js/Components/ui/input/Input.vue @@ -0,0 +1,29 @@ + + + diff --git a/resources/js/Components/ui/input/index.js b/resources/js/Components/ui/input/index.js new file mode 100644 index 0000000..110f046 --- /dev/null +++ b/resources/js/Components/ui/input/index.js @@ -0,0 +1 @@ +export { default as Input } from "./Input.vue"; diff --git a/resources/js/Components/ui/label/Label.vue b/resources/js/Components/ui/label/Label.vue new file mode 100644 index 0000000..b20aec0 --- /dev/null +++ b/resources/js/Components/ui/label/Label.vue @@ -0,0 +1,29 @@ + + + diff --git a/resources/js/Components/ui/label/index.js b/resources/js/Components/ui/label/index.js new file mode 100644 index 0000000..38eaa35 --- /dev/null +++ b/resources/js/Components/ui/label/index.js @@ -0,0 +1 @@ +export { default as Label } from "./Label.vue"; diff --git a/resources/js/Components/ui/popover/Popover.vue b/resources/js/Components/ui/popover/Popover.vue new file mode 100644 index 0000000..a5210da --- /dev/null +++ b/resources/js/Components/ui/popover/Popover.vue @@ -0,0 +1,18 @@ + + + diff --git a/resources/js/Components/ui/popover/PopoverContent.vue b/resources/js/Components/ui/popover/PopoverContent.vue new file mode 100644 index 0000000..789014f --- /dev/null +++ b/resources/js/Components/ui/popover/PopoverContent.vue @@ -0,0 +1,62 @@ + + + diff --git a/resources/js/Components/ui/popover/PopoverTrigger.vue b/resources/js/Components/ui/popover/PopoverTrigger.vue new file mode 100644 index 0000000..ed1cdee --- /dev/null +++ b/resources/js/Components/ui/popover/PopoverTrigger.vue @@ -0,0 +1,14 @@ + + + diff --git a/resources/js/Components/ui/popover/index.js b/resources/js/Components/ui/popover/index.js new file mode 100644 index 0000000..ce5130a --- /dev/null +++ b/resources/js/Components/ui/popover/index.js @@ -0,0 +1,4 @@ +export { default as Popover } from "./Popover.vue"; +export { default as PopoverContent } from "./PopoverContent.vue"; +export { default as PopoverTrigger } from "./PopoverTrigger.vue"; +export { PopoverAnchor } from "reka-ui"; diff --git a/resources/js/Components/ui/range-calendar/RangeCalendar.vue b/resources/js/Components/ui/range-calendar/RangeCalendar.vue new file mode 100644 index 0000000..b18bd9a --- /dev/null +++ b/resources/js/Components/ui/range-calendar/RangeCalendar.vue @@ -0,0 +1,103 @@ + + + diff --git a/resources/js/Components/ui/range-calendar/RangeCalendarCell.vue b/resources/js/Components/ui/range-calendar/RangeCalendarCell.vue new file mode 100644 index 0000000..e284d49 --- /dev/null +++ b/resources/js/Components/ui/range-calendar/RangeCalendarCell.vue @@ -0,0 +1,30 @@ + + + diff --git a/resources/js/Components/ui/range-calendar/RangeCalendarCellTrigger.vue b/resources/js/Components/ui/range-calendar/RangeCalendarCellTrigger.vue new file mode 100644 index 0000000..048adcc --- /dev/null +++ b/resources/js/Components/ui/range-calendar/RangeCalendarCellTrigger.vue @@ -0,0 +1,44 @@ + + + diff --git a/resources/js/Components/ui/range-calendar/RangeCalendarGrid.vue b/resources/js/Components/ui/range-calendar/RangeCalendarGrid.vue new file mode 100644 index 0000000..e85b8ef --- /dev/null +++ b/resources/js/Components/ui/range-calendar/RangeCalendarGrid.vue @@ -0,0 +1,24 @@ + + + diff --git a/resources/js/Components/ui/range-calendar/RangeCalendarGridBody.vue b/resources/js/Components/ui/range-calendar/RangeCalendarGridBody.vue new file mode 100644 index 0000000..a660623 --- /dev/null +++ b/resources/js/Components/ui/range-calendar/RangeCalendarGridBody.vue @@ -0,0 +1,14 @@ + + + diff --git a/resources/js/Components/ui/range-calendar/RangeCalendarGridHead.vue b/resources/js/Components/ui/range-calendar/RangeCalendarGridHead.vue new file mode 100644 index 0000000..44ceea4 --- /dev/null +++ b/resources/js/Components/ui/range-calendar/RangeCalendarGridHead.vue @@ -0,0 +1,14 @@ + + + diff --git a/resources/js/Components/ui/range-calendar/RangeCalendarGridRow.vue b/resources/js/Components/ui/range-calendar/RangeCalendarGridRow.vue new file mode 100644 index 0000000..d2e3425 --- /dev/null +++ b/resources/js/Components/ui/range-calendar/RangeCalendarGridRow.vue @@ -0,0 +1,24 @@ + + + diff --git a/resources/js/Components/ui/range-calendar/RangeCalendarHeadCell.vue b/resources/js/Components/ui/range-calendar/RangeCalendarHeadCell.vue new file mode 100644 index 0000000..b2cfeb7 --- /dev/null +++ b/resources/js/Components/ui/range-calendar/RangeCalendarHeadCell.vue @@ -0,0 +1,29 @@ + + + diff --git a/resources/js/Components/ui/range-calendar/RangeCalendarHeader.vue b/resources/js/Components/ui/range-calendar/RangeCalendarHeader.vue new file mode 100644 index 0000000..8312ee2 --- /dev/null +++ b/resources/js/Components/ui/range-calendar/RangeCalendarHeader.vue @@ -0,0 +1,26 @@ + + + diff --git a/resources/js/Components/ui/range-calendar/RangeCalendarHeading.vue b/resources/js/Components/ui/range-calendar/RangeCalendarHeading.vue new file mode 100644 index 0000000..b80ef45 --- /dev/null +++ b/resources/js/Components/ui/range-calendar/RangeCalendarHeading.vue @@ -0,0 +1,29 @@ + + + diff --git a/resources/js/Components/ui/range-calendar/RangeCalendarNextButton.vue b/resources/js/Components/ui/range-calendar/RangeCalendarNextButton.vue new file mode 100644 index 0000000..30be1e0 --- /dev/null +++ b/resources/js/Components/ui/range-calendar/RangeCalendarNextButton.vue @@ -0,0 +1,35 @@ + + + diff --git a/resources/js/Components/ui/range-calendar/RangeCalendarPrevButton.vue b/resources/js/Components/ui/range-calendar/RangeCalendarPrevButton.vue new file mode 100644 index 0000000..11a1018 --- /dev/null +++ b/resources/js/Components/ui/range-calendar/RangeCalendarPrevButton.vue @@ -0,0 +1,35 @@ + + + diff --git a/resources/js/Components/ui/range-calendar/index.js b/resources/js/Components/ui/range-calendar/index.js new file mode 100644 index 0000000..c7b3c6c --- /dev/null +++ b/resources/js/Components/ui/range-calendar/index.js @@ -0,0 +1,12 @@ +export { default as RangeCalendar } from "./RangeCalendar.vue"; +export { default as RangeCalendarCell } from "./RangeCalendarCell.vue"; +export { default as RangeCalendarCellTrigger } from "./RangeCalendarCellTrigger.vue"; +export { default as RangeCalendarGrid } from "./RangeCalendarGrid.vue"; +export { default as RangeCalendarGridBody } from "./RangeCalendarGridBody.vue"; +export { default as RangeCalendarGridHead } from "./RangeCalendarGridHead.vue"; +export { default as RangeCalendarGridRow } from "./RangeCalendarGridRow.vue"; +export { default as RangeCalendarHeadCell } from "./RangeCalendarHeadCell.vue"; +export { default as RangeCalendarHeader } from "./RangeCalendarHeader.vue"; +export { default as RangeCalendarHeading } from "./RangeCalendarHeading.vue"; +export { default as RangeCalendarNextButton } from "./RangeCalendarNextButton.vue"; +export { default as RangeCalendarPrevButton } from "./RangeCalendarPrevButton.vue"; diff --git a/resources/js/Components/ui/select/Select.vue b/resources/js/Components/ui/select/Select.vue new file mode 100644 index 0000000..3980c72 --- /dev/null +++ b/resources/js/Components/ui/select/Select.vue @@ -0,0 +1,26 @@ + + + diff --git a/resources/js/Components/ui/select/SelectContent.vue b/resources/js/Components/ui/select/SelectContent.vue new file mode 100644 index 0000000..f9cfbc8 --- /dev/null +++ b/resources/js/Components/ui/select/SelectContent.vue @@ -0,0 +1,80 @@ + + + diff --git a/resources/js/Components/ui/select/SelectGroup.vue b/resources/js/Components/ui/select/SelectGroup.vue new file mode 100644 index 0000000..f1f2d8a --- /dev/null +++ b/resources/js/Components/ui/select/SelectGroup.vue @@ -0,0 +1,19 @@ + + + diff --git a/resources/js/Components/ui/select/SelectItem.vue b/resources/js/Components/ui/select/SelectItem.vue new file mode 100644 index 0000000..ec569d1 --- /dev/null +++ b/resources/js/Components/ui/select/SelectItem.vue @@ -0,0 +1,46 @@ + + + diff --git a/resources/js/Components/ui/select/SelectItemText.vue b/resources/js/Components/ui/select/SelectItemText.vue new file mode 100644 index 0000000..fa595bf --- /dev/null +++ b/resources/js/Components/ui/select/SelectItemText.vue @@ -0,0 +1,14 @@ + + + diff --git a/resources/js/Components/ui/select/SelectLabel.vue b/resources/js/Components/ui/select/SelectLabel.vue new file mode 100644 index 0000000..0a77b37 --- /dev/null +++ b/resources/js/Components/ui/select/SelectLabel.vue @@ -0,0 +1,17 @@ + + + diff --git a/resources/js/Components/ui/select/SelectScrollDownButton.vue b/resources/js/Components/ui/select/SelectScrollDownButton.vue new file mode 100644 index 0000000..e21948b --- /dev/null +++ b/resources/js/Components/ui/select/SelectScrollDownButton.vue @@ -0,0 +1,29 @@ + + + diff --git a/resources/js/Components/ui/select/SelectScrollUpButton.vue b/resources/js/Components/ui/select/SelectScrollUpButton.vue new file mode 100644 index 0000000..63f5868 --- /dev/null +++ b/resources/js/Components/ui/select/SelectScrollUpButton.vue @@ -0,0 +1,29 @@ + + + diff --git a/resources/js/Components/ui/select/SelectSeparator.vue b/resources/js/Components/ui/select/SelectSeparator.vue new file mode 100644 index 0000000..c7f1bf7 --- /dev/null +++ b/resources/js/Components/ui/select/SelectSeparator.vue @@ -0,0 +1,20 @@ + + + diff --git a/resources/js/Components/ui/select/SelectTrigger.vue b/resources/js/Components/ui/select/SelectTrigger.vue new file mode 100644 index 0000000..35127a9 --- /dev/null +++ b/resources/js/Components/ui/select/SelectTrigger.vue @@ -0,0 +1,35 @@ + + + diff --git a/resources/js/Components/ui/select/SelectValue.vue b/resources/js/Components/ui/select/SelectValue.vue new file mode 100644 index 0000000..d890e8a --- /dev/null +++ b/resources/js/Components/ui/select/SelectValue.vue @@ -0,0 +1,15 @@ + + + diff --git a/resources/js/Components/ui/select/index.js b/resources/js/Components/ui/select/index.js new file mode 100644 index 0000000..d911c4e --- /dev/null +++ b/resources/js/Components/ui/select/index.js @@ -0,0 +1,11 @@ +export { default as Select } from "./Select.vue"; +export { default as SelectContent } from "./SelectContent.vue"; +export { default as SelectGroup } from "./SelectGroup.vue"; +export { default as SelectItem } from "./SelectItem.vue"; +export { default as SelectItemText } from "./SelectItemText.vue"; +export { default as SelectLabel } from "./SelectLabel.vue"; +export { default as SelectScrollDownButton } from "./SelectScrollDownButton.vue"; +export { default as SelectScrollUpButton } from "./SelectScrollUpButton.vue"; +export { default as SelectSeparator } from "./SelectSeparator.vue"; +export { default as SelectTrigger } from "./SelectTrigger.vue"; +export { default as SelectValue } from "./SelectValue.vue"; diff --git a/resources/js/Components/ui/separator/Separator.vue b/resources/js/Components/ui/separator/Separator.vue new file mode 100644 index 0000000..4a88242 --- /dev/null +++ b/resources/js/Components/ui/separator/Separator.vue @@ -0,0 +1,28 @@ + + + diff --git a/resources/js/Components/ui/separator/index.js b/resources/js/Components/ui/separator/index.js new file mode 100644 index 0000000..aae7f1a --- /dev/null +++ b/resources/js/Components/ui/separator/index.js @@ -0,0 +1 @@ +export { default as Separator } from "./Separator.vue"; diff --git a/resources/js/Components/ui/sonner/Sonner.vue b/resources/js/Components/ui/sonner/Sonner.vue new file mode 100644 index 0000000..9bb1384 --- /dev/null +++ b/resources/js/Components/ui/sonner/Sonner.vue @@ -0,0 +1,45 @@ + + + diff --git a/resources/js/Components/ui/sonner/index.js b/resources/js/Components/ui/sonner/index.js new file mode 100644 index 0000000..39a59dd --- /dev/null +++ b/resources/js/Components/ui/sonner/index.js @@ -0,0 +1 @@ +export { default as Toaster } from "./Sonner.vue"; diff --git a/resources/js/Components/ui/table/Table.vue b/resources/js/Components/ui/table/Table.vue new file mode 100644 index 0000000..7b7540f --- /dev/null +++ b/resources/js/Components/ui/table/Table.vue @@ -0,0 +1,15 @@ + + + diff --git a/resources/js/Components/ui/table/TableBody.vue b/resources/js/Components/ui/table/TableBody.vue new file mode 100644 index 0000000..fa451e1 --- /dev/null +++ b/resources/js/Components/ui/table/TableBody.vue @@ -0,0 +1,13 @@ + + + diff --git a/resources/js/Components/ui/table/TableCaption.vue b/resources/js/Components/ui/table/TableCaption.vue new file mode 100644 index 0000000..f6d4028 --- /dev/null +++ b/resources/js/Components/ui/table/TableCaption.vue @@ -0,0 +1,13 @@ + + + diff --git a/resources/js/Components/ui/table/TableCell.vue b/resources/js/Components/ui/table/TableCell.vue new file mode 100644 index 0000000..e9dab9c --- /dev/null +++ b/resources/js/Components/ui/table/TableCell.vue @@ -0,0 +1,20 @@ + + + diff --git a/resources/js/Components/ui/table/TableEmpty.vue b/resources/js/Components/ui/table/TableEmpty.vue new file mode 100644 index 0000000..0a94d54 --- /dev/null +++ b/resources/js/Components/ui/table/TableEmpty.vue @@ -0,0 +1,31 @@ + + + diff --git a/resources/js/Components/ui/table/TableFooter.vue b/resources/js/Components/ui/table/TableFooter.vue new file mode 100644 index 0000000..45bc82a --- /dev/null +++ b/resources/js/Components/ui/table/TableFooter.vue @@ -0,0 +1,17 @@ + + + diff --git a/resources/js/Components/ui/table/TableHead.vue b/resources/js/Components/ui/table/TableHead.vue new file mode 100644 index 0000000..96d8dcc --- /dev/null +++ b/resources/js/Components/ui/table/TableHead.vue @@ -0,0 +1,20 @@ + + + diff --git a/resources/js/Components/ui/table/TableHeader.vue b/resources/js/Components/ui/table/TableHeader.vue new file mode 100644 index 0000000..86901ef --- /dev/null +++ b/resources/js/Components/ui/table/TableHeader.vue @@ -0,0 +1,13 @@ + + + diff --git a/resources/js/Components/ui/table/TableRow.vue b/resources/js/Components/ui/table/TableRow.vue new file mode 100644 index 0000000..ef64daf --- /dev/null +++ b/resources/js/Components/ui/table/TableRow.vue @@ -0,0 +1,20 @@ + + + diff --git a/resources/js/Components/ui/table/index.js b/resources/js/Components/ui/table/index.js new file mode 100644 index 0000000..0afab4c --- /dev/null +++ b/resources/js/Components/ui/table/index.js @@ -0,0 +1,9 @@ +export { default as Table } from "./Table.vue"; +export { default as TableBody } from "./TableBody.vue"; +export { default as TableCaption } from "./TableCaption.vue"; +export { default as TableCell } from "./TableCell.vue"; +export { default as TableEmpty } from "./TableEmpty.vue"; +export { default as TableFooter } from "./TableFooter.vue"; +export { default as TableHead } from "./TableHead.vue"; +export { default as TableHeader } from "./TableHeader.vue"; +export { default as TableRow } from "./TableRow.vue"; diff --git a/resources/js/Components/ui/tabs/Tabs.vue b/resources/js/Components/ui/tabs/Tabs.vue new file mode 100644 index 0000000..c17213b --- /dev/null +++ b/resources/js/Components/ui/tabs/Tabs.vue @@ -0,0 +1,23 @@ + + + diff --git a/resources/js/Components/ui/tabs/TabsContent.vue b/resources/js/Components/ui/tabs/TabsContent.vue new file mode 100644 index 0000000..e733e8c --- /dev/null +++ b/resources/js/Components/ui/tabs/TabsContent.vue @@ -0,0 +1,29 @@ + + + diff --git a/resources/js/Components/ui/tabs/TabsList.vue b/resources/js/Components/ui/tabs/TabsList.vue new file mode 100644 index 0000000..4c26e0e --- /dev/null +++ b/resources/js/Components/ui/tabs/TabsList.vue @@ -0,0 +1,28 @@ + + + diff --git a/resources/js/Components/ui/tabs/TabsTrigger.vue b/resources/js/Components/ui/tabs/TabsTrigger.vue new file mode 100644 index 0000000..d4ec8e2 --- /dev/null +++ b/resources/js/Components/ui/tabs/TabsTrigger.vue @@ -0,0 +1,33 @@ + + + diff --git a/resources/js/Components/ui/tabs/index.js b/resources/js/Components/ui/tabs/index.js new file mode 100644 index 0000000..3b3741b --- /dev/null +++ b/resources/js/Components/ui/tabs/index.js @@ -0,0 +1,4 @@ +export { default as Tabs } from "./Tabs.vue"; +export { default as TabsContent } from "./TabsContent.vue"; +export { default as TabsList } from "./TabsList.vue"; +export { default as TabsTrigger } from "./TabsTrigger.vue"; diff --git a/resources/js/Components/ui/textarea/Textarea.vue b/resources/js/Components/ui/textarea/Textarea.vue new file mode 100644 index 0000000..230a5bf --- /dev/null +++ b/resources/js/Components/ui/textarea/Textarea.vue @@ -0,0 +1,29 @@ + + + + diff --git a/resources/js/Pages/Admin/SmsProfiles/Index.vue b/resources/js/Pages/Admin/SmsProfiles/Index.vue index fcd6f0a..6edd061 100644 --- a/resources/js/Pages/Admin/SmsProfiles/Index.vue +++ b/resources/js/Pages/Admin/SmsProfiles/Index.vue @@ -1,6 +1,6 @@ diff --git a/resources/js/Pages/Cases/Index.vue b/resources/js/Pages/Cases/Index.vue index 9eb4e67..c68367f 100644 --- a/resources/js/Pages/Cases/Index.vue +++ b/resources/js/Pages/Cases/Index.vue @@ -3,7 +3,9 @@ import AppLayout from "@/Layouts/AppLayout.vue"; import SectionTitle from "@/Components/SectionTitle.vue"; import { Link, router } from "@inertiajs/vue3"; import { ref } from "vue"; -import DataTableServer from "@/Components/DataTable/DataTableServer.vue"; +import DataTable from "@/Components/DataTable/DataTable.vue"; +import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; +import { faFolderOpen } from "@fortawesome/free-solid-svg-icons"; const props = defineProps({ client_cases: Object, @@ -47,7 +49,9 @@ const fmtDateDMY = (v) => { - - - + diff --git a/resources/js/Pages/Cases/Partials/ActivityDrawer.vue b/resources/js/Pages/Cases/Partials/ActivityDrawer.vue index 05fac8e..50ac639 100644 --- a/resources/js/Pages/Cases/Partials/ActivityDrawer.vue +++ b/resources/js/Pages/Cases/Partials/ActivityDrawer.vue @@ -1,12 +1,21 @@ diff --git a/resources/js/Pages/Cases/Partials/PaymentDialog.vue b/resources/js/Pages/Cases/Partials/PaymentDialog.vue index 7099cd0..fb1ad90 100644 --- a/resources/js/Pages/Cases/Partials/PaymentDialog.vue +++ b/resources/js/Pages/Cases/Partials/PaymentDialog.vue @@ -1,6 +1,9 @@ diff --git a/resources/js/Pages/Cases/Show.vue b/resources/js/Pages/Cases/Show.vue index 9de273c..317c5c1 100644 --- a/resources/js/Pages/Cases/Show.vue +++ b/resources/js/Pages/Cases/Show.vue @@ -1,8 +1,8 @@ \ No newline at end of file + + diff --git a/resources/js/Pages/Client/Show.vue b/resources/js/Pages/Client/Show.vue index 5f31a01..c53a00d 100644 --- a/resources/js/Pages/Client/Show.vue +++ b/resources/js/Pages/Client/Show.vue @@ -1,13 +1,16 @@ + +