@dasidev/dasi-ui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +346 -0
- package/bin/dasi-cli.cjs +184 -0
- package/dist/date-selector-test-BlukYeWl.js +91 -0
- package/dist/favicon.ico +0 -0
- package/dist/html2canvas.esm-CKxSAI8P.js +4886 -0
- package/dist/img/brand/ic_pln.svg +12 -0
- package/dist/img/brand/mapp_power_logo.svg +21 -0
- package/dist/img/common/pltu_ulumbu_flores_ntt.jpeg +0 -0
- package/dist/index-BQSA2aPs.js +126556 -0
- package/dist/index.es-DQWt-PZn.js +5769 -0
- package/dist/index.es.js +11 -0
- package/dist/index.umd.js +8564 -0
- package/dist/informasi-gudang-BmoEy2RL.js +164 -0
- package/dist/informasi-gudang-DXfS46Nh.js +50 -0
- package/dist/purify.es-C-9oolON.js +546 -0
- package/dist/scripts/pdf.worker.min.js +29 -0
- package/dist/scripts/pdf.worker.min.mjs +29 -0
- package/dist/scripts/pdf.worker.mjs +57722 -0
- package/dist/scripts/pdf.worker.mjs.map +1 -0
- package/dist/style.css +1 -0
- package/dist/test-schema-JFghGc0_.js +8 -0
- package/dist/test-schema-uusFsJe4.js +438 -0
- package/dist/types-l0sNRNKZ.js +1 -0
- package/package.json +178 -0
- package/src/App.vue +18 -0
- package/src/__tests__/index.test.ts +9 -0
- package/src/api/api.ts +117 -0
- package/src/assets/app-selector.svg +3 -0
- package/src/assets/dasi.png +0 -0
- package/src/assets/foto_ss.svg +21 -0
- package/src/assets/icons/circle-blue.svg +4 -0
- package/src/assets/icons/circle-gray.svg +15 -0
- package/src/assets/icons/circle-green.svg +4 -0
- package/src/assets/icons/circle-orange.svg +4 -0
- package/src/assets/icons/circle-purple.svg +4 -0
- package/src/assets/icons/circle-red.svg +15 -0
- package/src/assets/icons/harbor.svg +12 -0
- package/src/assets/icons/ic-box-red.svg +8 -0
- package/src/assets/icons/ic-chevron-right.svg +1 -0
- package/src/assets/icons/ic-loading.svg +9 -0
- package/src/assets/icons/ic-reset.svg +16 -0
- package/src/assets/icons/ic-sailing.svg +5 -0
- package/src/assets/icons/icon-app-selector.svg +3 -0
- package/src/assets/icons/icon-browser-check.svg +4 -0
- package/src/assets/icons/icon-calendar.svg +3 -0
- package/src/assets/icons/icon-chart-bar.svg +3 -0
- package/src/assets/icons/icon-chart-doc.svg +16 -0
- package/src/assets/icons/icon-chart-line.svg +10 -0
- package/src/assets/icons/icon-chart-mix.svg +15 -0
- package/src/assets/icons/icon-chart-pie.svg +11 -0
- package/src/assets/icons/icon-continue.svg +12 -0
- package/src/assets/icons/icon-dashboard-2.svg +17 -0
- package/src/assets/icons/icon-dashboard.svg +3 -0
- package/src/assets/icons/icon-data-kelistrikan.svg +19 -0
- package/src/assets/icons/icon-data-sentral.svg +11 -0
- package/src/assets/icons/icon-database.svg +5 -0
- package/src/assets/icons/icon-desktop.svg +3 -0
- package/src/assets/icons/icon-download.svg +13 -0
- package/src/assets/icons/icon-energi-primer.svg +12 -0
- package/src/assets/icons/icon-faba-apk2.svg +11 -0
- package/src/assets/icons/icon-faba.svg +11 -0
- package/src/assets/icons/icon-factory.svg +14 -0
- package/src/assets/icons/icon-globe-doc.svg +19 -0
- package/src/assets/icons/icon-ikk.svg +10 -0
- package/src/assets/icons/icon-kbb.svg +13 -0
- package/src/assets/icons/icon-kos.svg +16 -0
- package/src/assets/icons/icon-kpi-bod.svg +15 -0
- package/src/assets/icons/icon-kss.svg +14 -0
- package/src/assets/icons/icon-map.svg +12 -0
- package/src/assets/icons/icon-monitoring-harian.svg +13 -0
- package/src/assets/icons/icon-notification.svg +4 -0
- package/src/assets/icons/icon-overview.svg +17 -0
- package/src/assets/icons/icon-pltu.svg +13 -0
- package/src/assets/icons/icon-sebaran-sentral.svg +12 -0
- package/src/assets/icons/icon-select-data-kelistrikan.svg +19 -0
- package/src/assets/icons/icon-select-data-sentral.svg +11 -0
- package/src/assets/icons/icon-select-energi-primer.svg +12 -0
- package/src/assets/icons/icon-select-faba-apk2.svg +11 -0
- package/src/assets/icons/icon-select-ikk.svg +10 -0
- package/src/assets/icons/icon-select-kbb.svg +13 -0
- package/src/assets/icons/icon-select-kos.svg +16 -0
- package/src/assets/icons/icon-select-kpi-bod.svg +15 -0
- package/src/assets/icons/icon-select-kss.svg +14 -0
- package/src/assets/icons/icon-select-monitoring-harian.svg +13 -0
- package/src/assets/icons/icon-select-overview.svg +17 -0
- package/src/assets/icons/icon-select-sebaran-sentral.svg +12 -0
- package/src/assets/icons/icon-sentral-white.svg +13 -0
- package/src/assets/icons/icon-shipping.svg +5 -0
- package/src/assets/icons/icon-sort.svg +5 -0
- package/src/assets/icons/icon-tree-box.svg +14 -0
- package/src/assets/icons/icon-warehouse.svg +12 -0
- package/src/assets/icons/pin-green.svg +3 -0
- package/src/assets/icons/pin-orange.svg +3 -0
- package/src/assets/icons/pin-purple.svg +3 -0
- package/src/assets/icons/ship.svg +3 -0
- package/src/assets/icons/shipment/icon-antri.svg +15 -0
- package/src/assets/icons/shipment/icon-bongkar.svg +4 -0
- package/src/assets/icons/shipment/icon-invoice.svg +6 -0
- package/src/assets/icons/shipment/icon-loading.svg +8 -0
- package/src/assets/icons/shipment/icon-pembayaran.svg +13 -0
- package/src/assets/icons/shipment/icon-pengiriman.svg +4 -0
- package/src/assets/icons/shipment/icon-sailing.svg +4 -0
- package/src/assets/icons/shipment/icon-shipment-completed.svg +6 -0
- package/src/assets/icons/shipment/icon-shipment-in-progress.svg +6 -0
- package/src/assets/icons/shipment/icon-shipment-over-sla.svg +6 -0
- package/src/assets/icons/shipment/icon-spt.svg +4 -0
- package/src/assets/icons/shipment/icon-total-shipment.svg +4 -0
- package/src/assets/icons/upload_doc_icon.svg +42 -0
- package/src/assets/icons/upload_icon_blue.svg +14 -0
- package/src/assets/login-bg-day-min.jpg +0 -0
- package/src/assets/login-bg-night-min.jpg +0 -0
- package/src/assets/login-bg.jpg +0 -0
- package/src/assets/login-day.png +0 -0
- package/src/assets/login-night.png +0 -0
- package/src/assets/lucide-circle-plus-blue.svg +1 -0
- package/src/assets/pdf-logo.svg +11 -0
- package/src/assets/pemasok-card-bg.svg +6 -0
- package/src/assets/success_animation.gif +0 -0
- package/src/assets/success_animation.mp4 +0 -0
- package/src/assets/success_animation.webm +0 -0
- package/src/components/button/BtnAddOutline.vue +14 -0
- package/src/components/button/BtnCircle.vue +10 -0
- package/src/components/button/BtnOutline.vue +15 -0
- package/src/components/button/BtnPrimary.vue +25 -0
- package/src/components/button/BtnSecondary.vue +26 -0
- package/src/components/detail/AccountDetailTimeline.vue +144 -0
- package/src/components/detail/ApprovalInfo.vue +288 -0
- package/src/components/detail/DCI2.vue +164 -0
- package/src/components/detail/DetailContentHeader.vue +83 -0
- package/src/components/detail/DetailContentItem.vue +186 -0
- package/src/components/detail/DetailContentItems.vue +388 -0
- package/src/components/detail/DetailContentLoading.vue +12 -0
- package/src/components/detail/DetailContentTablet.vue +10 -0
- package/src/components/detail/DetailSheet.vue +294 -0
- package/src/components/detail/DetailTimeline.vue +191 -0
- package/src/components/detail/DocApprovalDialog.vue +29 -0
- package/src/components/detail/DocViewerContent.vue +991 -0
- package/src/components/dialog/ConfirmDialog.vue +96 -0
- package/src/components/dialog/DialogBase.vue +53 -0
- package/src/components/dialog/DialogSelect.vue +212 -0
- package/src/components/dialog/ErrorDialog.vue +63 -0
- package/src/components/dialog/FormDialog.vue +141 -0
- package/src/components/dialog/FormInputerDialog.vue +91 -0
- package/src/components/dialog/InfoDialog.vue +74 -0
- package/src/components/dialog/SuccessDialog.vue +51 -0
- package/src/components/examples/TestSchemaExample.vue +288 -0
- package/src/components/forms/auth/LoginForm.vue +806 -0
- package/src/components/forms/auth/PwdScore.vue +68 -0
- package/src/components/helper/ApiTester.vue +153 -0
- package/src/components/helper/ChangePwd.vue +150 -0
- package/src/components/helper/CheckboxElement.vue +43 -0
- package/src/components/helper/ConfigSwitcher.vue +54 -0
- package/src/components/helper/Copyright.vue +10 -0
- package/src/components/helper/ErrorScreen.vue +40 -0
- package/src/components/helper/LucideIcon.vue +27 -0
- package/src/components/helper/PdfViewer.vue +103 -0
- package/src/components/helper/PinInputer.vue +205 -0
- package/src/components/helper/PrivacyPolicy.vue +122 -0
- package/src/components/layout/PageActivityHeader.vue +48 -0
- package/src/components/layout/PageHeader.vue +70 -0
- package/src/components/loadings/LoadingDialog.vue +29 -0
- package/src/components/loadings/LoadingDialogSpin.vue +25 -0
- package/src/components/loadings/LoadingIndicator.vue +38 -0
- package/src/components/loadings/LoadingScreen.vue +23 -0
- package/src/components/notif/Notif.vue +103 -0
- package/src/components/notif/NotifItem.vue +41 -0
- package/src/components/pages/Header.vue +431 -0
- package/src/components/pages/Leftbar.vue +417 -0
- package/src/components/pages/PageActivity.vue +108 -0
- package/src/components/pages/PageActivityContent.vue +597 -0
- package/src/components/pages/PageContentTable.vue +589 -0
- package/src/components/pages/PageTab.vue +84 -0
- package/src/components/selector/BaseSelector.vue +1136 -0
- package/src/components/selector/ConfigDataSelector.vue +136 -0
- package/src/components/settings/SettingsItem.vue +38 -0
- package/src/components/tab/TabView.vue +11 -0
- package/src/components/tab/TabViewItem.vue +18 -0
- package/src/components/tab/TabViewItemBar.vue +9 -0
- package/src/components/tables/CellHover.vue +65 -0
- package/src/components/tables/DashboardDataTable.vue +707 -0
- package/src/components/tables/DataStatusTag.vue +52 -0
- package/src/components/tables/DataTable.vue +156 -0
- package/src/components/tables/DataTableAccordion.vue +249 -0
- package/src/components/tables/DataTableActionRow.vue +64 -0
- package/src/components/tables/DataTableCell.vue +272 -0
- package/src/components/tables/DataTableHeader.vue +60 -0
- package/src/components/tables/DataTableRow.vue +213 -0
- package/src/components/tables/ExpandedTable.vue +259 -0
- package/src/components/tables/PageTable.vue +73 -0
- package/src/components/tables/Pagination.vue +98 -0
- package/src/components/tables/dropdown/BaseDropdownTable.vue +140 -0
- package/src/components/tables/dropdown/DropdownTableActivity.vue +33 -0
- package/src/components/tables/dropdown/DropdownTableAsset.vue +30 -0
- package/src/components/tables/dropdown/DropdownTableConfig.vue +30 -0
- package/src/components/tables/dropdown/DropdownTableDataKonektor.vue +31 -0
- package/src/components/tables/dropdown/DropdownTableDataLabel.vue +30 -0
- package/src/components/tables/dropdown/DropdownTableDataSchema.vue +31 -0
- package/src/components/tables/dropdown/DropdownTableFabaPemanfaat.vue +30 -0
- package/src/components/tables/dropdown/DropdownTableGroup.vue +36 -0
- package/src/components/tables/dropdown/DropdownTableHalaman.vue +33 -0
- package/src/components/tables/dropdown/DropdownTableLevel.vue +66 -0
- package/src/components/tables/dropdown/DropdownTableOrganization.vue +47 -0
- package/src/components/tables/dropdown/DropdownTablePengelola.vue +28 -0
- package/src/components/tables/dropdown/DropdownTableQueryLayer.vue +29 -0
- package/src/components/tables/dropdown/DropdownTableSentral.vue +33 -0
- package/src/components/tables/dropdown/DropdownTableWarehouse.vue +30 -0
- package/src/components/tables/dropdown/TableDropdown.vue +52 -0
- package/src/components/ui/accordion/Accordion.vue +19 -0
- package/src/components/ui/accordion/AccordionContent.vue +24 -0
- package/src/components/ui/accordion/AccordionItem.vue +24 -0
- package/src/components/ui/accordion/AccordionTrigger.vue +42 -0
- package/src/components/ui/accordion/index.ts +4 -0
- package/src/components/ui/alert-dialog/AlertDialog.vue +14 -0
- package/src/components/ui/alert-dialog/AlertDialogAction.vue +20 -0
- package/src/components/ui/alert-dialog/AlertDialogCancel.vue +20 -0
- package/src/components/ui/alert-dialog/AlertDialogContent.vue +42 -0
- package/src/components/ui/alert-dialog/AlertDialogDescription.vue +25 -0
- package/src/components/ui/alert-dialog/AlertDialogFooter.vue +21 -0
- package/src/components/ui/alert-dialog/AlertDialogHeader.vue +16 -0
- package/src/components/ui/alert-dialog/AlertDialogTitle.vue +22 -0
- package/src/components/ui/alert-dialog/AlertDialogTrigger.vue +11 -0
- package/src/components/ui/alert-dialog/index.ts +9 -0
- package/src/components/ui/avatar/Avatar.vue +24 -0
- package/src/components/ui/avatar/AvatarFallback.vue +11 -0
- package/src/components/ui/avatar/AvatarImage.vue +9 -0
- package/src/components/ui/avatar/UsersAvatar.vue +28 -0
- package/src/components/ui/avatar/index.ts +24 -0
- package/src/components/ui/button/Button.vue +27 -0
- package/src/components/ui/button/index.ts +34 -0
- package/src/components/ui/calendar/Calendar.vue +325 -0
- package/src/components/ui/calendar/index.ts +22 -0
- package/src/components/ui/checkbox/Checkbox.vue +33 -0
- package/src/components/ui/checkbox/index.ts +1 -0
- package/src/components/ui/command/Command.vue +30 -0
- package/src/components/ui/command/CommandDialog.vue +21 -0
- package/src/components/ui/command/CommandEmpty.vue +20 -0
- package/src/components/ui/command/CommandGroup.vue +29 -0
- package/src/components/ui/command/CommandInput.vue +33 -0
- package/src/components/ui/command/CommandItem.vue +26 -0
- package/src/components/ui/command/CommandList.vue +27 -0
- package/src/components/ui/command/CommandSeparator.vue +23 -0
- package/src/components/ui/command/CommandShortcut.vue +14 -0
- package/src/components/ui/command/index.ts +9 -0
- package/src/components/ui/context-menu/ContextMenu.vue +15 -0
- package/src/components/ui/context-menu/ContextMenuCheckboxItem.vue +40 -0
- package/src/components/ui/context-menu/ContextMenuContent.vue +36 -0
- package/src/components/ui/context-menu/ContextMenuGroup.vue +11 -0
- package/src/components/ui/context-menu/ContextMenuItem.vue +34 -0
- package/src/components/ui/context-menu/ContextMenuLabel.vue +25 -0
- package/src/components/ui/context-menu/ContextMenuPortal.vue +11 -0
- package/src/components/ui/context-menu/ContextMenuRadioGroup.vue +19 -0
- package/src/components/ui/context-menu/ContextMenuRadioItem.vue +40 -0
- package/src/components/ui/context-menu/ContextMenuSeparator.vue +20 -0
- package/src/components/ui/context-menu/ContextMenuShortcut.vue +14 -0
- package/src/components/ui/context-menu/ContextMenuSub.vue +19 -0
- package/src/components/ui/context-menu/ContextMenuSubContent.vue +35 -0
- package/src/components/ui/context-menu/ContextMenuSubTrigger.vue +34 -0
- package/src/components/ui/context-menu/ContextMenuTrigger.vue +13 -0
- package/src/components/ui/context-menu/index.ts +14 -0
- package/src/components/ui/datetime/DatetimeRangeComponent.vue +52 -0
- package/src/components/ui/dialog/Dialog.vue +14 -0
- package/src/components/ui/dialog/DialogClose.vue +11 -0
- package/src/components/ui/dialog/DialogContent.vue +53 -0
- package/src/components/ui/dialog/DialogDescription.vue +24 -0
- package/src/components/ui/dialog/DialogFooter.vue +19 -0
- package/src/components/ui/dialog/DialogHeader.vue +16 -0
- package/src/components/ui/dialog/DialogScrollContent.vue +59 -0
- package/src/components/ui/dialog/DialogTitle.vue +29 -0
- package/src/components/ui/dialog/DialogTrigger.vue +11 -0
- package/src/components/ui/dialog/index.ts +9 -0
- package/src/components/ui/dropdown-menu/DropdownMenu.vue +14 -0
- package/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +40 -0
- package/src/components/ui/dropdown-menu/DropdownMenuContent.vue +38 -0
- package/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +11 -0
- package/src/components/ui/dropdown-menu/DropdownMenuItem.vue +28 -0
- package/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +24 -0
- package/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +19 -0
- package/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +41 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +22 -0
- package/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +14 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSub.vue +19 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +30 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +33 -0
- package/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +13 -0
- package/src/components/ui/dropdown-menu/index.ts +16 -0
- package/src/components/ui/form/FormControl.vue +16 -0
- package/src/components/ui/form/FormDescription.vue +20 -0
- package/src/components/ui/form/FormItem.vue +25 -0
- package/src/components/ui/form/FormLabel.vue +23 -0
- package/src/components/ui/form/FormMessage.vue +16 -0
- package/src/components/ui/form/index.ts +6 -0
- package/src/components/ui/form/useFormField.ts +30 -0
- package/src/components/ui/hover-card/HoverCard.vue +14 -0
- package/src/components/ui/hover-card/HoverCardContent.vue +41 -0
- package/src/components/ui/hover-card/HoverCardTrigger.vue +11 -0
- package/src/components/ui/hover-card/index.ts +3 -0
- package/src/components/ui/input/Input.vue +24 -0
- package/src/components/ui/input/index.ts +1 -0
- package/src/components/ui/label/Label.vue +27 -0
- package/src/components/ui/label/index.ts +1 -0
- package/src/components/ui/pagination/PaginationEllipsis.vue +22 -0
- package/src/components/ui/pagination/PaginationFirst.vue +29 -0
- package/src/components/ui/pagination/PaginationLast.vue +29 -0
- package/src/components/ui/pagination/PaginationNext.vue +29 -0
- package/src/components/ui/pagination/PaginationPrev.vue +29 -0
- package/src/components/ui/pagination/index.ts +10 -0
- package/src/components/ui/pin-input/PinInput.vue +23 -0
- package/src/components/ui/pin-input/PinInputGroup.vue +18 -0
- package/src/components/ui/pin-input/PinInputInput.vue +18 -0
- package/src/components/ui/pin-input/PinInputSeparator.vue +15 -0
- package/src/components/ui/pin-input/index.ts +4 -0
- package/src/components/ui/popover/Popover.vue +15 -0
- package/src/components/ui/popover/PopoverContent.vue +48 -0
- package/src/components/ui/popover/PopoverTrigger.vue +11 -0
- package/src/components/ui/popover/index.ts +4 -0
- package/src/components/ui/preview/PreviewPdf.vue +118 -0
- package/src/components/ui/progress/ProgressCircle.vue +27 -0
- package/src/components/ui/progress/SemiCircularProgressBar.vue +83 -0
- package/src/components/ui/progress/TotalCalories.vue +31 -0
- package/src/components/ui/radio-group/RadioGroup.vue +25 -0
- package/src/components/ui/radio-group/RadioGroupItem.vue +37 -0
- package/src/components/ui/radio-group/index.ts +2 -0
- package/src/components/ui/scroll-area/ScrollArea.vue +29 -0
- package/src/components/ui/scroll-area/ScrollBar.vue +30 -0
- package/src/components/ui/scroll-area/index.ts +2 -0
- package/src/components/ui/select/Select.vue +15 -0
- package/src/components/ui/select/SelectContent.vue +52 -0
- package/src/components/ui/select/SelectGroup.vue +19 -0
- package/src/components/ui/select/SelectInline.vue +84 -0
- package/src/components/ui/select/SelectItem.vue +44 -0
- package/src/components/ui/select/SelectItemText.vue +11 -0
- package/src/components/ui/select/SelectLabel.vue +13 -0
- package/src/components/ui/select/SelectScrollDownButton.vue +24 -0
- package/src/components/ui/select/SelectScrollUpButton.vue +24 -0
- package/src/components/ui/select/SelectSeparator.vue +17 -0
- package/src/components/ui/select/SelectTrigger.vue +31 -0
- package/src/components/ui/select/SelectTriggerCustom.vue +23 -0
- package/src/components/ui/select/SelectValue.vue +11 -0
- package/src/components/ui/select/index.ts +12 -0
- package/src/components/ui/separator/Separator.vue +20 -0
- package/src/components/ui/separator/index.ts +1 -0
- package/src/components/ui/sheet/Sheet.vue +14 -0
- package/src/components/ui/sheet/SheetClose.vue +11 -0
- package/src/components/ui/sheet/SheetContent.vue +48 -0
- package/src/components/ui/sheet/SheetDescription.vue +22 -0
- package/src/components/ui/sheet/SheetFooter.vue +19 -0
- package/src/components/ui/sheet/SheetHeader.vue +16 -0
- package/src/components/ui/sheet/SheetTitle.vue +22 -0
- package/src/components/ui/sheet/SheetTrigger.vue +11 -0
- package/src/components/ui/sheet/index.ts +31 -0
- package/src/components/ui/skeleton/Skeleton.vue +28 -0
- package/src/components/ui/skeleton/index.ts +1 -0
- package/src/components/ui/sonner/Sonner.vue +22 -0
- package/src/components/ui/sonner/index.ts +1 -0
- package/src/components/ui/star/StarRating.vue +19 -0
- package/src/components/ui/switch/Switch.vue +37 -0
- package/src/components/ui/switch/index.ts +1 -0
- package/src/components/ui/table/Table.vue +16 -0
- package/src/components/ui/table/TableBody.vue +14 -0
- package/src/components/ui/table/TableCaption.vue +14 -0
- package/src/components/ui/table/TableCell.vue +21 -0
- package/src/components/ui/table/TableEmpty.vue +37 -0
- package/src/components/ui/table/TableFooter.vue +14 -0
- package/src/components/ui/table/TableHead.vue +14 -0
- package/src/components/ui/table/TableHeader.vue +14 -0
- package/src/components/ui/table/TableRow.vue +14 -0
- package/src/components/ui/table/index.ts +8 -0
- package/src/components/ui/tabs/Tabs.vue +15 -0
- package/src/components/ui/tabs/TabsContent.vue +22 -0
- package/src/components/ui/tabs/TabsList.vue +25 -0
- package/src/components/ui/tabs/TabsTrigger.vue +27 -0
- package/src/components/ui/tabs/index.ts +4 -0
- package/src/components/ui/tags-input/TagsInput.vue +22 -0
- package/src/components/ui/tags-input/TagsInputInput.vue +19 -0
- package/src/components/ui/tags-input/TagsInputItem.vue +22 -0
- package/src/components/ui/tags-input/TagsInputItemDelete.vue +24 -0
- package/src/components/ui/tags-input/TagsInputItemText.vue +19 -0
- package/src/components/ui/tags-input/index.ts +5 -0
- package/src/components/ui/textarea/Textarea.vue +24 -0
- package/src/components/ui/textarea/index.ts +1 -0
- package/src/components/ui/tooltip/Tooltip.vue +14 -0
- package/src/components/ui/tooltip/TooltipContent.vue +31 -0
- package/src/components/ui/tooltip/TooltipProvider.vue +11 -0
- package/src/components/ui/tooltip/TooltipTrigger.vue +11 -0
- package/src/components/ui/tooltip/index.ts +4 -0
- package/src/composables/useAppConfig.ts +332 -0
- package/src/composables/useDarkMode.ts +71 -0
- package/src/config/app.config.ts +318 -0
- package/src/config/examples/ecommerce.config.ts +132 -0
- package/src/config/examples/generic.config.ts +132 -0
- package/src/config/menu.config.ts +149 -0
- package/src/config/my-app.config.ts +134 -0
- package/src/config/test-config.ts +32 -0
- package/src/config/theme.config.ts +250 -0
- package/src/docs/index.ts +21 -0
- package/src/docs.scss +403 -0
- package/src/index.d.ts +5 -0
- package/src/index.ts +20 -0
- package/src/layouts/AuthLayout.vue +68 -0
- package/src/layouts/DefaultLayout.vue +119 -0
- package/src/layouts/DocsLayout.vue +681 -0
- package/src/layouts/FormGlobal.vue +50 -0
- package/src/layouts/GlobalDialog.vue +122 -0
- package/src/layouts/RakorConfirmDialog.vue +95 -0
- package/src/layouts/SettingsLayout.vue +115 -0
- package/src/lib/constants.ts +2 -0
- package/src/lib/detail.utils.ts +213 -0
- package/src/lib/form.utils.ts +1009 -0
- package/src/lib/page.flow.utils.ts +81 -0
- package/src/lib/page.utils.ts +865 -0
- package/src/lib/performance.utils.ts +302 -0
- package/src/lib/tablerow.utils.ts +51 -0
- package/src/lib/utils.ts +643 -0
- package/src/main.scss +717 -0
- package/src/main.ts +74 -0
- package/src/menu.ts +78 -0
- package/src/nestedlist_color.scss +161 -0
- package/src/router/index.ts +92 -0
- package/src/stores/auth.ts +117 -0
- package/src/stores/counter.ts +12 -0
- package/src/stores/dialog.ts +168 -0
- package/src/stores/form.ts +103 -0
- package/src/stores/tabs.ts +52 -0
- package/src/tw.scss +419 -0
- package/src/types/form.types.ts +348 -0
- package/src/types/types.ts +7 -0
- package/src/utils/config.utils.ts +149 -0
- package/src/views/NotFound.vue +30 -0
- package/src/views/PageActivity.vue +15 -0
- package/src/views/auth/LoginView.vue +7 -0
- package/src/views/auth/OauthCallback.vue +101 -0
- package/src/views/dashboard/index.vue +16 -0
- package/src/views/settings/AccountSettingsView.vue +70 -0
- package/src/views/settings/AuditLogsSettingsView.vue +116 -0
- package/src/views/settings/DeviceSettingsView.vue +70 -0
- package/src/views/settings/MainSettingsView.vue +12 -0
- package/src/views/settings/ProfileSettingsView.vue +104 -0
- package/src/vueform/config/informasi-gudang.ts +47 -0
- package/src/vueform/config/test-schema.ts +8 -0
- package/src/vueform/config/types.ts +768 -0
- package/src/vueform/customization/classes.js +46 -0
- package/src/vueform/customization/tailwind.classes.js +2117 -0
- package/src/vueform/elements/ConfigDataSelectorElement.vue +50 -0
- package/src/vueform/elements/DateSelectorElement.vue +323 -0
- package/src/vueform/elements/SelectorElement.vue +153 -0
- package/src/vueform/schemas/date-selector-test.ts +103 -0
- package/src/vueform/schemas/informasi-gudang.ts +160 -0
- package/src/vueform/schemas/test-schema.ts +483 -0
- package/src/vueform.config.js +77 -0
- package/src/vueform.validator.ts +77 -0
|
@@ -0,0 +1,1136 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* BaseSelector - Unified Selector Component
|
|
4
|
+
*
|
|
5
|
+
* This component consolidates all selector variants (BaseSelectorSingle, BaseSelectorSingleAvatar, etc.)
|
|
6
|
+
* into a single, highly configurable component. It supports both single and multiple selection modes,
|
|
7
|
+
* avatar display, custom field mapping, and comprehensive API integration.
|
|
8
|
+
*
|
|
9
|
+
* Key Features:
|
|
10
|
+
* - Single & Multiple Selection: Flexible selection modes
|
|
11
|
+
* - Avatar Display: User avatars with fallback support
|
|
12
|
+
* - Auto-fill Organization: Automatic organization selection
|
|
13
|
+
* - Custom Field Mapping: Flexible field configuration
|
|
14
|
+
* - Debounced Search: 300ms search debouncing for performance
|
|
15
|
+
* - Pagination Support: Built-in pagination with navigation
|
|
16
|
+
* - Error Handling: Comprehensive error handling with user feedback
|
|
17
|
+
* - Loading States: Visual feedback during API calls
|
|
18
|
+
* - Keyboard Navigation: Full keyboard accessibility
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* // Basic single selection
|
|
22
|
+
* <BaseSelector
|
|
23
|
+
* v-model="selectedUserId"
|
|
24
|
+
* endpoint="/api/users"
|
|
25
|
+
* placeholder="Select a user..."
|
|
26
|
+
* />
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* // Multiple selection with avatars
|
|
30
|
+
* <BaseSelector
|
|
31
|
+
* v-model="selectedUsers"
|
|
32
|
+
* endpoint="/api/users"
|
|
33
|
+
* :multiple="true"
|
|
34
|
+
* :show-avatar="true"
|
|
35
|
+
* :return-object="true"
|
|
36
|
+
* />
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* // Organization selector with auto-fill
|
|
40
|
+
* <BaseSelector
|
|
41
|
+
* v-model="selectedOrg"
|
|
42
|
+
* endpoint="/api/organizations"
|
|
43
|
+
* :show-avatar="true"
|
|
44
|
+
* :auto-fill-org="true"
|
|
45
|
+
* access-level="admin"
|
|
46
|
+
* />
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
import api from "@/api/api";
|
|
50
|
+
import DialogSelect, { type PageMeta } from "@/components/dialog/DialogSelect.vue";
|
|
51
|
+
import BtnCircle from "@/components/button/BtnCircle.vue";
|
|
52
|
+
import { ChevronRight, LucideX } from "lucide-vue-next";
|
|
53
|
+
import { onMounted, watch, ref, computed, onUnmounted } from "vue";
|
|
54
|
+
import { cn } from "@/lib/utils";
|
|
55
|
+
import { useDebounceSearch, useMemoryManagement } from "@/lib/performance.utils";
|
|
56
|
+
import Avatar from "@/components/ui/avatar/Avatar.vue";
|
|
57
|
+
import AvatarFallback from "@/components/ui/avatar/AvatarFallback.vue";
|
|
58
|
+
import AvatarImage from "@/components/ui/avatar/AvatarImage.vue";
|
|
59
|
+
import CheckboxElement from "@/components/helper/CheckboxElement.vue";
|
|
60
|
+
import { toast } from "vue-sonner";
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Interface for items returned from the API
|
|
64
|
+
* Represents a selectable item with optional avatar and access level information
|
|
65
|
+
*/
|
|
66
|
+
interface SelectItem {
|
|
67
|
+
id: string | number;
|
|
68
|
+
name: string;
|
|
69
|
+
description?: string;
|
|
70
|
+
avatar?: string;
|
|
71
|
+
isChecked?: boolean;
|
|
72
|
+
accessLevel?: {
|
|
73
|
+
code: string;
|
|
74
|
+
name: string;
|
|
75
|
+
};
|
|
76
|
+
[key: string]: any; // Allow additional fields for flexibility
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Props interface for BaseSelector component
|
|
81
|
+
* Consolidates all functionality from previous selector variants
|
|
82
|
+
*/
|
|
83
|
+
interface BaseSelectorProps {
|
|
84
|
+
// Core functionality (from BaseSelectorSingle.vue)
|
|
85
|
+
/** API endpoint for data fetching */
|
|
86
|
+
endpoint: string;
|
|
87
|
+
/** Placeholder text when no item is selected */
|
|
88
|
+
placeholder?: string;
|
|
89
|
+
/** Title for the selection dialog */
|
|
90
|
+
title?: string;
|
|
91
|
+
/** Custom name field for display (supports dot notation like 'user.name') */
|
|
92
|
+
nameField?: string;
|
|
93
|
+
/** Custom function to format the name field */
|
|
94
|
+
nameFieldFunc?: Function;
|
|
95
|
+
/** Custom description field for display (supports dot notation) */
|
|
96
|
+
descriptionField?: string;
|
|
97
|
+
/** Custom function to format the description field */
|
|
98
|
+
descriptionFieldFunc?: Function;
|
|
99
|
+
/** Disable automatic data loading on mount */
|
|
100
|
+
disableAutoLoad?: boolean;
|
|
101
|
+
/** Disable the selector */
|
|
102
|
+
disabled?: boolean;
|
|
103
|
+
/** Columns to search in (comma-separated) */
|
|
104
|
+
searchColumns?: string;
|
|
105
|
+
|
|
106
|
+
// Avatar functionality (from BaseSelectorSingleAvatar.vue)
|
|
107
|
+
/** Show avatar display functionality */
|
|
108
|
+
showAvatar?: boolean;
|
|
109
|
+
/** Auto-fill with user organization */
|
|
110
|
+
autoFillOrg?: boolean;
|
|
111
|
+
/** Required access level for auto-fill */
|
|
112
|
+
accessLevel?: string;
|
|
113
|
+
|
|
114
|
+
// Return value configuration
|
|
115
|
+
/** If true, returns full object; if false, returns only ID */
|
|
116
|
+
returnObject?: boolean;
|
|
117
|
+
|
|
118
|
+
// Future extensibility
|
|
119
|
+
/** Enable multiple selection mode */
|
|
120
|
+
multiple?: boolean;
|
|
121
|
+
|
|
122
|
+
// Styling
|
|
123
|
+
/** Additional CSS classes */
|
|
124
|
+
class?: string;
|
|
125
|
+
/** Show the trigger element (for embedded usage) */
|
|
126
|
+
showTrigger?: boolean;
|
|
127
|
+
|
|
128
|
+
// Dynamic Parameters
|
|
129
|
+
/** Static query parameters to append to endpoint */
|
|
130
|
+
params?: Record<string, any>;
|
|
131
|
+
/** Condition parameters formatted as key:value,key2:value2 */
|
|
132
|
+
condition?: Record<string, any>;
|
|
133
|
+
/** Order by field for sorting */
|
|
134
|
+
orderBy?: string;
|
|
135
|
+
/** Sort direction (asc or desc) */
|
|
136
|
+
sort?: "asc" | "desc";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ===== REACTIVE STATE =====
|
|
140
|
+
/** Array of items fetched from the API */
|
|
141
|
+
const data = ref<SelectItem[]>([]);
|
|
142
|
+
/** Currently selected item (single selection mode) */
|
|
143
|
+
const selected = ref<SelectItem | null>(null);
|
|
144
|
+
/** Array of selected items (multiple selection mode) */
|
|
145
|
+
const selectedMultiple = ref<SelectItem[]>([]);
|
|
146
|
+
/** Array of checked items for multiple selection (temporary selections in dialog) */
|
|
147
|
+
const checkeds = ref<SelectItem[]>([]);
|
|
148
|
+
/** Array of checked IDs for multiple selection */
|
|
149
|
+
const checkedIds = ref<(string | number)[]>([]);
|
|
150
|
+
/** Two-way binding model for the component value */
|
|
151
|
+
const model = defineModel<string | number | (string | number)[] | SelectItem[] | null>();
|
|
152
|
+
/** Event emitter for change events */
|
|
153
|
+
const emits = defineEmits(["change"]);
|
|
154
|
+
|
|
155
|
+
/** Component props with default values */
|
|
156
|
+
const props = withDefaults(defineProps<BaseSelectorProps>(), {
|
|
157
|
+
placeholder: "Pilih item...",
|
|
158
|
+
searchColumns: "name",
|
|
159
|
+
showAvatar: false,
|
|
160
|
+
returnObject: false,
|
|
161
|
+
multiple: false,
|
|
162
|
+
disabled: false,
|
|
163
|
+
disableAutoLoad: false,
|
|
164
|
+
autoFillOrg: false,
|
|
165
|
+
nameField: "",
|
|
166
|
+
showTrigger: true,
|
|
167
|
+
params: () => ({}),
|
|
168
|
+
condition: () => ({}),
|
|
169
|
+
orderBy: undefined,
|
|
170
|
+
sort: "asc"
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// ===== INTERNAL STATE =====
|
|
174
|
+
/** Current search query */
|
|
175
|
+
const q = ref("");
|
|
176
|
+
/** Pagination metadata for API requests */
|
|
177
|
+
const pageMeta = ref<PageMeta>({ total: 0, limit: 20, page: 1 });
|
|
178
|
+
/** Loading state indicator */
|
|
179
|
+
const loading = ref(true);
|
|
180
|
+
/** Dialog open/close state */
|
|
181
|
+
const dialogOpen = ref(false);
|
|
182
|
+
/** Error message for display */
|
|
183
|
+
const error = ref<string | null>(null);
|
|
184
|
+
|
|
185
|
+
// ===== PERFORMANCE OPTIMIZATIONS =====
|
|
186
|
+
/** Memory management utilities for cleanup */
|
|
187
|
+
const { addCleanup, cleanup } = useMemoryManagement();
|
|
188
|
+
/** Debounced search function to prevent excessive API calls (300ms timeout as per requirement 8.1) */
|
|
189
|
+
const debouncedSearch = useDebounceSearch((query: string) => {
|
|
190
|
+
q.value = query;
|
|
191
|
+
pageMeta.value.page = 1; // Reset to first page on new search
|
|
192
|
+
getData();
|
|
193
|
+
}, 300);
|
|
194
|
+
|
|
195
|
+
// ===== COMPUTED PROPERTIES =====
|
|
196
|
+
/** Development mode flag for debugging */
|
|
197
|
+
const IS_DEV = (import.meta as any)?.env?.DEV === true;
|
|
198
|
+
/**
|
|
199
|
+
* Computed property for avatar background color based on endpoint
|
|
200
|
+
* Provides visual distinction between different data types
|
|
201
|
+
*/
|
|
202
|
+
const avatarColorClass = computed(() => {
|
|
203
|
+
const endpointPath = props.endpoint.split("?")[0];
|
|
204
|
+
// More robust endpoint matching for avatar colors
|
|
205
|
+
if (endpointPath === "/account" || endpointPath.includes("/account")) {
|
|
206
|
+
return "bg-green-200";
|
|
207
|
+
}
|
|
208
|
+
if (endpointPath === "/organization" || endpointPath.includes("/organization")) {
|
|
209
|
+
return "bg-blue-200";
|
|
210
|
+
}
|
|
211
|
+
// Default color for other endpoints
|
|
212
|
+
return "bg-gray-200";
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Computed property for avatar text color based on endpoint
|
|
217
|
+
* Ensures proper contrast with background colors
|
|
218
|
+
*/
|
|
219
|
+
const avatarTextColorClass = computed(() => {
|
|
220
|
+
const endpointPath = props.endpoint.split("?")[0];
|
|
221
|
+
// More robust endpoint matching for avatar text colors
|
|
222
|
+
if (endpointPath === "/account" || endpointPath.includes("/account")) {
|
|
223
|
+
return "text-green-700";
|
|
224
|
+
}
|
|
225
|
+
if (endpointPath === "/organization" || endpointPath.includes("/organization")) {
|
|
226
|
+
return "text-blue-700";
|
|
227
|
+
}
|
|
228
|
+
// Default text color for other endpoints
|
|
229
|
+
return "text-gray-700";
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Enhanced display name resolver with comprehensive field parsing
|
|
234
|
+
* Supports dot notation, template fields, custom functions, and fallbacks
|
|
235
|
+
*
|
|
236
|
+
* @param object - The object to extract the display name from
|
|
237
|
+
* @returns Formatted display name string
|
|
238
|
+
*/
|
|
239
|
+
function getEnhancedDisplayName(object: any): string {
|
|
240
|
+
if (!object) return "";
|
|
241
|
+
|
|
242
|
+
// Priority 1: Use nameFieldFunc if available
|
|
243
|
+
if (props.nameFieldFunc) {
|
|
244
|
+
try {
|
|
245
|
+
const result = props.nameFieldFunc(object);
|
|
246
|
+
if (result && String(result).trim() !== "" && String(result) !== "undefined") {
|
|
247
|
+
return String(result);
|
|
248
|
+
}
|
|
249
|
+
} catch (error) {
|
|
250
|
+
if (IS_DEV) console.warn("[BaseSelector] nameFieldFunc error:", error);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Priority 2: Use nameField with advanced parsing
|
|
255
|
+
if (props.nameField && props.nameField.trim() !== "") {
|
|
256
|
+
try {
|
|
257
|
+
// Handle template fields like "{user.name} - {user.role}"
|
|
258
|
+
const templateRegex = /\{([a-zA-Z0-9_\.]+)\}/g;
|
|
259
|
+
if (templateRegex.test(props.nameField)) {
|
|
260
|
+
return props.nameField.replace(templateRegex, (match, fieldPath) => {
|
|
261
|
+
const value = getNestedValue(object, fieldPath);
|
|
262
|
+
return value !== null && value !== undefined ? String(value) : "";
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Handle dot notation like "user.profile.name"
|
|
267
|
+
if (props.nameField.includes(".")) {
|
|
268
|
+
const value = getNestedValue(object, props.nameField);
|
|
269
|
+
if (value !== null && value !== undefined && String(value).trim() !== "") {
|
|
270
|
+
return String(value);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Handle simple field name
|
|
275
|
+
const value = object[props.nameField];
|
|
276
|
+
if (value !== null && value !== undefined && String(value).trim() !== "") {
|
|
277
|
+
return String(value);
|
|
278
|
+
}
|
|
279
|
+
} catch (error) {
|
|
280
|
+
if (IS_DEV) console.warn("[BaseSelector] nameField parsing error:", error);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Priority 3: Common fallback fields
|
|
285
|
+
const fallbackFields = ["name", "title", "label", "display", "text"];
|
|
286
|
+
for (const field of fallbackFields) {
|
|
287
|
+
const value = object[field];
|
|
288
|
+
if (value !== null && value !== undefined && String(value).trim() !== "") {
|
|
289
|
+
return String(value);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Priority 4: ID as last resort
|
|
294
|
+
if (object.id !== null && object.id !== undefined) {
|
|
295
|
+
return String(object.id);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return "";
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Enhanced nested value getter with robust error handling
|
|
303
|
+
* Supports deep object traversal with dot notation
|
|
304
|
+
*
|
|
305
|
+
* @param obj - Object to traverse
|
|
306
|
+
* @param path - Dot notation path like "user.profile.name"
|
|
307
|
+
* @returns The nested value or null if not found
|
|
308
|
+
*/
|
|
309
|
+
function getNestedValue(obj: any, path: string): any {
|
|
310
|
+
if (!obj || !path) return null;
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
const keys = path.split(".");
|
|
314
|
+
let current = obj;
|
|
315
|
+
|
|
316
|
+
for (const key of keys) {
|
|
317
|
+
if (current === null || current === undefined) return null;
|
|
318
|
+
if (typeof current !== "object") return null;
|
|
319
|
+
current = current[key];
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return current;
|
|
323
|
+
} catch (error) {
|
|
324
|
+
if (IS_DEV) console.warn("[BaseSelector] getNestedValue error:", error);
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Computed property for display text in the selector trigger
|
|
331
|
+
* Handles both single and multiple selection display logic with enhanced parsing
|
|
332
|
+
*/
|
|
333
|
+
const displayText = computed(() => {
|
|
334
|
+
// Multiple selection display logic
|
|
335
|
+
if (props.multiple && selectedMultiple.value.length > 0) {
|
|
336
|
+
if (selectedMultiple.value.length === 1) {
|
|
337
|
+
const displayName = getEnhancedDisplayName(selectedMultiple.value[0]);
|
|
338
|
+
return displayName || props.placeholder;
|
|
339
|
+
}
|
|
340
|
+
return `${selectedMultiple.value.length} items selected`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Single selection display logic
|
|
344
|
+
if (selected.value) {
|
|
345
|
+
const displayName = getEnhancedDisplayName(selected.value);
|
|
346
|
+
return displayName || props.placeholder;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return props.placeholder;
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Helper function to check if an item is currently selected
|
|
354
|
+
* Works for both single and multiple selection modes
|
|
355
|
+
*/
|
|
356
|
+
const isItemSelected = (item: SelectItem) => {
|
|
357
|
+
if (props.multiple) {
|
|
358
|
+
// For multiple selection, check if item is in the checkedIds array
|
|
359
|
+
return checkedIds.value.includes(item.id);
|
|
360
|
+
}
|
|
361
|
+
return selected.value?.id === item.id;
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Select all checkbox status
|
|
366
|
+
* Returns true if all visible items are selected, false if none, 'indeterminate' if some
|
|
367
|
+
*/
|
|
368
|
+
const checkedAllStatus = ref<boolean | "indeterminate">(false);
|
|
369
|
+
|
|
370
|
+
// ===== CORE FUNCTIONS =====
|
|
371
|
+
/**
|
|
372
|
+
* Handles item selection for both single and multiple modes
|
|
373
|
+
* Emits appropriate change events based on returnObject prop
|
|
374
|
+
*
|
|
375
|
+
* @param option - The selected item
|
|
376
|
+
*/
|
|
377
|
+
function selectItem(option: SelectItem) {
|
|
378
|
+
if (props.multiple) {
|
|
379
|
+
// Multiple selection mode - toggle selection using checkedIds pattern
|
|
380
|
+
const checkedIdx = checkedIds.value.indexOf(option.id);
|
|
381
|
+
if (checkedIdx >= 0) {
|
|
382
|
+
// Remove if already selected (toggle off)
|
|
383
|
+
checkeds.value.splice(checkedIdx, 1);
|
|
384
|
+
checkedIds.value.splice(checkedIdx, 1);
|
|
385
|
+
} else {
|
|
386
|
+
// Add if not selected (toggle on)
|
|
387
|
+
checkeds.value.push(option);
|
|
388
|
+
checkedIds.value.push(option.id);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Update the selectedMultiple for display purposes
|
|
392
|
+
selectedMultiple.value = [...checkeds.value];
|
|
393
|
+
|
|
394
|
+
// Set model value based on returnObject prop
|
|
395
|
+
if (props.returnObject) {
|
|
396
|
+
model.value = selectedMultiple.value;
|
|
397
|
+
} else {
|
|
398
|
+
model.value = selectedMultiple.value.map(item => item.id);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (IS_DEV) {
|
|
402
|
+
console.log("[BaseSelector] selectItem - returnObject:", props.returnObject, "model.value:", model.value);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Refresh checkbox states
|
|
406
|
+
refreshCheckeds();
|
|
407
|
+
|
|
408
|
+
// Emit change event with proper format based on returnObject prop
|
|
409
|
+
setTimeout(() => {
|
|
410
|
+
if (props.returnObject) {
|
|
411
|
+
emits(
|
|
412
|
+
"change",
|
|
413
|
+
selectedMultiple.value.map(item => item.id),
|
|
414
|
+
selectedMultiple.value
|
|
415
|
+
);
|
|
416
|
+
} else {
|
|
417
|
+
emits(
|
|
418
|
+
"change",
|
|
419
|
+
selectedMultiple.value.map(item => item.id)
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
} else {
|
|
424
|
+
// Single selection mode
|
|
425
|
+
selected.value = option;
|
|
426
|
+
|
|
427
|
+
if (props.returnObject) {
|
|
428
|
+
model.value = option.id;
|
|
429
|
+
// Emit the full object for returnObject mode (like BaseSelectorSingleAvatar)
|
|
430
|
+
setTimeout(() => {
|
|
431
|
+
emits("change", option.id, option);
|
|
432
|
+
});
|
|
433
|
+
} else {
|
|
434
|
+
model.value = option.id;
|
|
435
|
+
// Emit only the ID for non-returnObject mode (like BaseSelectorSingle)
|
|
436
|
+
setTimeout(() => {
|
|
437
|
+
emits("change", option.id);
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Clear search and close dialog after single selection (only for single selection mode)
|
|
442
|
+
q.value = "";
|
|
443
|
+
if (!props.multiple) {
|
|
444
|
+
dialogOpen.value = false;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Clears the current selection
|
|
451
|
+
* Handles both single and multiple selection modes
|
|
452
|
+
*/
|
|
453
|
+
function clearItem() {
|
|
454
|
+
if (props.disabled) return;
|
|
455
|
+
|
|
456
|
+
if (props.multiple) {
|
|
457
|
+
selectedMultiple.value = [];
|
|
458
|
+
checkeds.value = [];
|
|
459
|
+
checkedIds.value = [];
|
|
460
|
+
model.value = [];
|
|
461
|
+
} else {
|
|
462
|
+
selected.value = null;
|
|
463
|
+
model.value = null;
|
|
464
|
+
}
|
|
465
|
+
q.value = ""; // Clear search query
|
|
466
|
+
|
|
467
|
+
// Emit change event for clearing with proper format
|
|
468
|
+
setTimeout(() => {
|
|
469
|
+
if (props.multiple) {
|
|
470
|
+
if (props.returnObject) {
|
|
471
|
+
emits("change", [], []);
|
|
472
|
+
} else {
|
|
473
|
+
emits("change", []);
|
|
474
|
+
}
|
|
475
|
+
} else {
|
|
476
|
+
if (props.returnObject) {
|
|
477
|
+
emits("change", null, null);
|
|
478
|
+
} else {
|
|
479
|
+
emits("change", null);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Selects all visible items in multiple selection mode
|
|
487
|
+
*/
|
|
488
|
+
function selectAll() {
|
|
489
|
+
if (!props.multiple || props.disabled) return;
|
|
490
|
+
|
|
491
|
+
if (!checkedAllStatus.value || checkedAllStatus.value === "indeterminate") {
|
|
492
|
+
// Select all visible items
|
|
493
|
+
const newSelections = data.value.filter(item => !checkedIds.value.includes(item.id));
|
|
494
|
+
|
|
495
|
+
// Add to checkeds and checkedIds
|
|
496
|
+
checkeds.value = [...checkeds.value, ...newSelections];
|
|
497
|
+
checkedIds.value = [...checkedIds.value, ...newSelections.map(item => item.id)];
|
|
498
|
+
|
|
499
|
+
// Update selectedMultiple for display
|
|
500
|
+
selectedMultiple.value = [...checkeds.value];
|
|
501
|
+
|
|
502
|
+
// Set model value based on returnObject prop
|
|
503
|
+
if (props.returnObject) {
|
|
504
|
+
model.value = selectedMultiple.value;
|
|
505
|
+
} else {
|
|
506
|
+
model.value = selectedMultiple.value.map(item => item.id);
|
|
507
|
+
}
|
|
508
|
+
} else {
|
|
509
|
+
// Deselect all
|
|
510
|
+
checkeds.value = [];
|
|
511
|
+
checkedIds.value = [];
|
|
512
|
+
selectedMultiple.value = [];
|
|
513
|
+
model.value = [];
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Refresh checkbox states
|
|
517
|
+
refreshCheckeds();
|
|
518
|
+
|
|
519
|
+
// Emit change event
|
|
520
|
+
setTimeout(() => {
|
|
521
|
+
if (props.returnObject) {
|
|
522
|
+
emits(
|
|
523
|
+
"change",
|
|
524
|
+
selectedMultiple.value.map(item => item.id),
|
|
525
|
+
selectedMultiple.value
|
|
526
|
+
);
|
|
527
|
+
} else {
|
|
528
|
+
emits(
|
|
529
|
+
"change",
|
|
530
|
+
selectedMultiple.value.map(item => item.id)
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Clears all selections in multiple selection mode
|
|
538
|
+
*/
|
|
539
|
+
function clearAllSelections() {
|
|
540
|
+
if (!props.multiple || props.disabled) return;
|
|
541
|
+
|
|
542
|
+
checkeds.value = [];
|
|
543
|
+
checkedIds.value = [];
|
|
544
|
+
selectedMultiple.value = [];
|
|
545
|
+
model.value = [];
|
|
546
|
+
|
|
547
|
+
// Refresh checkbox states
|
|
548
|
+
refreshCheckeds();
|
|
549
|
+
|
|
550
|
+
// Emit change event
|
|
551
|
+
setTimeout(() => {
|
|
552
|
+
if (props.returnObject) {
|
|
553
|
+
emits("change", [], []);
|
|
554
|
+
} else {
|
|
555
|
+
emits("change", []);
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Refreshes the isChecked property on data items based on checkedIds
|
|
562
|
+
* This is essential for proper checkbox state display
|
|
563
|
+
*/
|
|
564
|
+
function refreshCheckeds() {
|
|
565
|
+
if (!props.multiple) return;
|
|
566
|
+
|
|
567
|
+
let checkedAll = true;
|
|
568
|
+
let notCheckedAll = true;
|
|
569
|
+
|
|
570
|
+
data.value.forEach((row: SelectItem) => {
|
|
571
|
+
row.isChecked = checkedIds.value && checkedIds.value.includes(row.id);
|
|
572
|
+
if (row.isChecked) notCheckedAll = false;
|
|
573
|
+
else checkedAll = false;
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
// Update checkedAllStatus
|
|
577
|
+
if (checkedAll === notCheckedAll) {
|
|
578
|
+
checkedAllStatus.value = "indeterminate";
|
|
579
|
+
} else {
|
|
580
|
+
checkedAllStatus.value = checkedAll;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Finishes selection and closes dialog in multiple selection mode
|
|
586
|
+
*/
|
|
587
|
+
function finishSelection() {
|
|
588
|
+
if (!props.multiple) return;
|
|
589
|
+
|
|
590
|
+
// Update selectedMultiple with current checkeds
|
|
591
|
+
selectedMultiple.value = [...checkeds.value];
|
|
592
|
+
|
|
593
|
+
// Set model value based on returnObject prop
|
|
594
|
+
if (props.returnObject) {
|
|
595
|
+
model.value = selectedMultiple.value;
|
|
596
|
+
} else {
|
|
597
|
+
model.value = selectedMultiple.value.map(item => item.id);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Close the dialog
|
|
601
|
+
dialogOpen.value = false;
|
|
602
|
+
|
|
603
|
+
// Emit final change event with current selections
|
|
604
|
+
setTimeout(() => {
|
|
605
|
+
if (props.returnObject) {
|
|
606
|
+
emits(
|
|
607
|
+
"change",
|
|
608
|
+
selectedMultiple.value.map(item => item.id),
|
|
609
|
+
selectedMultiple.value
|
|
610
|
+
);
|
|
611
|
+
} else {
|
|
612
|
+
emits(
|
|
613
|
+
"change",
|
|
614
|
+
selectedMultiple.value.map(item => item.id)
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Handles pagination navigation
|
|
622
|
+
* Supports first, previous, next, and last page navigation
|
|
623
|
+
*
|
|
624
|
+
* @param type - Navigation type ('first', 'prev', 'next', 'last')
|
|
625
|
+
*/
|
|
626
|
+
function navigate(type: string) {
|
|
627
|
+
const totalPages = Math.ceil(pageMeta.value.total / pageMeta.value.limit);
|
|
628
|
+
|
|
629
|
+
switch (type) {
|
|
630
|
+
case "first":
|
|
631
|
+
if (pageMeta.value.page !== 1) {
|
|
632
|
+
pageMeta.value.page = 1;
|
|
633
|
+
} else {
|
|
634
|
+
return; // Already on first page, no need to reload
|
|
635
|
+
}
|
|
636
|
+
break;
|
|
637
|
+
case "prev":
|
|
638
|
+
if (pageMeta.value.page > 1) {
|
|
639
|
+
pageMeta.value.page -= 1;
|
|
640
|
+
} else {
|
|
641
|
+
return; // Already on first page
|
|
642
|
+
}
|
|
643
|
+
break;
|
|
644
|
+
case "next":
|
|
645
|
+
if (pageMeta.value.page < totalPages) {
|
|
646
|
+
pageMeta.value.page += 1;
|
|
647
|
+
} else {
|
|
648
|
+
return; // Already on last page
|
|
649
|
+
}
|
|
650
|
+
break;
|
|
651
|
+
case "last":
|
|
652
|
+
if (pageMeta.value.page !== totalPages && totalPages > 0) {
|
|
653
|
+
pageMeta.value.page = totalPages;
|
|
654
|
+
} else {
|
|
655
|
+
return; // Already on last page or no data
|
|
656
|
+
}
|
|
657
|
+
break;
|
|
658
|
+
default:
|
|
659
|
+
console.warn("Invalid navigation type:", type);
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
getData(); // Fetch data for the new page
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Fetches data from the API endpoint
|
|
667
|
+
* Handles pagination, search, and error states
|
|
668
|
+
* Uses the standardized API response format: { data: [], totalData: number }
|
|
669
|
+
*/
|
|
670
|
+
function getData() {
|
|
671
|
+
loading.value = true;
|
|
672
|
+
error.value = null;
|
|
673
|
+
|
|
674
|
+
// Check if endpoint is defined
|
|
675
|
+
if (!props.endpoint) {
|
|
676
|
+
console.error("BaseSelector: endpoint prop is required but not provided");
|
|
677
|
+
error.value = "No endpoint configured";
|
|
678
|
+
loading.value = false;
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Build the API endpoint URL with query parameters
|
|
683
|
+
const endpoint = props.endpoint.indexOf("?") > 0 ? `${props.endpoint}&` : `${props.endpoint}?`;
|
|
684
|
+
|
|
685
|
+
// Add search columns if not already present in endpoint
|
|
686
|
+
const searchColumns = endpoint.includes("searchColumns") ? "" : `&searchColumns=${props.searchColumns}`;
|
|
687
|
+
|
|
688
|
+
// Build dynamic parameters
|
|
689
|
+
const buildParams = () => {
|
|
690
|
+
let paramString = "";
|
|
691
|
+
|
|
692
|
+
// Add static params
|
|
693
|
+
if (props.params && Object.keys(props.params).length > 0) {
|
|
694
|
+
const paramEntries = Object.entries(props.params)
|
|
695
|
+
.filter(([_, value]) => value !== null && value !== undefined && value !== "")
|
|
696
|
+
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`);
|
|
697
|
+
if (paramEntries.length > 0) {
|
|
698
|
+
paramString += `&${paramEntries.join("&")}`;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Add condition params
|
|
703
|
+
if (props.condition && Object.keys(props.condition).length > 0) {
|
|
704
|
+
const conditionEntries = Object.entries(props.condition)
|
|
705
|
+
.filter(([_, value]) => value !== null && value !== undefined && value !== "")
|
|
706
|
+
.map(([key, value]) => `${key}:${encodeURIComponent(value)}`);
|
|
707
|
+
if (conditionEntries.length > 0) {
|
|
708
|
+
paramString += `&condition=${conditionEntries.join(",")}`;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Add orderBy parameter
|
|
713
|
+
if (props.orderBy) {
|
|
714
|
+
paramString += `&orderBy=${encodeURIComponent(props.orderBy)}`;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Add sort parameter
|
|
718
|
+
if (props.sort) {
|
|
719
|
+
paramString += `&sort=${encodeURIComponent(props.sort)}`;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
return paramString;
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
// Construct the full URI with pagination, search, and dynamic parameters
|
|
726
|
+
const uri = `${endpoint}limit=${pageMeta.value.limit}&page=${pageMeta.value.page}&search=${q.value}${searchColumns}${buildParams()}`;
|
|
727
|
+
|
|
728
|
+
api
|
|
729
|
+
.get(uri)
|
|
730
|
+
.then(r => {
|
|
731
|
+
// Handle standardized API response format
|
|
732
|
+
pageMeta.value.total = r.data.totalData || 0;
|
|
733
|
+
data.value = r.data.data || [];
|
|
734
|
+
error.value = null;
|
|
735
|
+
|
|
736
|
+
// Refresh checkbox states after data is loaded
|
|
737
|
+
if (props.multiple) {
|
|
738
|
+
refreshCheckeds();
|
|
739
|
+
}
|
|
740
|
+
})
|
|
741
|
+
.catch(err => {
|
|
742
|
+
console.error("BaseSelector getData error:", err);
|
|
743
|
+
// Reset data on error
|
|
744
|
+
pageMeta.value.total = 0;
|
|
745
|
+
data.value = [];
|
|
746
|
+
error.value = err.response?.data?.message || "Failed to load data";
|
|
747
|
+
|
|
748
|
+
// Show error toast for user feedback (except for auth errors)
|
|
749
|
+
if (err.response?.status !== 401 && error.value) {
|
|
750
|
+
toast.error(error.value);
|
|
751
|
+
}
|
|
752
|
+
})
|
|
753
|
+
.finally(() => {
|
|
754
|
+
loading.value = false;
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Handles search input with debouncing
|
|
760
|
+
* Uses the optimized debounced search function to prevent excessive API calls
|
|
761
|
+
*
|
|
762
|
+
* @param query - Search query string
|
|
763
|
+
*/
|
|
764
|
+
function searchItem(query: string) {
|
|
765
|
+
debouncedSearch(query);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function parsedDescription(object: SelectItem) {
|
|
769
|
+
if (!object || !props.descriptionField) return object?.description || "";
|
|
770
|
+
|
|
771
|
+
try {
|
|
772
|
+
const regex = /\{([a-zA-Z0-9_]+)\}/g;
|
|
773
|
+
|
|
774
|
+
if (regex.test(props.descriptionField)) {
|
|
775
|
+
return props.descriptionField.replace(regex, (_: any, fieldName: any) => {
|
|
776
|
+
if (fieldName.includes(".")) {
|
|
777
|
+
const fields = fieldName.split(".");
|
|
778
|
+
let value: any = object;
|
|
779
|
+
for (const field of fields) {
|
|
780
|
+
if (value && typeof value === "object") {
|
|
781
|
+
value = value[field];
|
|
782
|
+
} else {
|
|
783
|
+
return "";
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
return value || "";
|
|
787
|
+
}
|
|
788
|
+
return (object as any)[fieldName] || "";
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
if (props.descriptionField?.includes(".")) {
|
|
793
|
+
const fields = props.descriptionField.split(".");
|
|
794
|
+
let value: any = object;
|
|
795
|
+
for (const field of fields) {
|
|
796
|
+
if (value && typeof value === "object") {
|
|
797
|
+
value = value[field];
|
|
798
|
+
} else {
|
|
799
|
+
return "";
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return value || "";
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
return (object as any)[props.descriptionField] || "";
|
|
806
|
+
} catch (error) {
|
|
807
|
+
console.warn("Error parsing description field:", error);
|
|
808
|
+
return object?.description || "";
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function parsedField(object: SelectItem) {
|
|
813
|
+
return getEnhancedDisplayName(object);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function triggerClicked() {
|
|
817
|
+
if (props.disabled) return;
|
|
818
|
+
|
|
819
|
+
// Initialize checked state for multiple selection
|
|
820
|
+
if (props.multiple) {
|
|
821
|
+
checkeds.value = [...selectedMultiple.value];
|
|
822
|
+
checkedIds.value = selectedMultiple.value.map(item => item.id);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Clear any pending search timeout (handled by debounced search utility)
|
|
826
|
+
q.value = "";
|
|
827
|
+
pageMeta.value.page = 1; // Reset page to 1 when opening dialog
|
|
828
|
+
error.value = null; // Clear any previous errors
|
|
829
|
+
dialogOpen.value = true;
|
|
830
|
+
|
|
831
|
+
if (props.disableAutoLoad) {
|
|
832
|
+
getData(); // Load fresh data with empty search
|
|
833
|
+
} else {
|
|
834
|
+
getData(); // Always refresh data when opening dialog
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
watch(model, (newVal, oldVal) => {
|
|
839
|
+
// Avoid infinite loops
|
|
840
|
+
if (newVal === oldVal) return;
|
|
841
|
+
if (!model.value && !newVal) return;
|
|
842
|
+
|
|
843
|
+
if (props.multiple) {
|
|
844
|
+
// Handle multiple selection mode
|
|
845
|
+
if (Array.isArray(newVal)) {
|
|
846
|
+
// Check if it's an array of IDs or array of objects
|
|
847
|
+
if (newVal.length > 0 && typeof newVal[0] === "object") {
|
|
848
|
+
// Array of objects (returnObject = true)
|
|
849
|
+
selectedMultiple.value = newVal as SelectItem[];
|
|
850
|
+
checkeds.value = [...selectedMultiple.value];
|
|
851
|
+
checkedIds.value = selectedMultiple.value.map(item => item.id);
|
|
852
|
+
} else {
|
|
853
|
+
// Array of IDs (returnObject = false) - need to fetch objects for display
|
|
854
|
+
checkedIds.value = newVal as (string | number)[];
|
|
855
|
+
// Keep selectedMultiple as is for display purposes, will be updated on data fetch
|
|
856
|
+
}
|
|
857
|
+
} else if (!newVal) {
|
|
858
|
+
selectedMultiple.value = [];
|
|
859
|
+
checkeds.value = [];
|
|
860
|
+
checkedIds.value = [];
|
|
861
|
+
}
|
|
862
|
+
} else {
|
|
863
|
+
// Handle single selection mode
|
|
864
|
+
if (newVal && typeof newVal === "string" && model.value === newVal) return;
|
|
865
|
+
|
|
866
|
+
if (newVal && typeof newVal === "object" && (newVal as any).length > 0) {
|
|
867
|
+
selected.value = (newVal as any)[0] as SelectItem;
|
|
868
|
+
model.value = selected.value ? selected.value.id : null;
|
|
869
|
+
} else if (!newVal) {
|
|
870
|
+
selected.value = null;
|
|
871
|
+
model.value = null;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// Emit change event with proper delay to avoid race conditions
|
|
876
|
+
setTimeout(() => {
|
|
877
|
+
if (props.multiple) {
|
|
878
|
+
if (props.returnObject) {
|
|
879
|
+
emits(
|
|
880
|
+
"change",
|
|
881
|
+
selectedMultiple.value.map(item => item.id),
|
|
882
|
+
selectedMultiple.value
|
|
883
|
+
);
|
|
884
|
+
} else {
|
|
885
|
+
emits(
|
|
886
|
+
"change",
|
|
887
|
+
selectedMultiple.value.map(item => item.id)
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
} else {
|
|
891
|
+
if (typeof model.value === "string" || typeof model.value === "number" || model.value === null) {
|
|
892
|
+
if (props.returnObject) {
|
|
893
|
+
emits("change", model.value, selected.value);
|
|
894
|
+
} else {
|
|
895
|
+
emits("change", model.value);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}, 0);
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
// Watch dialog open state to ensure search is reset
|
|
903
|
+
watch(dialogOpen, newValue => {
|
|
904
|
+
if (newValue) {
|
|
905
|
+
// Ensure search query is reset when dialog opens
|
|
906
|
+
q.value = "";
|
|
907
|
+
pageMeta.value.page = 1;
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
onMounted(() => {
|
|
912
|
+
setTimeout(() => {
|
|
913
|
+
if (model.value) {
|
|
914
|
+
if ((typeof model.value === "string" || typeof model.value === "number") && (!selected.value || (selected.value && selected.value.id !== model.value))) {
|
|
915
|
+
const detailUri = props.endpoint.split("?")[0];
|
|
916
|
+
api
|
|
917
|
+
.get(`${detailUri}/${model.value}`)
|
|
918
|
+
.then(r => {
|
|
919
|
+
selected.value = r.data.data as SelectItem;
|
|
920
|
+
model.value = selected.value ? selected.value.id : null;
|
|
921
|
+
})
|
|
922
|
+
.catch(err => {
|
|
923
|
+
console.error("Failed to load selected item details:", err);
|
|
924
|
+
// Don't show toast for this error as it might be expected in some cases
|
|
925
|
+
});
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
if (typeof model.value === "object" && (model.value as any).length > 0) {
|
|
930
|
+
selected.value = (model.value as any)[0] as SelectItem;
|
|
931
|
+
model.value = selected.value ? selected.value.id : null;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}, 0);
|
|
935
|
+
|
|
936
|
+
// Auto fill organization functionality (from BaseSelectorSingleAvatar)
|
|
937
|
+
if (props.autoFillOrg) {
|
|
938
|
+
api
|
|
939
|
+
.get("/organization/my")
|
|
940
|
+
.then(r => {
|
|
941
|
+
// Enhanced accessLevel filtering and validation
|
|
942
|
+
const orgData = r.data.data;
|
|
943
|
+
if (!props.accessLevel) {
|
|
944
|
+
// No access level restriction, auto-fill with organization
|
|
945
|
+
selectItem(orgData);
|
|
946
|
+
} else if (orgData.accessLevel && props.accessLevel === orgData.accessLevel.code) {
|
|
947
|
+
// Access level matches, auto-fill with organization
|
|
948
|
+
selectItem(orgData);
|
|
949
|
+
} else {
|
|
950
|
+
// Access level doesn't match, don't auto-fill but don't show error
|
|
951
|
+
console.info("Organization access level does not match required level:", {
|
|
952
|
+
required: props.accessLevel,
|
|
953
|
+
actual: orgData.accessLevel?.code
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
})
|
|
957
|
+
.catch(err => {
|
|
958
|
+
console.error("Auto fill organization failed:", err);
|
|
959
|
+
// Only show error toast if it's not a 401/403 (auth) error
|
|
960
|
+
if (err.response?.status !== 401 && err.response?.status !== 403) {
|
|
961
|
+
toast.error("Get my organization failed!");
|
|
962
|
+
}
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
if (!props.disableAutoLoad) getData();
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
// Cleanup on component unmount (performance optimization)
|
|
970
|
+
onUnmounted(() => {
|
|
971
|
+
cleanup();
|
|
972
|
+
});
|
|
973
|
+
</script>
|
|
974
|
+
|
|
975
|
+
<template>
|
|
976
|
+
<div class="relative w-full items-center" :class="{ '!cursor-default': props.disabled }" v-if="props.showTrigger !== false">
|
|
977
|
+
<div
|
|
978
|
+
:class="[
|
|
979
|
+
cn(
|
|
980
|
+
'form-input-group group flex min-h-0 w-full flex-1 cursor-pointer border-solid transition-input duration-200 form-radius-input-lg form-color-input form-bg-input form-shadow-input form-border-width-input form-border-color-input hover:form-color-input-hover hover:form-bg-input-hover hover:form-border-color-input-hover hover:form-shadow-input-hover focused:form-color-input-focus focused:form-bg-input-focus focused:form-border-color-input-focus focused:form-ring focused:form-shadow-input-focus focused-hover:form-shadow-input-hover',
|
|
981
|
+
props.class,
|
|
982
|
+
{ '!cursor-default !bg-gray-100 form-bg-disabled': props.disabled }
|
|
983
|
+
)
|
|
984
|
+
]"
|
|
985
|
+
v-on:click="triggerClicked">
|
|
986
|
+
<div
|
|
987
|
+
class="w-full border-0 bg-transparent form-text-lg form-radius-input-lg form-color-input form-autofill-default form-p-input-lg group-hover:form-color-input-hover with-floating:form-p-input-floating-lg"
|
|
988
|
+
:class="{ '': selected }">
|
|
989
|
+
<div class="text-left" v-if="(multiple && selectedMultiple.length > 0) || (!multiple && selected && model)">
|
|
990
|
+
<!-- Avatar display when showAvatar is true -->
|
|
991
|
+
<template v-if="showAvatar && !multiple">
|
|
992
|
+
<Avatar class="absolute left-2 top-1/2 -mt-3.5 size-7" :class="avatarColorClass">
|
|
993
|
+
<AvatarImage v-if="selected?.avatar" :src="selected.avatar" />
|
|
994
|
+
<AvatarFallback class="text-sm font-medium" :class="avatarTextColorClass">
|
|
995
|
+
{{ selected?.name?.substring(0, 1).toUpperCase() || "?" }}
|
|
996
|
+
</AvatarFallback>
|
|
997
|
+
</Avatar>
|
|
998
|
+
<div class="my-auto w-full pl-8 pr-7">
|
|
999
|
+
<div class="overflow-hidden text-ellipsis whitespace-nowrap font-medium">
|
|
1000
|
+
{{ displayText }}
|
|
1001
|
+
</div>
|
|
1002
|
+
</div>
|
|
1003
|
+
</template>
|
|
1004
|
+
<!-- Multiple selection display with avatar -->
|
|
1005
|
+
<template v-else-if="showAvatar && multiple">
|
|
1006
|
+
<div class="flex items-center gap-1 pl-2 pr-7">
|
|
1007
|
+
<div class="flex -space-x-1" v-if="selectedMultiple.length <= 3">
|
|
1008
|
+
<Avatar v-for="item in selectedMultiple.slice(0, 3)" :key="item.id" class="size-6 border-2 border-white" :class="avatarColorClass">
|
|
1009
|
+
<AvatarImage v-if="item.avatar" :src="item.avatar" />
|
|
1010
|
+
<AvatarFallback class="text-xs font-medium" :class="avatarTextColorClass">
|
|
1011
|
+
{{ item.name.substring(0, 1).toUpperCase() }}
|
|
1012
|
+
</AvatarFallback>
|
|
1013
|
+
</Avatar>
|
|
1014
|
+
</div>
|
|
1015
|
+
<Avatar v-else class="size-6" :class="avatarColorClass">
|
|
1016
|
+
<AvatarFallback class="text-xs font-medium" :class="avatarTextColorClass">
|
|
1017
|
+
{{ selectedMultiple.length }}
|
|
1018
|
+
</AvatarFallback>
|
|
1019
|
+
</Avatar>
|
|
1020
|
+
<div class="ml-2 overflow-hidden text-ellipsis whitespace-nowrap font-medium">
|
|
1021
|
+
{{ displayText }}
|
|
1022
|
+
</div>
|
|
1023
|
+
</div>
|
|
1024
|
+
</template>
|
|
1025
|
+
<!-- Regular display when showAvatar is false -->
|
|
1026
|
+
<template v-else>
|
|
1027
|
+
<div class="max-w-full pr-10">
|
|
1028
|
+
<div class="group relative w-full overflow-hidden text-ellipsis whitespace-nowrap font-medium">
|
|
1029
|
+
<div v-if="props.nameFieldFunc && !multiple && selected" class="overflow-hidden text-ellipsis whitespace-nowrap" v-html="props.nameFieldFunc(selected)"></div>
|
|
1030
|
+
<template v-else>
|
|
1031
|
+
{{ displayText }}
|
|
1032
|
+
<div class="absolute left-0 top-full hidden rounded-sm bg-black/75 px-1.5 pb-0.5 pt-0.5 text-[11px] font-normal leading-snug text-white group-hover:block">
|
|
1033
|
+
{{ displayText }}
|
|
1034
|
+
</div>
|
|
1035
|
+
</template>
|
|
1036
|
+
</div>
|
|
1037
|
+
</div>
|
|
1038
|
+
</template>
|
|
1039
|
+
</div>
|
|
1040
|
+
<div class="text-left text-gray-400 dark:text-gray-500" v-else>
|
|
1041
|
+
{{ props.placeholder }}
|
|
1042
|
+
</div>
|
|
1043
|
+
</div>
|
|
1044
|
+
</div>
|
|
1045
|
+
<span class="absolute inset-y-0 end-0 flex items-center justify-center px-2 text-slate-400 dark:text-slate-500" v-if="!props.disabled">
|
|
1046
|
+
<BtnCircle
|
|
1047
|
+
class="mr-1 h-6 w-6 cursor-pointer bg-slate-100 hover:bg-slate-200 dark:bg-slate-700 dark:hover:bg-slate-600"
|
|
1048
|
+
:class="{ '!size-6': showAvatar }"
|
|
1049
|
+
v-on:click.stop="clearItem"
|
|
1050
|
+
v-if="(multiple && selectedMultiple.length > 0) || (!multiple && selected && model)">
|
|
1051
|
+
<LucideX :size="14"></LucideX>
|
|
1052
|
+
</BtnCircle>
|
|
1053
|
+
<ChevronRight v-else />
|
|
1054
|
+
</span>
|
|
1055
|
+
</div>
|
|
1056
|
+
|
|
1057
|
+
<DialogSelect
|
|
1058
|
+
:options="data"
|
|
1059
|
+
@select="selectItem"
|
|
1060
|
+
@search="searchItem"
|
|
1061
|
+
@select-all="selectAll"
|
|
1062
|
+
@clear-selections="clearAllSelections"
|
|
1063
|
+
@finish-selection="finishSelection"
|
|
1064
|
+
:loading="loading"
|
|
1065
|
+
:page-meta="pageMeta"
|
|
1066
|
+
@navigate="navigate"
|
|
1067
|
+
:show-trigger="false"
|
|
1068
|
+
v-model:open="dialogOpen"
|
|
1069
|
+
option-attribute="name"
|
|
1070
|
+
value-attribute="id"
|
|
1071
|
+
description-attribute="description"
|
|
1072
|
+
:display-search="true"
|
|
1073
|
+
:display-pagination="true"
|
|
1074
|
+
:select-all-loading="false"
|
|
1075
|
+
:mutiple="multiple"
|
|
1076
|
+
:selected-count="checkeds.length"
|
|
1077
|
+
:checked-all-status="checkedAllStatus">
|
|
1078
|
+
<template #option="{ option }">
|
|
1079
|
+
<!-- Avatar option display when showAvatar is true -->
|
|
1080
|
+
<div
|
|
1081
|
+
v-if="showAvatar"
|
|
1082
|
+
class="flex flex-row items-start gap-3 rounded-md px-6 py-2"
|
|
1083
|
+
:class="{
|
|
1084
|
+
'bg-slate-200 dark:bg-dark_bg3': option.isChecked,
|
|
1085
|
+
'bg-blue-50': multiple && option.isChecked
|
|
1086
|
+
}">
|
|
1087
|
+
<Avatar class="size-9" :class="avatarColorClass">
|
|
1088
|
+
<AvatarImage v-if="option.avatar" :src="option.avatar" />
|
|
1089
|
+
<AvatarFallback class="text-sm font-semibold" :class="avatarTextColorClass">
|
|
1090
|
+
{{ option.name.substring(0, 1).toUpperCase() }}
|
|
1091
|
+
</AvatarFallback>
|
|
1092
|
+
</Avatar>
|
|
1093
|
+
<div class="my-auto flex-1">
|
|
1094
|
+
<div class="break-words font-semibold leading-[1.25]">{{ parsedField(option) || option.name || option.id }}</div>
|
|
1095
|
+
<div class="text-[13px] font-normal text-gray-500 dark:text-gray-400" v-if="props.condition?.levels">
|
|
1096
|
+
{{ option.accessLevel.name }}
|
|
1097
|
+
</div>
|
|
1098
|
+
</div>
|
|
1099
|
+
<!-- Checkbox for multiple selection -->
|
|
1100
|
+
<div v-if="multiple" class="flex items-center" @click.stop="selectItem(option)">
|
|
1101
|
+
<CheckboxElement :checked="option.isChecked" class="mt-1.5" />
|
|
1102
|
+
</div>
|
|
1103
|
+
</div>
|
|
1104
|
+
<!-- Regular option display when showAvatar is false -->
|
|
1105
|
+
<div
|
|
1106
|
+
v-else
|
|
1107
|
+
class="flex items-center rounded-md px-6 py-2"
|
|
1108
|
+
:class="{
|
|
1109
|
+
'bg-slate-200 dark:bg-dark_bg3': option.isChecked,
|
|
1110
|
+
'bg-blue-50': multiple && option.isChecked
|
|
1111
|
+
}">
|
|
1112
|
+
<div class="my-auto flex w-full flex-1 flex-col">
|
|
1113
|
+
<div class="font-semibold">
|
|
1114
|
+
<div v-if="props.nameFieldFunc" v-html="props.nameFieldFunc(option)"></div>
|
|
1115
|
+
<template v-else>
|
|
1116
|
+
{{ parsedField(option) || option.name || option.id }}
|
|
1117
|
+
</template>
|
|
1118
|
+
</div>
|
|
1119
|
+
<div class="-mt-[2px] w-full text-sm font-normal text-gray-500 dark:text-gray-400">
|
|
1120
|
+
<div v-if="props.descriptionFieldFunc" v-html="props.descriptionFieldFunc(option)"></div>
|
|
1121
|
+
<template v-else-if="parsedDescription(option)">
|
|
1122
|
+
{{ parsedDescription(option) }}
|
|
1123
|
+
</template>
|
|
1124
|
+
</div>
|
|
1125
|
+
</div>
|
|
1126
|
+
<!-- Checkbox for multiple selection -->
|
|
1127
|
+
<div v-if="multiple" class="ml-2 flex items-center" @click.stop="selectItem(option)">
|
|
1128
|
+
<CheckboxElement :checked="option.isChecked" class="mt-1.5" />
|
|
1129
|
+
</div>
|
|
1130
|
+
</div>
|
|
1131
|
+
</template>
|
|
1132
|
+
<template #title>
|
|
1133
|
+
{{ props.title }}
|
|
1134
|
+
</template>
|
|
1135
|
+
</DialogSelect>
|
|
1136
|
+
</template>
|