Compare commits
4 Commits
dev
...
7351-claim
Author | SHA1 | Date |
---|---|---|
|
c40cf4b9e8 | |
|
cfda06380a | |
|
473d4b1239 | |
|
342b80032b |
|
@ -1 +0,0 @@
|
|||
node_modules
|
|
@ -31,8 +31,3 @@ yarn-error.log*
|
|||
# Cypress directories and files
|
||||
/test/cypress/videos
|
||||
/test/cypress/screenshots
|
||||
/junit
|
||||
|
||||
# VitePress directories and files
|
||||
/docs/.vitepress/cache
|
||||
/docs/.vuepress
|
||||
|
|
477
CHANGELOG.md
477
CHANGELOG.md
|
@ -1,480 +1,3 @@
|
|||
# Version 25.08 - 2025-03-04
|
||||
|
||||
### Added 🆕
|
||||
|
||||
- feat: add order for table (origin/8681_ticketAdvance_updates) by:Javier Segarra
|
||||
- feat: detect when is descriptor proxy by:Javier Segarra
|
||||
- feat: refs #7356 update CrudModel by:Javier Segarra
|
||||
- feat: refs #8242 remove teleport by:Javier Segarra
|
||||
- feat: refs #8242 use stateStore by:Javier Segarra
|
||||
- fix: fixed negative bases style by:Jon
|
||||
- fix: fixed style when clicking on icons by:Jon
|
||||
- refactor: refs #6897 remove debug logs and unused style (origin/6897-fixSomeCaus) by:pablone
|
||||
- style: refs #7356 eslint format by:Javier Segarra
|
||||
|
||||
### Changed 📦
|
||||
|
||||
- perf: refs #7356 minor changes (origin/7356_ticketService) by:Javier Segarra
|
||||
- refactor: refs #6897 remove debug logs and unused style (origin/6897-fixSomeCaus) by:pablone
|
||||
- refactor: refs #6897 update component props and attributes for consistency and improved functionality (origin/6897-fixMinorIssues) by:pablone
|
||||
- refactor: refs #6897 update component props and improve UI handling in Entry pages by:pablone
|
||||
- refactor: refs #6897 update VnTable components for improved value handling and UI adjustments (origin/6897-minorFixes) by:pablone
|
||||
- refactor: refs #8697 simplify date handling in ItemDiary component by:pablone
|
||||
|
||||
### Fixed 🛠️
|
||||
|
||||
- fix: add datakey by:Javier Segarra
|
||||
- fix: fixed account descriptor menu and created e2e by:Jon
|
||||
- fix: fixed negative bases style by:Jon
|
||||
- fix: fixed style when clicking on icons by:Jon
|
||||
- fix: refs #6553 workerBusiness (origin/6553-fixWorkerBusinessV2) by:carlossa
|
||||
- fix: refs #6553 workerBusiness v3 by:carlossa
|
||||
- fix: refs #6897 prevent default event behavior in autocompleteExpense function by:pablone
|
||||
- fix: refs #7356 chaining params by:Javier Segarra
|
||||
- fix: refs #7356 ticketService by:Javier Segarra
|
||||
- fix: refs #8242 workerDepartmentTree bug (origin/8242_leftMenu_responsive) by:Javier Segarra
|
||||
- fix: workerBasicData by:carlossa
|
||||
- Revert "revert 1015acefb7e400be2d8b5958dba69b4d98276b34" (origin/fix_revert_revert, fix_revert_revert) by:alexm
|
||||
|
||||
# Version 25.06 - 2025-02-18
|
||||
|
||||
### Added 🆕
|
||||
|
||||
- chore: refs #7405 remove examples documentation by:jorgep
|
||||
- chore: refs #7405 remove VitePress cache files and update .gitignore by:jorgep
|
||||
- chore: refs #8316 remove search and searchInfo entries from shelving in English and Spanish locales by:jtubau
|
||||
- feat: #8258 added hover and description to uppercase button by:PAU ROVIRA ROSALENY
|
||||
- feat: add addressFk by:Javier Segarra
|
||||
- feat: add inactive car icon by:jorgep
|
||||
- feat: downgrade pnpm by:Javier Segarra
|
||||
- feat: new command by:Javier Segarra
|
||||
- feat: refs #6629 addressObservation by:robert
|
||||
- feat: refs #6629 change values by:robert
|
||||
- feat: refs #6629 customerAddressEdit by:robert
|
||||
- feat: refs #6629 delete consolelog by:robert
|
||||
- feat: refs #6629 traduction message by:robert
|
||||
- feat: refs #6629 update by:robert
|
||||
- feat: refs #6822 by:robert
|
||||
- feat: refs #6822 change request by:robert
|
||||
- feat: refs #6822 change traduction Partial delay (origin/6822-changeTitlePartialDelay) by:robert
|
||||
- feat: refs #6822 redirection by:robert
|
||||
- feat: refs #6943 addressPropagate by:Javier Segarra
|
||||
- feat: refs #6943 updateAndEmit param as object by:Javier Segarra
|
||||
- feat: refs #7065 created unit tests for UserPanel by:provira
|
||||
- feat: refs #7068 created VnVisibleColumns unit test by:Jon
|
||||
- feat: refs #7103 created test for VnSearchbar by:provira
|
||||
- feat: refs #7134 #7124 handle columns by:Javier Segarra
|
||||
- feat: refs #7134 #7124 handle filter by:Javier Segarra
|
||||
- feat: refs #7134 #7134 Create new route by:Javier Segarra
|
||||
- feat: refs #7134 #7134 Create SupplierBalance layout by:Javier Segarra
|
||||
- feat: refs #7134 #7134 split newPayment by:Javier Segarra
|
||||
- feat: refs #7134 add bank by:Javier Segarra
|
||||
- feat: refs #7134 apply supplierBalanceFilter by:Javier Segarra
|
||||
- feat: refs #7134 default currency parameter by:Javier Segarra
|
||||
- feat: refs #7134 minor changes by:Javier Segarra
|
||||
- feat: refs #7134 order by:Javier Segarra
|
||||
- feat: refs #7134 perf VnTable by:Javier Segarra
|
||||
- feat: refs #7134 remove add btn by:Javier Segarra
|
||||
- feat: refs #7134 unremovableParams by:Javier Segarra
|
||||
- feat: refs #7134 use tableFooter by:Javier Segarra
|
||||
- feat: refs #7134 use VnAccountNumber by:Javier Segarra
|
||||
- feat: refs #7134 vnTable setTableFooter by:Javier Segarra
|
||||
- feat: refs #7184 added myTeam filter at WorkerFilter by:Jon
|
||||
- feat: refs #7196 update vite and q-calendar by:alexm
|
||||
- feat: refs #7305 deleted warnings by:Jon
|
||||
- feat: refs #7308 remove warning by:Javier Segarra
|
||||
- feat: refs #7317 deleted warnings in fiscalData and dms by:Jon
|
||||
- feat: refs #7322 add address selection for ticket transfer by:jtubau
|
||||
- feat: refs #7405 add initial documentation and components for Lilium by:jorgep
|
||||
- feat: refs #7405 add navigation links and documentation for useArrayData composable by:jorgep
|
||||
- feat: refs #7826 add error handling and refresh icon to NavBar by:Javier Segarra
|
||||
- feat: refs #8077 change request by:robert
|
||||
- feat: refs #8077 changes request by:robert
|
||||
- feat: refs #8077 sumDefaulter by:robert
|
||||
- feat: refs #8120 added new style to summary popups by:Jon
|
||||
- feat: refs #8120 use new prop in the requierd modules by:Jon
|
||||
- feat: refs #8197 create advancedMenu and add in VnSection by:alexm
|
||||
- feat: refs #8316 added order param by:jtubau
|
||||
- feat: refs #8316 add slots on VnTable from VnFilterPanel by:alexm
|
||||
- feat: refs #8316 parking inside shelving by:alexm
|
||||
- feat: refs #8322 added RouteRoadmap and Agency by:provira
|
||||
- feat: refs #8322 fix route.js and unify with /agency by:alexm
|
||||
- feat: refs #8322 fix route.js and unify with /roadmap by:alexm
|
||||
- feat: refs #8339 define global.spreview by:Javier Segarra
|
||||
- feat: refs #8381 add carrier field to travel thermographs and update localization by:jgallego
|
||||
- feat: refs #8387 changes by:robert
|
||||
- feat: refs #8387 changes request by:robert
|
||||
- feat: refs #8387 crudModel by:robert
|
||||
- feat: refs #8387 refs#8387 change request by:robert
|
||||
- feat: refs #8395 added computed to calculate and display amounts by:provira
|
||||
- feat: refs #8395 added total column in invoiceInVat by:provira
|
||||
- feat: refs #8398 modify previous changes by:robert
|
||||
- feat: refs #8398 moveTicketsFuture by:robert
|
||||
- feat: refs #8409 added VnSelectSupplier by:Jon
|
||||
- feat: refs #8410 added new feature to module searchbar by:provira
|
||||
- feat: refs #8418 add data-cy attribute for print labels button in EntryBuysTableDialog by:jtubau
|
||||
- feat: refs #8450 added new version by:Jon
|
||||
- feat: toCurrency in risk icon by:Javier Segarra
|
||||
- feat: update quasar version by:Javier Segarra
|
||||
- feat: update vitest to 1.0 by:Javier Segarra
|
||||
- feat: update vue to 3.5 by:Javier Segarra
|
||||
- style: customerDescriptor by:Javier Segarra
|
||||
- style: refs #6943 order imports by:Javier Segarra
|
||||
|
||||
### Changed 📦
|
||||
|
||||
- feat: refs #7134 perf VnTable by:Javier Segarra
|
||||
- perf: pnpm-lock by:Javier Segarra
|
||||
- perf: refs #7134 #7134 changes by:Javier Segarra
|
||||
- perf: refs #7134 #7134 fix filter panel by:Javier Segarra
|
||||
- perf: refs #7134 #7134 global dialog newPayment and composable getRisk by:Javier Segarra
|
||||
- perf: refs #7134 currencies fetch by:Javier Segarra
|
||||
- perf: refs #7134 format columns by:Javier Segarra
|
||||
- perf: refs #7134 imports by:Javier Segarra
|
||||
- perf: refs #7134 use ForModelPopup by:Javier Segarra
|
||||
- perf: refs #7134 use map-key by:Javier Segarra
|
||||
- perf: refs #7134 use where to get only EUR currency by:Javier Segarra
|
||||
- perf: refs #7196 update eslint by:alexm
|
||||
- perf: refs #7308 call 1 time useSession by:Javier Segarra
|
||||
- perf: refs #7826 code onError by:Javier Segarra
|
||||
- perf: refs #7826 improve condition by:Javier Segarra
|
||||
- perf: refs #8197 default is object by:alexm
|
||||
- perf: refs #8197 fix and imrpove filters by:alexm
|
||||
- perf: refs #8197 perf by:alexm
|
||||
- perf: refs #8339 minor changes by:Javier Segarra
|
||||
- perf: refs #8339 removew preview tag by:Javier Segarra
|
||||
- perf: use util in OutLayout by:Javier Segarra
|
||||
- perf: vitest to 0.34.0 by:Javier Segarra
|
||||
- refactor: advancedMenu button inside searchbar by:alexm
|
||||
- refactor: move remaining data to descriptorMenu by:Jon
|
||||
- refactor: refs #6822 transferEntry moved to descriptor menu by:robert
|
||||
- refactor: refs #7068 adjust variables by:Jon
|
||||
- refactor: refs #7068 requested changes by:Jon
|
||||
- refactor: refs #7317 requested changes by:Jon
|
||||
- refactor: refs #7322 extract repeated functions and create tests by:jtubau
|
||||
- refactor: refs #7322 update API functions to accept filters for enhanced data retrieval by:jtubau
|
||||
- refactor: refs #7322 update getAgencies to handle client and return default agency by:jtubau
|
||||
- refactor: refs #8120 change prop and classes' names by:Jon
|
||||
- refactor: refs #8120 requested changes by:Jon
|
||||
- refactor: refs #8120 use only defineProps by:Jon
|
||||
- refactor: refs #8316 added shelvingCardBeta and localizations by:jtubau
|
||||
- refactor: refs #8316 add new localization keys and update existing ones for invoiceIn components by:jtubau
|
||||
- refactor: refs #8316 add new localization keys and update existing ones for invoiceOut components by:jtubau
|
||||
- refactor: refs #8316 moved userFilter to array-data-props by:jtubau
|
||||
- refactor: refs #8316 remove invoiceInSearchbar by:alexm
|
||||
- refactor: refs #8316 remove unused ItemTypeSearchbar component by:jtubau
|
||||
- refactor: refs #8316 restore exprBuilder function to filter invoice data by:jtubau
|
||||
- refactor: refs #8316 restore filter for supplier and related entities in InvoiceInCard by:jtubau
|
||||
- refactor: refs #8316 unify router item and itemType by:alexm
|
||||
- refactor: refs #8316 update prefix casing for InvoiceIn component by:jtubau
|
||||
- refactor: refs #8316 update Spanish translations for ItemsFilterPanel by:jtubau
|
||||
- refactor: refs #8316 used VnSection and VnBetaCard by:jtubau
|
||||
- refactor: refs #8316 used VnSection and VnCardBeta by:jtubau
|
||||
- refactor: refs #8316 used VnSection and VnCardBeta on ItemCard by:jtubau
|
||||
- refactor: refs #8322 changed Route component to use VnSection/VnCardBeta by:provira
|
||||
- refactor: refs #8322 changed Travel component to use VnSection/VnCardBeta by:provira
|
||||
- refactor: refs #8351 deleted skip and fixed TicketList e2e by:Jon
|
||||
- refactor: refs #8351 put appropriate name by:Jon
|
||||
- refactor: refs #8380 remove unnecessary stubs in VnImg test wrapper by:jtubau
|
||||
- refactor: refs #8381 update travel data handling in TravelThermographs component by:jgallego
|
||||
- refactor: refs #8409 deleted unused variable by:Jon
|
||||
- refactor: refs #8409 use defineModel instead or defineProps by:Jon
|
||||
- refactor: refs #8410 restructured code by:provira
|
||||
- refactor: refs #8418 remove commented issue reference from myEntry.spec.js by:jtubau
|
||||
- refactor: refs #8418 update data-cy attribute for print labels button in EntryBuysTableDialog by:jtubau
|
||||
- refactor: refs #8418 update selector to use cy.dataCy instead cy.get by:jtubau
|
||||
- refactor: request changes by:Jon
|
||||
|
||||
### Fixed 🛠️
|
||||
|
||||
- feat: refs #8322 fix route.js and unify with /agency by:alexm
|
||||
- feat: refs #8322 fix route.js and unify with /roadmap by:alexm
|
||||
- fix: added witdth when opening summary by:Jon
|
||||
- fix: defineProps not import by:alexm
|
||||
- fix: deleted duplicate request by:Jon
|
||||
- fix: fixed descriptor e2e by:Jon
|
||||
- fix: fixed InvoiceOutList e2e by:Jon
|
||||
- fix: fixed list and e2e by:Jon
|
||||
- fix: fixed pagiante by:Jon
|
||||
- fix: fixed rectificative class by:Jon
|
||||
- fix: fixed states column in claim list and filter by:Jon
|
||||
- fix: fixed VnLocation and warnings by:Jon
|
||||
- fix: fixed wagons e2e (origin/Fix-WagonModuleE2E) by:Jon
|
||||
- fix: fix grid two by:carlossa
|
||||
- fix: improve method (origin/warmfix_reload_scriptIsMissing) by:Javier Segarra
|
||||
- fix: init by:Javier Segarra
|
||||
- fix: minor cli error by:Javier Segarra
|
||||
- fix: modified front to show new field by:Jon
|
||||
- fix: move dialog to descriptorMenu by:Jon
|
||||
- fix: refs #6553 clean pr by:carlossa
|
||||
- fix: refs #6553 fix BeforeMount filters by:carlossa
|
||||
- fix: refs #6553 fix front and translations by:carlossa
|
||||
- fix: refs #6553 fix pr by:carlossa
|
||||
- fix: refs #6553 fix PR, fix vnTableCard by:carlossa
|
||||
- fix: refs #6553 fix qScrollArea by:carlossa
|
||||
- fix: refs #6553 fix summary by:carlossa
|
||||
- fix: refs #6553 fix user-filter by:carlossa
|
||||
- fix: refs #6553 fix vnTable by:carlossa
|
||||
- fix: refs #6553 fix vnTable css by:carlossa
|
||||
- fix: refs #6553 front advanced by:carlossa
|
||||
- fix: refs #6553 front by:carlossa
|
||||
- fix: refs #6553 label css by:carlossa
|
||||
- fix: refs #6553 onBeforeMount by:carlossa
|
||||
- fix: refs #6943 minor changes by:Javier Segarra
|
||||
- fix: refs #6943 redirect when change addressId by:Javier Segarra
|
||||
- fix: refs #6943 required by:Javier Segarra
|
||||
- fix: refs #7065 made consts for repeated values by:provira
|
||||
- fix: refs #7065 removed unnecessary code by:provira
|
||||
- fix: refs #7103 removed unused code on spies by:provira
|
||||
- fix: refs #7103 updated tests for new changes by:provira
|
||||
- fix: refs #7103 used consts for repeated variables by:provira
|
||||
- fix: refs #7134 getRiskComposable by:Javier Segarra
|
||||
- fix: refs #7134 minor change by:Javier Segarra
|
||||
- fix: refs #7134 params filter by:Javier Segarra
|
||||
- fix: refs #7134 remove risk by:Javier Segarra
|
||||
- fix: refs #7134 remove supplierRisk by:Javier Segarra
|
||||
- fix: refs #7134 solve comments by:Javier Segarra
|
||||
- fix: refs #7196 not neccessary by:alexm
|
||||
- fix: refs #7196 sass by:alexm
|
||||
- fix: refs #7322 handle null responses in client, agency and address fetching by:jtubau
|
||||
- fix: refs #7826 init by:Javier Segarra
|
||||
- fix: refs #8120 ticket descriptor & summary by:Jon
|
||||
- fix: refs #8172 Remove unused row and column fields from ParkingBasicData by:guillermo
|
||||
- fix: refs #8197 improve code robustness by adding optional chaining and fixing syntax errors by:alexm
|
||||
- fix: refs #8197 use rightMenu by:alexm
|
||||
- fix: refs #8197 use RightMenu in subsections by:alexm
|
||||
- fix: refs #8227 clean pr (origin/8227-warmfixRoute) by:carlossa
|
||||
- fix: refs #8227 fix front descriptor, Form by:carlossa
|
||||
- fix: refs #8227 warmfix by:carlossa
|
||||
- fix: refs #8316 advanced-menu by:alexm
|
||||
- fix: refs #8316 filter by:alexm
|
||||
- fix: refs #8316 fix broken localizations for entry descriptor menu and items filter panel by:jtubau
|
||||
- fix: refs #8316 icon by:alexm
|
||||
- fix: refs #8316 redirections by:alexm
|
||||
- fix: refs #8316 translations by:alexm
|
||||
- fix: refs #8316 user-filter by:alexm
|
||||
- fix: refs #8322 add userFilter by:alexm
|
||||
- fix: refs #8322 filter and params by:alexm
|
||||
- fix: refs #8322 fixed route creation url by:provira
|
||||
- fix: refs #8322 moved filter inside array-data-props by:provira
|
||||
- fix: refs #8322 use userFilter by:alexm
|
||||
- fix: refs #8347 remove skip, fix unpaid by:carlossa
|
||||
- fix: refs #8352 fix datacy by:carlossa
|
||||
- fix: refs #8352 fix right by:carlossa
|
||||
- fix: refs #8352 fix rightPanel vnLog by:carlossa
|
||||
- fix: refs #8381 update travel data fetching to use correct URL and include necessary fields by:jgallego
|
||||
- fix: refs #8381 update travel data reference in TravelThermographs component by:jgallego
|
||||
- fix: refs #8395 update label for total column by:provira
|
||||
- fix: refs #8409 deleted code due to merge by:Jon
|
||||
- fix: refs #8409 deleted code of merge by:Jon
|
||||
- fix: refs #8410 removed ref from searching boolean by:provira
|
||||
- fix: refs #8410 removed unused code by:provira
|
||||
- fix: refs #8410 removed unused condition by:provira
|
||||
- fix: refs #8410 removed unused ref by:provira
|
||||
- fix: refs #8410 simplified searchModule function by:provira
|
||||
- fix: refs #8418 adjusted route for button click by:jtubau
|
||||
- fix: refs #8418 correct casing in translation keys for supplier reference and issued date labels by:jtubau
|
||||
- fix: refs #8419 modified list and fixed e2e by:Jon
|
||||
- fix: refs #8420 ensure search bar is visible before typing and enable details test by:jtubau
|
||||
- fix: refs #8422 fixed ItemTag e2e test not working by:provira
|
||||
- fix: refs #8422 optimized get and dataCy by:provira
|
||||
- fix: refs #8423 fixed zoneWarehouse e2e test not working by:provira
|
||||
- fix: refs #8423 removed data-cy usage by:provira
|
||||
- fix: refs #8423 used dataCy to get data-cy by:provira
|
||||
- fix: refs #8524 parking section router by:alexm
|
||||
- fix: refs #8524 parking test (origin/8524-devToTest, 8524-devToTest) by:alexm
|
||||
- fix: remove console by:Javier Segarra
|
||||
- fix: replace labels by:Javier Segarra
|
||||
- fix: rightAdvancedMenu by:alexm
|
||||
- fix: routeCard use customUrl by:alexm
|
||||
- fix: show descriptors when click on it by:Javier Segarra
|
||||
- fix: update query parameters for thermograph routing by:jgallego
|
||||
- fix: update selector for buyLabel button in myEntry.spec.js (origin/fix-myEntryTest) by:jtubau
|
||||
- fix: update setupNodeEvents to use async/await for plugin import by:jgallego
|
||||
- fix: use model by:alexm
|
||||
- fix: use rightMenu by:alexm
|
||||
- fix(VnSection): destroy data when unmounted by:alexm
|
||||
- fix(VnSection): refs #8197 check route by:alexm
|
||||
- fix(WorkerBusiness): fix card label by:alexm
|
||||
- fix: workerSummary by:alexm
|
||||
- perf: refs #7134 #7134 fix filter panel by:Javier Segarra
|
||||
- perf: refs #8197 fix and imrpove filters by:alexm
|
||||
- refactor: refs #8316 update prefix casing for InvoiceIn component by:jtubau
|
||||
- refactor: refs #8351 deleted skip and fixed TicketList e2e by:Jon
|
||||
- refs #6553 fix business slot by:carlossa
|
||||
- refs #6553 fix business summary by:carlossa
|
||||
- refs #6553 fix business summary traductions by:carlossa
|
||||
- refs #6553 fix front ibject by:carlossa
|
||||
- refs #6553 fix front trad by:carlossa
|
||||
- refs #6553 fix names by:carlossa
|
||||
- refs #6553 fix reactivateWorker by:carlossa
|
||||
- refs #6553 fix relations by:carlossa
|
||||
- refs #6553 fix VnTable by:carlossa
|
||||
- refs #7917 fix routeCard by:carlossa
|
||||
- revert: refs #7134 change by:Javier Segarra
|
||||
- revert: refs #7134 customer changes by:Javier Segarra
|
||||
- revert: vitest to 0.31.1 by:Javier Segarra
|
||||
- test: fix clientList spec by:Javier Segarra
|
||||
- test: fix component by:Javier Segarra
|
||||
- test: fix VnSearchbar by:alexm
|
||||
- test: refs #6943 fix tests by:Javier Segarra
|
||||
- test: refs #7308 fix axios.spec.js by:Javier Segarra
|
||||
- test: refs #8524 fix by:alexm
|
||||
|
||||
# Version 25.04 - 2025-01-28
|
||||
|
||||
### Added 🆕
|
||||
|
||||
- chore: add task comment by:jorgep
|
||||
- chore: refs #8198 rollback by:jorgep
|
||||
- chore: refs #8322 unnecessary prop by:alexm
|
||||
- feat: refs #7055 added new test case by:provira
|
||||
- feat: refs #7055 created FilterItemForm test by:provira
|
||||
- feat: refs #7077 created test for VnInputTime by:provira
|
||||
- feat: refs #7078 created test for VnJsonValue by:provira
|
||||
- feat: refs #7087 added more test cases by:provira
|
||||
- feat: refs #7087 added new test by:provira
|
||||
- feat: refs #7087 created CardSummary test by:provira
|
||||
- feat: refs #7088 created test for FetchedTags by:provira
|
||||
- feat: refs #7202 added new field by:Jon
|
||||
- feat: refs #7882 Added coords to create a address by:guillermo
|
||||
- feat: refs #7957 add tooltip and i18n support for search link in VnSearchbar component by:jorgep
|
||||
- feat: refs #7957 enhance search functionality and improve data filtering logic by:jorgep
|
||||
- feat: refs #7957 open in new tab by:jorgep
|
||||
- feat: refs #7957 simplify fn to by:jorgep
|
||||
- feat: refs #7957 update VnSearchbar component with improved search URL handling and styling enhancements by:jorgep
|
||||
- feat: refs #8117 filters and values added as needed by:jtubau
|
||||
- feat: refs #8197 useHasContent and use in VnSection and RightMenu by:alexm
|
||||
- feat: refs #8219 added invoice out e2e tests by:Jon
|
||||
- feat: refs #8219 global invoicing e2e by:Jon
|
||||
- feat: refs #8220 added barcodes e2e test by:Jon
|
||||
- feat: refs #8220 created items e2e by:Jon
|
||||
- feat: refs #8220 modified create item form and added respective e2e by:Jon
|
||||
- feat: refs #8225 added account and invoiceOut modules by:Jon
|
||||
- feat: refs #8225 added entry module and fixed translations by:Jon
|
||||
- feat: refs #8225 added invoiceIn and travel module by:Jon
|
||||
- feat: refs #8225 added moreOptions and use it in customer and ticket summary by:Jon
|
||||
- feat: refs #8225 added route and shelving module by:Jon
|
||||
- feat: refs #8225 added worker and zone modules by:Jon
|
||||
- feat: refs #8225 use it in claim, item and order modules by:Jon
|
||||
- feat: refs #8258 added button to pass to uppercase by:provira
|
||||
- feat: refs #8258 added uppercase option to VnInput by:provira
|
||||
- feat: refs #8258 added uppercase validation on supplier create by:provira
|
||||
- feat: refs #8298 add price optimum input and update translations for bonus and price optimum by:jgallego
|
||||
- feat: refs #8316 add entryFilter prop to VnTable component in EntryList by:jtubau
|
||||
- feat: refs #8322 added department changes by:provira
|
||||
- feat: refs #8372 workerPBX by:robert
|
||||
- feat: refs #8381 add initial and final temperature fields to entry forms and summaries by:jgallego
|
||||
- feat: refs #8381 add initial and final temperature labels in English and Spanish locales by:jgallego
|
||||
- feat: refs #8381 add toCelsius filter and update temperature fields in entry forms and summaries by:jgallego
|
||||
- feat: skip tests by:jorgep
|
||||
- style: refs #7957 update VnSearchbar padding for improved layout by:jorgep
|
||||
|
||||
### Changed 📦
|
||||
|
||||
- perf: refs #8219 #8219 minor change by:Javier Segarra
|
||||
- perf: refs #8220 on-fetch and added missing translations by:Jon
|
||||
- perf: refs #8220 on-fetch by:Jon
|
||||
- perf: refs #8220 translations by:Jon
|
||||
- perf: refs #8220 use searchbar selector in e2e tests by:Jon
|
||||
- perf: remove warning default value by:Javier Segarra
|
||||
- refactor: redirect using params by:Jon
|
||||
- refactor: refs #7077 removed some comments by:provira
|
||||
- refactor: refs #7087 removed unused imports by:provira
|
||||
- refactor: refs #7100 added const mockData by:jtubau
|
||||
- refactor: refs #7100 delete unnecesary set prop by:jtubau
|
||||
- refactor: refs #7100 refactorized with methods by:jtubau
|
||||
- refactor: refs #7957 remove blank by:jorgep
|
||||
- refactor: refs #8198 simplify data fetching and filtering logic by:jorgep
|
||||
- refactor: refs #8198 simplify state management and data fetching in ItemDiary component by:jorgep
|
||||
- refactor: refs #8219 modified e2e tests and fixed some translations by:Jon
|
||||
- refactor: refs #8219 modified list test, created cypress download folder and added to gitignore by:Jon
|
||||
- refactor: refs #8219 requested changes by:Jon
|
||||
- refactor: refs #8219 use checkNotification command by:Jon
|
||||
- refactor: refs #8220 added data-cy for e2e tests by:Jon
|
||||
- refactor: refs #8220 requested changes by:Jon
|
||||
- refactor: refs #8220 skip failling test and modifed tag test by:Jon
|
||||
- refactor: refs #8225 requested changes by:Jon
|
||||
- refactor: refs #8247 use new acl for sysadmin by:Jon
|
||||
- refactor: refs #8316 added claimFilter by:jtubau
|
||||
- refactor: refs #8316 added entryFilter by:jtubau
|
||||
- refactor: refs #8316 add new localization keys and update existing ones for entry components by:jtubau
|
||||
- refactor: refs #8316 moved localizations to local locale by:jtubau
|
||||
- refactor: refs #8316 move order localization by:jtubau
|
||||
- refactor: refs #8316 remove unused OrderSearchbar component by:jtubau
|
||||
- refactor: refs #8316 update EntryCard to use user-filter prop and remove exprBuilder from EntryList by:jtubau
|
||||
- refactor: refs #8316 used VnSection and VnCardBeta by:jtubau
|
||||
- refactor: refs #8322 changed translations by:provira
|
||||
- refactor: refs #8322 changed Worker component to use VnSection/VnCardBeta by:provira
|
||||
- refactor: refs #8322 set department inside worker by:alexm
|
||||
- refactor: skip intermitent failing test by:Jon
|
||||
|
||||
### Fixed 🛠️
|
||||
|
||||
- feat: refs #8225 added entry module and fixed translations by:Jon
|
||||
- fix: added missing translations in InvoiceIn by:provira
|
||||
- fix: changed invoiceIn for InvoiceIn by:provira
|
||||
- fix: changed translations to only use "invoicein" by:provira
|
||||
- fix: department descriptor link by:Jon
|
||||
- fix: e2e tests by:Jon
|
||||
- fix: entry summary view and build warnings by:Jon
|
||||
- fix: fixed InvoiceIn filter translations by:provira
|
||||
- fix: modified setData in customerDescriptor to show the icons by:Jon
|
||||
- fix: redirect to TicketSale from OrderLines (origin/Fix-RedirectToTicketSale) by:Jon
|
||||
- fix: redirect when confirming lines by:Jon
|
||||
- fix: refs #7055 #7055 #7055 fixed some tests by:provira
|
||||
- fix: refs #7077 removed unused imports by:provira
|
||||
- fix: refs #7078 added missing case with array by:provira
|
||||
- fix: refs #7087 fixed some tests by:provira
|
||||
- fix: refs #7088 changed "vm.vm" to "vm" by:provira
|
||||
- fix: refs #7088 changed wrapper to vm by:provira
|
||||
- fix: refs #7699 add icons and hint by:carlossa
|
||||
- fix: refs #7699 add pwd vnInput by:carlossa
|
||||
- fix: refs #7699 fix component by:carlossa
|
||||
- fix: refs #7699 fix password visibility by:carlossa
|
||||
- fix: refs #7699 fix tfront clean code by:carlossa
|
||||
- fix: refs #7699 fix vnChangePassword, clean VnInput by:carlossa
|
||||
- fix: refs #7699 fix vnInputPassword by:carlossa
|
||||
- fix: refs #7957 add missing closing brace by:jorgep
|
||||
- fix: refs #7957 css by:jorgep
|
||||
- fix: refs #7957 rollback by:jorgep
|
||||
- fix: refs #7957 update data-cy by:jorgep
|
||||
- fix: refs #7957 update visibility handling for clear icon in VnInput component by:jorgep
|
||||
- fix: refs #7957 vn-searchbar test by:jorgep
|
||||
- fix: refs #8117 update salesPersonFk filter options and URL for improved data retrieval by:jtubau
|
||||
- fix: refs #8197 not use yet by:alexm
|
||||
- fix: refs #8198 update query param by:jorgep
|
||||
- fix: refs #8219 fixed e2e tests by:Jon
|
||||
- fix: refs #8219 fixed summary and global tests by:Jon
|
||||
- fix: refs #8219 forgotten dataCy by:Jon
|
||||
- fix: refs #8219 global e2e by:Jon
|
||||
- fix: refs #8219 requested changes by:Jon
|
||||
- fix: refs #8220 itemTag test by:Javier Segarra
|
||||
- fix: refs #8225 invoice in translations by:Jon
|
||||
- fix: refs #8243 fixed SkeletonSummary by:provira
|
||||
- fix: refs #8247 conflicts by:Jon
|
||||
- fix: refs #8247 fixed acls and added lost options by:Jon
|
||||
- fix: refs #8316 ref="claimFilterRef" by:alexm
|
||||
- fix: refs #8316 userFilter by:alexm
|
||||
- fix: refs #8316 use rightMenu by:alexm
|
||||
- fix: refs #8316 use section-searchbar by:alexm
|
||||
- fix: refs #8317 disable action buttons when no rows are selected in ItemFixedPrice by:jtubau
|
||||
- fix: refs #8322 unnecessary section by:alexm
|
||||
- fix: refs #8338 fixed VnTable translations by:provira
|
||||
- fix: refs #8338 removed chipLocale property/added more translations by:provira
|
||||
- fix: refs #8448 e2e by:Jon
|
||||
- fix: refs #8448 not use croppie by:alexm
|
||||
- fix: remove departmentCode by:Javier Segarra
|
||||
- fix: removed unused searchbar by:PAU ROVIRA ROSALENY
|
||||
- fix: skip failling e2e by:Jon
|
||||
- fix: sort by name in description by:Jon
|
||||
- fix: translations by:Jon
|
||||
- fix: use entryFilter by:alexm
|
||||
- fix(VnCardBeta): add userFilter by:alexm
|
||||
- refactor: refs #8219 modified e2e tests and fixed some translations by:Jon
|
||||
- revert: revert header by:alexm
|
||||
- test: fix expedition e2e by:alexm
|
||||
|
||||
# Version 25.00 - 2025-01-14
|
||||
|
||||
### Added 🆕
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#!/usr/bin/env groovy
|
||||
|
||||
def PROTECTED_BRANCH
|
||||
def IS_LATEST
|
||||
|
||||
def BRANCH_ENV = [
|
||||
test: 'test',
|
||||
|
@ -11,22 +10,19 @@ def BRANCH_ENV = [
|
|||
|
||||
node {
|
||||
stage('Setup') {
|
||||
env.FRONT_REPLICAS = 1
|
||||
env.NODE_ENV = BRANCH_ENV[env.BRANCH_NAME] ?: 'dev'
|
||||
|
||||
PROTECTED_BRANCH = [
|
||||
'dev',
|
||||
'test',
|
||||
'master',
|
||||
'main',
|
||||
'beta'
|
||||
]
|
||||
|
||||
IS_PROTECTED_BRANCH = PROTECTED_BRANCH.contains(env.BRANCH_NAME)
|
||||
IS_LATEST = ['master', 'main'].contains(env.BRANCH_NAME)
|
||||
].contains(env.BRANCH_NAME)
|
||||
|
||||
// https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#using-environment-variables
|
||||
echo "NODE_NAME: ${env.NODE_NAME}"
|
||||
echo "WORKSPACE: ${env.WORKSPACE}"
|
||||
echo "CHANGE_TARGET: ${env.CHANGE_TARGET}"
|
||||
|
||||
configFileProvider([
|
||||
configFile(fileId: 'salix-front.properties',
|
||||
|
@ -37,7 +33,7 @@ node {
|
|||
props.each {key, value -> echo "${key}: ${value}" }
|
||||
}
|
||||
|
||||
if (IS_PROTECTED_BRANCH) {
|
||||
if (PROTECTED_BRANCH) {
|
||||
configFileProvider([
|
||||
configFile(fileId: "salix-front.branch.${env.BRANCH_NAME}",
|
||||
variable: 'BRANCH_PROPS_FILE')
|
||||
|
@ -62,19 +58,6 @@ pipeline {
|
|||
PROJECT_NAME = 'lilium'
|
||||
}
|
||||
stages {
|
||||
stage('Version') {
|
||||
when {
|
||||
expression { IS_PROTECTED_BRANCH }
|
||||
}
|
||||
steps {
|
||||
script {
|
||||
def packageJson = readJSON file: 'package.json'
|
||||
def version = "${packageJson.version}-build${env.BUILD_ID}"
|
||||
writeFile(file: 'VERSION.txt', text: version)
|
||||
echo "VERSION: ${version}"
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Install') {
|
||||
environment {
|
||||
NODE_ENV = ""
|
||||
|
@ -85,89 +68,48 @@ pipeline {
|
|||
}
|
||||
stage('Test') {
|
||||
when {
|
||||
expression { !IS_PROTECTED_BRANCH }
|
||||
expression { !PROTECTED_BRANCH }
|
||||
}
|
||||
environment {
|
||||
NODE_ENV = ''
|
||||
CI = 'true'
|
||||
TZ = 'Europe/Madrid'
|
||||
NODE_ENV = ""
|
||||
}
|
||||
parallel {
|
||||
stage('Unit') {
|
||||
steps {
|
||||
sh 'pnpm run test:front:ci'
|
||||
sh 'pnpm run test:unit:ci'
|
||||
}
|
||||
post {
|
||||
always {
|
||||
junit(
|
||||
testResults: 'junit/vitest.xml',
|
||||
testResults: 'junitresults.xml',
|
||||
allowEmptyResults: true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('E2E') {
|
||||
environment {
|
||||
CREDS = credentials('docker-registry')
|
||||
COMPOSE_PROJECT = "${PROJECT_NAME}-${env.BUILD_ID}"
|
||||
COMPOSE_PARAMS = "-p ${env.COMPOSE_PROJECT} -f test/cypress/docker-compose.yml --project-directory ."
|
||||
}
|
||||
steps {
|
||||
script {
|
||||
sh 'rm -f junit/e2e-*.xml'
|
||||
env.COMPOSE_TAG = PROTECTED_BRANCH.contains(env.CHANGE_TARGET) ? env.CHANGE_TARGET : 'dev'
|
||||
|
||||
def image = docker.build('lilium-dev', '-f docs/Dockerfile.dev docs')
|
||||
|
||||
sh 'docker login --username $CREDS_USR --password $CREDS_PSW $REGISTRY'
|
||||
sh "docker-compose ${env.COMPOSE_PARAMS} up -d"
|
||||
|
||||
image.inside("--network ${env.COMPOSE_PROJECT}_default -e CI -e TZ --init") {
|
||||
sh 'sh test/cypress/cypressParallel.sh 3'
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
sh "docker-compose ${env.COMPOSE_PARAMS} down -v"
|
||||
junit(
|
||||
testResults: 'junit/e2e-*.xml',
|
||||
allowEmptyResults: true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Build') {
|
||||
when {
|
||||
expression { IS_PROTECTED_BRANCH }
|
||||
expression { PROTECTED_BRANCH }
|
||||
}
|
||||
environment {
|
||||
VERSION = readFile 'VERSION.txt'
|
||||
CREDENTIALS = credentials('docker-registry')
|
||||
}
|
||||
steps {
|
||||
script {
|
||||
sh 'quasar build'
|
||||
|
||||
def baseImage = "salix-frontend:${env.VERSION}"
|
||||
def image = docker.build(baseImage, ".")
|
||||
docker.withRegistry("https://${env.REGISTRY}", 'docker-registry') {
|
||||
image.push()
|
||||
image.push(env.BRANCH_NAME)
|
||||
if (IS_LATEST) image.push('latest')
|
||||
}
|
||||
script {
|
||||
def packageJson = readJSON file: 'package.json'
|
||||
env.VERSION = "${packageJson.version}-build${env.BUILD_ID}"
|
||||
}
|
||||
dockerBuild()
|
||||
}
|
||||
}
|
||||
stage('Deploy') {
|
||||
when {
|
||||
expression { IS_PROTECTED_BRANCH }
|
||||
}
|
||||
environment {
|
||||
VERSION = readFile 'VERSION.txt'
|
||||
expression { PROTECTED_BRANCH }
|
||||
}
|
||||
steps {
|
||||
script {
|
||||
def packageJson = readJSON file: 'package.json'
|
||||
env.VERSION = "${packageJson.version}-build${env.BUILD_ID}"
|
||||
}
|
||||
withKubeConfig([
|
||||
serverUrl: "$KUBERNETES_API",
|
||||
credentialsId: 'kubernetes',
|
||||
|
|
|
@ -23,7 +23,7 @@ quasar dev
|
|||
### Run unit tests
|
||||
|
||||
```bash
|
||||
pnpm run test:front
|
||||
pnpm run test:unit
|
||||
```
|
||||
|
||||
### Run e2e tests
|
||||
|
|
|
@ -1,44 +1,12 @@
|
|||
import { defineConfig } from 'cypress';
|
||||
|
||||
let urlHost, reporter, reporterOptions, timeouts;
|
||||
|
||||
if (process.env.CI) {
|
||||
urlHost = 'front';
|
||||
reporter = 'junit';
|
||||
reporterOptions = {
|
||||
mochaFile: 'junit/e2e-[hash].xml',
|
||||
};
|
||||
timeouts = {
|
||||
defaultCommandTimeout: 30000,
|
||||
requestTimeout: 30000,
|
||||
responseTimeout: 60000,
|
||||
pageLoadTimeout: 60000,
|
||||
};
|
||||
} else {
|
||||
urlHost = 'localhost';
|
||||
reporter = 'cypress-mochawesome-reporter';
|
||||
reporterOptions = {
|
||||
charts: true,
|
||||
reportPageTitle: 'Cypress Inline Reporter',
|
||||
reportFilename: '[status]_[datetime]-report',
|
||||
embeddedScreenshots: true,
|
||||
reportDir: 'test/cypress/reports',
|
||||
inlineAssets: true,
|
||||
};
|
||||
timeouts = {
|
||||
defaultCommandTimeout: 10000,
|
||||
requestTimeout: 10000,
|
||||
responseTimeout: 30000,
|
||||
pageLoadTimeout: 60000,
|
||||
};
|
||||
}
|
||||
// https://docs.cypress.io/app/tooling/reporters
|
||||
// https://docs.cypress.io/app/references/configuration
|
||||
// https://www.npmjs.com/package/cypress-mochawesome-reporter
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
baseUrl: `http://${urlHost}:9000`,
|
||||
experimentalStudio: false,
|
||||
trashAssetsBeforeRuns: false,
|
||||
defaultBrowser: 'chromium',
|
||||
baseUrl: 'http://localhost:9000/',
|
||||
experimentalStudio: true,
|
||||
fixturesFolder: 'test/cypress/fixtures',
|
||||
screenshotsFolder: 'test/cypress/screenshots',
|
||||
supportFile: 'test/cypress/support/index.js',
|
||||
|
@ -48,17 +16,25 @@ export default defineConfig({
|
|||
specPattern: 'test/cypress/integration/**/*.spec.js',
|
||||
experimentalRunAllSpecs: true,
|
||||
watchForFileChanges: true,
|
||||
reporter,
|
||||
reporterOptions,
|
||||
reporter: 'cypress-mochawesome-reporter',
|
||||
reporterOptions: {
|
||||
charts: true,
|
||||
reportPageTitle: 'Cypress Inline Reporter',
|
||||
reportFilename: '[status]_[datetime]-report',
|
||||
embeddedScreenshots: true,
|
||||
reportDir: 'test/cypress/reports',
|
||||
inlineAssets: true,
|
||||
},
|
||||
component: {
|
||||
componentFolder: 'src',
|
||||
testFiles: '**/*.spec.js',
|
||||
supportFile: 'test/cypress/support/unit.js',
|
||||
},
|
||||
setupNodeEvents(on, config) {
|
||||
import('cypress-mochawesome-reporter/plugin').then((plugin) => plugin.default(on));
|
||||
// implement node event listeners here
|
||||
},
|
||||
viewportWidth: 1280,
|
||||
viewportHeight: 720,
|
||||
...timeouts,
|
||||
includeShadowDom: true,
|
||||
waitForAnimations: true,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
version: '3.7'
|
||||
services:
|
||||
main:
|
||||
image: registry.verdnatura.es/salix-frontend:${VERSION:?}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./Dockerfile
|
|
@ -1,38 +0,0 @@
|
|||
import { defineConfig } from 'vitepress';
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
title: 'Lilium',
|
||||
description: 'Lilium docs',
|
||||
themeConfig: {
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
nav: [
|
||||
{ text: 'Home', link: '/' },
|
||||
{ text: 'Components', link: '/components/vnInput' },
|
||||
{ text: 'Composables', link: '/composables/useArrayData' },
|
||||
],
|
||||
|
||||
sidebar: [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
text: 'Components',
|
||||
collapsible: true,
|
||||
collapsed: true,
|
||||
items: [{ text: 'VnInput', link: '/components/vnInput' }],
|
||||
},
|
||||
{
|
||||
text: 'Composables',
|
||||
collapsible: true,
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'useArrayData', link: '/composables/useArrayData' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
socialLinks: [{ icon: 'github', link: 'https://github.com/vuejs/vitepress' }],
|
||||
},
|
||||
});
|
|
@ -1,45 +0,0 @@
|
|||
FROM debian:12.9-slim
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg2 \
|
||||
&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
||||
&& apt-get install -y --no-install-recommends nodejs \
|
||||
&& npm install -g corepack@0.31.0 \
|
||||
&& corepack enable pnpm \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get -y --no-install-recommends install \
|
||||
apt-utils \
|
||||
chromium \
|
||||
libasound2 \
|
||||
libgbm-dev \
|
||||
libgtk-3-0 \
|
||||
libgtk2.0-0 \
|
||||
libnotify-dev \
|
||||
libnss3 \
|
||||
libxss1 \
|
||||
libxtst6 \
|
||||
xauth \
|
||||
xvfb \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN groupadd -r -g 1000 app \
|
||||
&& useradd -r -u 1000 -g app -m -d /home/app app
|
||||
USER app
|
||||
|
||||
ENV SHELL=bash
|
||||
ENV PNPM_HOME="/home/app/.local/share/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
|
||||
RUN pnpm setup \
|
||||
&& pnpm install --global cypress@14.1.0 \
|
||||
&& cypress install
|
||||
|
||||
WORKDIR /app
|
|
@ -1,136 +0,0 @@
|
|||
# VnInput
|
||||
|
||||
`VnInput` is a custom input component that provides various useful features such as validation, input clearing, and more.
|
||||
|
||||
## Props
|
||||
|
||||
### `modelValue`
|
||||
|
||||
- **Type:** `String | Number`
|
||||
- **Default:** `null`
|
||||
- **Description:** The value of the model bound to the component.
|
||||
|
||||
### `isOutlined`
|
||||
|
||||
- **Type:** `Boolean`
|
||||
- **Default:** `false`
|
||||
- **Description:** If `true`, the component is rendered with an outlined style.
|
||||
|
||||
### `info`
|
||||
|
||||
- **Type:** `String`
|
||||
- **Default:** `''`
|
||||
- **Description:** Additional information displayed alongside the component.
|
||||
|
||||
### `clearable`
|
||||
|
||||
- **Type:** `Boolean`
|
||||
- **Default:** `true`
|
||||
- **Description:** If `true`, the component shows a button to clear the input.
|
||||
|
||||
### `emptyToNull`
|
||||
|
||||
- **Type:** `Boolean`
|
||||
- **Default:** `true`
|
||||
- **Description:** If `true`, converts empty inputs to `null`.
|
||||
|
||||
### `insertable`
|
||||
|
||||
- **Type:** `Boolean`
|
||||
- **Default:** `false`
|
||||
- **Description:** If `true`, allows the insertion of new values.
|
||||
|
||||
### `maxlength`
|
||||
|
||||
- **Type:** `Number`
|
||||
- **Default:** `null`
|
||||
- **Description:** The maximum number of characters allowed in the input.
|
||||
|
||||
### `uppercase`
|
||||
|
||||
- **Type:** `Boolean`
|
||||
- **Default:** `false`
|
||||
- **Description:** If `true`, converts the input text to uppercase.
|
||||
|
||||
## Emits
|
||||
|
||||
### `update:modelValue`
|
||||
|
||||
- **Description:** Emits the updated model value.
|
||||
- **Behavior:** This event is emitted whenever the input value changes. It is used to update the model value bound to the component.
|
||||
|
||||
### `update:options`
|
||||
|
||||
- **Description:** Emits the updated options.
|
||||
- **Behavior:** This event is emitted when the component's options change. It is useful for components with dynamic options.
|
||||
|
||||
### `keyup.enter`
|
||||
|
||||
- **Description:** Emits an event when the Enter key is pressed.
|
||||
- **Behavior:** This event is emitted whenever the Enter key is pressed while the input is focused. It can be used to handle specific actions when the input is confirmed.
|
||||
|
||||
### `remove`
|
||||
|
||||
- **Description:** Emits an event to remove the current value.
|
||||
- **Behavior:** This event is emitted when the clear button (close icon) is clicked. It is used to handle the removal of the current input value.
|
||||
|
||||
## Functions
|
||||
|
||||
### `focus`
|
||||
|
||||
- **Description:** Focuses the input.
|
||||
- **Behavior:** This function is exposed so it can be called from outside the component. It uses `vnInputRef.value.focus()` to focus the input.
|
||||
|
||||
### `handleKeydown`
|
||||
|
||||
- **Description:** Handles the `keydown` event of the input.
|
||||
- **Behavior:** This function is called whenever a key is pressed while the input is focused. If the pressed key is `Backspace`, it does nothing. If `insertable` is `true` and the pressed key is a number, it calls `handleInsertMode`.
|
||||
|
||||
### `handleInsertMode`
|
||||
|
||||
- **Description:** Handles the insertion mode of values.
|
||||
- **Behavior:** This function is called when `insertable` is `true` and a numeric key is pressed. It inserts the value at the cursor position and updates the input value. Then, it moves the cursor to the correct position.
|
||||
|
||||
### `handleUppercase`
|
||||
|
||||
- **Description:** Converts the input value to uppercase.
|
||||
- **Behavior:** This function is called when the uppercase icon is clicked. It converts the current input value to uppercase.
|
||||
|
||||
## Usage
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<VnInput
|
||||
v-model="inputValue"
|
||||
:isOutlined="true"
|
||||
info="Additional information"
|
||||
:clearable="true"
|
||||
:emptyToNull="true"
|
||||
:insertable="false"
|
||||
:maxlength="50"
|
||||
:uppercase="true"
|
||||
@update:modelValue="handleUpdate"
|
||||
@keyup.enter="handleEnter"
|
||||
@remove="handleRemove"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
|
||||
const inputValue = ref('');
|
||||
|
||||
const handleUpdate = (value) => {
|
||||
console.log('Updated value:', value);
|
||||
};
|
||||
|
||||
const handleEnter = () => {
|
||||
console.log('Enter pressed');
|
||||
};
|
||||
|
||||
const handleRemove = () => {
|
||||
console.log('Value removed');
|
||||
};
|
||||
</script>
|
||||
```
|
|
@ -1,215 +0,0 @@
|
|||
# useArrayData
|
||||
|
||||
`useArrayData` is a composable function that provides a set of utilities for managing array data in a Vue component. It leverages Pinia for state management and provides various methods for fetching, filtering, and manipulating data.
|
||||
|
||||
## Usage
|
||||
|
||||
```javascript
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
|
||||
const {
|
||||
fetch,
|
||||
applyFilter,
|
||||
addFilter,
|
||||
getCurrentFilter,
|
||||
setCurrentFilter,
|
||||
addFilterWhere,
|
||||
addOrder,
|
||||
deleteOrder,
|
||||
refresh,
|
||||
destroy,
|
||||
loadMore,
|
||||
store,
|
||||
totalRows,
|
||||
updateStateParams,
|
||||
isLoading,
|
||||
deleteOption,
|
||||
reset,
|
||||
resetPagination,
|
||||
} = useArrayData('myKey', userOptions);
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
### `key`
|
||||
|
||||
- **Type:** `String`
|
||||
- **Description:** A unique key to identify the data store.
|
||||
|
||||
### `userOptions`
|
||||
|
||||
- **Type:** `Object`
|
||||
- **Description:** An object containing user-defined options for configuring the data store.
|
||||
|
||||
## Methods
|
||||
|
||||
### `fetch`
|
||||
|
||||
Fetches data from the server.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- **`options`** : An object with the following properties:
|
||||
- `append` (Boolean): Whether to append the fetched data to the existing data.
|
||||
- `updateRouter` (Boolean): Whether to update the router with the current filter.
|
||||
|
||||
#### Returns
|
||||
|
||||
- **`Promise`** : A promise that resolves with the fetched data.
|
||||
|
||||
### `applyFilter`
|
||||
|
||||
Applies a filter to the data.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- **`filter`** : An object containing the filter criteria.
|
||||
- **`params`** : Additional parameters for the filter.
|
||||
- **`fetchOptions`** : Options for the fetch method.
|
||||
|
||||
#### Returns
|
||||
|
||||
- **`Promise`** : A promise that resolves with the filtered data.
|
||||
|
||||
### `addFilter`
|
||||
|
||||
Adds a filter to the existing filters.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- **`filter`** : An object containing the filter criteria.
|
||||
- **`params`** : Additional parameters for the filter.
|
||||
|
||||
#### Returns
|
||||
|
||||
- **`Promise`** : A promise that resolves with the updated filter and parameters.
|
||||
|
||||
### `getCurrentFilter`
|
||||
|
||||
Gets the current filter applied to the data.
|
||||
|
||||
#### Returns
|
||||
|
||||
- **`Object`** : The current filter and parameters.
|
||||
|
||||
### `setCurrentFilter`
|
||||
|
||||
Sets the current filter for the data.
|
||||
|
||||
#### Returns
|
||||
|
||||
- **`Object`** : The current filter and parameters.
|
||||
|
||||
### `addFilterWhere`
|
||||
|
||||
Adds a `where` clause to the existing filters.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- **`where`** : An object containing the `where` clause.
|
||||
|
||||
#### Returns
|
||||
|
||||
- **`Promise`** : A promise that resolves when the filter is applied.
|
||||
|
||||
### `addOrder`
|
||||
|
||||
Adds an order to the existing orders.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- **`field`** : The field to order by.
|
||||
- **`direction`** : The direction of the order (`ASC` or `DESC`).
|
||||
|
||||
#### Returns
|
||||
|
||||
- **`Promise`** : A promise that resolves with the updated order.
|
||||
|
||||
### `deleteOrder`
|
||||
|
||||
Deletes an order from the existing orders.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- **`field`** : The field to delete the order for.
|
||||
|
||||
#### Returns
|
||||
|
||||
- **`Promise`** : A promise that resolves when the order is deleted.
|
||||
|
||||
### `refresh`
|
||||
|
||||
Refreshes the data by re-fetching it from the server.
|
||||
|
||||
#### Returns
|
||||
|
||||
- **`Promise`** : A promise that resolves with the refreshed data.
|
||||
|
||||
### `destroy`
|
||||
|
||||
Destroys the data store for the given key.
|
||||
|
||||
### `loadMore`
|
||||
|
||||
Loads more data by incrementing the pagination.
|
||||
|
||||
#### Returns
|
||||
|
||||
- **`Promise`** : A promise that resolves with the additional data.
|
||||
|
||||
### `updateStateParams`
|
||||
|
||||
Updates the state parameters with the given data.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- **`data`** : The data to update the state parameters with.
|
||||
|
||||
### `deleteOption`
|
||||
|
||||
Deletes an option from the store.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- **`option`** : The option to delete.
|
||||
|
||||
### `reset`
|
||||
|
||||
Resets the store to its default state.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- **`opts`** : An array of options to reset.
|
||||
|
||||
### `resetPagination`
|
||||
|
||||
Resets the pagination for the store.
|
||||
|
||||
## Computed Properties
|
||||
|
||||
### `totalRows`
|
||||
|
||||
- **Description:** The total number of rows in the data.
|
||||
- **Type:** `Number`
|
||||
|
||||
### `isLoading`
|
||||
|
||||
- **Description:** Whether the data is currently being loaded.
|
||||
- **Type:** `Boolean`
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
|
||||
const userOptions = {
|
||||
url: '/api/data',
|
||||
limit: 10,
|
||||
};
|
||||
|
||||
const arrayData = useArrayData('myKey', userOptions);
|
||||
</script>
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
```
|
|
@ -1,13 +0,0 @@
|
|||
---
|
||||
# https://vitepress.dev/reference/default-theme-home-page
|
||||
layout: home
|
||||
|
||||
hero:
|
||||
name: 'Lilium'
|
||||
text: 'Lilium docs'
|
||||
tagline: Powered by Verdnatura
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Docs
|
||||
link: /components/vnInput
|
||||
---
|
20
package.json
20
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "salix-front",
|
||||
"version": "25.12.0",
|
||||
"version": "25.06.0",
|
||||
"description": "Salix frontend",
|
||||
"productName": "Salix",
|
||||
"author": "Verdnatura",
|
||||
|
@ -13,17 +13,12 @@
|
|||
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
|
||||
"test:e2e": "cypress open",
|
||||
"test:e2e:ci": "npm run resetDatabase && cd ../salix-front && cypress run",
|
||||
"test:e2e:parallel": "bash ./test/cypress/cypressParallel.sh",
|
||||
"test:e2e:summary": "bash ./test/cypress/summary.sh",
|
||||
"test": "echo \"See package.json => scripts for available tests.\" && exit 0",
|
||||
"test:front": "vitest",
|
||||
"test:front:ci": "vitest run",
|
||||
"test:unit": "vitest",
|
||||
"test:unit:ci": "vitest run",
|
||||
"commitlint": "commitlint --edit",
|
||||
"prepare": "npx husky install",
|
||||
"addReferenceTag": "node .husky/addReferenceTag.js",
|
||||
"docs:dev": "vitepress dev docs",
|
||||
"docs:build": "vitepress build docs",
|
||||
"docs:preview": "vitepress preview docs"
|
||||
"addReferenceTag": "node .husky/addReferenceTag.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@quasar/cli": "^2.4.1",
|
||||
|
@ -49,20 +44,17 @@
|
|||
"@quasar/quasar-app-extension-testing-unit-vitest": "^0.4.0",
|
||||
"@vue/test-utils": "^2.4.4",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"cypress": "^14.1.0",
|
||||
"cypress": "^13.6.6",
|
||||
"cypress-mochawesome-reporter": "^3.8.2",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-plugin-cypress": "^4.1.0",
|
||||
"eslint-plugin-vue": "^9.32.0",
|
||||
"husky": "^8.0.0",
|
||||
"mocha": "^11.1.0",
|
||||
"postcss": "^8.4.23",
|
||||
"prettier": "^3.4.2",
|
||||
"sass": "^1.83.4",
|
||||
"vitepress": "^1.6.3",
|
||||
"vitest": "^0.34.0",
|
||||
"xunit-viewer": "^10.6.1"
|
||||
"vitest": "^0.34.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20 || ^18 || ^16",
|
||||
|
|
2438
pnpm-lock.yaml
2438
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +0,0 @@
|
|||
export default [
|
||||
{
|
||||
path: '/api',
|
||||
rule: { target: 'http://127.0.0.1:3000' },
|
||||
},
|
||||
];
|
|
@ -11,7 +11,6 @@
|
|||
import { configure } from 'quasar/wrappers';
|
||||
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
|
||||
import path from 'path';
|
||||
const target = `http://${process.env.CI ? 'back' : 'localhost'}:3000`;
|
||||
|
||||
export default configure(function (/* ctx */) {
|
||||
return {
|
||||
|
@ -31,6 +30,7 @@ export default configure(function (/* ctx */) {
|
|||
// --> boot files are part of "main.js"
|
||||
// https://v2.quasar.dev/quasar-cli/boot-files
|
||||
boot: ['i18n', 'axios', 'vnDate', 'validations', 'quasar', 'quasar.defaults'],
|
||||
|
||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
|
||||
css: ['app.scss'],
|
||||
|
||||
|
@ -109,17 +109,13 @@ export default configure(function (/* ctx */) {
|
|||
},
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: target,
|
||||
target: 'http://0.0.0.0:3000',
|
||||
logLevel: 'debug',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
},
|
||||
open: false,
|
||||
allowedHosts: [
|
||||
'front', // Agrega este nombre de host
|
||||
'localhost', // Opcional, para pruebas locales
|
||||
],
|
||||
},
|
||||
|
||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework
|
||||
|
@ -183,6 +179,7 @@ export default configure(function (/* ctx */) {
|
|||
'render', // keep this as last one
|
||||
],
|
||||
},
|
||||
|
||||
// https://v2.quasar.dev/quasar-cli/developing-pwa/configuring-pwa
|
||||
pwa: {
|
||||
workboxMode: 'generateSW', // or 'injectManifest'
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
export const langs = ['en', 'es'];
|
||||
export const decimalPlaces = 2;
|
|
@ -1,6 +1,6 @@
|
|||
export default {
|
||||
mounted(el, binding) {
|
||||
const shortcut = binding.value || '+';
|
||||
mounted: function (el, binding) {
|
||||
const shortcut = binding.value ?? '+';
|
||||
|
||||
const { key, ctrl, alt, callback } =
|
||||
typeof shortcut === 'string'
|
||||
|
@ -8,24 +8,25 @@ export default {
|
|||
key: shortcut,
|
||||
ctrl: true,
|
||||
alt: true,
|
||||
callback: () => el?.click(),
|
||||
callback: () =>
|
||||
document
|
||||
.querySelector(`button[shortcut="${shortcut}"]`)
|
||||
?.click(),
|
||||
}
|
||||
: binding.value;
|
||||
|
||||
if (!el.hasAttribute('shortcut')) {
|
||||
el.setAttribute('shortcut', key);
|
||||
}
|
||||
|
||||
const handleKeydown = (event) => {
|
||||
if (event.key === key && (!ctrl || event.ctrlKey) && (!alt || event.altKey)) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
// Attach the event listener to the window
|
||||
window.addEventListener('keydown', handleKeydown);
|
||||
|
||||
el._handleKeydown = handleKeydown;
|
||||
},
|
||||
unmounted(el) {
|
||||
unmounted: function (el) {
|
||||
if (el._handleKeydown) {
|
||||
window.removeEventListener('keydown', el._handleKeydown);
|
||||
}
|
||||
|
|
|
@ -9,19 +9,19 @@ export default {
|
|||
if (!form) return;
|
||||
try {
|
||||
const inputsFormCard = form.querySelectorAll(
|
||||
`input:not([disabled]):not([type="checkbox"])`,
|
||||
`input:not([disabled]):not([type="checkbox"])`
|
||||
);
|
||||
if (inputsFormCard.length) {
|
||||
focusFirstInput(inputsFormCard[0]);
|
||||
}
|
||||
const textareas = document.querySelectorAll(
|
||||
'textarea:not([disabled]), [contenteditable]:not([disabled])',
|
||||
'textarea:not([disabled]), [contenteditable]:not([disabled])'
|
||||
);
|
||||
if (textareas.length) {
|
||||
focusFirstInput(textareas[textareas.length - 1]);
|
||||
}
|
||||
const inputs = document.querySelectorAll(
|
||||
'form#formModel input:not([disabled]):not([type="checkbox"])',
|
||||
'form#formModel input:not([disabled]):not([type="checkbox"])'
|
||||
);
|
||||
const input = inputs[0];
|
||||
if (!input) return;
|
||||
|
@ -30,5 +30,22 @@ export default {
|
|||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
form.addEventListener('keyup', function (evt) {
|
||||
if (evt.key === 'Enter' && !that.$attrs['prevent-submit']) {
|
||||
const input = evt.target;
|
||||
if (input.type == 'textarea' && evt.shiftKey) {
|
||||
evt.preventDefault();
|
||||
let { selectionStart, selectionEnd } = input;
|
||||
input.value =
|
||||
input.value.substring(0, selectionStart) +
|
||||
'\n' +
|
||||
input.value.substring(selectionEnd);
|
||||
selectionStart = selectionEnd = selectionStart + 1;
|
||||
return;
|
||||
}
|
||||
evt.preventDefault();
|
||||
that.onSubmit();
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -51,5 +51,4 @@ export default boot(({ app }) => {
|
|||
|
||||
await useCau(response, message);
|
||||
};
|
||||
app.provide('app', app);
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@ const { t } = useI18n();
|
|||
const bicInputRef = ref(null);
|
||||
const state = useState();
|
||||
|
||||
const customer = computed(() => state.get('Customer'));
|
||||
const customer = computed(() => state.get('customer'));
|
||||
|
||||
const countriesFilter = {
|
||||
fields: ['id', 'name', 'code'],
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { reactive, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnSelectProvince from 'src/components/VnSelectProvince.vue';
|
||||
|
@ -20,11 +21,14 @@ const postcodeFormData = reactive({
|
|||
provinceFk: null,
|
||||
townFk: null,
|
||||
});
|
||||
const townsFetchDataRef = ref(false);
|
||||
const townFilter = ref({});
|
||||
|
||||
const countriesRef = ref(false);
|
||||
const provincesOptions = ref([]);
|
||||
const townsOptions = ref([]);
|
||||
const town = ref({});
|
||||
const countryFilter = ref({});
|
||||
|
||||
function onDataSaved(formData) {
|
||||
const newPostcode = {
|
||||
|
@ -47,6 +51,7 @@ async function setCountry(countryFk, data) {
|
|||
data.townFk = null;
|
||||
data.provinceFk = null;
|
||||
data.countryFk = countryFk;
|
||||
await fetchTowns();
|
||||
}
|
||||
|
||||
// Province
|
||||
|
@ -55,11 +60,22 @@ async function setProvince(id, data) {
|
|||
const newProvince = provincesOptions.value.find((province) => province.id == id);
|
||||
if (newProvince) data.countryFk = newProvince.countryFk;
|
||||
postcodeFormData.provinceFk = id;
|
||||
await fetchTowns();
|
||||
}
|
||||
|
||||
async function onProvinceCreated(data) {
|
||||
postcodeFormData.provinceFk = data.id;
|
||||
}
|
||||
function provinceByCountry(countryFk = postcodeFormData.countryFk) {
|
||||
return provincesOptions.value
|
||||
.filter((province) => province.countryFk === countryFk)
|
||||
.map(({ id }) => id);
|
||||
}
|
||||
|
||||
// Town
|
||||
async function handleTowns(data) {
|
||||
townsOptions.value = data;
|
||||
}
|
||||
function setTown(newTown, data) {
|
||||
town.value = newTown;
|
||||
data.provinceFk = newTown?.provinceFk ?? newTown;
|
||||
|
@ -72,6 +88,18 @@ async function onCityCreated(newTown, formData) {
|
|||
formData.townFk = newTown;
|
||||
setTown(newTown, formData);
|
||||
}
|
||||
async function fetchTowns(countryFk = postcodeFormData.countryFk) {
|
||||
if (!countryFk) return;
|
||||
const provinces = postcodeFormData.provinceFk
|
||||
? [postcodeFormData.provinceFk]
|
||||
: provinceByCountry();
|
||||
townFilter.value.where = {
|
||||
provinceFk: {
|
||||
inq: provinces,
|
||||
},
|
||||
};
|
||||
await townsFetchDataRef.value?.fetch();
|
||||
}
|
||||
|
||||
async function filterTowns(name) {
|
||||
if (name !== '') {
|
||||
|
@ -80,11 +108,22 @@ async function filterTowns(name) {
|
|||
like: `%${name}%`,
|
||||
},
|
||||
};
|
||||
await townsFetchDataRef.value?.fetch();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
ref="townsFetchDataRef"
|
||||
:sort-by="['name ASC']"
|
||||
:limit="30"
|
||||
:filter="townFilter"
|
||||
@on-fetch="handleTowns"
|
||||
auto-load
|
||||
url="Towns/location"
|
||||
/>
|
||||
|
||||
<FormModelPopup
|
||||
url-create="postcodes"
|
||||
model="postcode"
|
||||
|
@ -110,13 +149,14 @@ async function filterTowns(name) {
|
|||
@filter="filterTowns"
|
||||
:tooltip="t('Create city')"
|
||||
v-model="data.townFk"
|
||||
url="Towns/location"
|
||||
:options="townsOptions"
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
:rules="validate('postcode.city')"
|
||||
:acls="[{ model: 'Town', props: '*', accessType: 'WRITE' }]"
|
||||
:emit-value="false"
|
||||
required
|
||||
data-cy="locationTown"
|
||||
sort-by="name ASC"
|
||||
>
|
||||
<template #option="{ itemProps, opt }">
|
||||
<QItem v-bind="itemProps">
|
||||
|
@ -157,12 +197,16 @@ async function filterTowns(name) {
|
|||
/>
|
||||
<VnSelect
|
||||
ref="countriesRef"
|
||||
:limit="30"
|
||||
:filter="countryFilter"
|
||||
:sort-by="['name ASC']"
|
||||
auto-load
|
||||
url="Countries"
|
||||
required
|
||||
:label="t('Country')"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="data.countryFk"
|
||||
:rules="validate('postcode.countryFk')"
|
||||
@update:model-value="(value) => setCountry(value, data)"
|
||||
|
|
|
@ -62,9 +62,12 @@ const where = computed(() => {
|
|||
auto-load
|
||||
:where="where"
|
||||
url="Autonomies/location"
|
||||
sort-by="name ASC"
|
||||
:sort-by="['name ASC']"
|
||||
:limit="30"
|
||||
:label="t('Autonomy')"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="data.autonomyFk"
|
||||
:rules="validate('province.autonomyFk')"
|
||||
>
|
||||
|
|
|
@ -64,10 +64,6 @@ const $props = defineProps({
|
|||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
beforeSaveFn: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
goTo: {
|
||||
type: String,
|
||||
default: '',
|
||||
|
@ -153,7 +149,7 @@ function filter(value, update, filterOptions) {
|
|||
(ref) => {
|
||||
ref.setOptionIndex(-1);
|
||||
ref.moveOptionSelection(1, true);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -180,15 +176,8 @@ async function saveChanges(data) {
|
|||
hasChanges.value = false;
|
||||
return;
|
||||
}
|
||||
let changes = data || getChanges();
|
||||
if ($props.beforeSaveFn) {
|
||||
changes = await $props.beforeSaveFn(changes, getChanges);
|
||||
}
|
||||
const changes = data || getChanges();
|
||||
try {
|
||||
if (changes?.creates?.length === 0 && changes?.updates?.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await axios.post($props.saveUrl || $props.url + '/crud', changes);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
|
@ -226,7 +215,7 @@ async function remove(data) {
|
|||
|
||||
if (preRemove.length) {
|
||||
newData = newData.filter(
|
||||
(form) => !preRemove.some((index) => index == form.$index),
|
||||
(form) => !preRemove.some((index) => index == form.$index)
|
||||
);
|
||||
const changes = getChanges();
|
||||
if (!changes.creates?.length && !changes.updates?.length)
|
||||
|
@ -240,12 +229,12 @@ async function remove(data) {
|
|||
componentProps: {
|
||||
title: t('globals.confirmDeletion'),
|
||||
message: t('globals.confirmDeletionMessage'),
|
||||
data: { deletes: ids },
|
||||
newData,
|
||||
ids,
|
||||
promise: saveChanges,
|
||||
},
|
||||
})
|
||||
.onOk(async () => {
|
||||
await saveChanges({ deletes: ids });
|
||||
newData = newData.filter((form) => !ids.some((id) => id == form[pk]));
|
||||
fetch(newData);
|
||||
});
|
||||
|
@ -385,8 +374,6 @@ watch(formUrl, async () => {
|
|||
@click="onSubmit"
|
||||
:disable="!hasChanges"
|
||||
:title="t('globals.save')"
|
||||
v-shortcut="'s'"
|
||||
shortcut="s"
|
||||
data-cy="crudModelDefaultSaveBtn"
|
||||
/>
|
||||
<slot name="moreAfterActions" />
|
||||
|
|
|
@ -42,6 +42,7 @@ const itemFilter = {
|
|||
const itemFilterParams = reactive({});
|
||||
const closeButton = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const producersOptions = ref([]);
|
||||
const ItemTypesOptions = ref([]);
|
||||
const InksOptions = ref([]);
|
||||
const tableRows = ref([]);
|
||||
|
@ -120,17 +121,23 @@ const selectItem = ({ id }) => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
url="Producers"
|
||||
@on-fetch="(data) => (producersOptions = data)"
|
||||
:filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
url="ItemTypes"
|
||||
:filter="{ fields: ['id', 'name'], order: 'name ASC' }"
|
||||
order="name ASC"
|
||||
:filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }"
|
||||
order="name"
|
||||
@on-fetch="(data) => (ItemTypesOptions = data)"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
url="Inks"
|
||||
:filter="{ fields: ['id', 'name'], order: 'name ASC' }"
|
||||
order="name ASC"
|
||||
:filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }"
|
||||
order="name"
|
||||
@on-fetch="(data) => (InksOptions = data)"
|
||||
auto-load
|
||||
/>
|
||||
|
@ -145,11 +152,11 @@ const selectItem = ({ id }) => {
|
|||
<VnInput :label="t('entry.buys.size')" v-model="itemFilterParams.size" />
|
||||
<VnSelect
|
||||
:label="t('globals.producer')"
|
||||
:options="producersOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="itemFilterParams.producerFk"
|
||||
url="Producers"
|
||||
:fields="['id', 'name']"
|
||||
sort-by="name ASC"
|
||||
/>
|
||||
<VnSelect
|
||||
:label="t('globals.type')"
|
||||
|
|
|
@ -124,7 +124,7 @@ const selectTravel = ({ id }) => {
|
|||
<FetchData
|
||||
url="AgencyModes"
|
||||
@on-fetch="(data) => (agenciesOptions = data)"
|
||||
:filter="{ fields: ['id', 'name'], order: ['name ASC'] }"
|
||||
:filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
|
@ -181,7 +181,6 @@ const selectTravel = ({ id }) => {
|
|||
color="primary"
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
data-cy="save-filter-travel-form"
|
||||
/>
|
||||
</div>
|
||||
<QTable
|
||||
|
@ -192,10 +191,9 @@ const selectTravel = ({ id }) => {
|
|||
:no-data-label="t('Enter a new search')"
|
||||
class="q-mt-lg"
|
||||
@row-click="(_, row) => selectTravel(row)"
|
||||
data-cy="table-filter-travel-form"
|
||||
>
|
||||
<template #body-cell-id="{ row }">
|
||||
<QTd auto-width @click.stop data-cy="travelFk-travel-form">
|
||||
<QTd auto-width @click.stop>
|
||||
<QBtn flat color="blue">{{ row.id }}</QBtn>
|
||||
<TravelDescriptorProxy :id="row.id" />
|
||||
</QTd>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup>
|
||||
import axios from 'axios';
|
||||
import { onMounted, onUnmounted, computed, ref, watch, nextTick, useAttrs } from 'vue';
|
||||
import { onMounted, onUnmounted, computed, ref, watch, nextTick } from 'vue';
|
||||
import { onBeforeRouteLeave, useRouter, useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
@ -12,7 +12,6 @@ import SkeletonForm from 'components/ui/SkeletonForm.vue';
|
|||
import VnConfirm from './ui/VnConfirm.vue';
|
||||
import { tMobile } from 'src/composables/tMobile';
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
import { getDifferences, getUpdatedValues } from 'src/filters';
|
||||
|
||||
const { push } = useRouter();
|
||||
const quasar = useQuasar();
|
||||
|
@ -23,7 +22,6 @@ const { validate } = useValidator();
|
|||
const { notify } = useNotify();
|
||||
const route = useRoute();
|
||||
const myForm = ref(null);
|
||||
const attrs = useAttrs();
|
||||
const $props = defineProps({
|
||||
url: {
|
||||
type: String,
|
||||
|
@ -86,7 +84,7 @@ const $props = defineProps({
|
|||
},
|
||||
reload: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
default: false,
|
||||
},
|
||||
defaultTrim: {
|
||||
type: Boolean,
|
||||
|
@ -96,14 +94,10 @@ const $props = defineProps({
|
|||
type: [String, Boolean],
|
||||
default: '800px',
|
||||
},
|
||||
onDataSaved: {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['onFetch', 'onDataSaved']);
|
||||
const modelValue = computed(
|
||||
() => $props.model ?? `formModel_${route?.meta?.title ?? route.name}`,
|
||||
() => $props.model ?? `formModel_${route?.meta?.title ?? route.name}`
|
||||
).value;
|
||||
const componentIsRendered = ref(false);
|
||||
const arrayData = useArrayData(modelValue);
|
||||
|
@ -111,15 +105,15 @@ const isLoading = ref(false);
|
|||
// Si elegimos observar los cambios del form significa que inicialmente las actions estaran deshabilitadas
|
||||
const isResetting = ref(false);
|
||||
const hasChanges = ref(!$props.observeFormChanges);
|
||||
const originalData = computed(() => state.get(modelValue));
|
||||
const formData = ref();
|
||||
const originalData = ref({});
|
||||
const formData = computed(() => state.get(modelValue));
|
||||
const defaultButtons = computed(() => ({
|
||||
save: {
|
||||
dataCy: 'saveDefaultBtn',
|
||||
color: 'primary',
|
||||
icon: 'save',
|
||||
label: 'globals.save',
|
||||
click: async () => await save(),
|
||||
click: () => myForm.value.submit(),
|
||||
type: 'submit',
|
||||
},
|
||||
reset: {
|
||||
|
@ -133,6 +127,8 @@ const defaultButtons = computed(() => ({
|
|||
}));
|
||||
|
||||
onMounted(async () => {
|
||||
originalData.value = JSON.parse(JSON.stringify($props.formInitialData ?? {}));
|
||||
|
||||
nextTick(() => (componentIsRendered.value = true));
|
||||
|
||||
// Podemos enviarle al form la estructura de data inicial sin necesidad de fetchearla
|
||||
|
@ -140,8 +136,7 @@ onMounted(async () => {
|
|||
|
||||
if (!$props.formInitialData) {
|
||||
if ($props.autoLoad && $props.url) await fetch();
|
||||
else if (arrayData.store.data)
|
||||
updateAndEmit('onFetch', { val: arrayData.store.data });
|
||||
else if (arrayData.store.data) updateAndEmit('onFetch', arrayData.store.data);
|
||||
}
|
||||
if ($props.observeFormChanges) {
|
||||
watch(
|
||||
|
@ -153,7 +148,7 @@ onMounted(async () => {
|
|||
JSON.stringify(newVal) !== JSON.stringify(originalData.value);
|
||||
isResetting.value = false;
|
||||
},
|
||||
{ deep: true },
|
||||
{ deep: true }
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -161,24 +156,16 @@ onMounted(async () => {
|
|||
if (!$props.url)
|
||||
watch(
|
||||
() => arrayData.store.data,
|
||||
(val) => updateAndEmit('onFetch', { val }),
|
||||
(val) => updateAndEmit('onFetch', val)
|
||||
);
|
||||
|
||||
watch(
|
||||
originalData,
|
||||
(val) => {
|
||||
if (val) formData.value = JSON.parse(JSON.stringify(val));
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => [$props.url, $props.filter],
|
||||
async () => {
|
||||
state.set(modelValue, null);
|
||||
originalData.value = null;
|
||||
reset();
|
||||
await fetch();
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
onBeforeRouteLeave((to, from, next) => {
|
||||
|
@ -207,9 +194,10 @@ async function fetch() {
|
|||
});
|
||||
if (Array.isArray(data)) data = data[0] ?? {};
|
||||
|
||||
updateAndEmit('onFetch', { val: data });
|
||||
updateAndEmit('onFetch', data);
|
||||
} catch (e) {
|
||||
state.set(modelValue, {});
|
||||
originalData.value = {};
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
@ -234,11 +222,7 @@ async function save() {
|
|||
|
||||
if ($props.urlCreate) notify('globals.dataCreated', 'positive');
|
||||
|
||||
updateAndEmit('onDataSaved', {
|
||||
val: formData.value,
|
||||
res: response?.data,
|
||||
old: originalData.value,
|
||||
});
|
||||
updateAndEmit('onDataSaved', formData.value, response?.data);
|
||||
if ($props.reload) await arrayData.fetch({});
|
||||
hasChanges.value = false;
|
||||
} finally {
|
||||
|
@ -252,8 +236,7 @@ async function saveAndGo() {
|
|||
}
|
||||
|
||||
function reset() {
|
||||
formData.value = JSON.parse(JSON.stringify(originalData.value));
|
||||
updateAndEmit('onFetch', { val: originalData.value });
|
||||
updateAndEmit('onFetch', originalData.value);
|
||||
if ($props.observeFormChanges) {
|
||||
hasChanges.value = false;
|
||||
isResetting.value = true;
|
||||
|
@ -271,15 +254,16 @@ function filter(value, update, filterOptions) {
|
|||
(ref) => {
|
||||
ref.setOptionIndex(-1);
|
||||
ref.moveOptionSelection(1, true);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function updateAndEmit(evt, { val, res, old } = { val: null, res: null, old: null }) {
|
||||
function updateAndEmit(evt, val, res) {
|
||||
state.set(modelValue, val);
|
||||
originalData.value = val && JSON.parse(JSON.stringify(val));
|
||||
if (!$props.url) arrayData.store.data = val;
|
||||
|
||||
emit(evt, state.get(modelValue), res, old);
|
||||
emit(evt, state.get(modelValue), res);
|
||||
}
|
||||
|
||||
function trimData(data) {
|
||||
|
@ -289,27 +273,6 @@ function trimData(data) {
|
|||
}
|
||||
return data;
|
||||
}
|
||||
function onBeforeSave(formData, originalData) {
|
||||
return getUpdatedValues(
|
||||
Object.keys(getDifferences(formData, originalData)),
|
||||
formData,
|
||||
);
|
||||
}
|
||||
async function onKeyup(evt) {
|
||||
if (evt.key === 'Enter' && !('prevent-submit' in attrs)) {
|
||||
const input = evt.target;
|
||||
if (input.type == 'textarea' && evt.shiftKey) {
|
||||
let { selectionStart, selectionEnd } = input;
|
||||
input.value =
|
||||
input.value.substring(0, selectionStart) +
|
||||
'\n' +
|
||||
input.value.substring(selectionEnd);
|
||||
selectionStart = selectionEnd = selectionStart + 1;
|
||||
return;
|
||||
}
|
||||
await save();
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
save,
|
||||
|
@ -325,13 +288,12 @@ defineExpose({
|
|||
<QForm
|
||||
ref="myForm"
|
||||
v-if="formData"
|
||||
@submit.prevent
|
||||
@keyup.prevent="onKeyup"
|
||||
@submit="save"
|
||||
@reset="reset"
|
||||
class="q-pa-md"
|
||||
:style="maxWidth ? 'max-width: ' + maxWidth : ''"
|
||||
id="formModel"
|
||||
:mapper="onBeforeSave"
|
||||
:prevent-submit="$attrs['prevent-submit']"
|
||||
>
|
||||
<QCard>
|
||||
<slot
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
<script setup>
|
||||
import { ref, computed, useAttrs, nextTick } from 'vue';
|
||||
import { ref, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useState } from 'src/composables/useState';
|
||||
|
||||
import FormModel from 'components/FormModel.vue';
|
||||
|
||||
const emit = defineEmits(['onDataSaved', 'onDataCanceled']);
|
||||
|
||||
const props = defineProps({
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
|
@ -16,41 +15,23 @@ const props = defineProps({
|
|||
type: String,
|
||||
default: '',
|
||||
},
|
||||
showSaveAndContinueBtn: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const attrs = useAttrs();
|
||||
const state = useState();
|
||||
|
||||
const formModelRef = ref(null);
|
||||
const closeButton = ref(null);
|
||||
const isSaveAndContinue = ref(props.showSaveAndContinueBtn);
|
||||
const isLoading = computed(() => formModelRef.value?.isLoading);
|
||||
const reset = computed(() => formModelRef.value?.reset);
|
||||
|
||||
const onDataSaved = async (formData, requestResponse) => {
|
||||
if (!isSaveAndContinue.value) closeButton.value?.click();
|
||||
if (isSaveAndContinue.value) {
|
||||
await nextTick();
|
||||
state.set(attrs.model, attrs.formInitialData);
|
||||
}
|
||||
isSaveAndContinue.value = props.showSaveAndContinueBtn;
|
||||
const onDataSaved = (formData, requestResponse) => {
|
||||
if (closeButton.value) closeButton.value.click();
|
||||
emit('onDataSaved', formData, requestResponse);
|
||||
};
|
||||
|
||||
const onClick = async (saveAndContinue) => {
|
||||
isSaveAndContinue.value = saveAndContinue;
|
||||
await formModelRef.value.save();
|
||||
};
|
||||
const isLoading = computed(() => formModelRef.value?.isLoading);
|
||||
|
||||
defineExpose({
|
||||
isLoading,
|
||||
onDataSaved,
|
||||
isSaveAndContinue,
|
||||
reset,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -78,16 +59,15 @@ defineExpose({
|
|||
flat
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
data-cy="FormModelPopup_cancel"
|
||||
v-close-popup
|
||||
z-max
|
||||
@click="emit('onDataCanceled')"
|
||||
v-close-popup
|
||||
data-cy="FormModelPopup_cancel"
|
||||
z-max
|
||||
/>
|
||||
<QBtn
|
||||
:flat="showSaveAndContinueBtn"
|
||||
:label="t('globals.save')"
|
||||
:title="t('globals.save')"
|
||||
@click="onClick(false)"
|
||||
type="submit"
|
||||
color="primary"
|
||||
class="q-ml-sm"
|
||||
:disabled="isLoading"
|
||||
|
@ -95,18 +75,6 @@ defineExpose({
|
|||
data-cy="FormModelPopup_save"
|
||||
z-max
|
||||
/>
|
||||
<QBtn
|
||||
v-if="showSaveAndContinueBtn"
|
||||
:label="t('globals.isSaveAndContinue')"
|
||||
:title="t('globals.isSaveAndContinue')"
|
||||
color="primary"
|
||||
class="q-ml-sm"
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
data-cy="FormModelPopup_isSaveAndContinue"
|
||||
z-max
|
||||
@click="onClick(true)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</FormModel>
|
||||
|
|
|
@ -31,6 +31,7 @@ const props = defineProps({
|
|||
const route = useRoute();
|
||||
|
||||
const itemTypesOptions = ref([]);
|
||||
const suppliersOptions = ref([]);
|
||||
const tagOptions = ref([]);
|
||||
const tagValues = ref([]);
|
||||
const categoryList = ref(null);
|
||||
|
@ -39,13 +40,13 @@ const selectedTypeFk = ref(getParamWhere(route.query.table, 'typeFk', false));
|
|||
|
||||
const selectedCategory = computed(() => {
|
||||
return (categoryList.value || []).find(
|
||||
(category) => category?.id === selectedCategoryFk.value,
|
||||
(category) => category?.id === selectedCategoryFk.value
|
||||
);
|
||||
});
|
||||
|
||||
const selectedType = computed(() => {
|
||||
return (itemTypesOptions.value || []).find(
|
||||
(type) => type?.id === selectedTypeFk.value,
|
||||
(type) => type?.id === selectedTypeFk.value
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -121,7 +122,9 @@ const removeTag = (index, params, search) => {
|
|||
applyTags(params, search);
|
||||
};
|
||||
const setCategoryList = (data) => {
|
||||
categoryList.value = (data || []).map((category) => ({
|
||||
categoryList.value = (data || [])
|
||||
.filter((category) => category.display)
|
||||
.map((category) => ({
|
||||
...category,
|
||||
icon: `vn:${(category.icon || '').split('-')[1]}`,
|
||||
}));
|
||||
|
@ -130,16 +133,19 @@ const setCategoryList = (data) => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData url="ItemCategories" limit="30" auto-load @on-fetch="setCategoryList" />
|
||||
<FetchData
|
||||
url="ItemCategories"
|
||||
url="Suppliers"
|
||||
limit="30"
|
||||
auto-load
|
||||
@on-fetch="setCategoryList"
|
||||
:where="{ display: { neq: 0 } }"
|
||||
:filter="{ fields: ['id', 'name', 'nickname'], order: 'name ASC', limit: 30 }"
|
||||
@on-fetch="(data) => (suppliersOptions = data)"
|
||||
/>
|
||||
<FetchData
|
||||
url="Tags"
|
||||
:filter="{ fields: ['id', 'name', 'isFree'] }"
|
||||
auto-load
|
||||
limit="30"
|
||||
@on-fetch="(data) => (tagOptions = data)"
|
||||
/>
|
||||
<VnFilterPanel
|
||||
|
@ -197,6 +203,8 @@ const setCategoryList = (data) => {
|
|||
:label="t('components.itemsFilterPanel.typeFk')"
|
||||
v-model="params.typeFk"
|
||||
:options="itemTypesOptions"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
|
@ -234,6 +242,7 @@ const setCategoryList = (data) => {
|
|||
:label="t('globals.tag')"
|
||||
v-model="value.selectedTag"
|
||||
:options="tagOptions"
|
||||
option-label="name"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
|
@ -281,7 +290,7 @@ const setCategoryList = (data) => {
|
|||
<QItem class="q-mt-lg">
|
||||
<QBtn
|
||||
icon="add_circle"
|
||||
v-shortcut="'+'"
|
||||
shortcut="+"
|
||||
flat
|
||||
class="fill-icon-on-hover q-px-xs"
|
||||
color="primary"
|
||||
|
@ -327,6 +336,7 @@ en:
|
|||
active: Is active
|
||||
visible: Is visible
|
||||
floramondo: Is floramondo
|
||||
salesPersonFk: Buyer
|
||||
categoryFk: Category
|
||||
|
||||
es:
|
||||
|
@ -337,12 +347,6 @@ es:
|
|||
active: Activo
|
||||
visible: Visible
|
||||
floramondo: Floramondo
|
||||
salesPersonFk: Comprador
|
||||
categoryFk: Categoría
|
||||
Plant: Planta natural
|
||||
Flower: Flor fresca
|
||||
Handmade: Hecho a mano
|
||||
Artificial: Artificial
|
||||
Green: Verdes frescos
|
||||
Accessories: Complementos florales
|
||||
Fruit: Fruta
|
||||
</i18n>
|
||||
|
|
|
@ -10,13 +10,12 @@ import routes from 'src/router/modules';
|
|||
import LeftMenuItem from './LeftMenuItem.vue';
|
||||
import LeftMenuItemGroup from './LeftMenuItemGroup.vue';
|
||||
import VnInput from './common/VnInput.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const quasar = useQuasar();
|
||||
const navigation = useNavigationStore();
|
||||
const router = useRouter();
|
||||
|
||||
const props = defineProps({
|
||||
source: {
|
||||
type: String,
|
||||
|
@ -41,6 +40,7 @@ const filteredItems = computed(() => {
|
|||
return locale.includes(normalizedSearch);
|
||||
});
|
||||
});
|
||||
|
||||
const filteredPinnedModules = computed(() => {
|
||||
if (!search.value) return pinnedModules.value;
|
||||
const normalizedSearch = search.value
|
||||
|
@ -71,7 +71,7 @@ watch(
|
|||
items.value = [];
|
||||
getRoutes();
|
||||
},
|
||||
{ deep: true },
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
function findMatches(search, item) {
|
||||
|
@ -103,22 +103,12 @@ function addChildren(module, route, parent) {
|
|||
}
|
||||
|
||||
function getRoutes() {
|
||||
const handleRoutes = {
|
||||
main: getMainRoutes,
|
||||
card: getCardRoutes,
|
||||
};
|
||||
try {
|
||||
handleRoutes[props.source]();
|
||||
} catch (error) {
|
||||
throw new Error(`Method is not defined`);
|
||||
}
|
||||
}
|
||||
function getMainRoutes() {
|
||||
if (props.source === 'main') {
|
||||
const modules = Object.assign([], navigation.getModules().value);
|
||||
|
||||
for (const item of modules) {
|
||||
const moduleDef = routes.find(
|
||||
(route) => toLowerCamel(route.name) === item.module,
|
||||
(route) => toLowerCamel(route.name) === item.module
|
||||
);
|
||||
if (!moduleDef) continue;
|
||||
item.children = [];
|
||||
|
@ -127,16 +117,19 @@ function getMainRoutes() {
|
|||
}
|
||||
|
||||
items.value = modules;
|
||||
}
|
||||
}
|
||||
|
||||
function getCardRoutes() {
|
||||
if (props.source === 'card') {
|
||||
const currentRoute = route.matched[1];
|
||||
const currentModule = toLowerCamel(currentRoute.name);
|
||||
let moduleDef = routes.find((route) => toLowerCamel(route.name) === currentModule);
|
||||
let moduleDef = routes.find(
|
||||
(route) => toLowerCamel(route.name) === currentModule
|
||||
);
|
||||
|
||||
if (!moduleDef) return;
|
||||
if (!moduleDef?.menus) moduleDef = betaGetRoutes();
|
||||
addChildren(currentModule, moduleDef, items.value);
|
||||
}
|
||||
}
|
||||
|
||||
function betaGetRoutes() {
|
||||
|
@ -181,10 +174,6 @@ function normalize(text) {
|
|||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.toLowerCase();
|
||||
}
|
||||
const searchModule = () => {
|
||||
const [item] = filteredItems.value;
|
||||
if (item) router.push({ name: item.name });
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -199,11 +188,10 @@ const searchModule = () => {
|
|||
filled
|
||||
dense
|
||||
autofocus
|
||||
@keyup.enter.stop="searchModule()"
|
||||
/>
|
||||
</QItem>
|
||||
<QSeparator />
|
||||
<template v-if="filteredPinnedModules.size && !search">
|
||||
<template v-if="filteredPinnedModules.size">
|
||||
<LeftMenuItem
|
||||
v-for="[key, pinnedModule] of filteredPinnedModules"
|
||||
:key="key"
|
||||
|
@ -227,18 +215,11 @@ const searchModule = () => {
|
|||
</LeftMenuItem>
|
||||
<QSeparator />
|
||||
</template>
|
||||
<template v-for="(item, index) in filteredItems" :key="item.name">
|
||||
<template v-for="item in filteredItems" :key="item.name">
|
||||
<template
|
||||
v-if="
|
||||
search ||
|
||||
(item.children && !filteredPinnedModules.has(item.name))
|
||||
"
|
||||
>
|
||||
<LeftMenuItem
|
||||
:item="item"
|
||||
group="modules"
|
||||
:class="search && index === 0 ? 'searched' : ''"
|
||||
v-if="item.children && !filteredPinnedModules.has(item.name)"
|
||||
>
|
||||
<LeftMenuItem :item="item" group="modules">
|
||||
<template #side>
|
||||
<QBtn
|
||||
v-if="item.isPinned === true"
|
||||
|
@ -355,9 +336,6 @@ const searchModule = () => {
|
|||
.header {
|
||||
color: var(--vn-label-color);
|
||||
}
|
||||
.searched {
|
||||
background-color: var(--vn-section-hover-color);
|
||||
}
|
||||
</style>
|
||||
<i18n>
|
||||
es:
|
||||
|
|
|
@ -26,7 +26,6 @@ const itemComputed = computed(() => {
|
|||
:to="{ name: itemComputed.name }"
|
||||
clickable
|
||||
v-ripple
|
||||
:data-cy="`${itemComputed.name}-menu-item`"
|
||||
>
|
||||
<QItemSection avatar v-if="itemComputed.icon">
|
||||
<QIcon :name="itemComputed.icon" />
|
||||
|
|
|
@ -20,7 +20,6 @@ const appName = 'Lilium';
|
|||
const pinnedModulesRef = ref();
|
||||
|
||||
onMounted(() => stateStore.setMounted());
|
||||
const refresh = () => window.location.reload();
|
||||
</script>
|
||||
<template>
|
||||
<QHeader color="white" elevated>
|
||||
|
@ -57,7 +56,7 @@ const refresh = () => window.location.reload();
|
|||
:class="{
|
||||
'no-visible': !stateQuery.isLoading().value,
|
||||
}"
|
||||
size="sm"
|
||||
size="xs"
|
||||
data-cy="loading-spinner"
|
||||
/>
|
||||
<QSpace />
|
||||
|
@ -65,13 +64,6 @@ const refresh = () => window.location.reload();
|
|||
<QSpace />
|
||||
<div class="q-pl-sm q-gutter-sm row items-center no-wrap">
|
||||
<div id="actions-prepend"></div>
|
||||
<QIcon
|
||||
name="refresh"
|
||||
size="md"
|
||||
color="red"
|
||||
v-if="state.get('error')"
|
||||
@click="refresh"
|
||||
/>
|
||||
<QBtn
|
||||
:class="{ 'q-pa-none': quasar.platform.is.mobile }"
|
||||
id="pinnedModules"
|
||||
|
@ -85,15 +77,7 @@ const refresh = () => window.location.reload();
|
|||
</QTooltip>
|
||||
<PinnedModules ref="pinnedModulesRef" />
|
||||
</QBtn>
|
||||
<QBtn
|
||||
class="q-pa-none"
|
||||
rounded
|
||||
dense
|
||||
flat
|
||||
no-wrap
|
||||
id="user"
|
||||
data-cy="userPanel_btn"
|
||||
>
|
||||
<QBtn class="q-pa-none" rounded dense flat no-wrap id="user">
|
||||
<VnAvatar
|
||||
:worker-id="user.id"
|
||||
:title="user.name"
|
||||
|
|
|
@ -9,7 +9,6 @@ import VnSelect from 'components/common/VnSelect.vue';
|
|||
import FormPopup from './FormPopup.vue';
|
||||
import axios from 'axios';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
import VnCheckbox from 'src/components/common/VnCheckbox.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
invoiceOutData: {
|
||||
|
@ -132,11 +131,15 @@ const refund = async () => {
|
|||
:required="true"
|
||||
/> </VnRow
|
||||
><VnRow>
|
||||
<VnCheckbox
|
||||
v-model="invoiceParams.inheritWarehouse"
|
||||
<div>
|
||||
<QCheckbox
|
||||
:label="t('Inherit warehouse')"
|
||||
:info="t('Inherit warehouse tooltip')"
|
||||
v-model="invoiceParams.inheritWarehouse"
|
||||
/>
|
||||
<QIcon name="info" class="cursor-info q-ml-sm" size="sm">
|
||||
<QTooltip>{{ t('Inherit warehouse tooltip') }}</QTooltip>
|
||||
</QIcon>
|
||||
</div>
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormPopup>
|
||||
|
|
|
@ -1,90 +1,39 @@
|
|||
<script setup>
|
||||
import { toCurrency } from 'src/filters';
|
||||
|
||||
defineProps({ row: { type: Object, required: true } });
|
||||
</script>
|
||||
<template>
|
||||
<span class="q-gutter-x-xs">
|
||||
<router-link
|
||||
v-if="row.claim?.claimFk"
|
||||
:to="{ name: 'ClaimBasicData', params: { id: row.claim?.claimFk } }"
|
||||
class="link"
|
||||
>
|
||||
<QIcon name="vn:claims" size="xs">
|
||||
<QTooltip>
|
||||
{{ $t('ticketSale.claim') }}:
|
||||
{{ row.claim?.claimFk }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</router-link>
|
||||
<span>
|
||||
<QIcon
|
||||
v-if="row?.reserved"
|
||||
v-if="row.isTaxDataChecked === 0"
|
||||
name="vn:no036"
|
||||
color="primary"
|
||||
name="vn:reserva"
|
||||
size="xs"
|
||||
data-cy="ticketSaleReservedIcon"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('ticketSale.reserved') }}
|
||||
</QTooltip>
|
||||
<QTooltip>{{ $t('salesTicketsTable.noVerifiedData') }}</QTooltip>
|
||||
</QIcon>
|
||||
<QIcon v-if="row.hasTicketRequest" name="vn:buyrequest" color="primary" size="xs">
|
||||
<QTooltip>{{ $t('salesTicketsTable.purchaseRequest') }}</QTooltip>
|
||||
</QIcon>
|
||||
<QIcon v-if="row.itemShortage" name="vn:unavailable" color="primary" size="xs">
|
||||
<QTooltip>{{ $t('salesTicketsTable.notVisible') }}</QTooltip>
|
||||
</QIcon>
|
||||
<QIcon v-if="row.isFreezed" name="vn:frozen" color="primary" size="xs">
|
||||
<QTooltip>{{ $t('salesTicketsTable.clientFrozen') }}</QTooltip>
|
||||
</QIcon>
|
||||
<QIcon
|
||||
v-if="row?.hasRisk"
|
||||
v-if="row.risk"
|
||||
name="vn:risk"
|
||||
:color="row.hasHighRisk ? 'negative' : 'primary'"
|
||||
size="xs"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ $t('salesTicketsTable.risk') }}:
|
||||
{{ toCurrency(row.risk - (row.credit ?? 0)) }}
|
||||
{{ $t('salesTicketsTable.risk') }}: {{ row.risk - row.credit }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
<QIcon
|
||||
v-if="row?.hasComponentLack"
|
||||
name="vn:components"
|
||||
color="primary"
|
||||
size="xs"
|
||||
>
|
||||
<QIcon v-if="row.hasComponentLack" name="vn:components" color="primary" size="xs">
|
||||
<QTooltip>{{ $t('salesTicketsTable.componentLack') }}</QTooltip>
|
||||
</QIcon>
|
||||
<QIcon v-if="row?.hasItemDelay" color="primary" size="xs" name="vn:hasItemDelay">
|
||||
<QTooltip>
|
||||
{{ $t('ticket.summary.hasItemDelay') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
<QIcon v-if="row?.hasItemLost" color="primary" size="xs" name="vn:hasItemLost">
|
||||
<QTooltip>
|
||||
{{ $t('salesTicketsTable.hasItemLost') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
<QIcon
|
||||
v-if="row?.hasItemShortage"
|
||||
name="vn:unavailable"
|
||||
color="primary"
|
||||
size="xs"
|
||||
>
|
||||
<QTooltip>{{ $t('salesTicketsTable.notVisible') }}</QTooltip>
|
||||
</QIcon>
|
||||
<QIcon v-if="row?.hasRounding" color="primary" name="sync_problem" size="xs">
|
||||
<QTooltip>
|
||||
{{ $t('ticketList.rounding') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
<QIcon
|
||||
v-if="row?.hasTicketRequest"
|
||||
name="vn:buyrequest"
|
||||
color="primary"
|
||||
size="xs"
|
||||
>
|
||||
<QTooltip>{{ $t('salesTicketsTable.purchaseRequest') }}</QTooltip>
|
||||
</QIcon>
|
||||
<QIcon v-if="row?.isTaxDataChecked" name="vn:no036" color="primary" size="xs">
|
||||
<QTooltip>{{ $t('salesTicketsTable.noVerifiedData') }}</QTooltip>
|
||||
</QIcon>
|
||||
<QIcon v-if="row?.isFreezed" name="vn:frozen" color="primary" size="xs">
|
||||
<QTooltip>{{ $t('salesTicketsTable.clientFrozen') }}</QTooltip>
|
||||
</QIcon>
|
||||
<QIcon v-if="row?.isTooLittle" name="vn:isTooLittle" color="primary" size="xs">
|
||||
<QIcon v-if="row.isTooLittle" name="vn:isTooLittle" color="primary" size="xs">
|
||||
<QTooltip>{{ $t('salesTicketsTable.tooLittle') }}</QTooltip>
|
||||
</QIcon>
|
||||
</span>
|
||||
|
|
|
@ -10,7 +10,6 @@ import VnSelect from 'components/common/VnSelect.vue';
|
|||
import FormPopup from './FormPopup.vue';
|
||||
import axios from 'axios';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
import VnCheckbox from './common/VnCheckbox.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
invoiceOutData: {
|
||||
|
@ -87,7 +86,7 @@ const makeInvoice = async () => {
|
|||
(data) => (
|
||||
(rectificativeTypeOptions = data),
|
||||
(transferInvoiceParams.cplusRectificationTypeFk = data.filter(
|
||||
(type) => type.description == 'I – Por diferencias',
|
||||
(type) => type.description == 'I – Por diferencias'
|
||||
)[0].id)
|
||||
)
|
||||
"
|
||||
|
@ -100,7 +99,7 @@ const makeInvoice = async () => {
|
|||
(data) => (
|
||||
(siiTypeInvoiceOutsOptions = data),
|
||||
(transferInvoiceParams.siiTypeInvoiceOutFk = data.filter(
|
||||
(type) => type.code == 'R4',
|
||||
(type) => type.code == 'R4'
|
||||
)[0].id)
|
||||
)
|
||||
"
|
||||
|
@ -122,6 +121,7 @@ const makeInvoice = async () => {
|
|||
<VnRow>
|
||||
<VnSelect
|
||||
:label="t('Client')"
|
||||
:options="clientsOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
|
@ -186,11 +186,15 @@ const makeInvoice = async () => {
|
|||
/>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<VnCheckbox
|
||||
v-model="checked"
|
||||
<div>
|
||||
<QCheckbox
|
||||
:label="t('Bill destination client')"
|
||||
:info="t('transferInvoiceInfo')"
|
||||
v-model="checked"
|
||||
/>
|
||||
<QIcon name="info" class="cursor-info q-ml-sm" size="sm">
|
||||
<QTooltip>{{ t('transferInvoiceInfo') }}</QTooltip>
|
||||
</QIcon>
|
||||
</div>
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormPopup>
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<script setup>
|
||||
import { markRaw, computed } from 'vue';
|
||||
import { QIcon, QToggle } from 'quasar';
|
||||
import { QIcon, QCheckbox } from 'quasar';
|
||||
import { dashIfEmpty } from 'src/filters';
|
||||
|
||||
/* basic input */
|
||||
import VnSelect from 'components/common/VnSelect.vue';
|
||||
import VnSelectCache from 'components/common/VnSelectCache.vue';
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
|
@ -11,11 +12,8 @@ import VnInputDate from 'components/common/VnInputDate.vue';
|
|||
import VnInputTime from 'components/common/VnInputTime.vue';
|
||||
import VnComponent from 'components/common/VnComponent.vue';
|
||||
import VnUserLink from 'components/ui/VnUserLink.vue';
|
||||
import VnSelectEnum from '../common/VnSelectEnum.vue';
|
||||
import VnCheckbox from '../common/VnCheckbox.vue';
|
||||
|
||||
const model = defineModel(undefined, { required: true });
|
||||
const emit = defineEmits(['blur']);
|
||||
const $props = defineProps({
|
||||
column: {
|
||||
type: Object,
|
||||
|
@ -41,18 +39,10 @@ const $props = defineProps({
|
|||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
autofocus: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showLabel: {
|
||||
type: Boolean,
|
||||
default: null,
|
||||
},
|
||||
eventHandlers: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const defaultSelect = {
|
||||
|
@ -109,8 +99,7 @@ const defaultComponents = {
|
|||
},
|
||||
},
|
||||
checkbox: {
|
||||
ref: 'checkbox',
|
||||
component: markRaw(VnCheckbox),
|
||||
component: markRaw(QCheckbox),
|
||||
attrs: ({ model }) => {
|
||||
const defaultAttrs = {
|
||||
disable: !$props.isEditable,
|
||||
|
@ -126,10 +115,6 @@ const defaultComponents = {
|
|||
},
|
||||
forceAttrs: {
|
||||
label: $props.showLabel && $props.column.label,
|
||||
autofocus: true,
|
||||
},
|
||||
events: {
|
||||
blur: () => emit('blur'),
|
||||
},
|
||||
},
|
||||
select: {
|
||||
|
@ -140,19 +125,12 @@ const defaultComponents = {
|
|||
component: markRaw(VnSelect),
|
||||
...defaultSelect,
|
||||
},
|
||||
selectEnum: {
|
||||
component: markRaw(VnSelectEnum),
|
||||
...defaultSelect,
|
||||
},
|
||||
icon: {
|
||||
component: markRaw(QIcon),
|
||||
},
|
||||
userLink: {
|
||||
component: markRaw(VnUserLink),
|
||||
},
|
||||
toggle: {
|
||||
component: markRaw(QToggle),
|
||||
},
|
||||
};
|
||||
|
||||
const value = computed(() => {
|
||||
|
@ -182,28 +160,7 @@ const col = computed(() => {
|
|||
return newColumn;
|
||||
});
|
||||
|
||||
const components = computed(() => {
|
||||
const sourceComponents = $props.components ?? defaultComponents;
|
||||
|
||||
return Object.keys(sourceComponents).reduce((acc, key) => {
|
||||
const component = sourceComponents[key];
|
||||
|
||||
if (!component || typeof component !== 'object') {
|
||||
acc[key] = component;
|
||||
return acc;
|
||||
}
|
||||
|
||||
acc[key] = {
|
||||
...component,
|
||||
attrs: {
|
||||
...(component.attrs || {}),
|
||||
autofocus: $props.autofocus,
|
||||
},
|
||||
event: { ...component?.event, ...$props?.eventHandlers },
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
});
|
||||
const components = computed(() => $props.components ?? defaultComponents);
|
||||
</script>
|
||||
<template>
|
||||
<div class="row no-wrap">
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
<script setup>
|
||||
import { markRaw, computed } from 'vue';
|
||||
import { QCheckbox, QToggle } from 'quasar';
|
||||
import { QCheckbox } from 'quasar';
|
||||
import { useArrayData } from 'composables/useArrayData';
|
||||
|
||||
/* basic input */
|
||||
import VnSelect from 'components/common/VnSelect.vue';
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnInputTime from 'components/common/VnInputTime.vue';
|
||||
import VnColumn from 'components/VnTable/VnColumn.vue';
|
||||
import VnTableColumn from 'components/VnTable/VnColumn.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
column: {
|
||||
|
@ -25,10 +27,6 @@ const $props = defineProps({
|
|||
type: String,
|
||||
default: 'table',
|
||||
},
|
||||
customClass: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
defineExpose({ addFilter, props: $props });
|
||||
|
@ -36,7 +34,7 @@ defineExpose({ addFilter, props: $props });
|
|||
const model = defineModel(undefined, { required: true });
|
||||
const arrayData = useArrayData(
|
||||
$props.dataKey,
|
||||
$props.searchUrl ? { searchUrl: $props.searchUrl } : null,
|
||||
$props.searchUrl ? { searchUrl: $props.searchUrl } : null
|
||||
);
|
||||
const columnFilter = computed(() => $props.column?.columnFilter);
|
||||
|
||||
|
@ -48,18 +46,19 @@ const enterEvent = {
|
|||
|
||||
const defaultAttrs = {
|
||||
filled: !$props.showTitle,
|
||||
class: 'q-px-xs q-pb-xs q-pt-none fit',
|
||||
dense: true,
|
||||
};
|
||||
|
||||
const forceAttrs = {
|
||||
label: $props.showTitle ? '' : (columnFilter.value?.label ?? $props.column.label),
|
||||
label: $props.showTitle ? '' : columnFilter.value?.label ?? $props.column.label,
|
||||
};
|
||||
|
||||
const selectComponent = {
|
||||
component: markRaw(VnSelect),
|
||||
event: updateEvent,
|
||||
attrs: {
|
||||
class: `q-pt-none fit ${$props.customClass}`,
|
||||
class: 'q-px-sm q-pb-xs q-pt-none fit',
|
||||
dense: true,
|
||||
filled: !$props.showTitle,
|
||||
},
|
||||
|
@ -91,6 +90,7 @@ const components = {
|
|||
event: updateEvent,
|
||||
attrs: {
|
||||
...defaultAttrs,
|
||||
style: 'min-width: 150px',
|
||||
},
|
||||
forceAttrs,
|
||||
},
|
||||
|
@ -109,24 +109,14 @@ const components = {
|
|||
component: markRaw(QCheckbox),
|
||||
event: updateEvent,
|
||||
attrs: {
|
||||
class: $props.showTitle ? 'q-py-sm' : 'q-px-md q-py-xs fit',
|
||||
dense: true,
|
||||
class: $props.showTitle ? 'q-py-sm q-mt-md' : 'q-px-md q-py-xs fit',
|
||||
'toggle-indeterminate': true,
|
||||
size: 'sm',
|
||||
},
|
||||
forceAttrs,
|
||||
},
|
||||
select: selectComponent,
|
||||
rawSelect: selectComponent,
|
||||
toggle: {
|
||||
component: markRaw(QToggle),
|
||||
event: updateEvent,
|
||||
attrs: {
|
||||
class: $props.showTitle ? 'q-py-sm' : 'q-px-md q-py-xs fit',
|
||||
'toggle-indeterminate': true,
|
||||
size: 'sm',
|
||||
},
|
||||
forceAttrs,
|
||||
},
|
||||
};
|
||||
|
||||
async function addFilter(value, name) {
|
||||
|
@ -142,8 +132,19 @@ async function addFilter(value, name) {
|
|||
await arrayData.addFilter({ params: { [field]: value } });
|
||||
}
|
||||
|
||||
function alignRow() {
|
||||
switch ($props.column.align) {
|
||||
case 'left':
|
||||
return 'justify-start items-start';
|
||||
case 'right':
|
||||
return 'justify-end items-end';
|
||||
default:
|
||||
return 'flex-center';
|
||||
}
|
||||
}
|
||||
|
||||
const showFilter = computed(
|
||||
() => $props.column?.columnFilter !== false && $props.column.name != 'tableActions',
|
||||
() => $props.column?.columnFilter !== false && $props.column.name != 'tableActions'
|
||||
);
|
||||
|
||||
const onTabPressed = async () => {
|
||||
|
@ -151,8 +152,13 @@ const onTabPressed = async () => {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="showFilter" class="full-width" style="overflow: hidden">
|
||||
<VnColumn
|
||||
<div
|
||||
v-if="showFilter"
|
||||
class="full-width"
|
||||
:class="alignRow()"
|
||||
style="max-height: 45px; overflow: hidden"
|
||||
>
|
||||
<VnTableColumn
|
||||
:column="$props.column"
|
||||
default="input"
|
||||
v-model="model"
|
||||
|
@ -162,8 +168,3 @@ const onTabPressed = async () => {
|
|||
/>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
label.vn-label-padding > .q-field__inner > .q-field__control {
|
||||
padding: inherit !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -23,10 +23,6 @@ const $props = defineProps({
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
align: {
|
||||
type: String,
|
||||
default: 'end',
|
||||
},
|
||||
});
|
||||
const hover = ref();
|
||||
const arrayData = useArrayData($props.dataKey, { searchUrl: $props.searchUrl });
|
||||
|
@ -45,33 +41,21 @@ async function orderBy(name, direction) {
|
|||
break;
|
||||
}
|
||||
if (!direction) return await arrayData.deleteOrder(name);
|
||||
|
||||
await arrayData.addOrder(name, direction);
|
||||
}
|
||||
|
||||
defineExpose({ orderBy });
|
||||
|
||||
function textAlignToFlex(textAlign) {
|
||||
return `justify-content: ${
|
||||
{
|
||||
'text-center': 'center',
|
||||
'text-left': 'start',
|
||||
'text-right': 'end',
|
||||
}[textAlign] || 'start'
|
||||
};`;
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
@mouseenter="hover = true"
|
||||
@mouseleave="hover = false"
|
||||
@click="orderBy(name, model?.direction)"
|
||||
class="items-center no-wrap cursor-pointer title"
|
||||
:style="textAlignToFlex(align)"
|
||||
class="row items-center no-wrap cursor-pointer"
|
||||
>
|
||||
<span :title="label">{{ label }}</span>
|
||||
<div v-if="name && model?.index">
|
||||
<QChip
|
||||
v-if="name"
|
||||
:label="!vertical ? model?.index : ''"
|
||||
:icon="
|
||||
(model?.index || hover) && !vertical
|
||||
|
@ -87,7 +71,7 @@ function textAlignToFlex(textAlign) {
|
|||
]"
|
||||
class="no-box-shadow"
|
||||
:clickable="true"
|
||||
style="min-width: 40px; max-height: 30px"
|
||||
style="min-width: 40px"
|
||||
>
|
||||
<div
|
||||
class="column flex-center"
|
||||
|
@ -108,15 +92,4 @@ function textAlignToFlex(textAlign) {
|
|||
</div>
|
||||
</QChip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
color: var(--vn-label-color);
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,39 +1,22 @@
|
|||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
onBeforeMount,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
computed,
|
||||
watch,
|
||||
h,
|
||||
render,
|
||||
inject,
|
||||
useAttrs,
|
||||
nextTick,
|
||||
} from 'vue';
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
import { ref, onBeforeMount, onMounted, computed, watch, useAttrs } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useQuasar, date } from 'quasar';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { useFilterParams } from 'src/composables/useFilterParams';
|
||||
import { dashIfEmpty, toDate } from 'src/filters';
|
||||
|
||||
import CrudModel from 'src/components/CrudModel.vue';
|
||||
import FormModelPopup from 'components/FormModelPopup.vue';
|
||||
|
||||
import VnColumn from 'components/VnTable/VnColumn.vue';
|
||||
import VnTableColumn from 'components/VnTable/VnColumn.vue';
|
||||
import VnFilter from 'components/VnTable/VnFilter.vue';
|
||||
import VnTableChip from 'components/VnTable/VnChip.vue';
|
||||
import VnVisibleColumn from 'src/components/VnTable/VnVisibleColumn.vue';
|
||||
import VnLv from 'components/ui/VnLv.vue';
|
||||
import VnTableOrder from 'src/components/VnTable/VnOrder.vue';
|
||||
import VnTableFilter from './VnTableFilter.vue';
|
||||
import { getColAlign } from 'src/composables/getColAlign';
|
||||
import RightMenu from '../common/RightMenu.vue';
|
||||
|
||||
const arrayData = useArrayData(useAttrs()['data-key']);
|
||||
const $props = defineProps({
|
||||
columns: {
|
||||
type: Array,
|
||||
|
@ -51,6 +34,10 @@ const $props = defineProps({
|
|||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
rightSearchIcon: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
rowClick: {
|
||||
type: [Function, Boolean],
|
||||
default: null,
|
||||
|
@ -127,23 +114,7 @@ const $props = defineProps({
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
withFilters: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
overlay: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
createComplement: {
|
||||
type: Object,
|
||||
},
|
||||
dataCy: {
|
||||
type: String,
|
||||
default: 'vn-table',
|
||||
},
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const stateStore = useStateStore();
|
||||
const route = useRoute();
|
||||
|
@ -161,17 +132,10 @@ const showForm = ref(false);
|
|||
const splittedColumns = ref({ columns: [] });
|
||||
const columnsVisibilitySkipped = ref();
|
||||
const createForm = ref();
|
||||
const createRef = ref(null);
|
||||
const tableRef = ref();
|
||||
const params = ref(useFilterParams($attrs['data-key']).params);
|
||||
const orders = ref(useFilterParams($attrs['data-key']).orders);
|
||||
const app = inject('app');
|
||||
|
||||
const editingRow = ref(null);
|
||||
const editingField = ref(null);
|
||||
const isTableMode = computed(() => mode.value == TABLE_MODE);
|
||||
const selectRegex = /select/;
|
||||
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
|
||||
const tableModes = [
|
||||
{
|
||||
icon: 'view_column',
|
||||
|
@ -192,8 +156,7 @@ onBeforeMount(() => {
|
|||
hasParams.value = urlParams && Object.keys(urlParams).length !== 0;
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
if ($props.isEditable) document.addEventListener('click', clickHandler);
|
||||
onMounted(() => {
|
||||
mode.value =
|
||||
quasar.platform.is.mobile && !$props.disableOption?.card
|
||||
? CARD_MODE
|
||||
|
@ -215,25 +178,14 @@ onMounted(async () => {
|
|||
}
|
||||
});
|
||||
|
||||
onUnmounted(async () => {
|
||||
if ($props.isEditable) document.removeEventListener('click', clickHandler);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => $props.columns,
|
||||
(value) => splitColumns(value),
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
create: createForm,
|
||||
reload,
|
||||
redirect: redirectFn,
|
||||
selected,
|
||||
CrudModelRef,
|
||||
params,
|
||||
tableRef,
|
||||
});
|
||||
const isTableMode = computed(() => mode.value == TABLE_MODE);
|
||||
const showRightIcon = computed(() => $props.rightSearch || $props.rightSearchIcon);
|
||||
|
||||
function splitColumns(columns) {
|
||||
splittedColumns.value = {
|
||||
|
@ -257,9 +209,7 @@ function splitColumns(columns) {
|
|||
col.columnFilter = { inWhere: true, ...col.columnFilter };
|
||||
splittedColumns.value.columns.push(col);
|
||||
}
|
||||
|
||||
splittedColumns.value.create = createOrderSort(splittedColumns.value.create);
|
||||
|
||||
// Status column
|
||||
if (splittedColumns.value.chips.length) {
|
||||
splittedColumns.value.columnChips = splittedColumns.value.chips.filter(
|
||||
(c) => !c.isId,
|
||||
|
@ -275,30 +225,22 @@ function splitColumns(columns) {
|
|||
}
|
||||
}
|
||||
|
||||
function createOrderSort(columns) {
|
||||
const orderedColumn = columns
|
||||
.map((column, index) =>
|
||||
column.createOrder !== undefined ? { ...column, originalIndex: index } : null,
|
||||
)
|
||||
.filter((item) => item !== null);
|
||||
|
||||
orderedColumn.sort((a, b) => a.createOrder - b.createOrder);
|
||||
|
||||
const filteredColumns = columns.filter((col) => col.createOrder === undefined);
|
||||
|
||||
orderedColumn.forEach((col) => {
|
||||
filteredColumns.splice(col.createOrder, 0, col);
|
||||
});
|
||||
|
||||
return filteredColumns;
|
||||
}
|
||||
|
||||
const rowClickFunction = computed(() => {
|
||||
if ($props.rowClick != undefined) return $props.rowClick;
|
||||
if ($props.redirect) return ({ id }) => redirectFn(id);
|
||||
return () => {};
|
||||
});
|
||||
|
||||
const rowCtrlClickFunction = computed(() => {
|
||||
if ($props.rowCtrlClick != undefined) return $props.rowCtrlClick;
|
||||
if ($props.redirect)
|
||||
return (evt, { id }) => {
|
||||
stopEventPropagation(evt);
|
||||
window.open(`/#/${$props.redirect}/${id}`, '_blank');
|
||||
};
|
||||
return () => {};
|
||||
});
|
||||
|
||||
function redirectFn(id) {
|
||||
router.push({ path: `/${$props.redirect}/${id}` });
|
||||
}
|
||||
|
@ -320,6 +262,21 @@ function columnName(col) {
|
|||
return name;
|
||||
}
|
||||
|
||||
function getColAlign(col) {
|
||||
return 'text-' + (col.align ?? 'left');
|
||||
}
|
||||
|
||||
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
|
||||
defineExpose({
|
||||
create: createForm,
|
||||
reload,
|
||||
redirect: redirectFn,
|
||||
selected,
|
||||
CrudModelRef,
|
||||
params,
|
||||
tableRef,
|
||||
});
|
||||
|
||||
function handleOnDataSaved(_) {
|
||||
if (_.onDataSaved) _.onDataSaved({ CrudModelRef: CrudModelRef.value });
|
||||
else $props.create.onDataSaved(_);
|
||||
|
@ -338,14 +295,8 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
|
|||
if (evt?.shiftKey && added) {
|
||||
const rowIndex = selectedRows[0].$index;
|
||||
const selectedIndexes = new Set(selected.value.map((row) => row.$index));
|
||||
const minIndex = selectedIndexes.size
|
||||
? Math.min(...selectedIndexes, rowIndex)
|
||||
: 0;
|
||||
const maxIndex = Math.max(...selectedIndexes, rowIndex);
|
||||
|
||||
for (let i = minIndex; i <= maxIndex; i++) {
|
||||
const row = rows[i];
|
||||
if (row.$index == rowIndex) continue;
|
||||
for (const row of rows) {
|
||||
if (row.$index == rowIndex) break;
|
||||
if (!selectedIndexes.has(row.$index)) {
|
||||
selected.value.push(row);
|
||||
selectedIndexes.add(row.$index);
|
||||
|
@ -353,283 +304,16 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isEditableColumn(column) {
|
||||
const isEditableCol = column?.isEditable ?? true;
|
||||
const isVisible = column?.visible ?? true;
|
||||
const hasComponent = column?.component;
|
||||
|
||||
return $props.isEditable && isVisible && hasComponent && isEditableCol;
|
||||
}
|
||||
|
||||
function hasEditableFormat(column) {
|
||||
if (isEditableColumn(column)) return 'editable-text';
|
||||
}
|
||||
|
||||
const clickHandler = async (event) => {
|
||||
const clickedElement = event.target.closest('td');
|
||||
const isDateElement = event.target.closest('.q-date');
|
||||
const isTimeElement = event.target.closest('.q-time');
|
||||
const isQSelectDropDown = event.target.closest('.q-select__dropdown-icon');
|
||||
|
||||
if (isDateElement || isTimeElement || isQSelectDropDown) return;
|
||||
|
||||
if (clickedElement === null) {
|
||||
await destroyInput(editingRow.value, editingField.value);
|
||||
return;
|
||||
}
|
||||
const rowIndex = clickedElement.getAttribute('data-row-index');
|
||||
const colField = clickedElement.getAttribute('data-col-field');
|
||||
const column = $props.columns.find((col) => col.name === colField);
|
||||
|
||||
if (editingRow.value !== null && editingField.value !== null) {
|
||||
if (editingRow.value == rowIndex && editingField.value == colField) return;
|
||||
|
||||
await destroyInput(editingRow.value, editingField.value);
|
||||
}
|
||||
|
||||
if (isEditableColumn(column)) {
|
||||
await renderInput(Number(rowIndex), colField, clickedElement);
|
||||
}
|
||||
};
|
||||
|
||||
async function handleTabKey(event, rowIndex, colField) {
|
||||
if (editingRow.value == rowIndex && editingField.value == colField)
|
||||
await destroyInput(editingRow.value, editingField.value);
|
||||
|
||||
const direction = event.shiftKey ? -1 : 1;
|
||||
const { nextRowIndex, nextColumnName } = await handleTabNavigation(
|
||||
rowIndex,
|
||||
colField,
|
||||
direction,
|
||||
);
|
||||
|
||||
if (nextRowIndex < 0 || nextRowIndex >= arrayData.store.data.length) return;
|
||||
|
||||
event.preventDefault();
|
||||
await renderInput(nextRowIndex, nextColumnName, null);
|
||||
}
|
||||
|
||||
async function renderInput(rowId, field, clickedElement) {
|
||||
editingField.value = field;
|
||||
editingRow.value = rowId;
|
||||
|
||||
const originalColumn = $props.columns.find((col) => col.name === field);
|
||||
const column = { ...originalColumn, ...{ label: '' } };
|
||||
const row = CrudModelRef.value.formData[rowId];
|
||||
const oldValue = CrudModelRef.value.formData[rowId][column?.name];
|
||||
|
||||
if (!clickedElement)
|
||||
clickedElement = document.querySelector(
|
||||
`[data-row-index="${rowId}"][data-col-field="${field}"]`,
|
||||
);
|
||||
|
||||
Array.from(clickedElement.childNodes).forEach((child) => {
|
||||
child.style.visibility = 'hidden';
|
||||
child.style.position = 'relative';
|
||||
});
|
||||
|
||||
const isSelect = selectRegex.test(column?.component);
|
||||
if (isSelect) column.attrs = { ...column.attrs, 'emit-value': false };
|
||||
|
||||
const node = h(VnColumn, {
|
||||
row: row,
|
||||
class: 'temp-input',
|
||||
column: column,
|
||||
modelValue: row[column.name],
|
||||
componentProp: 'columnField',
|
||||
autofocus: true,
|
||||
focusOnMount: true,
|
||||
eventHandlers: {
|
||||
'update:modelValue': async (value) => {
|
||||
if (isSelect && value) {
|
||||
await updateSelectValue(value, column, row, oldValue);
|
||||
} else row[column.name] = value;
|
||||
await column?.cellEvent?.['update:modelValue']?.(value, oldValue, row);
|
||||
},
|
||||
keyup: async (event) => {
|
||||
if (event.key === 'Enter')
|
||||
await destroyInput(rowId, field, clickedElement);
|
||||
},
|
||||
keydown: async (event) => {
|
||||
switch (event.key) {
|
||||
case 'Tab':
|
||||
await handleTabKey(event, rowId, field);
|
||||
event.stopPropagation();
|
||||
break;
|
||||
case 'Escape':
|
||||
await destroyInput(rowId, field, clickedElement);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
click: (event) => {
|
||||
column?.cellEvent?.['click']?.(event, row);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
node.appContext = app._context;
|
||||
render(node, clickedElement);
|
||||
|
||||
if (['toggle'].includes(column?.component))
|
||||
node.el?.querySelector('span > div').focus();
|
||||
|
||||
if (['checkbox', undefined].includes(column?.component))
|
||||
node.el?.querySelector('span > div > div').focus();
|
||||
}
|
||||
|
||||
async function updateSelectValue(value, column, row, oldValue) {
|
||||
row[column.name] = value[column.attrs?.optionValue ?? 'id'];
|
||||
|
||||
row[column?.name + 'VnTableTextValue'] = value[column.attrs?.optionLabel ?? 'name'];
|
||||
|
||||
if (column?.attrs?.find?.label)
|
||||
row[column?.attrs?.find?.label] = value[column.attrs?.optionLabel ?? 'name'];
|
||||
|
||||
await column?.cellEvent?.['update:modelValue']?.(value, oldValue, row);
|
||||
}
|
||||
|
||||
async function destroyInput(rowIndex, field, clickedElement) {
|
||||
if (!clickedElement)
|
||||
clickedElement = document.querySelector(
|
||||
`[data-row-index="${rowIndex}"][data-col-field="${field}"]`,
|
||||
);
|
||||
if (clickedElement) {
|
||||
await nextTick();
|
||||
render(null, clickedElement);
|
||||
Array.from(clickedElement.childNodes).forEach((child) => {
|
||||
child.style.visibility = 'visible';
|
||||
child.style.position = '';
|
||||
});
|
||||
}
|
||||
if (editingRow.value !== rowIndex || editingField.value !== field) return;
|
||||
editingRow.value = null;
|
||||
editingField.value = null;
|
||||
}
|
||||
|
||||
async function handleTabNavigation(rowIndex, colName, direction) {
|
||||
const columns = $props.columns;
|
||||
const totalColumns = columns.length;
|
||||
let currentColumnIndex = columns.findIndex((col) => col.name === colName);
|
||||
|
||||
let iterations = 0;
|
||||
let newColumnIndex = currentColumnIndex;
|
||||
|
||||
do {
|
||||
iterations++;
|
||||
newColumnIndex = (newColumnIndex + direction + totalColumns) % totalColumns;
|
||||
|
||||
if (isEditableColumn(columns[newColumnIndex])) break;
|
||||
} while (iterations < totalColumns);
|
||||
|
||||
if (iterations >= totalColumns + 1) return;
|
||||
|
||||
if (direction === 1 && newColumnIndex <= currentColumnIndex) {
|
||||
rowIndex++;
|
||||
} else if (direction === -1 && newColumnIndex >= currentColumnIndex) {
|
||||
rowIndex--;
|
||||
}
|
||||
return { nextRowIndex: rowIndex, nextColumnName: columns[newColumnIndex].name };
|
||||
}
|
||||
|
||||
function getCheckboxIcon(value) {
|
||||
switch (typeof value) {
|
||||
case 'boolean':
|
||||
return value ? 'check' : 'close';
|
||||
case 'number':
|
||||
return value === 0 ? 'close' : 'check';
|
||||
case 'undefined':
|
||||
return 'indeterminate_check_box';
|
||||
default:
|
||||
return 'indeterminate_check_box';
|
||||
}
|
||||
}
|
||||
|
||||
function getToggleIcon(value) {
|
||||
if (value === null) return 'help_outline';
|
||||
return value ? 'toggle_on' : 'toggle_off';
|
||||
}
|
||||
|
||||
function formatColumnValue(col, row, dashIfEmpty) {
|
||||
if (col?.format || row[col?.name + 'VnTableTextValue']) {
|
||||
if (selectRegex.test(col?.component) && row[col?.name + 'VnTableTextValue']) {
|
||||
return dashIfEmpty(row[col?.name + 'VnTableTextValue']);
|
||||
} else {
|
||||
return col.format(row, dashIfEmpty);
|
||||
}
|
||||
}
|
||||
|
||||
if (col?.component === 'date') return dashIfEmpty(toDate(row[col?.name]));
|
||||
|
||||
if (col?.component === 'time')
|
||||
return row[col?.name] >= 5
|
||||
? dashIfEmpty(date.formatDate(new Date(row[col?.name]), 'HH:mm'))
|
||||
: row[col?.name];
|
||||
|
||||
if (selectRegex.test(col?.component) && $props.isEditable) {
|
||||
const { find, url } = col.attrs;
|
||||
const urlRelation = url?.charAt(0)?.toLocaleLowerCase() + url?.slice(1, -1);
|
||||
|
||||
if (col?.attrs.options) {
|
||||
const find = col?.attrs.options.find((option) => option.id === row[col.name]);
|
||||
if (!col.attrs?.optionLabel || !find) return dashIfEmpty(row[col?.name]);
|
||||
return dashIfEmpty(find[col.attrs?.optionLabel ?? 'name']);
|
||||
}
|
||||
|
||||
if (typeof row[urlRelation] == 'object') {
|
||||
if (typeof find == 'object')
|
||||
return dashIfEmpty(row[urlRelation][find?.label ?? 'name']);
|
||||
|
||||
return dashIfEmpty(row[urlRelation][col?.attrs.optionLabel ?? 'name']);
|
||||
}
|
||||
if (typeof row[urlRelation] == 'string') return dashIfEmpty(row[urlRelation]);
|
||||
}
|
||||
return dashIfEmpty(row[col?.name]);
|
||||
}
|
||||
|
||||
function cardClick(_, row) {
|
||||
if ($props.redirect) router.push({ path: `/${$props.redirect}/${row.id}` });
|
||||
}
|
||||
|
||||
function removeTextValue(data, getChanges) {
|
||||
let changes = data.updates;
|
||||
if (!changes) return data;
|
||||
|
||||
for (const change of changes) {
|
||||
for (const key in change.data) {
|
||||
if (key.endsWith('VnTableTextValue')) {
|
||||
delete change.data[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.updates = changes.filter((change) => Object.keys(change.data).length > 0);
|
||||
|
||||
if ($attrs?.beforeSaveFn) data = $attrs.beforeSaveFn(data, getChanges);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function handleRowClick(event, row) {
|
||||
if (event.ctrlKey) return rowCtrlClickFunction.value(event, row);
|
||||
if (rowClickFunction.value) rowClickFunction.value(row);
|
||||
}
|
||||
|
||||
const rowCtrlClickFunction = computed(() => {
|
||||
if ($props.rowCtrlClick != undefined) return $props.rowCtrlClick;
|
||||
if ($props.redirect)
|
||||
return (evt, { id }) => {
|
||||
stopEventPropagation(evt);
|
||||
window.open(`/#/${$props.redirect}/${id}`, '_blank');
|
||||
};
|
||||
return () => {};
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<RightMenu v-if="$props.rightSearch" :overlay="overlay">
|
||||
<template #right-panel>
|
||||
<QDrawer
|
||||
v-if="$props.rightSearch"
|
||||
v-model="stateStore.rightDrawer"
|
||||
side="right"
|
||||
:width="256"
|
||||
show-if-above
|
||||
>
|
||||
<QScrollArea class="fit">
|
||||
<VnTableFilter
|
||||
:data-key="$attrs['data-key']"
|
||||
:columns="columns"
|
||||
|
@ -643,17 +327,16 @@ const rowCtrlClickFunction = computed(() => {
|
|||
<slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" />
|
||||
</template>
|
||||
</VnTableFilter>
|
||||
</template>
|
||||
</RightMenu>
|
||||
</QScrollArea>
|
||||
</QDrawer>
|
||||
<CrudModel
|
||||
v-bind="$attrs"
|
||||
:class="$attrs['class'] ?? 'q-px-md'"
|
||||
:limit="$attrs['limit'] ?? 100"
|
||||
:limit="$attrs['limit'] ?? 20"
|
||||
ref="CrudModelRef"
|
||||
@on-fetch="(...args) => emit('onFetch', ...args)"
|
||||
:search-url="searchUrl"
|
||||
:disable-infinite-scroll="isTableMode"
|
||||
:before-save-fn="removeTextValue"
|
||||
@save-changes="reload"
|
||||
:has-sub-toolbar="$props.hasSubToolbar ?? isEditable"
|
||||
:auto-load="hasParams || $attrs['auto-load']"
|
||||
|
@ -665,12 +348,8 @@ const rowCtrlClickFunction = computed(() => {
|
|||
<QTable
|
||||
ref="tableRef"
|
||||
v-bind="table"
|
||||
:class="[
|
||||
'vnTable',
|
||||
table ? 'selection-cell' : '',
|
||||
$props.footer ? 'last-row-sticky' : '',
|
||||
]"
|
||||
wrap-cells
|
||||
class="vnTable"
|
||||
:class="{ 'last-row-sticky': $props.footer }"
|
||||
:columns="splittedColumns.columns"
|
||||
:rows="rows"
|
||||
v-model:selected="selected"
|
||||
|
@ -681,17 +360,14 @@ const rowCtrlClickFunction = computed(() => {
|
|||
:style="isTableMode && `max-height: ${tableHeight}`"
|
||||
:virtual-scroll="isTableMode"
|
||||
@virtual-scroll="handleScroll"
|
||||
@row-click="(event, row) => handleRowClick(event, row)"
|
||||
@row-click="(_, row) => rowClickFunction && rowClickFunction(row)"
|
||||
@update:selected="emit('update:selected', $event)"
|
||||
@selection="(details) => handleSelection(details, rows)"
|
||||
:hide-selected-banner="true"
|
||||
:data-cy="$props.dataCy ?? 'vnTable'"
|
||||
>
|
||||
<template #top-left v-if="!$props.withoutHeader">
|
||||
<slot name="top-left"> </slot>
|
||||
<slot name="top-left"></slot>
|
||||
</template>
|
||||
<template #top-right v-if="!$props.withoutHeader">
|
||||
<slot name="top-right"></slot>
|
||||
<VnVisibleColumn
|
||||
v-if="isTableMode"
|
||||
v-model="splittedColumns.columns"
|
||||
|
@ -699,50 +375,49 @@ const rowCtrlClickFunction = computed(() => {
|
|||
:skip="columnsVisibilitySkipped"
|
||||
/>
|
||||
<QBtnToggle
|
||||
v-if="!tableModes.some((mode) => mode.disable)"
|
||||
v-model="mode"
|
||||
toggle-color="primary"
|
||||
class="bg-vn-section-color"
|
||||
dense
|
||||
:options="tableModes.filter((mode) => !mode.disable)"
|
||||
/>
|
||||
<QBtn
|
||||
v-if="showRightIcon"
|
||||
icon="filter_alt"
|
||||
class="bg-vn-section-color q-ml-sm"
|
||||
dense
|
||||
@click="stateStore.toggleRightDrawer()"
|
||||
/>
|
||||
</template>
|
||||
<template #header-cell="{ col }">
|
||||
<QTh
|
||||
v-if="col.visible ?? true"
|
||||
v-bind:class="col.headerClass"
|
||||
class="body-cell"
|
||||
:style="col?.width ? `max-width: ${col?.width}` : ''"
|
||||
:style="col.headerStyle"
|
||||
:class="col.headerClass"
|
||||
>
|
||||
<div
|
||||
class="no-padding"
|
||||
:style="[
|
||||
withFilters && $props.columnSearch ? 'height: 75px' : '',
|
||||
]"
|
||||
class="column ellipsis"
|
||||
:class="`text-${col?.align ?? 'left'}`"
|
||||
:style="$props.columnSearch ? 'height: 75px' : ''"
|
||||
>
|
||||
<div style="height: 30px">
|
||||
<div class="row items-center no-wrap" style="height: 30px">
|
||||
<QTooltip v-if="col.toolTip">{{ col.toolTip }}</QTooltip>
|
||||
<VnTableOrder
|
||||
v-model="orders[col.orderBy ?? col.name]"
|
||||
:name="col.orderBy ?? col.name"
|
||||
:label="col?.labelAbbreviation ?? col?.label"
|
||||
:label="col?.label"
|
||||
:data-key="$attrs['data-key']"
|
||||
:search-url="searchUrl"
|
||||
:align="getColAlign(col)"
|
||||
/>
|
||||
</div>
|
||||
<VnFilter
|
||||
v-if="
|
||||
$props.columnSearch &&
|
||||
col.columnSearch !== false &&
|
||||
withFilters
|
||||
"
|
||||
v-if="$props.columnSearch"
|
||||
:column="col"
|
||||
:show-title="true"
|
||||
:data-key="$attrs['data-key']"
|
||||
v-model="params[columnName(col)]"
|
||||
:search-url="searchUrl"
|
||||
customClass="header-filter"
|
||||
class="full-width"
|
||||
/>
|
||||
</div>
|
||||
</QTh>
|
||||
|
@ -760,27 +435,16 @@ const rowCtrlClickFunction = computed(() => {
|
|||
</QTd>
|
||||
</template>
|
||||
<template #body-cell="{ col, row, rowIndex }">
|
||||
<!-- Columns -->
|
||||
<QTd
|
||||
class="no-margin q-px-xs"
|
||||
auto-width
|
||||
class="no-margin"
|
||||
:class="[getColAlign(col), col.columnClass]"
|
||||
:style="col.style"
|
||||
v-if="col.visible ?? true"
|
||||
:style="{
|
||||
'max-width': col?.width ?? false,
|
||||
position: 'relative',
|
||||
}"
|
||||
:class="[
|
||||
col.columnClass,
|
||||
'body-cell no-margin no-padding',
|
||||
getColAlign(col),
|
||||
]"
|
||||
:data-row-index="rowIndex"
|
||||
:data-col-field="col?.name"
|
||||
>
|
||||
<div
|
||||
class="no-padding no-margin peter"
|
||||
style="
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@click.ctrl="
|
||||
($event) =>
|
||||
rowCtrlClickFunction && rowCtrlClickFunction($event, row)
|
||||
"
|
||||
>
|
||||
<slot
|
||||
|
@ -789,38 +453,14 @@ const rowCtrlClickFunction = computed(() => {
|
|||
:row="row"
|
||||
:row-index="rowIndex"
|
||||
>
|
||||
<QIcon
|
||||
v-if="col?.component === 'toggle'"
|
||||
:name="
|
||||
col?.getIcon
|
||||
? col.getIcon(row[col?.name])
|
||||
: getToggleIcon(row[col?.name])
|
||||
"
|
||||
style="color: var(--vn-text-color)"
|
||||
:class="hasEditableFormat(col)"
|
||||
size="14px"
|
||||
<VnTableColumn
|
||||
:column="col"
|
||||
:row="row"
|
||||
:is-editable="col.isEditable ?? isEditable"
|
||||
v-model="row[col.name]"
|
||||
component-prop="columnField"
|
||||
/>
|
||||
<QIcon
|
||||
v-else-if="col?.component === 'checkbox'"
|
||||
:name="getCheckboxIcon(row[col?.name])"
|
||||
style="color: var(--vn-text-color)"
|
||||
:class="hasEditableFormat(col)"
|
||||
size="14px"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
:class="hasEditableFormat(col)"
|
||||
:style="
|
||||
typeof col?.style == 'function'
|
||||
? col.style(row)
|
||||
: col?.style
|
||||
"
|
||||
style="bottom: 0"
|
||||
>
|
||||
{{ formatColumnValue(col, row, dashIfEmpty) }}
|
||||
</span>
|
||||
</slot>
|
||||
</div>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-tableActions="{ col, row }">
|
||||
|
@ -841,7 +481,7 @@ const rowCtrlClickFunction = computed(() => {
|
|||
flat
|
||||
dense
|
||||
:class="
|
||||
btn.isPrimary ? 'text-primary-light' : 'color-vn-label'
|
||||
btn.isPrimary ? 'text-primary-light' : 'color-vn-text '
|
||||
"
|
||||
:style="`visibility: ${
|
||||
((btn.show && btn.show(row)) ?? true)
|
||||
|
@ -849,19 +489,23 @@ const rowCtrlClickFunction = computed(() => {
|
|||
: 'hidden'
|
||||
}`"
|
||||
@click="btn.action(row)"
|
||||
:data-cy="btn?.name ?? `tableAction-${index}`"
|
||||
/>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #item="{ row, colsMap }">
|
||||
<component
|
||||
v-bind:is="'div'"
|
||||
@click="(event) => cardClick(event, row)"
|
||||
:is="$props.redirect ? 'router-link' : 'span'"
|
||||
:to="`/${$props.redirect}/` + row.id"
|
||||
>
|
||||
<QCard
|
||||
bordered
|
||||
flat
|
||||
class="row no-wrap justify-between cursor-pointer q-pa-sm"
|
||||
class="row no-wrap justify-between cursor-pointer"
|
||||
@click="
|
||||
(_, row) => {
|
||||
$props.rowClick && $props.rowClick(row);
|
||||
}
|
||||
"
|
||||
style="height: 100%"
|
||||
>
|
||||
<QCardSection
|
||||
|
@ -898,7 +542,7 @@ const rowCtrlClickFunction = computed(() => {
|
|||
</QCardSection>
|
||||
<!-- Fields -->
|
||||
<QCardSection
|
||||
class="q-pl-sm q-py-xs"
|
||||
class="q-pl-sm q-pr-lg q-py-xs"
|
||||
:class="$props.cardClass"
|
||||
>
|
||||
<div
|
||||
|
@ -919,7 +563,7 @@ const rowCtrlClickFunction = computed(() => {
|
|||
:row="row"
|
||||
:row-index="index"
|
||||
>
|
||||
<VnColumn
|
||||
<VnTableColumn
|
||||
:column="col"
|
||||
:row="row"
|
||||
:is-editable="false"
|
||||
|
@ -937,6 +581,7 @@ const rowCtrlClickFunction = computed(() => {
|
|||
<!-- Actions -->
|
||||
<QCardSection
|
||||
v-if="colsMap.tableActions"
|
||||
class="column flex-center w-10 no-margin q-pa-xs q-gutter-y-xs"
|
||||
@click="stopEventPropagation($event)"
|
||||
>
|
||||
<QBtn
|
||||
|
@ -945,14 +590,13 @@ const rowCtrlClickFunction = computed(() => {
|
|||
:key="index"
|
||||
:title="btn.title"
|
||||
:icon="btn.icon"
|
||||
data-cy="cardBtn"
|
||||
class="q-pa-xs"
|
||||
flat
|
||||
:class="
|
||||
btn.isPrimary
|
||||
? 'text-primary-light'
|
||||
: 'color-vn-label'
|
||||
: 'color-vn-text '
|
||||
"
|
||||
flat
|
||||
@click="btn.action(row)"
|
||||
/>
|
||||
</QCardSection>
|
||||
|
@ -960,17 +604,14 @@ const rowCtrlClickFunction = computed(() => {
|
|||
</component>
|
||||
</template>
|
||||
<template #bottom-row="{ cols }" v-if="$props.footer">
|
||||
<QTr v-if="rows.length" style="height: 45px">
|
||||
<QTh v-if="table.selection" />
|
||||
<QTr v-if="rows.length" style="height: 30px">
|
||||
<QTh
|
||||
v-for="col of cols.filter((cols) => cols.visible ?? true)"
|
||||
:key="col?.id"
|
||||
class="text-center"
|
||||
:class="getColAlign(col)"
|
||||
>
|
||||
<slot
|
||||
:name="`column-footer-${col.name}`"
|
||||
:isEditableColumn="isEditableColumn(col)"
|
||||
/>
|
||||
<slot :name="`column-footer-${col.name}`" />
|
||||
</QTh>
|
||||
</QTr>
|
||||
</template>
|
||||
|
@ -989,7 +630,7 @@ const rowCtrlClickFunction = computed(() => {
|
|||
size="md"
|
||||
round
|
||||
flat
|
||||
v-shortcut="'+'"
|
||||
shortcut="+"
|
||||
:disabled="!disabledAttr"
|
||||
/>
|
||||
<QTooltip>
|
||||
|
@ -1007,35 +648,21 @@ const rowCtrlClickFunction = computed(() => {
|
|||
color="primary"
|
||||
fab
|
||||
icon="add"
|
||||
v-shortcut="'+'"
|
||||
shortcut="+"
|
||||
data-cy="vnTableCreateBtn"
|
||||
/>
|
||||
<QTooltip self="top right">
|
||||
{{ createForm?.title }}
|
||||
</QTooltip>
|
||||
</QPageSticky>
|
||||
<QDialog
|
||||
v-model="showForm"
|
||||
transition-show="scale"
|
||||
transition-hide="scale"
|
||||
:full-width="createComplement?.isFullWidth ?? false"
|
||||
data-cy="vn-table-create-dialog"
|
||||
>
|
||||
<QDialog v-model="showForm" transition-show="scale" transition-hide="scale">
|
||||
<FormModelPopup
|
||||
ref="createRef"
|
||||
v-bind="createForm"
|
||||
:model="$attrs['data-key'] + 'Create'"
|
||||
@on-data-saved="(_, res) => createForm.onDataSaved(res)"
|
||||
>
|
||||
<template #form-inputs="{ data }">
|
||||
<div :style="createComplement?.containerStyle">
|
||||
<div
|
||||
:style="createComplement?.previousStyle"
|
||||
v-if="!quasar.screen.xs"
|
||||
>
|
||||
<slot name="previous-create-dialog" :data="data" />
|
||||
</div>
|
||||
<div class="grid-create" :style="createComplement?.columnGridStyle">
|
||||
<div class="grid-create">
|
||||
<slot
|
||||
v-for="column of splittedColumns.create"
|
||||
:key="column.name"
|
||||
|
@ -1044,22 +671,17 @@ const rowCtrlClickFunction = computed(() => {
|
|||
:column-name="column.name"
|
||||
:label="column.label"
|
||||
>
|
||||
<VnColumn
|
||||
:column="{
|
||||
...column,
|
||||
...{ disable: column?.createDisable ?? false },
|
||||
}"
|
||||
<VnTableColumn
|
||||
:column="column"
|
||||
:row="{}"
|
||||
default="input"
|
||||
v-model="data[column.name]"
|
||||
:show-label="true"
|
||||
component-prop="columnCreate"
|
||||
:data-cy="`${column.name}-create-popup`"
|
||||
/>
|
||||
</slot>
|
||||
<slot name="more-create-dialog" :data="data" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</FormModelPopup>
|
||||
</QDialog>
|
||||
|
@ -1076,42 +698,6 @@ es:
|
|||
</i18n>
|
||||
|
||||
<style lang="scss">
|
||||
.selection-cell {
|
||||
table td:first-child {
|
||||
padding: 0px;
|
||||
}
|
||||
}
|
||||
.side-padding {
|
||||
padding-left: 1px;
|
||||
padding-right: 1px;
|
||||
}
|
||||
.editable-text:hover {
|
||||
border-bottom: 1px dashed var(--q-primary);
|
||||
@extend .side-padding;
|
||||
}
|
||||
.editable-text {
|
||||
border-bottom: 1px dashed var(--vn-label-color);
|
||||
@extend .side-padding;
|
||||
}
|
||||
.cell-input {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding-top: 0px !important;
|
||||
}
|
||||
.q-field--labeled .q-field__native,
|
||||
.q-field--labeled .q-field__prefix,
|
||||
.q-field--labeled .q-field__suffix {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.body-cell {
|
||||
padding-left: 4px !important;
|
||||
padding-right: 4px !important;
|
||||
position: relative;
|
||||
}
|
||||
.bg-chip-secondary {
|
||||
background-color: var(--vn-page-color);
|
||||
color: var(--vn-text-color);
|
||||
|
@ -1128,8 +714,8 @@ es:
|
|||
|
||||
.grid-three {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, max-content));
|
||||
width: 100%;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, max-content));
|
||||
max-width: 100%;
|
||||
grid-gap: 20px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
@ -1137,6 +723,7 @@ es:
|
|||
.grid-create {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, max-content));
|
||||
max-width: 100%;
|
||||
grid-gap: 20px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
@ -1152,9 +739,7 @@ es:
|
|||
}
|
||||
}
|
||||
}
|
||||
.q-table tbody tr td {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.q-table {
|
||||
th {
|
||||
padding: 0;
|
||||
|
@ -1203,7 +788,6 @@ es:
|
|||
.vn-label-value {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
color: var(--vn-text-color);
|
||||
.value {
|
||||
overflow: hidden;
|
||||
|
@ -1223,15 +807,12 @@ es:
|
|||
|
||||
.grid-two {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 2fr;
|
||||
.vn-label-value {
|
||||
flex-direction: column;
|
||||
white-space: nowrap;
|
||||
.fields {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
white-space: nowrap;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, max-content));
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
overflow: scroll;
|
||||
white-space: wrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.w-80 {
|
||||
|
@ -1255,15 +836,4 @@ es:
|
|||
.q-table__middle.q-virtual-scroll.q-virtual-scroll--vertical.scroll {
|
||||
background-color: var(--vn-section-color);
|
||||
}
|
||||
.temp-input {
|
||||
top: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
label.header-filter > .q-field__inner > .q-field__control {
|
||||
padding: inherit;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -27,13 +27,12 @@ function columnName(col) {
|
|||
</script>
|
||||
<template>
|
||||
<VnFilterPanel v-bind="$attrs" :search-button="true" :disable-submit-event="true">
|
||||
<template #body="{ params, orders, searchFn }">
|
||||
<template #body="{ params, orders }">
|
||||
<div
|
||||
class="container"
|
||||
class="row no-wrap flex-center"
|
||||
v-for="col of columns.filter((c) => c.columnFilter ?? true)"
|
||||
:key="col.id"
|
||||
>
|
||||
<div class="filter">
|
||||
<VnFilter
|
||||
ref="tableFilterRef"
|
||||
:column="col"
|
||||
|
@ -41,8 +40,6 @@ function columnName(col) {
|
|||
v-model="params[columnName(col)]"
|
||||
:search-url="searchUrl"
|
||||
/>
|
||||
</div>
|
||||
<div class="order">
|
||||
<VnTableOrder
|
||||
v-if="col?.columnFilter !== false && col?.name !== 'tableActions'"
|
||||
v-model="orders[col.orderBy ?? col.name]"
|
||||
|
@ -52,11 +49,9 @@ function columnName(col) {
|
|||
:vertical="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<slot
|
||||
name="moreFilterPanel"
|
||||
:params="params"
|
||||
:search-fn="searchFn"
|
||||
:orders="orders"
|
||||
:columns="columns"
|
||||
/>
|
||||
|
@ -72,21 +67,3 @@ function columnName(col) {
|
|||
</template>
|
||||
</VnFilterPanel>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 45px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.filter {
|
||||
width: 70%;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
.order {
|
||||
width: 10%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -32,21 +32,16 @@ const areAllChecksMarked = computed(() => {
|
|||
|
||||
function setUserConfigViewData(data, isLocal) {
|
||||
if (!data) return;
|
||||
// Importante: El name de las columnas de la tabla debe conincidir con el name de las variables que devuelve la view config
|
||||
if (!isLocal) localColumns.value = [];
|
||||
|
||||
// Array to Object
|
||||
const skippeds = $props.skip.reduce((a, v) => ({ ...a, [v]: v }), {});
|
||||
|
||||
for (let column of columns.value) {
|
||||
const { label, name, labelAbbreviation } = column;
|
||||
const { label, name } = column;
|
||||
if (skippeds[name]) continue;
|
||||
column.visible = data[name] ?? true;
|
||||
if (!isLocal)
|
||||
localColumns.value.push({
|
||||
name,
|
||||
label,
|
||||
labelAbbreviation,
|
||||
visible: column.visible,
|
||||
});
|
||||
if (!isLocal) localColumns.value.push({ name, label, visible: column.visible });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,11 +152,7 @@ onMounted(async () => {
|
|||
<QCheckbox
|
||||
v-for="col in localColumns"
|
||||
:key="col.name"
|
||||
:label="
|
||||
col?.labelAbbreviation
|
||||
? col.labelAbbreviation + ` (${col.label ?? col.name})`
|
||||
: (col.label ?? col.name)
|
||||
"
|
||||
:label="col.label ?? col.name"
|
||||
v-model="col.visible"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -27,58 +27,30 @@ describe('VnTable', () => {
|
|||
beforeEach(() => (vm.selected = []));
|
||||
|
||||
describe('handleSelection()', () => {
|
||||
const rows = [
|
||||
{ $index: 0 },
|
||||
{ $index: 1 },
|
||||
{ $index: 2 },
|
||||
{ $index: 3 },
|
||||
{ $index: 4 },
|
||||
];
|
||||
|
||||
it('should add rows to selected when shift key is pressed and rows are added in ascending order', () => {
|
||||
const rows = [{ $index: 0 }, { $index: 1 }, { $index: 2 }];
|
||||
const selectedRows = [{ $index: 1 }];
|
||||
it('should add rows to selected when shift key is pressed and rows are added except last one', () => {
|
||||
vm.handleSelection(
|
||||
{ evt: { shiftKey: true }, added: true, rows: selectedRows },
|
||||
rows,
|
||||
rows
|
||||
);
|
||||
expect(vm.selected).toEqual([{ $index: 0 }]);
|
||||
});
|
||||
|
||||
it('should add rows to selected when shift key is pressed and rows are added in descending order', () => {
|
||||
const selectedRows = [{ $index: 3 }];
|
||||
vm.handleSelection(
|
||||
{ evt: { shiftKey: true }, added: true, rows: selectedRows },
|
||||
rows,
|
||||
);
|
||||
expect(vm.selected).toEqual([{ $index: 0 }, { $index: 1 }, { $index: 2 }]);
|
||||
});
|
||||
|
||||
it('should not add rows to selected when shift key is not pressed', () => {
|
||||
const selectedRows = [{ $index: 1 }];
|
||||
vm.handleSelection(
|
||||
{ evt: { shiftKey: false }, added: true, rows: selectedRows },
|
||||
rows,
|
||||
rows
|
||||
);
|
||||
expect(vm.selected).toEqual([]);
|
||||
});
|
||||
|
||||
it('should not add rows to selected when rows are not added', () => {
|
||||
const selectedRows = [{ $index: 1 }];
|
||||
vm.handleSelection(
|
||||
{ evt: { shiftKey: true }, added: false, rows: selectedRows },
|
||||
rows,
|
||||
rows
|
||||
);
|
||||
expect(vm.selected).toEqual([]);
|
||||
});
|
||||
|
||||
it('should add all rows between the smallest and largest selected indexes', () => {
|
||||
vm.selected = [{ $index: 1 }, { $index: 3 }];
|
||||
const selectedRows = [{ $index: 4 }];
|
||||
vm.handleSelection(
|
||||
{ evt: { shiftKey: true }, added: true, rows: selectedRows },
|
||||
rows,
|
||||
);
|
||||
expect(vm.selected).toEqual([{ $index: 1 }, { $index: 3 }, { $index: 2 }]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
export default function (initialFooter, data) {
|
||||
const footer = data.reduce(
|
||||
(acc, row) => {
|
||||
Object.entries(initialFooter).forEach(([key, initialValue]) => {
|
||||
acc[key] += row?.[key] !== undefined ? row[key] : initialValue;
|
||||
});
|
||||
return acc;
|
||||
},
|
||||
{ ...initialFooter }
|
||||
);
|
||||
return footer;
|
||||
}
|
|
@ -30,8 +30,8 @@ describe('CrudModel', () => {
|
|||
saveFn: '',
|
||||
},
|
||||
});
|
||||
wrapper = wrapper.wrapper;
|
||||
vm = wrapper.vm;
|
||||
wrapper=wrapper.wrapper;
|
||||
vm=wrapper.vm;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -143,14 +143,14 @@ describe('CrudModel', () => {
|
|||
});
|
||||
|
||||
it('should return true if object is empty', async () => {
|
||||
dummyObj = {};
|
||||
dummyObj ={};
|
||||
result = vm.isEmpty(dummyObj);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if object is not empty', async () => {
|
||||
dummyObj = { a: 1, b: 2, c: 3 };
|
||||
dummyObj = {a:1, b:2, c:3};
|
||||
result = vm.isEmpty(dummyObj);
|
||||
|
||||
expect(result).toBe(false);
|
||||
|
@ -164,22 +164,20 @@ describe('CrudModel', () => {
|
|||
});
|
||||
|
||||
it('should return false if array is not empty', async () => {
|
||||
dummyArray = [1, 2, 3];
|
||||
dummyArray = [1,2,3];
|
||||
result = vm.isEmpty(dummyArray);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
describe('resetData()', () => {
|
||||
it('should add $index to elements in data[] and sets originalData and formData with data', async () => {
|
||||
data = [
|
||||
{
|
||||
data = [{
|
||||
name: 'Tony',
|
||||
lastName: 'Stark',
|
||||
age: 42,
|
||||
},
|
||||
];
|
||||
}];
|
||||
|
||||
vm.resetData(data);
|
||||
|
||||
|
@ -212,13 +210,11 @@ describe('CrudModel', () => {
|
|||
});
|
||||
|
||||
describe('saveChanges()', () => {
|
||||
data = [
|
||||
{
|
||||
data = [{
|
||||
name: 'Tony',
|
||||
lastName: 'Stark',
|
||||
age: 42,
|
||||
},
|
||||
];
|
||||
}];
|
||||
|
||||
it('should call saveFn if exists', async () => {
|
||||
await wrapper.setProps({ saveFn: vi.fn() });
|
||||
|
@ -233,15 +229,13 @@ describe('CrudModel', () => {
|
|||
});
|
||||
|
||||
it("should use default url if there's not saveFn", async () => {
|
||||
const postMock = vi.spyOn(axios, 'post');
|
||||
const postMock =vi.spyOn(axios, 'post');
|
||||
|
||||
vm.formData = [
|
||||
{
|
||||
vm.formData = [{
|
||||
name: 'Bruce',
|
||||
lastName: 'Wayne',
|
||||
age: 45,
|
||||
},
|
||||
];
|
||||
}]
|
||||
|
||||
await vm.saveChanges(data);
|
||||
|
||||
|
|
|
@ -57,7 +57,6 @@ describe('FormModel', () => {
|
|||
vm.state.set(model, formInitialData);
|
||||
expect(vm.hasChanges).toBe(false);
|
||||
|
||||
await vm.$nextTick();
|
||||
vm.formData.mockKey = 'newVal';
|
||||
await vm.$nextTick();
|
||||
expect(vm.hasChanges).toBe(true);
|
||||
|
@ -94,13 +93,9 @@ describe('FormModel', () => {
|
|||
|
||||
it('should call axios.patch with the right data', async () => {
|
||||
const spy = vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} });
|
||||
const { vm } = mount({ propsData: { url, model } });
|
||||
|
||||
vm.formData = {};
|
||||
const { vm } = mount({ propsData: { url, model, formInitialData } });
|
||||
vm.formData.mockKey = 'newVal';
|
||||
await vm.$nextTick();
|
||||
vm.formData = { mockKey: 'newVal' };
|
||||
await vm.$nextTick();
|
||||
|
||||
await vm.save();
|
||||
expect(spy).toHaveBeenCalled();
|
||||
vm.formData.mockKey = 'mockVal';
|
||||
|
@ -111,7 +106,6 @@ describe('FormModel', () => {
|
|||
const { vm } = mount({
|
||||
propsData: { url, model, formInitialData, urlCreate: 'mockUrlCreate' },
|
||||
});
|
||||
await vm.$nextTick();
|
||||
vm.formData.mockKey = 'newVal';
|
||||
await vm.$nextTick();
|
||||
await vm.save();
|
||||
|
@ -125,7 +119,7 @@ describe('FormModel', () => {
|
|||
});
|
||||
const spyPatch = vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} });
|
||||
const spySaveFn = vi.spyOn(vm.$props, 'saveFn');
|
||||
await vm.$nextTick();
|
||||
|
||||
vm.formData.mockKey = 'newVal';
|
||||
await vm.$nextTick();
|
||||
await vm.save();
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
import { vi, describe, expect, it, beforeAll, beforeEach, afterEach } from 'vitest';
|
||||
import { vi, describe, expect, it, beforeAll } from 'vitest';
|
||||
import { createWrapper, axios } from 'app/test/vitest/helper';
|
||||
import Leftmenu from 'components/LeftMenu.vue';
|
||||
import * as vueRouter from 'vue-router';
|
||||
import { useNavigationStore } from 'src/stores/useNavigationStore';
|
||||
|
||||
let vm;
|
||||
let navigation;
|
||||
import { useNavigationStore } from 'src/stores/useNavigationStore';
|
||||
|
||||
vi.mock('src/router/modules', () => ({
|
||||
default: [
|
||||
|
@ -24,16 +21,6 @@ vi.mock('src/router/modules', () => ({
|
|||
{
|
||||
path: '',
|
||||
name: 'CustomerMain',
|
||||
meta: {
|
||||
menu: 'Customer',
|
||||
menuChildren: [
|
||||
{
|
||||
name: 'CustomerCreditContracts',
|
||||
title: 'creditContracts',
|
||||
icon: 'vn:solunion',
|
||||
},
|
||||
],
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'list',
|
||||
|
@ -41,13 +28,6 @@ vi.mock('src/router/modules', () => ({
|
|||
meta: {
|
||||
title: 'list',
|
||||
icon: 'view_list',
|
||||
menuChildren: [
|
||||
{
|
||||
name: 'CustomerCreditContracts',
|
||||
title: 'creditContracts',
|
||||
icon: 'vn:solunion',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -64,59 +44,20 @@ vi.mock('src/router/modules', () => ({
|
|||
},
|
||||
],
|
||||
}));
|
||||
vi.spyOn(vueRouter, 'useRoute').mockReturnValue({
|
||||
matched: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: {
|
||||
name: 'Dashboard',
|
||||
},
|
||||
name: 'Main',
|
||||
meta: {},
|
||||
props: {
|
||||
default: false,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'Dashboard',
|
||||
meta: {
|
||||
title: 'dashboard',
|
||||
icon: 'dashboard',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/customer',
|
||||
redirect: {
|
||||
name: 'CustomerMain',
|
||||
},
|
||||
name: 'Customer',
|
||||
meta: {
|
||||
title: 'customers',
|
||||
icon: 'vn:client',
|
||||
moduleName: 'Customer',
|
||||
keyBinding: 'c',
|
||||
menu: 'customer',
|
||||
},
|
||||
},
|
||||
],
|
||||
query: {},
|
||||
params: {},
|
||||
meta: { moduleName: 'mockName' },
|
||||
path: 'mockName/1',
|
||||
name: 'Customer',
|
||||
});
|
||||
function mount(source = 'main') {
|
||||
|
||||
describe('Leftmenu', () => {
|
||||
let vm;
|
||||
let navigation;
|
||||
beforeAll(() => {
|
||||
vi.spyOn(axios, 'get').mockResolvedValue({
|
||||
data: [],
|
||||
});
|
||||
const wrapper = createWrapper(Leftmenu, {
|
||||
|
||||
vm = createWrapper(Leftmenu, {
|
||||
propsData: {
|
||||
source,
|
||||
source: 'main',
|
||||
},
|
||||
});
|
||||
}).vm;
|
||||
|
||||
navigation = useNavigationStore();
|
||||
navigation.fetchPinned = vi.fn().mockReturnValue(Promise.resolve(true));
|
||||
|
@ -130,259 +71,24 @@ function mount(source = 'main') {
|
|||
},
|
||||
],
|
||||
});
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
describe('getRoutes', () => {
|
||||
afterEach(() => vi.clearAllMocks());
|
||||
const getRoutes = vi.fn().mockImplementation((props, getMethodA, getMethodB) => {
|
||||
const handleRoutes = {
|
||||
methodA: getMethodA,
|
||||
methodB: getMethodB,
|
||||
};
|
||||
try {
|
||||
handleRoutes[props.source]();
|
||||
} catch (error) {
|
||||
throw Error('Method not defined');
|
||||
}
|
||||
});
|
||||
|
||||
const getMethodA = vi.fn();
|
||||
const getMethodB = vi.fn();
|
||||
const fn = (props) => getRoutes(props, getMethodA, getMethodB);
|
||||
|
||||
it('should call getMethodB when source is card', () => {
|
||||
let props = { source: 'methodB' };
|
||||
fn(props);
|
||||
|
||||
expect(getMethodB).toHaveBeenCalled();
|
||||
expect(getMethodA).not.toHaveBeenCalled();
|
||||
});
|
||||
it('should call getMethodA when source is main', () => {
|
||||
let props = { source: 'methodA' };
|
||||
fn(props);
|
||||
|
||||
expect(getMethodA).toHaveBeenCalled();
|
||||
expect(getMethodB).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call getMethodA when source is not exists or undefined', () => {
|
||||
let props = { source: 'methodC' };
|
||||
expect(() => fn(props)).toThrowError('Method not defined');
|
||||
|
||||
expect(getMethodA).not.toHaveBeenCalled();
|
||||
expect(getMethodB).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Leftmenu as card', () => {
|
||||
beforeAll(() => {
|
||||
vm = mount('card').vm;
|
||||
});
|
||||
|
||||
it('should get routes for card source', async () => {
|
||||
vm.getRoutes();
|
||||
});
|
||||
});
|
||||
describe('Leftmenu as main', () => {
|
||||
beforeEach(() => {
|
||||
vm = mount().vm;
|
||||
});
|
||||
|
||||
it('should initialize with default props', () => {
|
||||
expect(vm.source).toBe('main');
|
||||
});
|
||||
|
||||
it('should filter items based on search input', async () => {
|
||||
vm.search = 'cust';
|
||||
await vm.$nextTick();
|
||||
expect(vm.filteredItems[0].name).toEqual('customer');
|
||||
expect(vm.filteredItems[0].module).toEqual('customer');
|
||||
});
|
||||
it('should filter items based on search input', async () => {
|
||||
vm.search = 'Rou';
|
||||
await vm.$nextTick();
|
||||
expect(vm.filteredItems).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return pinned items', () => {
|
||||
vm.items = [
|
||||
{ name: 'Item 1', isPinned: false },
|
||||
{ name: 'Item 2', isPinned: true },
|
||||
it('should return a proper formated object with two child items', async () => {
|
||||
const expectedMenuItem = [
|
||||
{
|
||||
children: null,
|
||||
name: 'CustomerList',
|
||||
title: 'globals.pageTitles.list',
|
||||
icon: 'view_list',
|
||||
},
|
||||
{
|
||||
children: null,
|
||||
name: 'CustomerCreate',
|
||||
title: 'globals.pageTitles.createCustomer',
|
||||
icon: 'vn:addperson',
|
||||
},
|
||||
];
|
||||
expect(vm.pinnedModules).toEqual(
|
||||
new Map([['Item 2', { name: 'Item 2', isPinned: true }]]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should find matches in routes', () => {
|
||||
const search = 'child1';
|
||||
const item = {
|
||||
children: [
|
||||
{ name: 'child1', children: [] },
|
||||
{ name: 'child2', children: [] },
|
||||
],
|
||||
};
|
||||
const matches = vm.findMatches(search, item);
|
||||
expect(matches).toEqual([{ name: 'child1', children: [] }]);
|
||||
});
|
||||
it('should not proceed if event is already prevented', async () => {
|
||||
const item = { module: 'testModule', isPinned: false };
|
||||
const event = {
|
||||
preventDefault: vi.fn(),
|
||||
stopPropagation: vi.fn(),
|
||||
defaultPrevented: true,
|
||||
};
|
||||
|
||||
await vm.togglePinned(item, event);
|
||||
|
||||
expect(event.preventDefault).not.toHaveBeenCalled();
|
||||
expect(event.stopPropagation).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call quasar.notify with success message', async () => {
|
||||
const item = { module: 'testModule', isPinned: false };
|
||||
const event = {
|
||||
preventDefault: vi.fn(),
|
||||
stopPropagation: vi.fn(),
|
||||
defaultPrevented: false,
|
||||
};
|
||||
const response = { data: { id: 1 } };
|
||||
|
||||
vi.spyOn(axios, 'post').mockResolvedValue(response);
|
||||
vi.spyOn(vm.quasar, 'notify');
|
||||
|
||||
await vm.togglePinned(item, event);
|
||||
|
||||
expect(vm.quasar.notify).toHaveBeenCalledWith({
|
||||
message: 'Data saved',
|
||||
type: 'positive',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle a single matched route with a menu', () => {
|
||||
const route = {
|
||||
matched: [{ meta: { menu: 'customer' } }],
|
||||
};
|
||||
|
||||
const result = vm.betaGetRoutes();
|
||||
|
||||
expect(result.meta.menu).toEqual(route.matched[0].meta.menu);
|
||||
});
|
||||
it('should get routes for main source', () => {
|
||||
vm.props.source = 'main';
|
||||
vm.getRoutes();
|
||||
expect(navigation.getModules).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should find direct child matches', () => {
|
||||
const search = 'child1';
|
||||
const item = {
|
||||
children: [{ name: 'child1' }, { name: 'child2' }],
|
||||
};
|
||||
const result = vm.findMatches(search, item);
|
||||
expect(result).toEqual([{ name: 'child1' }]);
|
||||
});
|
||||
|
||||
it('should find nested child matches', () => {
|
||||
const search = 'child3';
|
||||
const item = {
|
||||
children: [
|
||||
{ name: 'child1' },
|
||||
{
|
||||
name: 'child2',
|
||||
children: [{ name: 'child3' }],
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = vm.findMatches(search, item);
|
||||
expect(result).toEqual([{ name: 'child3' }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalize', () => {
|
||||
beforeAll(() => {
|
||||
vm = mount('card').vm;
|
||||
});
|
||||
it('should normalize and lowercase text', () => {
|
||||
const input = 'ÁÉÍÓÚáéíóú';
|
||||
const expected = 'aeiouaeiou';
|
||||
expect(vm.normalize(input)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle empty string', () => {
|
||||
const input = '';
|
||||
const expected = '';
|
||||
expect(vm.normalize(input)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle text without diacritics', () => {
|
||||
const input = 'hello';
|
||||
const expected = 'hello';
|
||||
expect(vm.normalize(input)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle mixed text', () => {
|
||||
const input = 'Héllo Wórld!';
|
||||
const expected = 'hello world!';
|
||||
expect(vm.normalize(input)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addChildren', () => {
|
||||
const module = 'testModule';
|
||||
beforeEach(() => {
|
||||
vm = mount().vm;
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should add menu items to parent if matches are found', () => {
|
||||
const parent = 'testParent';
|
||||
const route = {
|
||||
meta: {
|
||||
menu: 'testMenu',
|
||||
},
|
||||
children: [{ name: 'child1' }, { name: 'child2' }],
|
||||
};
|
||||
vm.addChildren(module, route, parent);
|
||||
|
||||
expect(navigation.addMenuItem).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle routes with no meta menu', () => {
|
||||
const route = {
|
||||
meta: {},
|
||||
menus: {},
|
||||
};
|
||||
|
||||
const parent = [];
|
||||
|
||||
vm.addChildren(module, route, parent);
|
||||
expect(navigation.addMenuItem).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle empty parent array', () => {
|
||||
const parent = [];
|
||||
const route = {
|
||||
meta: {
|
||||
menu: 'child11',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'child1',
|
||||
meta: {
|
||||
menuChildren: [
|
||||
{
|
||||
name: 'CustomerCreditContracts',
|
||||
title: 'creditContracts',
|
||||
icon: 'vn:solunion',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
vm.addChildren(module, route, parent);
|
||||
expect(navigation.addMenuItem).toHaveBeenCalled();
|
||||
const firstMenuItem = vm.items[0];
|
||||
expect(firstMenuItem.children).toEqual(expect.arrayContaining(expectedMenuItem));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
import { vi, describe, expect, it, beforeEach, afterEach } from 'vitest';
|
||||
import { createWrapper } from 'app/test/vitest/helper';
|
||||
import UserPanel from 'src/components/UserPanel.vue';
|
||||
import axios from 'axios';
|
||||
import { useState } from 'src/composables/useState';
|
||||
|
||||
vi.mock('src/utils/quasarLang', () => ({
|
||||
default: vi.fn(),
|
||||
}));
|
||||
|
||||
describe('UserPanel', () => {
|
||||
let wrapper;
|
||||
let vm;
|
||||
let state;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = createWrapper(UserPanel, {});
|
||||
state = useState();
|
||||
state.setUser({
|
||||
id: 115,
|
||||
name: 'itmanagement',
|
||||
nickname: 'itManagementNick',
|
||||
lang: 'en',
|
||||
darkMode: false,
|
||||
companyFk: 442,
|
||||
warehouseFk: 1,
|
||||
});
|
||||
wrapper = wrapper.wrapper;
|
||||
vm = wrapper.vm;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should fetch warehouses data on mounted', async () => {
|
||||
const fetchData = wrapper.findComponent({ name: 'FetchData' });
|
||||
expect(fetchData.props('url')).toBe('Warehouses');
|
||||
expect(fetchData.props('autoLoad')).toBe(true);
|
||||
});
|
||||
|
||||
it('should toggle dark mode correctly and update preferences', async () => {
|
||||
await vm.saveDarkMode(true);
|
||||
expect(axios.patch).toHaveBeenCalledWith('/UserConfigs/115', { darkMode: true });
|
||||
expect(vm.user.darkMode).toBe(true);
|
||||
await vm.updatePreferences();
|
||||
expect(vm.darkMode).toBe(true);
|
||||
});
|
||||
|
||||
it('should change user language and update preferences', async () => {
|
||||
const userLanguage = 'es';
|
||||
await vm.saveLanguage(userLanguage);
|
||||
expect(axios.patch).toHaveBeenCalledWith('/VnUsers/115', { lang: userLanguage });
|
||||
expect(vm.user.lang).toBe(userLanguage);
|
||||
await vm.updatePreferences();
|
||||
expect(vm.locale).toBe(userLanguage);
|
||||
});
|
||||
|
||||
it('should update user data', async () => {
|
||||
const key = 'name';
|
||||
const value = 'itboss';
|
||||
await vm.saveUserData(key, value);
|
||||
expect(axios.post).toHaveBeenCalledWith('UserConfigs/setUserConfig', { [key]: value });
|
||||
});
|
||||
});
|
|
@ -11,13 +11,6 @@ const stateStore = useStateStore();
|
|||
const slots = useSlots();
|
||||
const hasContent = useHasContent('#right-panel');
|
||||
|
||||
defineProps({
|
||||
overlay: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if ((!slots['right-panel'] && !hasContent.value) || quasar.platform.is.mobile)
|
||||
stateStore.rightDrawer = false;
|
||||
|
@ -33,7 +26,6 @@ onMounted(() => {
|
|||
round
|
||||
dense
|
||||
icon="dock_to_left"
|
||||
data-cy="toggle-right-drawer"
|
||||
>
|
||||
<QTooltip bottom anchor="bottom right">
|
||||
{{ t('globals.collapseMenu') }}
|
||||
|
@ -41,12 +33,7 @@ onMounted(() => {
|
|||
</QBtn>
|
||||
</div>
|
||||
</Teleport>
|
||||
<QDrawer
|
||||
v-model="stateStore.rightDrawer"
|
||||
side="right"
|
||||
:width="256"
|
||||
:overlay="overlay"
|
||||
>
|
||||
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256">
|
||||
<QScrollArea class="fit">
|
||||
<div id="right-panel"></div>
|
||||
<slot v-if="!hasContent" name="right-panel" />
|
||||
|
|
|
@ -56,12 +56,7 @@ async function confirm() {
|
|||
{{ t('The notification will be sent to the following address') }}
|
||||
</QCardSection>
|
||||
<QCardSection class="q-pt-none">
|
||||
<VnInput
|
||||
v-model="address"
|
||||
is-outlined
|
||||
autofocus
|
||||
data-cy="SendEmailNotifiactionDialogInput"
|
||||
/>
|
||||
<VnInput v-model="address" is-outlined autofocus />
|
||||
</QCardSection>
|
||||
<QCardActions align="right">
|
||||
<QBtn :label="t('globals.cancel')" color="primary" flat v-close-popup />
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
<script setup>
|
||||
import { nextTick, ref } from 'vue';
|
||||
import VnInput from './VnInput.vue';
|
||||
import { useAccountShortToStandard } from 'src/composables/useAccountShortToStandard';
|
||||
import { nextTick, ref, watch } from 'vue';
|
||||
import { QInput } from 'quasar';
|
||||
|
||||
const $props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
insertable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
|
@ -11,25 +14,70 @@ const $props = defineProps({
|
|||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'accountShortToStandard']);
|
||||
const model = defineModel({ prop: 'modelValue' });
|
||||
const inputRef = ref(false);
|
||||
|
||||
function setCursorPosition(pos) {
|
||||
const input = inputRef.value.vnInputRef.$el.querySelector('input');
|
||||
input.focus();
|
||||
input.setSelectionRange(pos, pos);
|
||||
let internalValue = ref($props.modelValue);
|
||||
|
||||
watch(
|
||||
() => $props.modelValue,
|
||||
(newVal) => {
|
||||
internalValue.value = newVal;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => internalValue.value,
|
||||
(newVal) => {
|
||||
emit('update:modelValue', newVal);
|
||||
accountShortToStandard();
|
||||
}
|
||||
);
|
||||
|
||||
const handleKeydown = (e) => {
|
||||
if (e.key === 'Backspace') return;
|
||||
if (e.key === '.') {
|
||||
accountShortToStandard();
|
||||
// TODO: Fix this setTimeout, with nextTick doesn't work
|
||||
setTimeout(() => {
|
||||
setCursorPosition(0, e.target);
|
||||
}, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($props.insertable && e.key.match(/[0-9]/)) {
|
||||
handleInsertMode(e);
|
||||
}
|
||||
};
|
||||
function setCursorPosition(pos, el = vnInputRef.value) {
|
||||
el.focus();
|
||||
el.setSelectionRange(pos, pos);
|
||||
}
|
||||
|
||||
async function handleUpdateModel(val) {
|
||||
model.value = val?.at(-1) === '.' ? useAccountShortToStandard(val) : val;
|
||||
await nextTick(() => setCursorPosition(0));
|
||||
const vnInputRef = ref(false);
|
||||
const handleInsertMode = (e) => {
|
||||
e.preventDefault();
|
||||
const input = e.target;
|
||||
const cursorPos = input.selectionStart;
|
||||
const { maxlength } = vnInputRef.value;
|
||||
let currentValue = internalValue.value;
|
||||
if (!currentValue) currentValue = e.key;
|
||||
const newValue = e.key;
|
||||
if (newValue && !isNaN(newValue) && cursorPos < maxlength) {
|
||||
internalValue.value =
|
||||
currentValue.substring(0, cursorPos) +
|
||||
newValue +
|
||||
currentValue.substring(cursorPos + 1);
|
||||
}
|
||||
nextTick(() => {
|
||||
input.setSelectionRange(cursorPos + 1, cursorPos + 1);
|
||||
});
|
||||
};
|
||||
function accountShortToStandard() {
|
||||
internalValue.value = internalValue.value?.replace(
|
||||
'.',
|
||||
'0'.repeat(11 - internalValue.value.length)
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VnInput
|
||||
v-model="model"
|
||||
ref="inputRef"
|
||||
:insertable
|
||||
@update:model-value="handleUpdateModel"
|
||||
/>
|
||||
<QInput @keydown="handleKeydown" ref="vnInputRef" v-model="internalValue" />
|
||||
</template>
|
||||
|
|
|
@ -10,11 +10,11 @@ import LeftMenu from 'components/LeftMenu.vue';
|
|||
import RightMenu from 'components/common/RightMenu.vue';
|
||||
const props = defineProps({
|
||||
dataKey: { type: String, required: true },
|
||||
url: { type: String, default: undefined },
|
||||
baseUrl: { type: String, default: undefined },
|
||||
customUrl: { type: String, default: undefined },
|
||||
filter: { type: Object, default: () => {} },
|
||||
descriptor: { type: Object, required: true },
|
||||
filterPanel: { type: Object, default: undefined },
|
||||
idInWhere: { type: Boolean, default: false },
|
||||
searchDataKey: { type: String, default: undefined },
|
||||
searchbarProps: { type: Object, default: undefined },
|
||||
redirectOnError: { type: Boolean, default: false },
|
||||
|
@ -23,20 +23,25 @@ const props = defineProps({
|
|||
const stateStore = useStateStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const url = computed(() => {
|
||||
if (props.baseUrl) {
|
||||
return `${props.baseUrl}/${route.params.id}`;
|
||||
}
|
||||
return props.customUrl;
|
||||
});
|
||||
const searchRightDataKey = computed(() => {
|
||||
if (!props.searchDataKey) return route.name;
|
||||
return props.searchDataKey;
|
||||
});
|
||||
|
||||
const arrayData = useArrayData(props.dataKey, {
|
||||
url: props.url,
|
||||
userFilter: props.filter,
|
||||
oneRecord: true,
|
||||
url: url.value,
|
||||
filter: props.filter,
|
||||
});
|
||||
|
||||
onBeforeMount(async () => {
|
||||
try {
|
||||
await fetch(route.params.id);
|
||||
if (!props.baseUrl) arrayData.store.filter.where = { id: route.params.id };
|
||||
await arrayData.fetch({ append: false, updateRouter: false });
|
||||
} catch {
|
||||
const { matched: matches } = router.currentRoute.value;
|
||||
const { path } = matches.at(-1);
|
||||
|
@ -44,17 +49,13 @@ onBeforeMount(async () => {
|
|||
}
|
||||
});
|
||||
|
||||
onBeforeRouteUpdate(async (to, from) => {
|
||||
const id = to.params.id;
|
||||
if (id !== from.params.id) await fetch(id, true);
|
||||
});
|
||||
|
||||
async function fetch(id, append = false) {
|
||||
const regex = /\/(\d+)/;
|
||||
if (props.idInWhere) arrayData.store.filter.where = { id };
|
||||
else if (!regex.test(props.url)) arrayData.store.url = `${props.url}/${id}`;
|
||||
else arrayData.store.url = props.url.replace(regex, `/${id}`);
|
||||
await arrayData.fetch({ append, updateRouter: false });
|
||||
if (props.baseUrl) {
|
||||
onBeforeRouteUpdate(async (to, from) => {
|
||||
if (to.params.id !== from.params.id) {
|
||||
arrayData.store.url = `${props.baseUrl}/${to.params.id}`;
|
||||
await arrayData.fetch({ append: false, updateRouter: false });
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
|
@ -82,7 +83,7 @@ async function fetch(id, append = false) {
|
|||
<QPage>
|
||||
<VnSubToolbar />
|
||||
<div :class="[useCardSize(), $attrs.class]">
|
||||
<RouterView :key="$route.path" />
|
||||
<RouterView :key="route.path" />
|
||||
</div>
|
||||
</QPage>
|
||||
</QPageContainer>
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
<script setup>
|
||||
import { onBeforeMount } from 'vue';
|
||||
import { useRouter, onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router';
|
||||
import { onBeforeMount, computed } from 'vue';
|
||||
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import useCardSize from 'src/composables/useCardSize';
|
||||
import LeftMenu from 'components/LeftMenu.vue';
|
||||
import VnSubToolbar from '../ui/VnSubToolbar.vue';
|
||||
|
||||
const props = defineProps({
|
||||
dataKey: { type: String, required: true },
|
||||
url: { type: String, default: undefined },
|
||||
idInWhere: { type: Boolean, default: false },
|
||||
baseUrl: { type: String, default: undefined },
|
||||
customUrl: { type: String, default: undefined },
|
||||
filter: { type: Object, default: () => {} },
|
||||
userFilter: { type: Object, default: () => {} },
|
||||
descriptor: { type: Object, required: true },
|
||||
filterPanel: { type: Object, default: undefined },
|
||||
searchDataKey: { type: String, default: undefined },
|
||||
|
@ -19,56 +21,49 @@ const props = defineProps({
|
|||
});
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const arrayData = useArrayData(props.dataKey, {
|
||||
url: props.url,
|
||||
userFilter: props.filter,
|
||||
oneRecord: true,
|
||||
const url = computed(() => {
|
||||
if (props.baseUrl) {
|
||||
return `${props.baseUrl}/${route.params.id}`;
|
||||
}
|
||||
return props.customUrl;
|
||||
});
|
||||
|
||||
onBeforeRouteLeave(() => {
|
||||
stateStore.cardDescriptorChangeValue(null);
|
||||
const arrayData = useArrayData(props.dataKey, {
|
||||
url: url.value,
|
||||
filter: props.filter,
|
||||
userFilter: props.userFilter,
|
||||
});
|
||||
|
||||
onBeforeMount(async () => {
|
||||
stateStore.cardDescriptorChangeValue(props.descriptor);
|
||||
|
||||
const route = router.currentRoute.value;
|
||||
try {
|
||||
await fetch(route.params.id);
|
||||
if (!props.baseUrl) arrayData.store.filter.where = { id: route.params.id };
|
||||
await arrayData.fetch({ append: false, updateRouter: false });
|
||||
} catch {
|
||||
const { matched: matches } = route;
|
||||
const { matched: matches } = router.currentRoute.value;
|
||||
const { path } = matches.at(-1);
|
||||
router.push({ path: path.replace(/:id.*/, '') });
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeRouteUpdate(async (to, from) => {
|
||||
if (hasRouteParam(to.params)) {
|
||||
const { matched } = router.currentRoute.value;
|
||||
const { name } = matched.at(-3);
|
||||
if (name) {
|
||||
router.push({ name, params: to.params });
|
||||
if (props.baseUrl) {
|
||||
onBeforeRouteUpdate(async (to, from) => {
|
||||
if (to.params.id !== from.params.id) {
|
||||
arrayData.store.url = `${props.baseUrl}/${to.params.id}`;
|
||||
await arrayData.fetch({ append: false, updateRouter: false });
|
||||
}
|
||||
}
|
||||
const id = to.params.id;
|
||||
if (id !== from.params.id) await fetch(id, true);
|
||||
});
|
||||
|
||||
async function fetch(id, append = false) {
|
||||
const regex = /\/(\d+)/;
|
||||
if (props.idInWhere) arrayData.store.filter.where = { id };
|
||||
else if (!regex.test(props.url)) arrayData.store.url = `${props.url}/${id}`;
|
||||
else arrayData.store.url = props.url.replace(regex, `/${id}`);
|
||||
await arrayData.fetch({ append, updateRouter: false });
|
||||
}
|
||||
function hasRouteParam(params, valueToCheck = ':addressId') {
|
||||
return Object.values(params).includes(valueToCheck);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<Teleport to="#left-panel" v-if="stateStore.isHeaderMounted()">
|
||||
<component :is="descriptor" />
|
||||
<QSeparator />
|
||||
<LeftMenu source="card" />
|
||||
</Teleport>
|
||||
<VnSubToolbar />
|
||||
<div :class="[useCardSize(), $attrs.class]">
|
||||
<RouterView :key="$route.path" />
|
||||
<RouterView :key="route.path" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
const model = defineModel({ type: [Number, Boolean] });
|
||||
const $props = defineProps({
|
||||
info: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const checkboxModel = computed({
|
||||
get() {
|
||||
if (typeof model.value === 'number') {
|
||||
return model.value !== 0;
|
||||
}
|
||||
return model.value;
|
||||
},
|
||||
set(value) {
|
||||
if (typeof model.value === 'number') {
|
||||
model.value = value ? 1 : 0;
|
||||
} else {
|
||||
model.value = value;
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<QCheckbox v-bind="$attrs" v-model="checkboxModel" />
|
||||
<QIcon
|
||||
v-if="info"
|
||||
v-bind="$attrs"
|
||||
class="cursor-info q-ml-sm"
|
||||
name="info"
|
||||
size="sm"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ info }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</div>
|
||||
</template>
|
|
@ -1,32 +0,0 @@
|
|||
<script setup>
|
||||
const $props = defineProps({
|
||||
colors: {
|
||||
type: String,
|
||||
default: '{"value": []}',
|
||||
},
|
||||
});
|
||||
|
||||
const colorArray = JSON.parse($props.colors)?.value;
|
||||
const maxHeight = 30;
|
||||
const colorHeight = maxHeight / colorArray?.length;
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="colors" class="color-div" :style="{ height: `${maxHeight}px` }">
|
||||
<div
|
||||
v-for="(color, index) in colorArray"
|
||||
:key="index"
|
||||
:style="{
|
||||
backgroundColor: `#${color}`,
|
||||
height: `${colorHeight}px`,
|
||||
}"
|
||||
>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
.color-div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
|
@ -17,8 +17,6 @@ const $props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['blur']);
|
||||
|
||||
const componentArray = computed(() => {
|
||||
if (typeof $props.prop === 'object') return [$props.prop];
|
||||
return $props.prop;
|
||||
|
@ -48,8 +46,7 @@ function toValueAttrs(attrs) {
|
|||
<span
|
||||
v-for="toComponent of componentArray"
|
||||
:key="toComponent.name"
|
||||
class="column fit"
|
||||
:class="toComponent?.component == 'checkbox' ? 'flex-center' : ''"
|
||||
class="column flex-center fit"
|
||||
>
|
||||
<component
|
||||
v-if="toComponent?.component"
|
||||
|
@ -57,7 +54,6 @@ function toValueAttrs(attrs) {
|
|||
v-bind="mix(toComponent).attrs"
|
||||
v-on="mix(toComponent).event ?? {}"
|
||||
v-model="model"
|
||||
@blur="emit('blur')"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
@ -17,7 +17,7 @@ import { useSession } from 'src/composables/useSession';
|
|||
const route = useRoute();
|
||||
const quasar = useQuasar();
|
||||
const { t } = useI18n();
|
||||
const rows = ref([]);
|
||||
const rows = ref();
|
||||
const dmsRef = ref();
|
||||
const formDialog = ref({});
|
||||
const token = useSession().getTokenMultimedia();
|
||||
|
@ -389,14 +389,6 @@ defineExpose({
|
|||
</div>
|
||||
</template>
|
||||
</QTable>
|
||||
<div
|
||||
v-else
|
||||
class="info-row q-pa-md text-center"
|
||||
>
|
||||
<h5>
|
||||
{{ t('No data to display') }}
|
||||
</h5>
|
||||
</div>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
<QDialog v-model="formDialog.show">
|
||||
|
@ -413,7 +405,7 @@ defineExpose({
|
|||
fab
|
||||
color="primary"
|
||||
icon="add"
|
||||
v-shortcut
|
||||
shortcut="+"
|
||||
@click="showFormDialog()"
|
||||
class="fill-icon"
|
||||
>
|
||||
|
|
|
@ -11,7 +11,6 @@ const emit = defineEmits([
|
|||
'update:options',
|
||||
'keyup.enter',
|
||||
'remove',
|
||||
'blur',
|
||||
]);
|
||||
|
||||
const $props = defineProps({
|
||||
|
@ -83,7 +82,7 @@ const mixinRules = [
|
|||
requiredFieldRule,
|
||||
...($attrs.rules ?? []),
|
||||
(val) => {
|
||||
const maxlength = $props.maxlength;
|
||||
const { maxlength } = vnInputRef.value;
|
||||
if (maxlength && +val.length > maxlength)
|
||||
return t(`maxLength`, { value: maxlength });
|
||||
const { min, max } = vnInputRef.value.$attrs;
|
||||
|
@ -108,7 +107,7 @@ const handleInsertMode = (e) => {
|
|||
e.preventDefault();
|
||||
const input = e.target;
|
||||
const cursorPos = input.selectionStart;
|
||||
const maxlength = $props.maxlength;
|
||||
const { maxlength } = vnInputRef.value;
|
||||
let currentValue = value.value;
|
||||
if (!currentValue) currentValue = e.key;
|
||||
const newValue = e.key;
|
||||
|
@ -137,15 +136,14 @@ const handleUppercase = () => {
|
|||
:type="$attrs.type"
|
||||
:class="{ required: isRequired }"
|
||||
@keyup.enter="emit('keyup.enter')"
|
||||
@blur="emit('blur')"
|
||||
@keydown="handleKeydown"
|
||||
:clearable="false"
|
||||
:rules="mixinRules"
|
||||
:lazy-rules="true"
|
||||
hide-bottom-space
|
||||
:data-cy="($attrs['data-cy'] ?? $attrs.label) + '_input'"
|
||||
:data-cy="$attrs.dataCy ?? $attrs.label + '_input'"
|
||||
>
|
||||
<template #prepend v-if="$slots.prepend">
|
||||
<template #prepend>
|
||||
<slot name="prepend" />
|
||||
</template>
|
||||
<template #append>
|
||||
|
@ -174,14 +172,10 @@ const handleUppercase = () => {
|
|||
<QIcon
|
||||
name="match_case"
|
||||
size="xs"
|
||||
v-if="!$attrs.disabled && !$attrs.readonly && $props.uppercase"
|
||||
v-if="!$attrs.disabled && !($attrs.readonly) && $props.uppercase"
|
||||
@click="handleUppercase"
|
||||
class="uppercase-icon"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('Convert to uppercase') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
/>
|
||||
|
||||
<slot name="append" v-if="$slots.append && !$attrs.disabled" />
|
||||
<QIcon v-if="info" name="info">
|
||||
|
@ -194,28 +188,13 @@ const handleUppercase = () => {
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.uppercase-icon {
|
||||
transition:
|
||||
color 0.3s,
|
||||
transform 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.uppercase-icon:hover {
|
||||
color: #ed9937;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
</style>
|
||||
<i18n>
|
||||
en:
|
||||
inputMin: Must be more than {value}
|
||||
maxLength: The value exceeds {value} characters
|
||||
inputMax: Must be less than {value}
|
||||
|
||||
es:
|
||||
inputMin: Debe ser mayor a {value}
|
||||
maxLength: El valor excede los {value} carácteres
|
||||
inputMax: Debe ser menor a {value}
|
||||
Convert to uppercase: Convertir a mayúsculas
|
||||
</i18n>
|
|
@ -42,7 +42,7 @@ const formattedDate = computed({
|
|||
if (value.at(2) == '/') value = value.split('/').reverse().join('/');
|
||||
value = date.formatDate(
|
||||
new Date(value).toISOString(),
|
||||
'YYYY-MM-DDTHH:mm:ss.SSSZ',
|
||||
'YYYY-MM-DDTHH:mm:ss.SSSZ'
|
||||
);
|
||||
}
|
||||
const [year, month, day] = value.split('-').map((e) => parseInt(e));
|
||||
|
@ -55,7 +55,7 @@ const formattedDate = computed({
|
|||
orgDate.getHours(),
|
||||
orgDate.getMinutes(),
|
||||
orgDate.getSeconds(),
|
||||
orgDate.getMilliseconds(),
|
||||
orgDate.getMilliseconds()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ const formattedDate = computed({
|
|||
});
|
||||
|
||||
const popupDate = computed(() =>
|
||||
model.value ? date.formatDate(new Date(model.value), 'YYYY/MM/DD') : model.value,
|
||||
model.value ? date.formatDate(new Date(model.value), 'YYYY/MM/DD') : model.value
|
||||
);
|
||||
onMounted(() => {
|
||||
// fix quasar bug
|
||||
|
@ -73,7 +73,7 @@ onMounted(() => {
|
|||
watch(
|
||||
() => model.value,
|
||||
(val) => (formattedDate.value = val),
|
||||
{ immediate: true },
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const styleAttrs = computed(() => {
|
||||
|
@ -107,7 +107,6 @@ const manageDate = (date) => {
|
|||
@click="isPopupOpen = !isPopupOpen"
|
||||
@keydown="isPopupOpen = false"
|
||||
hide-bottom-space
|
||||
:data-cy="$attrs.dataCy ?? $attrs.label + '_inputDate'"
|
||||
>
|
||||
<template #append>
|
||||
<QIcon
|
||||
|
|
|
@ -8,7 +8,6 @@ defineProps({
|
|||
});
|
||||
|
||||
const model = defineModel({ type: [Number, String] });
|
||||
const emit = defineEmits(['blur']);
|
||||
</script>
|
||||
<template>
|
||||
<VnInput
|
||||
|
@ -25,6 +24,5 @@ const emit = defineEmits(['blur']);
|
|||
model = parseFloat(val).toFixed(decimalPlaces);
|
||||
}
|
||||
"
|
||||
@blur="emit('blur')"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -85,7 +85,6 @@ const handleModelValue = (data) => {
|
|||
:tooltip="t('Create new location')"
|
||||
:rules="mixinRules"
|
||||
:lazy-rules="true"
|
||||
required
|
||||
>
|
||||
<template #form>
|
||||
<CreateNewPostcode
|
||||
|
|
|
@ -268,7 +268,7 @@ async function applyFilter() {
|
|||
filter.where.and.push(selectedFilters.value);
|
||||
}
|
||||
|
||||
paginate.value.fetch({ filter });
|
||||
paginate.value.fetch(filter);
|
||||
}
|
||||
|
||||
function setDate(type) {
|
||||
|
@ -404,7 +404,7 @@ watch(
|
|||
ref="paginate"
|
||||
:data-key="`${model}Log`"
|
||||
:url="`${model}Logs`"
|
||||
:user-filter="filter"
|
||||
:filter="filter"
|
||||
:skeleton="false"
|
||||
auto-load
|
||||
@on-fetch="setLogTree"
|
||||
|
@ -641,7 +641,15 @@ watch(
|
|||
>
|
||||
{{ prop.nameI18n }}:
|
||||
</span>
|
||||
<VnJsonValue :value="prop.val.val" />
|
||||
<span
|
||||
v-if="prop.val.id"
|
||||
class="id-value"
|
||||
>
|
||||
#{{ prop.val.id }}
|
||||
</span>
|
||||
<span v-if="log.action == 'update'">
|
||||
←
|
||||
<VnJsonValue
|
||||
:value="prop.old.val"
|
||||
/>
|
||||
|
@ -651,26 +659,6 @@ watch(
|
|||
>
|
||||
#{{ prop.old.id }}
|
||||
</span>
|
||||
→
|
||||
<VnJsonValue
|
||||
:value="prop.val.val"
|
||||
/>
|
||||
<span
|
||||
v-if="prop.val.id"
|
||||
class="id-value"
|
||||
>
|
||||
#{{ prop.val.id }}
|
||||
</span>
|
||||
</span>
|
||||
<span v-else="prop.old.val">
|
||||
<VnJsonValue
|
||||
:value="prop.val.val"
|
||||
/>
|
||||
<span
|
||||
v-if="prop.old.id"
|
||||
class="id-value"
|
||||
>#{{ prop.old.id }}</span
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
|
|
|
@ -12,7 +12,7 @@ const $props = defineProps({
|
|||
},
|
||||
});
|
||||
onMounted(
|
||||
() => (stateStore.leftDrawer = useQuasar().screen.gt.xs ? $props.leftDrawer : false),
|
||||
() => (stateStore.leftDrawer = useQuasar().screen.gt.xs ? $props.leftDrawer : false)
|
||||
);
|
||||
|
||||
const teleportRef = ref({});
|
||||
|
@ -35,14 +35,8 @@ onMounted(() => {
|
|||
<template>
|
||||
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
|
||||
<QScrollArea class="fit text-grey-8">
|
||||
<div id="left-panel" ref="teleportRef">
|
||||
<template v-if="stateStore.cardDescriptor">
|
||||
<component :is="stateStore.cardDescriptor" />
|
||||
<QSeparator />
|
||||
<LeftMenu source="card" />
|
||||
</template>
|
||||
<template v-else> <LeftMenu /></template>
|
||||
</div>
|
||||
<div id="left-panel" ref="teleportRef"></div>
|
||||
<LeftMenu v-if="!hasContent" />
|
||||
</QScrollArea>
|
||||
</QDrawer>
|
||||
<QPageContainer>
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: null,
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: 'primary',
|
||||
},
|
||||
tooltip: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
const popupProxyRef = ref(null);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<QBtn :color="$props.color" :icon="$props.icon" :label="$t($props.label)">
|
||||
<template #default>
|
||||
<slot name="extraIcon"></slot>
|
||||
<QPopupProxy ref="popupProxyRef" style="max-width: none">
|
||||
<QCard>
|
||||
<slot :popup="popupProxyRef"></slot>
|
||||
</QCard>
|
||||
</QPopupProxy>
|
||||
<QTooltip>{{ $t($props.tooltip) }}</QTooltip>
|
||||
</template>
|
||||
</QBtn>
|
||||
</template>
|
|
@ -98,22 +98,15 @@ function checkIsMain() {
|
|||
/>
|
||||
<div :id="searchbarId"></div>
|
||||
</slot>
|
||||
<RightAdvancedMenu :is-main-section="isMainSection">
|
||||
<template #advanced-menu v-if="$slots[advancedMenuSlot] || rightFilter">
|
||||
<RightAdvancedMenu :is-main-section="isMainSection && rightFilter">
|
||||
<template #advanced-menu v-if="$slots[advancedMenuSlot]">
|
||||
<slot :name="advancedMenuSlot">
|
||||
<VnTableFilter
|
||||
v-if="rightFilter && columns"
|
||||
:data-key="dataKey"
|
||||
:array-data="arrayData"
|
||||
:columns="columns"
|
||||
>
|
||||
<template #moreFilterPanel="{ params, orders, searchFn }">
|
||||
<slot
|
||||
name="moreFilterPanel"
|
||||
v-bind="{ params, orders, searchFn }"
|
||||
/>
|
||||
</template>
|
||||
</VnTableFilter>
|
||||
</slot>
|
||||
</template>
|
||||
</RightAdvancedMenu>
|
||||
|
|
|
@ -171,8 +171,7 @@ onMounted(() => {
|
|||
});
|
||||
|
||||
const arrayDataKey =
|
||||
$props.dataKey ??
|
||||
($props.url?.length > 0 ? $props.url : ($attrs.name ?? $attrs.label));
|
||||
$props.dataKey ?? ($props.url?.length > 0 ? $props.url : $attrs.name ?? $attrs.label);
|
||||
|
||||
const arrayData = useArrayData(arrayDataKey, {
|
||||
url: $props.url,
|
||||
|
@ -221,7 +220,7 @@ async function fetchFilter(val) {
|
|||
optionFilterValue.value ??
|
||||
(new RegExp(/\d/g).test(val)
|
||||
? optionValue.value
|
||||
: (optionFilter.value ?? optionLabel.value));
|
||||
: optionFilter.value ?? optionLabel.value);
|
||||
|
||||
let defaultWhere = {};
|
||||
if ($props.filterOptions.length) {
|
||||
|
@ -240,7 +239,7 @@ async function fetchFilter(val) {
|
|||
|
||||
const { data } = await arrayData.applyFilter(
|
||||
{ filter: filterOptions },
|
||||
{ updateRouter: false },
|
||||
{ updateRouter: false }
|
||||
);
|
||||
setOptions(data);
|
||||
return data;
|
||||
|
@ -273,7 +272,7 @@ async function filterHandler(val, update) {
|
|||
ref.setOptionIndex(-1);
|
||||
ref.moveOptionSelection(1, true);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -309,7 +308,7 @@ function handleKeyDown(event) {
|
|||
if (inputValue) {
|
||||
const matchingOption = myOptions.value.find(
|
||||
(option) =>
|
||||
option[optionLabel.value].toLowerCase() === inputValue.toLowerCase(),
|
||||
option[optionLabel.value].toLowerCase() === inputValue.toLowerCase()
|
||||
);
|
||||
|
||||
if (matchingOption) {
|
||||
|
@ -321,11 +320,11 @@ function handleKeyDown(event) {
|
|||
}
|
||||
|
||||
const focusableElements = document.querySelectorAll(
|
||||
'a:not([disabled]), button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), details:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled])',
|
||||
'a:not([disabled]), button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), details:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled])'
|
||||
);
|
||||
const currentIndex = Array.prototype.indexOf.call(
|
||||
focusableElements,
|
||||
event.target,
|
||||
event.target
|
||||
);
|
||||
if (currentIndex >= 0 && currentIndex < focusableElements.length - 1) {
|
||||
focusableElements[currentIndex + 1].focus();
|
||||
|
|
|
@ -14,7 +14,7 @@ const $props = defineProps({
|
|||
},
|
||||
});
|
||||
const options = ref([]);
|
||||
const emit = defineEmits(['blur']);
|
||||
|
||||
onBeforeMount(async () => {
|
||||
const { url, optionValue, optionLabel } = useAttrs();
|
||||
const findBy = $props.find ?? url?.charAt(0)?.toLocaleLowerCase() + url?.slice(1, -1);
|
||||
|
@ -35,5 +35,5 @@ onBeforeMount(async () => {
|
|||
});
|
||||
</script>
|
||||
<template>
|
||||
<VnSelect v-bind="$attrs" :options="$attrs.options ?? options" @blur="emit('blur')" />
|
||||
<VnSelect v-bind="$attrs" :options="$attrs.options ?? options" />
|
||||
</template>
|
||||
|
|
|
@ -37,6 +37,7 @@ const isAllowedToCreate = computed(() => {
|
|||
|
||||
defineExpose({ vnSelectDialogRef: select });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VnSelect
|
||||
ref="select"
|
||||
|
@ -66,6 +67,7 @@ defineExpose({ vnSelectDialogRef: select });
|
|||
</template>
|
||||
</VnSelect>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.default-icon {
|
||||
cursor: pointer;
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
<script setup>
|
||||
import VnSelect from 'components/common/VnSelect.vue';
|
||||
|
||||
const model = defineModel({ type: [String, Number, Object] });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VnSelect
|
||||
:label="$t('globals.supplier')"
|
||||
v-bind="$attrs"
|
||||
v-model="model"
|
||||
url="Suppliers"
|
||||
option-value="id"
|
||||
option-label="nickname"
|
||||
:fields="['id', 'name', 'nickname', 'nif']"
|
||||
:filter-options="['id', 'name', 'nickname', 'nif']"
|
||||
sort-by="name ASC"
|
||||
data-cy="vnSupplierSelect"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>
|
||||
{{ scope.opt?.name }}
|
||||
</QItemLabel>
|
||||
<QItemLabel caption>
|
||||
{{
|
||||
`#${scope.opt?.id} , ${scope.opt?.nickname} (${scope.opt?.nif})`
|
||||
}}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelect>
|
||||
</template>
|
|
@ -1,50 +0,0 @@
|
|||
<script setup>
|
||||
import VnSelectDialog from './VnSelectDialog.vue';
|
||||
import FilterTravelForm from 'src/components/FilterTravelForm.vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { toDate } from 'src/filters';
|
||||
const { t } = useI18n();
|
||||
|
||||
const $props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
onFilterTravelSelected: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<VnSelectDialog
|
||||
:label="t('entry.basicData.travel')"
|
||||
v-bind="$attrs"
|
||||
url="Travels/filter"
|
||||
:fields="['id', 'warehouseInName']"
|
||||
option-value="id"
|
||||
option-label="warehouseInName"
|
||||
map-options
|
||||
hide-selected
|
||||
:required="true"
|
||||
action-icon="filter_alt"
|
||||
:roles-allowed-to-create="['buyer']"
|
||||
>
|
||||
<template #form>
|
||||
<FilterTravelForm @travel-selected="onFilterTravelSelected(data, $event)" />
|
||||
</template>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>
|
||||
{{ scope.opt?.agencyModeName }} -
|
||||
{{ scope.opt?.warehouseInName }}
|
||||
({{ toDate(scope.opt?.shipped) }}) →
|
||||
{{ scope.opt?.warehouseOutName }}
|
||||
({{ toDate(scope.opt?.landed) }})
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelectDialog>
|
||||
</template>
|
|
@ -53,7 +53,6 @@ const url = computed(() => {
|
|||
:fields="['id', 'name', 'nickname', 'code']"
|
||||
:filter-options="['id', 'name', 'nickname', 'code']"
|
||||
sort-by="nickname ASC"
|
||||
data-cy="vnWorkerSelect"
|
||||
>
|
||||
<template #prepend v-if="$props.hasAvatar">
|
||||
<VnAvatar :worker-id="value" color="primary" v-bind="$attrs" />
|
||||
|
|
|
@ -1,78 +1,51 @@
|
|||
import {
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
vi,
|
||||
beforeAll,
|
||||
afterEach,
|
||||
beforeEach,
|
||||
afterAll,
|
||||
} from 'vitest';
|
||||
import { describe, it, expect, vi, beforeAll, afterEach, beforeEach } from 'vitest';
|
||||
import { createWrapper, axios } from 'app/test/vitest/helper';
|
||||
import VnNotes from 'src/components/ui/VnNotes.vue';
|
||||
import vnDate from 'src/boot/vnDate';
|
||||
|
||||
describe('VnNotes', () => {
|
||||
let vm;
|
||||
let wrapper;
|
||||
let spyFetch;
|
||||
let postMock;
|
||||
let patchMock;
|
||||
let expectedInsertBody;
|
||||
let expectedUpdateBody;
|
||||
const defaultOptions = {
|
||||
let expectedBody;
|
||||
const mockData= {name: 'Tony', lastName: 'Stark', text: 'Test Note', observationTypeFk: 1};
|
||||
|
||||
function generateExpectedBody() {
|
||||
expectedBody = {...vm.$props.body, ...{ text: vm.newNote.text, observationTypeFk: vm.newNote.observationTypeFk }};
|
||||
}
|
||||
|
||||
async function setTestParams(text, observationType, type){
|
||||
vm.newNote.text = text;
|
||||
vm.newNote.observationTypeFk = observationType;
|
||||
wrapper.setProps({ selectType: type });
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
vi.spyOn(axios, 'get').mockReturnValue({ data: [] });
|
||||
|
||||
wrapper = createWrapper(VnNotes, {
|
||||
propsData: {
|
||||
url: '/test',
|
||||
body: { name: 'Tony', lastName: 'Stark' },
|
||||
selectType: false,
|
||||
saveUrl: null,
|
||||
justInput: false,
|
||||
};
|
||||
function generateWrapper(
|
||||
options = defaultOptions,
|
||||
text = null,
|
||||
observationType = null,
|
||||
) {
|
||||
vi.spyOn(axios, 'get').mockResolvedValue({ data: [] });
|
||||
wrapper = createWrapper(VnNotes, {
|
||||
propsData: options,
|
||||
}
|
||||
});
|
||||
wrapper = wrapper.wrapper;
|
||||
vm = wrapper.vm;
|
||||
vm.newNote.text = text;
|
||||
vm.newNote.observationTypeFk = observationType;
|
||||
}
|
||||
|
||||
function createSpyFetch() {
|
||||
spyFetch = vi.spyOn(vm.$refs.vnPaginateRef, 'fetch');
|
||||
}
|
||||
|
||||
function generateExpectedBody() {
|
||||
expectedInsertBody = {
|
||||
...vm.$props.body,
|
||||
...{ text: vm.newNote.text, observationTypeFk: vm.newNote.observationTypeFk },
|
||||
};
|
||||
expectedUpdateBody = { ...vm.$props.body, ...{ notes: vm.newNote.text } };
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
postMock = vi.spyOn(axios, 'post');
|
||||
patchMock = vi.spyOn(axios, 'patch');
|
||||
postMock = vi.spyOn(axios, 'post').mockResolvedValue(mockData);
|
||||
spyFetch = vi.spyOn(vm.vnPaginateRef, 'fetch').mockImplementation(() => vi.fn());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
expectedInsertBody = {};
|
||||
expectedUpdateBody = {};
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
vi.restoreAllMocks();
|
||||
expectedBody = {};
|
||||
});
|
||||
|
||||
describe('insert', () => {
|
||||
it('should not call axios.post and vnPaginateRef.fetch when newNote.text is null', async () => {
|
||||
generateWrapper({ selectType: true });
|
||||
createSpyFetch();
|
||||
it('should not call axios.post and vnPaginateRef.fetch if newNote.text is null', async () => {
|
||||
await setTestParams( null, null, true );
|
||||
|
||||
await vm.insert();
|
||||
|
||||
|
@ -80,9 +53,8 @@ describe('VnNotes', () => {
|
|||
expect(spyFetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not call axios.post and vnPaginateRef.fetch when newNote.text is empty', async () => {
|
||||
generateWrapper(null, '');
|
||||
createSpyFetch();
|
||||
it('should not call axios.post and vnPaginateRef.fetch if newNote.text is empty', async () => {
|
||||
await setTestParams( "", null, false );
|
||||
|
||||
await vm.insert();
|
||||
|
||||
|
@ -90,9 +62,8 @@ describe('VnNotes', () => {
|
|||
expect(spyFetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not call axios.post and vnPaginateRef.fetch when observationTypeFk is null and selectType is true', async () => {
|
||||
generateWrapper({ selectType: true }, 'Test Note');
|
||||
createSpyFetch();
|
||||
it('should not call axios.post and vnPaginateRef.fetch if observationTypeFk is missing and selectType is true', async () => {
|
||||
await setTestParams( "Test Note", null, true );
|
||||
|
||||
await vm.insert();
|
||||
|
||||
|
@ -100,57 +71,37 @@ describe('VnNotes', () => {
|
|||
expect(spyFetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call axios.post and vnPaginateRef.fetch when observationTypeFk is missing and selectType is false', async () => {
|
||||
generateWrapper(null, 'Test Note');
|
||||
createSpyFetch();
|
||||
it('should call axios.post and vnPaginateRef.fetch if observationTypeFk is missing and selectType is false', async () => {
|
||||
await setTestParams( "Test Note", null, false );
|
||||
|
||||
generateExpectedBody();
|
||||
|
||||
await vm.insert();
|
||||
|
||||
expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedInsertBody);
|
||||
expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedBody);
|
||||
expect(spyFetch).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call axios.post and vnPaginateRef.fetch if observationTypeFk is setted and selectType is false', async () => {
|
||||
await setTestParams( "Test Note", 1, false );
|
||||
|
||||
generateExpectedBody();
|
||||
|
||||
await vm.insert();
|
||||
|
||||
expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedBody);
|
||||
expect(spyFetch).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call axios.post and vnPaginateRef.fetch when newNote is valid', async () => {
|
||||
generateWrapper({ selectType: true }, 'Test Note', 1);
|
||||
createSpyFetch();
|
||||
await setTestParams( "Test Note", 1, true );
|
||||
|
||||
generateExpectedBody();
|
||||
|
||||
await vm.insert();
|
||||
|
||||
expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedInsertBody);
|
||||
expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedBody);
|
||||
expect(spyFetch).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should call axios.patch with saveUrl when saveUrl is set and justInput is true', async () => {
|
||||
generateWrapper({
|
||||
url: '/business',
|
||||
justInput: true,
|
||||
saveUrl: '/saveUrlTest',
|
||||
});
|
||||
generateExpectedBody();
|
||||
|
||||
await vm.update();
|
||||
|
||||
expect(patchMock).toHaveBeenCalledWith(vm.$props.saveUrl, expectedUpdateBody);
|
||||
});
|
||||
|
||||
it('should call axios.patch with url when saveUrl is not set and justInput is true', async () => {
|
||||
generateWrapper({
|
||||
url: '/business',
|
||||
body: { workerFk: 1110 },
|
||||
justInput: true,
|
||||
});
|
||||
generateExpectedBody();
|
||||
|
||||
await vm.update();
|
||||
|
||||
expect(patchMock).toHaveBeenCalledWith(
|
||||
`${vm.$props.url}/${vm.$props.body.workerFk}`,
|
||||
expectedUpdateBody,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,8 +5,7 @@ import SkeletonDescriptor from 'components/ui/SkeletonDescriptor.vue';
|
|||
import { useArrayData } from 'composables/useArrayData';
|
||||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
||||
import { useState } from 'src/composables/useState';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useClipboard } from 'src/composables/useClipboard';
|
||||
import { useRoute } from 'vue-router';
|
||||
import VnMoreOptions from './VnMoreOptions.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
|
@ -30,6 +29,10 @@ const $props = defineProps({
|
|||
type: String,
|
||||
default: null,
|
||||
},
|
||||
module: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
summary: {
|
||||
type: Object,
|
||||
default: null,
|
||||
|
@ -42,30 +45,24 @@ const $props = defineProps({
|
|||
|
||||
const state = useState();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const { copyText } = useClipboard();
|
||||
const { viewSummary } = useSummaryDialog();
|
||||
let arrayData;
|
||||
let store;
|
||||
let entity;
|
||||
const isLoading = ref(false);
|
||||
const isSameDataKey = computed(() => $props.dataKey === route.meta.moduleName);
|
||||
const DESCRIPTOR_PROXY = 'DescriptorProxy';
|
||||
const moduleName = ref();
|
||||
const isSameModuleName = route.matched[1].meta.moduleName !== moduleName.value;
|
||||
defineExpose({ getData });
|
||||
|
||||
onBeforeMount(async () => {
|
||||
arrayData = useArrayData($props.dataKey, {
|
||||
url: $props.url,
|
||||
userFilter: $props.filter,
|
||||
filter: $props.filter,
|
||||
skip: 0,
|
||||
oneRecord: true,
|
||||
});
|
||||
store = arrayData.store;
|
||||
entity = computed(() => {
|
||||
const data = store.data ?? {};
|
||||
const data = (Array.isArray(store.data) ? store.data[0] : store.data) ?? {};
|
||||
if (data) emit('onFetch', data);
|
||||
return data;
|
||||
});
|
||||
|
@ -76,20 +73,8 @@ onBeforeMount(async () => {
|
|||
() => [$props.url, $props.filter],
|
||||
async () => {
|
||||
if (!isSameDataKey.value) await getData();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
function getName() {
|
||||
let name = $props.dataKey;
|
||||
if ($props.dataKey.includes(DESCRIPTOR_PROXY)) {
|
||||
name = name.split(DESCRIPTOR_PROXY)[0];
|
||||
}
|
||||
return name;
|
||||
}
|
||||
const routeName = computed(() => {
|
||||
let routeName = getName();
|
||||
return `${routeName}Summary`;
|
||||
);
|
||||
});
|
||||
|
||||
async function getData() {
|
||||
|
@ -99,7 +84,7 @@ async function getData() {
|
|||
try {
|
||||
const { data } = await arrayData.fetch({ append: false, updateRouter: false });
|
||||
state.set($props.dataKey, data);
|
||||
emit('onFetch', data);
|
||||
emit('onFetch', Array.isArray(data) ? data[0] : data);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
@ -117,45 +102,22 @@ function getValueFromPath(path) {
|
|||
return current;
|
||||
}
|
||||
|
||||
function copyIdText(id) {
|
||||
copyText(id, {
|
||||
component: {
|
||||
copyValue: id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const emit = defineEmits(['onFetch']);
|
||||
|
||||
const iconModule = computed(() => {
|
||||
moduleName.value = getName();
|
||||
if (isSameModuleName) {
|
||||
return router.options.routes[1].children.find((r) => r.name === moduleName.value)
|
||||
?.meta?.icon;
|
||||
} else {
|
||||
return route.matched[1].meta.icon;
|
||||
}
|
||||
});
|
||||
|
||||
const toModule = computed(() => {
|
||||
moduleName.value = getName();
|
||||
if (isSameModuleName) {
|
||||
return router.options.routes[1].children.find((r) => r.name === moduleName.value)
|
||||
?.children[0]?.redirect;
|
||||
} else {
|
||||
return route.matched[1].path.split('/').length > 2
|
||||
const iconModule = computed(() => route.matched[1].meta.icon);
|
||||
const toModule = computed(() =>
|
||||
route.matched[1].path.split('/').length > 2
|
||||
? route.matched[1].redirect
|
||||
: route.matched[1].children[0].redirect;
|
||||
}
|
||||
});
|
||||
: route.matched[1].children[0].redirect
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="descriptor">
|
||||
<template v-if="entity && !isLoading">
|
||||
<div class="header bg-primary q-pa-sm justify-between">
|
||||
<slot name="header-extra-action">
|
||||
<QBtn
|
||||
<slot name="header-extra-action"
|
||||
><QBtn
|
||||
round
|
||||
flat
|
||||
dense
|
||||
|
@ -163,13 +125,13 @@ const toModule = computed(() => {
|
|||
:icon="iconModule"
|
||||
color="white"
|
||||
class="link"
|
||||
:to="toModule"
|
||||
:to="$attrs['to-module'] ?? toModule"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('globals.goToModuleIndex') }}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
</slot>
|
||||
</QBtn></slot
|
||||
>
|
||||
<QBtn
|
||||
@click.stop="viewSummary(entity.id, $props.summary, $props.width)"
|
||||
round
|
||||
|
@ -185,7 +147,7 @@ const toModule = computed(() => {
|
|||
{{ t('components.smartCard.openSummary') }}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
<RouterLink :to="{ name: routeName, params: { id: entity.id } }">
|
||||
<RouterLink :to="{ name: `${module}Summary`, params: { id: entity.id } }">
|
||||
<QBtn
|
||||
class="link"
|
||||
color="white"
|
||||
|
@ -221,23 +183,10 @@ const toModule = computed(() => {
|
|||
</slot>
|
||||
</div>
|
||||
</QItemLabel>
|
||||
<QItem>
|
||||
<QItemLabel class="subtitle">
|
||||
<QItem dense>
|
||||
<QItemLabel class="subtitle" caption>
|
||||
#{{ getValueFromPath(subtitle) ?? entity.id }}
|
||||
</QItemLabel>
|
||||
<QBtn
|
||||
round
|
||||
flat
|
||||
dense
|
||||
size="sm"
|
||||
icon="content_copy"
|
||||
color="primary"
|
||||
@click.stop="copyIdText(entity.id)"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('globals.copyId') }}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
</QItem>
|
||||
</QList>
|
||||
<div class="list-box q-mt-xs">
|
||||
|
@ -247,11 +196,12 @@ const toModule = computed(() => {
|
|||
<div class="icons">
|
||||
<slot name="icons" :entity="entity" />
|
||||
</div>
|
||||
<div class="actions justify-center" data-cy="descriptor_actions">
|
||||
<div class="actions justify-center">
|
||||
<slot name="actions" :entity="entity" />
|
||||
</div>
|
||||
<slot name="after" />
|
||||
</template>
|
||||
<!-- Skeleton -->
|
||||
<SkeletonDescriptor v-if="!entity || isLoading" />
|
||||
</div>
|
||||
<QInnerLoading
|
||||
|
@ -343,11 +293,3 @@ const toModule = computed(() => {
|
|||
}
|
||||
}
|
||||
</style>
|
||||
<i18n>
|
||||
en:
|
||||
globals:
|
||||
copyId: Copy ID
|
||||
es:
|
||||
globals:
|
||||
copyId: Copiar ID
|
||||
</i18n>
|
||||
|
|
|
@ -15,10 +15,6 @@ const props = defineProps({
|
|||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
userFilter: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
entityId: {
|
||||
type: [Number, String],
|
||||
default: null,
|
||||
|
@ -38,12 +34,10 @@ const isSummary = ref();
|
|||
const arrayData = useArrayData(props.dataKey, {
|
||||
url: props.url,
|
||||
filter: props.filter,
|
||||
userFilter: props.userFilter,
|
||||
skip: 0,
|
||||
oneRecord: true,
|
||||
});
|
||||
const { store } = arrayData;
|
||||
const entity = computed(() => store.data);
|
||||
const entity = computed(() => (Array.isArray(store.data) ? store.data[0] : store.data));
|
||||
const isLoading = ref(false);
|
||||
|
||||
defineExpose({
|
||||
|
@ -62,7 +56,7 @@ async function fetch() {
|
|||
store.filter = props.filter ?? {};
|
||||
isLoading.value = true;
|
||||
const { data } = await arrayData.fetch({ append: false, updateRouter: false });
|
||||
emit('onFetch', data);
|
||||
emit('onFetch', Array.isArray(data) ? data[0] : data);
|
||||
isLoading.value = false;
|
||||
}
|
||||
</script>
|
||||
|
@ -209,13 +203,4 @@ async function fetch() {
|
|||
.summaryHeader {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.cardSummary :deep(.q-card__section[content]) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 0;
|
||||
> * {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -132,8 +132,7 @@ const card = toRef(props, 'item');
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
white-space: nowrap;
|
||||
width: 192px;
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
|
|
@ -1,32 +1,53 @@
|
|||
<script setup>
|
||||
defineProps({
|
||||
hasImage: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div id="descriptor-skeleton" class="bg-vn-page">
|
||||
<div id="descriptor-skeleton">
|
||||
<div class="row justify-between q-pa-sm">
|
||||
<QSkeleton square size="30px" v-for="i in 3" :key="i" />
|
||||
<QSkeleton square size="40px" />
|
||||
<QSkeleton square size="40px" />
|
||||
<QSkeleton square height="40px" width="20px" />
|
||||
</div>
|
||||
<div class="q-pa-xs" v-if="hasImage">
|
||||
<QSkeleton square height="200px" width="100%" />
|
||||
<div class="col justify-between q-pa-sm q-gutter-y-xs">
|
||||
<QSkeleton square height="40px" width="150px" />
|
||||
<QSkeleton square height="30px" width="70px" />
|
||||
</div>
|
||||
<div class="col justify-between q-pa-md q-gutter-y-xs">
|
||||
<QSkeleton square height="25px" width="150px" />
|
||||
<QSkeleton square height="15px" width="70px" />
|
||||
<div class="col q-pl-sm q-pa-sm q-mb-md">
|
||||
<div class="row justify-between">
|
||||
<QSkeleton type="text" square height="30px" width="20%" />
|
||||
<QSkeleton type="text" square height="30px" width="60%" />
|
||||
</div>
|
||||
<div class="q-pl-sm q-pa-sm q-mb-md">
|
||||
<div class="row q-gutter-x-sm q-pa-none q-ma-none" v-for="i in 5" :key="i">
|
||||
<QSkeleton type="text" square height="20px" width="30%" />
|
||||
<QSkeleton type="text" square height="20px" width="60%" />
|
||||
<div class="row justify-between">
|
||||
<QSkeleton type="text" square height="30px" width="20%" />
|
||||
<QSkeleton type="text" square height="30px" width="60%" />
|
||||
</div>
|
||||
<div class="row justify-between">
|
||||
<QSkeleton type="text" square height="30px" width="20%" />
|
||||
<QSkeleton type="text" square height="30px" width="60%" />
|
||||
</div>
|
||||
<div class="row justify-between">
|
||||
<QSkeleton type="text" square height="30px" width="20%" />
|
||||
<QSkeleton type="text" square height="30px" width="60%" />
|
||||
</div>
|
||||
<div class="row justify-between">
|
||||
<QSkeleton type="text" square height="30px" width="20%" />
|
||||
<QSkeleton type="text" square height="30px" width="60%" />
|
||||
</div>
|
||||
<div class="row justify-between">
|
||||
<QSkeleton type="text" square height="30px" width="20%" />
|
||||
<QSkeleton type="text" square height="30px" width="60%" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<QCardActions class="q-gutter-x-sm justify-between">
|
||||
<QSkeleton size="40px" v-for="i in 5" :key="i" />
|
||||
<QCardActions>
|
||||
<QSkeleton size="40px" />
|
||||
<QSkeleton size="40px" />
|
||||
<QSkeleton size="40px" />
|
||||
<QSkeleton size="40px" />
|
||||
<QSkeleton size="40px" />
|
||||
</QCardActions>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#descriptor-skeleton .q-card__actions {
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -82,7 +82,7 @@ function cancel() {
|
|||
@click="cancel()"
|
||||
/>
|
||||
</QCardSection>
|
||||
<QCardSection class="q-pb-none" data-cy="VnConfirm_message">
|
||||
<QCardSection class="q-pb-none">
|
||||
<span v-if="message !== false" v-html="message" />
|
||||
</QCardSection>
|
||||
<QCardSection class="row items-center q-pt-none">
|
||||
|
@ -95,7 +95,6 @@ function cancel() {
|
|||
:disable="isLoading"
|
||||
flat
|
||||
@click="cancel()"
|
||||
data-cy="VnConfirm_cancel"
|
||||
/>
|
||||
<QBtn
|
||||
:label="t('globals.confirm')"
|
||||
|
|
|
@ -114,7 +114,7 @@ async function clearFilters() {
|
|||
arrayData.resetPagination();
|
||||
// Filtrar los params no removibles
|
||||
const removableFilters = Object.keys(userParams.value).filter((param) =>
|
||||
$props.unremovableParams.includes(param),
|
||||
$props.unremovableParams.includes(param)
|
||||
);
|
||||
const newParams = {};
|
||||
// Conservar solo los params que no son removibles
|
||||
|
@ -162,13 +162,13 @@ const formatTags = (tags) => {
|
|||
|
||||
const tags = computed(() => {
|
||||
const filteredTags = tagsList.value.filter(
|
||||
(tag) => !($props.customTags || []).includes(tag.label),
|
||||
(tag) => !($props.customTags || []).includes(tag.label)
|
||||
);
|
||||
return formatTags(filteredTags);
|
||||
});
|
||||
|
||||
const customTags = computed(() =>
|
||||
tagsList.value.filter((tag) => ($props.customTags || []).includes(tag.label)),
|
||||
tagsList.value.filter((tag) => ($props.customTags || []).includes(tag.label))
|
||||
);
|
||||
|
||||
async function remove(key) {
|
||||
|
@ -188,13 +188,10 @@ function formatValue(value) {
|
|||
const getLocale = (label) => {
|
||||
const param = label.split('.').at(-1);
|
||||
const globalLocale = `globals.params.${param}`;
|
||||
const moduleName = route.meta.moduleName;
|
||||
const moduleLocale = `${moduleName.toLowerCase()}.${param}`;
|
||||
if (te(globalLocale)) return t(globalLocale);
|
||||
else if (te(moduleLocale)) return t(moduleLocale);
|
||||
else if (te(t(`params.${param}`)));
|
||||
else {
|
||||
const camelCaseModuleName =
|
||||
moduleName.charAt(0).toLowerCase() + moduleName.slice(1);
|
||||
const camelCaseModuleName = route.meta.moduleName.charAt(0).toLowerCase() + route.meta.moduleName.slice(1);
|
||||
return t(`${camelCaseModuleName}.params.${param}`);
|
||||
}
|
||||
};
|
||||
|
@ -293,9 +290,6 @@ const getLocale = (label) => {
|
|||
/>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.q-field__label.no-pointer-events.absolute.ellipsis {
|
||||
margin-left: 6px !important;
|
||||
}
|
||||
.list {
|
||||
width: 256px;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<QTooltip>
|
||||
{{ $t('components.cardDescriptor.moreOptions') }}
|
||||
</QTooltip>
|
||||
<QMenu ref="menuRef" data-cy="descriptor-more-opts-menu">
|
||||
<QMenu ref="menuRef">
|
||||
<QList>
|
||||
<slot name="menu" :menu-ref="$refs.menuRef" />
|
||||
</QList>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup>
|
||||
import axios from 'axios';
|
||||
import { ref, reactive, useAttrs, computed } from 'vue';
|
||||
import { ref, reactive } from 'vue';
|
||||
import { onBeforeRouteLeave } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
@ -16,23 +16,12 @@ import VnSelect from 'components/common/VnSelect.vue';
|
|||
import FetchData from 'components/FetchData.vue';
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
|
||||
const emit = defineEmits(['onFetch']);
|
||||
|
||||
const $attrs = useAttrs();
|
||||
|
||||
const isRequired = computed(() => {
|
||||
return Object.keys($attrs).includes('required');
|
||||
});
|
||||
|
||||
const $props = defineProps({
|
||||
url: { type: String, default: null },
|
||||
saveUrl: { type: String, default: null },
|
||||
userFilter: { type: Object, default: () => {} },
|
||||
filter: { type: Object, default: () => {} },
|
||||
body: { type: Object, default: () => {} },
|
||||
addNote: { type: Boolean, default: false },
|
||||
selectType: { type: Boolean, default: false },
|
||||
justInput: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
|
@ -40,13 +29,6 @@ const quasar = useQuasar();
|
|||
const newNote = reactive({ text: null, observationTypeFk: null });
|
||||
const observationTypes = ref([]);
|
||||
const vnPaginateRef = ref();
|
||||
let originalText;
|
||||
|
||||
function handleClick(e) {
|
||||
if (e.shiftKey && e.key === 'Enter') return;
|
||||
if ($props.justInput) confirmAndUpdate();
|
||||
else insert();
|
||||
}
|
||||
|
||||
async function insert() {
|
||||
if (!newNote.text || ($props.selectType && !newNote.observationTypeFk)) return;
|
||||
|
@ -59,42 +41,8 @@ async function insert() {
|
|||
await axios.post($props.url, newBody);
|
||||
await vnPaginateRef.value.fetch();
|
||||
}
|
||||
|
||||
function confirmAndUpdate() {
|
||||
if (!newNote.text && originalText)
|
||||
quasar
|
||||
.dialog({
|
||||
component: VnConfirm,
|
||||
componentProps: {
|
||||
title: t('New note is empty'),
|
||||
message: t('Are you sure remove this note?'),
|
||||
},
|
||||
})
|
||||
.onOk(update)
|
||||
.onCancel(() => {
|
||||
newNote.text = originalText;
|
||||
});
|
||||
else update();
|
||||
}
|
||||
|
||||
async function update() {
|
||||
originalText = newNote.text;
|
||||
const body = $props.body;
|
||||
const newBody = {
|
||||
...body,
|
||||
...{ notes: newNote.text },
|
||||
};
|
||||
await axios.patch(
|
||||
`${$props.saveUrl ?? `${$props.url}/${$props.body.workerFk}`}`,
|
||||
newBody,
|
||||
);
|
||||
}
|
||||
|
||||
onBeforeRouteLeave((to, from, next) => {
|
||||
if (
|
||||
(newNote.text && !$props.justInput) ||
|
||||
(newNote.text !== originalText && $props.justInput)
|
||||
)
|
||||
if (newNote.text)
|
||||
quasar.dialog({
|
||||
component: VnConfirm,
|
||||
componentProps: {
|
||||
|
@ -105,12 +53,6 @@ onBeforeRouteLeave((to, from, next) => {
|
|||
});
|
||||
else next();
|
||||
});
|
||||
|
||||
function fetchData([data]) {
|
||||
newNote.text = data?.notes;
|
||||
originalText = data?.notes;
|
||||
emit('onFetch', data);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<FetchData
|
||||
|
@ -120,19 +62,8 @@ function fetchData([data]) {
|
|||
auto-load
|
||||
@on-fetch="(data) => (observationTypes = data)"
|
||||
/>
|
||||
<FetchData
|
||||
v-if="justInput"
|
||||
:url="url"
|
||||
:filter="filter"
|
||||
@on-fetch="fetchData"
|
||||
auto-load
|
||||
/>
|
||||
<QCard
|
||||
class="q-pa-xs q-mb-lg full-width"
|
||||
:class="{ 'just-input': $props.justInput }"
|
||||
v-if="$props.addNote || $props.justInput"
|
||||
>
|
||||
<QCardSection horizontal v-if="!$props.justInput">
|
||||
<QCard class="q-pa-xs q-mb-lg full-width" v-if="$props.addNote">
|
||||
<QCardSection horizontal>
|
||||
{{ t('New note') }}
|
||||
</QCardSection>
|
||||
<QCardSection class="q-px-xs q-my-none q-py-none">
|
||||
|
@ -144,19 +75,19 @@ function fetchData([data]) {
|
|||
v-model="newNote.observationTypeFk"
|
||||
option-label="description"
|
||||
style="flex: 0.15"
|
||||
:required="isRequired"
|
||||
:required="true"
|
||||
@keyup.enter.stop="insert"
|
||||
/>
|
||||
<VnInput
|
||||
v-model.trim="newNote.text"
|
||||
type="textarea"
|
||||
:label="$props.justInput && newNote.text ? '' : t('Add note here...')"
|
||||
:label="t('Add note here...')"
|
||||
filled
|
||||
size="lg"
|
||||
autogrow
|
||||
@keyup.enter.stop="handleClick"
|
||||
:required="isRequired"
|
||||
@keyup.enter.stop="insert"
|
||||
clearable
|
||||
:required="true"
|
||||
>
|
||||
<template #append>
|
||||
<QBtn
|
||||
|
@ -164,7 +95,7 @@ function fetchData([data]) {
|
|||
icon="save"
|
||||
color="primary"
|
||||
flat
|
||||
@click="handleClick"
|
||||
@click="insert"
|
||||
class="q-mb-xs"
|
||||
dense
|
||||
data-cy="saveNote"
|
||||
|
@ -175,13 +106,11 @@ function fetchData([data]) {
|
|||
</QCardSection>
|
||||
</QCard>
|
||||
<VnPaginate
|
||||
v-if="!$props.justInput"
|
||||
:data-key="$props.url"
|
||||
:url="$props.url"
|
||||
order="created DESC"
|
||||
:limit="0"
|
||||
:user-filter="userFilter"
|
||||
:filter="filter"
|
||||
:user-filter="$props.filter"
|
||||
auto-load
|
||||
ref="vnPaginateRef"
|
||||
class="show"
|
||||
|
@ -220,7 +149,7 @@ function fetchData([data]) {
|
|||
>
|
||||
{{
|
||||
observationTypes.find(
|
||||
(ot) => ot.id === note.observationTypeFk,
|
||||
(ot) => ot.id === note.observationTypeFk
|
||||
)?.description
|
||||
}}
|
||||
</QBadge>
|
||||
|
@ -269,11 +198,6 @@ function fetchData([data]) {
|
|||
}
|
||||
}
|
||||
}
|
||||
.just-input {
|
||||
padding-right: 18px;
|
||||
margin-bottom: 2px;
|
||||
box-shadow: none;
|
||||
}
|
||||
</style>
|
||||
<i18n>
|
||||
es:
|
||||
|
@ -281,6 +205,4 @@ function fetchData([data]) {
|
|||
New note: Nueva nota
|
||||
Save (Enter): Guardar (Intro)
|
||||
Observation type: Tipo de observación
|
||||
New note is empty: La nueva nota esta vacia
|
||||
Are you sure remove this note?: Estas seguro de quitar esta nota?
|
||||
</i18n>
|
||||
|
|
|
@ -123,7 +123,7 @@ watch(
|
|||
() => props.data,
|
||||
() => {
|
||||
store.data = props.data;
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
|
@ -132,12 +132,12 @@ watch(
|
|||
if (!mounted.value) return;
|
||||
emit('onChange', data);
|
||||
},
|
||||
{ immediate: true },
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => [props.url, props.filter],
|
||||
([url, filter]) => mounted.value && fetch({ url, filter }),
|
||||
([url, filter]) => mounted.value && fetch({ url, filter })
|
||||
);
|
||||
const addFilter = async (filter, params) => {
|
||||
await arrayData.addFilter({ filter, params });
|
||||
|
@ -170,7 +170,7 @@ function emitStoreData() {
|
|||
async function paginate() {
|
||||
const { page, rowsPerPage, sortBy, descending } = pagination.value;
|
||||
|
||||
if (!arrayData.store.url) return;
|
||||
if (!props.url) return;
|
||||
|
||||
isLoading.value = true;
|
||||
await arrayData.loadMore();
|
||||
|
@ -198,7 +198,7 @@ function endPagination() {
|
|||
async function onLoad(index, done) {
|
||||
if (!store.data || !mounted.value) return done();
|
||||
|
||||
if (store.data.length === 0 || !arrayData.store.url) return done(false);
|
||||
if (store.data.length === 0 || !props.url) return done(false);
|
||||
|
||||
pagination.value.page = pagination.value.page + 1;
|
||||
|
||||
|
|
|
@ -33,10 +33,6 @@ const props = defineProps({
|
|||
type: String,
|
||||
default: '',
|
||||
},
|
||||
userFilter: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
filter: {
|
||||
type: Object,
|
||||
default: null,
|
||||
|
@ -208,9 +204,8 @@ async function search() {
|
|||
}
|
||||
|
||||
:deep(.q-field--focused) {
|
||||
.q-icon,
|
||||
.q-placeholder {
|
||||
color: var(--vn-black-text-color);
|
||||
.q-icon {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ function formatNumber(number) {
|
|||
<VnPaginate
|
||||
:data-key="$props.url"
|
||||
:url="$props.url"
|
||||
:user-filter="filter"
|
||||
:filter="filter"
|
||||
order="smsFk DESC"
|
||||
:offset="100"
|
||||
:limit="5"
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
<script setup>
|
||||
import { toPercentage } from 'filters/index';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const valueClass = computed(() =>
|
||||
props.value === 0 ? 'neutral' : props.value > 0 ? 'positive' : 'negative',
|
||||
);
|
||||
const iconName = computed(() =>
|
||||
props.value === 0 ? 'equal' : props.value > 0 ? 'arrow_upward' : 'arrow_downward',
|
||||
);
|
||||
const formattedValue = computed(() => props.value);
|
||||
</script>
|
||||
<template>
|
||||
<span :class="valueClass">
|
||||
<QIcon :name="iconName" size="sm" class="value-icon" />
|
||||
{{ toPercentage(formattedValue) }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.positive {
|
||||
color: $secondary;
|
||||
}
|
||||
.negative {
|
||||
color: $negative;
|
||||
}
|
||||
.neutral {
|
||||
color: $primary;
|
||||
}
|
||||
.value-icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
</style>
|
|
@ -19,26 +19,23 @@ onMounted(() => {
|
|||
const observer = new MutationObserver(
|
||||
() =>
|
||||
(hasContent.value =
|
||||
actions.value?.childNodes?.length + data.value?.childNodes?.length),
|
||||
actions.value?.childNodes?.length + data.value?.childNodes?.length)
|
||||
);
|
||||
if (actions.value) observer.observe(actions.value, opts);
|
||||
if (data.value) observer.observe(data.value, opts);
|
||||
});
|
||||
|
||||
const actionsChildCount = () => !!actions.value?.childNodes?.length;
|
||||
|
||||
onBeforeUnmount(() => stateStore.toggleSubToolbar() && hasSubToolbar);
|
||||
onBeforeUnmount(() => stateStore.toggleSubToolbar());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<QToolbar
|
||||
id="subToolbar"
|
||||
v-show="hasContent || $slots['st-actions'] || $slots['st-data']"
|
||||
class="justify-end sticky"
|
||||
v-show="hasContent || $slots['st-actions'] || $slots['st-data']"
|
||||
>
|
||||
<slot name="st-data">
|
||||
<div id="st-data" :class="{ 'full-width': !actionsChildCount() }">
|
||||
</div>
|
||||
<div id="st-data"></div>
|
||||
</slot>
|
||||
<QSpace />
|
||||
<slot name="st-actions">
|
||||
|
|
|
@ -53,8 +53,3 @@ const manaCode = ref(props.manaCode);
|
|||
/>
|
||||
</div>
|
||||
</template>
|
||||
<i18n>
|
||||
es:
|
||||
Promotion mana: Maná promoción
|
||||
Claim mana: Maná reclamación
|
||||
</i18n>
|
||||
|
|
|
@ -51,6 +51,16 @@ describe('CardSummary', () => {
|
|||
expect(vm.store.filter).toEqual('cardFilter');
|
||||
});
|
||||
|
||||
it('should compute entity correctly from store data', () => {
|
||||
vm.store.data = [{ id: 1, name: 'Entity 1' }];
|
||||
expect(vm.entity).toEqual({ id: 1, name: 'Entity 1' });
|
||||
});
|
||||
|
||||
it('should handle empty data gracefully', () => {
|
||||
vm.store.data = [];
|
||||
expect(vm.entity).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should respond to prop changes and refetch data', async () => {
|
||||
const newUrl = 'CardSummary/35';
|
||||
const newKey = 'cardSummaryKey/35';
|
||||
|
@ -62,7 +72,7 @@ describe('CardSummary', () => {
|
|||
expect(vm.store.filter).toEqual({ key: newKey });
|
||||
});
|
||||
|
||||
it('should return true if route path ends with /summary', () => {
|
||||
it('should return true if route path ends with /summary' , () => {
|
||||
expect(vm.isSummary).toBe(true);
|
||||
});
|
||||
});
|
|
@ -16,7 +16,7 @@ describe('useArrayData', () => {
|
|||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should fetch and replace url with new params', async () => {
|
||||
it('should fetch and repalce url with new params', async () => {
|
||||
vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [] });
|
||||
|
||||
const arrayData = useArrayData('ArrayData', { url: 'mockUrl' });
|
||||
|
@ -33,11 +33,11 @@ describe('useArrayData', () => {
|
|||
});
|
||||
expect(routerReplace.path).toEqual('mockSection/list');
|
||||
expect(JSON.parse(routerReplace.query.params)).toEqual(
|
||||
expect.objectContaining(params),
|
||||
expect.objectContaining(params)
|
||||
);
|
||||
});
|
||||
|
||||
it('should get data and send new URL without keeping parameters, if there is only one record', async () => {
|
||||
it('Should get data and send new URL without keeping parameters, if there is only one record', async () => {
|
||||
vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [{ id: 1 }] });
|
||||
|
||||
const arrayData = useArrayData('ArrayData', { url: 'mockUrl', navigate: {} });
|
||||
|
@ -56,7 +56,7 @@ describe('useArrayData', () => {
|
|||
expect(routerPush.query).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should get data and send new URL keeping parameters, if you have more than one record', async () => {
|
||||
it('Should get data and send new URL keeping parameters, if you have more than one record', async () => {
|
||||
vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [{ id: 1 }, { id: 2 }] });
|
||||
|
||||
vi.spyOn(vueRouter, 'useRoute').mockReturnValue({
|
||||
|
@ -95,25 +95,4 @@ describe('useArrayData', () => {
|
|||
expect(routerPush.path).toEqual('mockName/');
|
||||
expect(routerPush.query.params).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return one record', async () => {
|
||||
vi.spyOn(axios, 'get').mockReturnValueOnce({
|
||||
data: [
|
||||
{ id: 1, name: 'Entity 1' },
|
||||
{ id: 2, name: 'Entity 2' },
|
||||
],
|
||||
});
|
||||
const arrayData = useArrayData('ArrayData', { url: 'mockUrl', oneRecord: true });
|
||||
await arrayData.fetch({});
|
||||
|
||||
expect(arrayData.store.data).toEqual({ id: 1, name: 'Entity 1' });
|
||||
});
|
||||
|
||||
it('should handle empty data gracefully if has to return one record', async () => {
|
||||
vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [] });
|
||||
const arrayData = useArrayData('ArrayData', { url: 'mockUrl', oneRecord: true });
|
||||
await arrayData.fetch({});
|
||||
|
||||
expect(arrayData.store.data).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { useRequired } from '../useRequired';
|
||||
|
||||
vi.mock('../useValidator', () => ({
|
||||
useValidator: () => ({
|
||||
validations: () => ({
|
||||
required: vi.fn((isRequired, val) => {
|
||||
if (!isRequired) return true;
|
||||
return val !== null && val !== undefined && val !== '';
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('useRequired', () => {
|
||||
it('should detect required when attr is boolean true', () => {
|
||||
const attrs = { required: true };
|
||||
const { isRequired } = useRequired(attrs);
|
||||
expect(isRequired).toBe(true);
|
||||
});
|
||||
|
||||
it('should detect required when attr is boolean false', () => {
|
||||
const attrs = { required: false };
|
||||
const { isRequired } = useRequired(attrs);
|
||||
expect(isRequired).toBe(false);
|
||||
});
|
||||
|
||||
it('should detect required when attr exists without value', () => {
|
||||
const attrs = { required: '' };
|
||||
const { isRequired } = useRequired(attrs);
|
||||
expect(isRequired).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when required attr does not exist', () => {
|
||||
const attrs = { someOtherAttr: 'value' };
|
||||
const { isRequired } = useRequired(attrs);
|
||||
expect(isRequired).toBe(false);
|
||||
});
|
||||
|
||||
describe('requiredFieldRule', () => {
|
||||
it('should validate required field with value', () => {
|
||||
const attrs = { required: true };
|
||||
const { requiredFieldRule } = useRequired(attrs);
|
||||
expect(requiredFieldRule('some value')).toBe(true);
|
||||
});
|
||||
|
||||
it('should validate required field with empty value', () => {
|
||||
const attrs = { required: true };
|
||||
const { requiredFieldRule } = useRequired(attrs);
|
||||
expect(requiredFieldRule('')).toBe(false);
|
||||
});
|
||||
|
||||
it('should pass validation when field is not required', () => {
|
||||
const attrs = { required: false };
|
||||
const { requiredFieldRule } = useRequired(attrs);
|
||||
expect(requiredFieldRule('')).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle null and undefined values', () => {
|
||||
const attrs = { required: true };
|
||||
const { requiredFieldRule } = useRequired(attrs);
|
||||
expect(requiredFieldRule(null)).toBe(false);
|
||||
expect(requiredFieldRule(undefined)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,64 +0,0 @@
|
|||
import { useQuasar } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
import VnConfirm from 'components/ui/VnConfirm.vue';
|
||||
|
||||
export async function checkEntryLock(entryFk, userFk) {
|
||||
const { t } = useI18n();
|
||||
const quasar = useQuasar();
|
||||
const { push } = useRouter();
|
||||
const { data } = await axios.get(`Entries/${entryFk}`, {
|
||||
params: {
|
||||
filter: JSON.stringify({
|
||||
fields: ['id', 'locked', 'lockerUserFk'],
|
||||
include: { relation: 'user', scope: { fields: ['id', 'nickname'] } },
|
||||
}),
|
||||
},
|
||||
});
|
||||
const entryConfig = await axios.get('EntryConfigs/findOne');
|
||||
|
||||
if (data?.lockerUserFk && data?.locked) {
|
||||
const now = new Date(Date.vnNow()).getTime();
|
||||
const lockedTime = new Date(data.locked).getTime();
|
||||
const timeDiff = (now - lockedTime) / 1000;
|
||||
const isMaxTimeLockExceeded = entryConfig.data.maxLockTime > timeDiff;
|
||||
|
||||
if (data?.lockerUserFk !== userFk && isMaxTimeLockExceeded) {
|
||||
quasar
|
||||
.dialog({
|
||||
component: VnConfirm,
|
||||
componentProps: {
|
||||
title: t('entry.lock.title'),
|
||||
message: t('entry.lock.message', {
|
||||
userName: data?.user?.nickname,
|
||||
time: timeDiff / 60,
|
||||
}),
|
||||
},
|
||||
})
|
||||
.onOk(
|
||||
async () =>
|
||||
await axios.patch(`Entries/${entryFk}`, {
|
||||
locked: Date.vnNow(),
|
||||
lockerUserFk: userFk,
|
||||
}),
|
||||
)
|
||||
.onCancel(() => {
|
||||
push({ path: `summary` });
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await axios
|
||||
.patch(`Entries/${entryFk}`, {
|
||||
locked: Date.vnNow(),
|
||||
lockerUserFk: userFk,
|
||||
})
|
||||
.then(
|
||||
quasar.notify({
|
||||
message: t('entry.lock.success'),
|
||||
color: 'positive',
|
||||
group: false,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
export function getColAlign(col) {
|
||||
let align;
|
||||
switch (col.component) {
|
||||
case 'time':
|
||||
case 'date':
|
||||
case 'select':
|
||||
align = 'left';
|
||||
break;
|
||||
case 'number':
|
||||
align = 'right';
|
||||
break;
|
||||
case 'checkbox':
|
||||
align = 'center';
|
||||
break;
|
||||
default:
|
||||
align = col?.align;
|
||||
}
|
||||
|
||||
if (/^is[A-Z]/.test(col.name) || /^has[A-Z]/.test(col.name)) align = 'center';
|
||||
|
||||
return 'text-' + (align ?? 'center');
|
||||
}
|
|
@ -57,7 +57,6 @@ export function useArrayData(key, userOptions) {
|
|||
'navigate',
|
||||
'mapKey',
|
||||
'keepData',
|
||||
'oneRecord',
|
||||
];
|
||||
if (typeof userOptions === 'object') {
|
||||
for (const option in userOptions) {
|
||||
|
@ -94,9 +93,6 @@ export function useArrayData(key, userOptions) {
|
|||
|
||||
if (params.filter.where || exprFilter)
|
||||
params.filter.where = { ...params.filter.where, ...exprFilter };
|
||||
|
||||
if (!params?.filter?.order?.length) delete params?.filter?.order;
|
||||
|
||||
params.filter = JSON.stringify(params.filter);
|
||||
|
||||
store.isLoading = true;
|
||||
|
@ -113,11 +109,7 @@ export function useArrayData(key, userOptions) {
|
|||
store.isLoading = false;
|
||||
canceller = null;
|
||||
|
||||
processData(response.data, {
|
||||
map: !!store.mapKey,
|
||||
append,
|
||||
oneRecord: store.oneRecord,
|
||||
});
|
||||
processData(response.data, { map: !!store.mapKey, append });
|
||||
|
||||
return response;
|
||||
}
|
||||
|
@ -148,7 +140,8 @@ export function useArrayData(key, userOptions) {
|
|||
}
|
||||
|
||||
async function applyFilter({ filter, params }, fetchOptions = {}) {
|
||||
if (filter) store.filter = filter;
|
||||
if (filter) store.userFilter = filter;
|
||||
store.filter = {};
|
||||
if (params) store.userParams = { ...params };
|
||||
|
||||
const response = await fetch(fetchOptions);
|
||||
|
@ -244,7 +237,7 @@ export function useArrayData(key, userOptions) {
|
|||
async function loadMore() {
|
||||
if (!store.hasMoreData) return;
|
||||
|
||||
store.skip = (store?.filter?.limit ?? store.limit) * store.page;
|
||||
store.skip = store.limit * store.page;
|
||||
store.page += 1;
|
||||
|
||||
await fetch({ append: true });
|
||||
|
@ -318,11 +311,7 @@ export function useArrayData(key, userOptions) {
|
|||
return { params, limit };
|
||||
}
|
||||
|
||||
function processData(data, { map = true, append = true, oneRecord = false }) {
|
||||
if (oneRecord) {
|
||||
store.data = Array.isArray(data) ? data[0] : data;
|
||||
return;
|
||||
}
|
||||
function processData(data, { map = true, append = true }) {
|
||||
if (!append) {
|
||||
store.data = [];
|
||||
store.map = new Map();
|
||||
|
|
|
@ -11,7 +11,6 @@ export async function useCau(res, message) {
|
|||
const { config, headers, request, status, statusText, data } = res || {};
|
||||
const { params, url, method, signal, headers: confHeaders } = config || {};
|
||||
const { message: resMessage, code, name } = data?.error || {};
|
||||
delete confHeaders?.Authorization;
|
||||
|
||||
const additionalData = {
|
||||
path: location.hash,
|
||||
|
@ -41,7 +40,7 @@ export async function useCau(res, message) {
|
|||
handler: async () => {
|
||||
const locale = i18n.global.t;
|
||||
const reason = ref(
|
||||
code == 'ACCESS_DENIED' ? locale('cau.askPrivileges') : '',
|
||||
code == 'ACCESS_DENIED' ? locale('cau.askPrivileges') : ''
|
||||
);
|
||||
openConfirmationModal(
|
||||
locale('cau.title'),
|
||||
|
@ -60,9 +59,10 @@ export async function useCau(res, message) {
|
|||
'onUpdate:modelValue': (val) => (reason.value = val),
|
||||
label: locale('cau.inputLabel'),
|
||||
class: 'full-width',
|
||||
required: true,
|
||||
autofocus: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
|
|
|
@ -2,10 +2,14 @@ import { useValidator } from 'src/composables/useValidator';
|
|||
|
||||
export function useRequired($attrs) {
|
||||
const { validations } = useValidator();
|
||||
const isRequired =
|
||||
typeof $attrs['required'] === 'boolean'
|
||||
? $attrs['required']
|
||||
: Object.keys($attrs).includes('required');
|
||||
const hasRequired = Object.keys($attrs).includes('required');
|
||||
let isRequired = false;
|
||||
if (hasRequired) {
|
||||
const required = $attrs['required'];
|
||||
if (typeof required === 'boolean') {
|
||||
isRequired = required;
|
||||
}
|
||||
}
|
||||
const requiredFieldRule = (val) => validations().required(isRequired, val);
|
||||
|
||||
return {
|
||||
|
|
|
@ -27,15 +27,6 @@ export function useRole() {
|
|||
|
||||
return false;
|
||||
}
|
||||
function likeAny(roles) {
|
||||
const roleStore = state.getRoles();
|
||||
for (const role of roles) {
|
||||
if (!roleStore.value.findIndex((rs) => rs.startsWith(role)) !== -1)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
function isEmployee() {
|
||||
return hasAny(['employee']);
|
||||
}
|
||||
|
@ -44,7 +35,6 @@ export function useRole() {
|
|||
isEmployee,
|
||||
fetch,
|
||||
hasAny,
|
||||
likeAny,
|
||||
state,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -21,10 +21,7 @@ body.body--light {
|
|||
.q-header .q-toolbar {
|
||||
color: var(--vn-text-color);
|
||||
}
|
||||
|
||||
--vn-color-negative: $negative;
|
||||
}
|
||||
|
||||
body.body--dark {
|
||||
--vn-header-color: #5d5d5d;
|
||||
--vn-page-color: #222;
|
||||
|
@ -40,8 +37,6 @@ body.body--dark {
|
|||
--vn-text-color-contrast: black;
|
||||
|
||||
background-color: var(--vn-page-color);
|
||||
|
||||
--vn-color-negative: $negative;
|
||||
}
|
||||
|
||||
a {
|
||||
|
@ -80,6 +75,7 @@ a {
|
|||
text-decoration: underline;
|
||||
}
|
||||
|
||||
// Removes chrome autofill background
|
||||
input:-webkit-autofill,
|
||||
select:-webkit-autofill {
|
||||
color: var(--vn-text-color);
|
||||
|
@ -153,6 +149,11 @@ select:-webkit-autofill {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vn-table-separation-row {
|
||||
height: 16px !important;
|
||||
background-color: var(--vn-section-color) !important;
|
||||
}
|
||||
|
||||
/* Estilo para el asterisco en campos requeridos */
|
||||
.q-field.required .q-field__label:after {
|
||||
content: ' *';
|
||||
|
@ -211,10 +212,6 @@ select:-webkit-autofill {
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
.q-card__section[dense] {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input[type='number'] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
@ -229,12 +226,10 @@ input::-webkit-inner-spin-button {
|
|||
max-width: 100%;
|
||||
}
|
||||
|
||||
.remove-bg {
|
||||
filter: brightness(1.1);
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
|
||||
.q-table__container {
|
||||
/* ===== Scrollbar CSS ===== /
|
||||
/ Firefox */
|
||||
|
||||
* {
|
||||
scrollbar-width: auto;
|
||||
scrollbar-color: var(--vn-label-color) transparent;
|
||||
|
@ -275,6 +270,8 @@ input::-webkit-inner-spin-button {
|
|||
font-size: 11pt;
|
||||
}
|
||||
td {
|
||||
font-size: 11pt;
|
||||
border-top: 1px solid var(--vn-page-color);
|
||||
border-collapse: collapse;
|
||||
}
|
||||
}
|
||||
|
@ -318,6 +315,9 @@ input::-webkit-inner-spin-button {
|
|||
max-width: fit-content;
|
||||
}
|
||||
|
||||
.row > .column:has(.q-checkbox) {
|
||||
max-width: fit-content;
|
||||
}
|
||||
.q-field__inner {
|
||||
.q-field__control {
|
||||
min-height: auto !important;
|
||||
|
@ -335,7 +335,3 @@ input::-webkit-inner-spin-button {
|
|||
border: 1px solid;
|
||||
box-shadow: 0 4px 6px #00000000;
|
||||
}
|
||||
|
||||
.containerShrinked {
|
||||
width: 70%;
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -127,7 +127,6 @@
|
|||
<glyph unicode="" glyph-name="stowaway" d="M1006.933 452.267l-260.267 106.667 29.867 29.867c4.267 4.267 4.267 12.8 4.267 17.067-4.267 4.267-8.533 8.533-12.8 8.533h-157.867c0 93.867 76.8 157.867 174.933 157.867 4.267 0 8.533 4.267 12.8 8.533s4.267 8.533 0 17.067l-81.067 153.6c-4.267 0-12.8 4.267-17.067 4.267-46.933 0-93.867-17.067-132.267-42.667-21.333-17.067-42.667-38.4-55.467-59.733-12.8 21.333-29.867 42.667-55.467 59.733-34.133 12.8-81.067 34.133-128 34.133-4.267 0-12.8-4.267-12.8-8.533l-85.333-153.6c-4.267-4.267-4.267-4.267 0-12.8 4.267-4.267 8.533-8.533 12.8-8.533 98.133 0 174.933-59.733 174.933-153.6v0h-140.8c-4.267 0-12.8-4.267-12.8-8.533-8.533-4.267-4.267-17.067 0-21.333l21.333-21.333-277.333-110.933c-8.533-8.533-12.8-12.8-8.533-21.333 0-8.533 8.533-12.8 17.067-12.8v0l98.133 4.267-81.067-85.333c0-4.267-4.267-8.533 0-12.8 0-4.267 4.267-8.533 8.533-8.533l85.333-34.133v-179.2c0-8.533 4.267-12.8 8.533-12.8l358.4-145.067h8.533l358.4 145.067c4.267 4.267 8.533 8.533 8.533 12.8v179.2l85.333 34.133c4.267 0 8.533 4.267 8.533 8.533s0 8.533-4.267 12.8l-68.267 98.133 102.4-4.267c8.533 0 12.8 4.267 17.067 12.8 8.533 0 4.267 4.267-4.267 12.8zM110.933 456.533l196.267 76.8 8.533-8.533-166.4-64-38.4-4.267zM153.6 285.867v0l-68.267 34.133 68.267 98.133 328.533-132.267-68.267-98.133-260.267 98.133zM490.667-29.867l-328.533 132.267v153.6l243.2-98.133h12.8c0 0 0 0 4.267 0v0c0 0 4.267 0 4.267 4.267l64 85.333c0-4.267 0-277.333 0-277.333zM490.667 324.267l-298.667 115.2 149.333 64 153.6-157.867v-17.067h-4.267zM529.067 337.067l157.867 157.867 140.8-55.467-298.667-115.2c0 0 0 12.8 0 12.8zM849.067 102.4l-328.533-132.267v281.6l64-85.333c0 0 0-4.267 4.267-4.267v0h17.067l243.2 98.133v-157.867zM938.667 324.267l-324.267-132.267-68.267 98.133 328.533 132.267 64-98.133zM870.4 460.8l-157.867 64 12.8 8.533 187.733-76.8-42.667 4.267z" />
|
||||
<glyph unicode="" glyph-name="supplier" d="M797.867 405.333l98.133 34.133 21.333-59.733-98.133-34.133-21.333 59.733zM1019.733 341.333c-4.267-8.533-8.533-12.8-17.067-17.067l-332.8-119.467c4.267-4.267 8.533-12.8 12.8-17.067l277.333 102.4 21.333-59.733-277.333-102.4c0-8.533 4.267-12.8 4.267-21.333 0-85.333-68.267-157.867-157.867-157.867-85.333 0-157.867 68.267-157.867 157.867 0 55.467 29.867 106.667 72.533 132.267l-217.6 610.133c-8.533 25.6-38.4 42.667-68.267 29.867l-157.867-55.467-21.333 59.733 157.867 59.733c59.733 17.067 123.733-12.8 149.333-72.533l221.867-614.4c8.533 0 12.8 0 21.333 4.267l-119.467 332.8c-4.267 17.067 4.267 34.133 17.067 38.4l136.533 51.2c0 0 0 0 0 0l115.2 42.667c0 0 0 0 0 0l136.533 51.2c8.533 4.267 17.067 4.267 25.6 0s12.8-8.533 17.067-17.067l145.067-396.8c0-4.267 0-12.8-4.267-21.333zM695.467 657.067l-59.733-21.333 8.533-21.333 59.733 21.333-8.533 21.333zM644.267 106.667c0 51.2-42.667 93.867-93.867 93.867s-93.867-42.667-93.867-93.867c0-51.2 42.667-93.867 93.867-93.867s93.867 38.4 93.867 93.867zM951.467 371.2l-119.467 332.8-76.8-29.867 17.067-51.2c4.267-8.533 4.267-17.067 0-25.6s-8.533-12.8-17.067-17.067l-115.2-42.667c-4.267 0-8.533 0-12.8 0-12.8 0-25.6 8.533-29.867 21.333l-17.067 51.2-76.8-29.867 119.467-332.8 328.533 123.733z" />
|
||||
<glyph unicode="" glyph-name="supplierfalse" d="M198.827 882.773c22.187 0.427 41.813-14.080 48.64-34.133l8.107-22.187 105.813-105.813-54.187 149.333c-25.6 59.733-89.6 89.6-149.333 72.533l-13.653-5.12 54.613-54.613zM708.693 129.28l-173.653 173.653 15.36-43.093c-8.533-4.267-12.8-4.267-21.333-4.267l-29.867 83.2-108.373 108.373 74.24-208.64c-42.667-25.6-72.533-76.8-72.533-132.267 0-89.6 72.533-157.867 157.867-157.867 89.6 0 157.867 72.533 157.867 157.867 0 8.533-4.267 12.8-4.267 21.333l4.693 1.707zM550.4 12.373c-51.2 0-93.867 42.667-93.867 93.867s42.667 93.867 93.867 93.867 93.867-42.667 93.867-93.867c0-55.467-42.667-93.867-93.867-93.867zM960 289.707l-122.453-45.227 49.493-49.067 94.293 34.56zM504.32 577.707l-0.853 2.133 76.8 29.867 17.067-51.2c4.267-12.8 17.067-21.333 29.867-21.333 4.267 0 8.533 0 12.8 0l115.2 42.667c8.533 4.267 12.8 8.533 17.067 17.067s4.267 17.067 0 25.6l-17.067 51.2 76.8 29.867 119.467-332.8-174.507-65.707 45.653-45.653 180.053 64.427c8.533 4.267 12.8 8.533 17.067 17.067s4.267 17.067 4.267 21.333l-145.067 396.8c-4.267 8.533-8.533 12.8-17.067 17.067s-17.067 4.267-25.6 0l-136.533-51.2-115.2-42.667-134.4-50.347 54.187-54.187zM695.467 656.64l8.533-21.333-59.733-21.333-8.533 21.333 59.733 21.333zM896 439.040l-98.133-34.133 21.333-59.733 98.133 34.133zM39.253 960c-9.813 0-20.053-3.84-27.733-11.52-15.36-15.787-15.36-40.533 0-55.893l945.493-945.067c7.68-7.68 17.493-11.52 27.733-11.52 9.813 0 20.053 3.84 27.733 11.52 15.36 15.787 15.36 40.533 0 55.893l-945.493 945.067c-7.68 7.68-17.493 11.52-27.733 11.52z" />
|
||||
<glyph unicode="" glyph-name="inactive-car" d="M1024 900.267l-59.733 59.733-964.267-964.267 59.733-59.733 964.267 964.267zM0 55.893v448.853l119.467 341.333c5.547 17.067 15.787 30.72 30.72 41.387 14.507 10.24 31.147 15.787 49.067 15.787h625.92c6.827 0 13.653-0.853 20.053-2.133l-111.36-111.36h-514.987l-59.733-170.667h403.2l-113.92-113.92h-334.507v-284.587h50.773l-164.693-164.693zM316.587 423.253c8.96-8.96 15.787-19.627 19.627-30.72l-110.080-110.080c-11.52 4.267-21.76 10.667-30.72 19.627-16.64 16.64-24.747 36.693-24.747 60.587s8.107 43.947 24.747 60.587c16.64 16.64 36.693 24.747 60.587 24.747s43.947-8.107 60.587-24.747zM768 277.333c-23.893 0-43.947 8.107-60.587 24.747s-24.747 36.693-24.747 60.587 8.107 43.947 24.747 60.587c16.64 16.64 36.693 24.747 60.587 24.747s43.947-8.107 60.587-24.747c16.64-16.64 24.747-36.693 24.747-60.587s-8.107-43.947-24.747-60.587c-16.64-16.64-36.693-24.747-60.587-24.747zM936.96 753.067l87.040-248.32v-455.253c0-16.213-5.547-29.44-16.213-40.533s-24.32-16.213-40.533-16.213h-56.747c-16.213 0-29.44 5.547-40.533 16.213s-16.213 24.32-16.213 40.533v56.747h-563.2l113.92 113.92h505.6v284.587h-221.44l113.92 113.92h61.867l-16.213 46.080 88.32 88.32z" />
|
||||
<glyph unicode="" glyph-name="tags" d="M729.6 960c-42.667 0-89.6 0-132.267 0-21.333 0-38.4-8.533-51.2-21.333-140.8-140.8-281.6-281.6-422.4-422.4-25.6-25.6-25.6-51.2 0-76.8 93.867-93.867 187.733-187.733 281.6-281.6 25.6-25.6 51.2-25.6 76.8 0 140.8 140.8 281.6 281.6 422.4 422.4 17.067 12.8 21.333 29.867 21.333 51.2 0 93.867 0 183.467 0 277.333 0 34.133-17.067 51.2-51.2 51.2-51.2 0-98.133 0-145.067 0zM682.667 763.733c0 25.6 17.067 46.933 42.667 46.933s46.933-21.333 46.933-46.933c0-25.6-21.333-46.933-46.933-46.933-21.333 0-42.667 21.333-42.667 46.933zM878.933 482.133c4.267-12.8 0-21.333-8.533-29.867-34.133-51.2-64-98.133-98.133-149.333-76.8-115.2-153.6-234.667-230.4-349.867-12.8-17.067-21.333-21.333-38.4-8.533-115.2 76.8-226.133 149.333-337.067 226.133-17.067 8.533-17.067 21.333-8.533 38.4 12.8 21.333 29.867 46.933 42.667 68.267 8.533 12.8 8.533 12.8 17.067 0 55.467-55.467 115.2-115.2 170.667-170.667 8.533-8.533 17.067-17.067 29.867-21.333 29.867-12.8 55.467-4.267 76.8 21.333 123.733 123.733 247.467 247.467 371.2 371.2 4.267 4.267 4.267 8.533 8.533 12.8 0-8.533 0-8.533 4.267-8.533z" />
|
||||
<glyph unicode="" glyph-name="tax" d="M448 192c0 174.933 145.067 320 320 320 76.8 0 145.067-25.6 196.267-68.267v324.267c4.267 51.2-38.4 98.133-93.867 98.133h-204.8c-21.333 55.467-72.533 93.867-136.533 93.867s-115.2-38.4-136.533-98.133h-209.067c-55.467 0-98.133-42.667-98.133-93.867v-674.133c0-51.2 42.667-98.133 98.133-98.133h332.8c-42.667 55.467-68.267 123.733-68.267 196.267zM529.067 861.867c29.867 0 46.933-21.333 46.933-46.933 0-29.867-25.6-46.933-46.933-46.933-29.867 0-46.933 21.333-46.933 46.933-4.267 29.867 17.067 46.933 46.933 46.933zM708.267 247.467c-8.533 0-12.8 4.267-17.067 8.533s-8.533 8.533-8.533 17.067v17.067c0 8.533 0 12.8 4.267 17.067s8.533 8.533 17.067 8.533c8.533 0 12.8-4.267 17.067-8.533s4.267-12.8 4.267-17.067v-12.8c4.267-21.333-4.267-29.867-17.067-29.867zM870.4 132.267c4.267-4.267 4.267-12.8 4.267-17.067v-21.333c0-12.8-8.533-21.333-21.333-21.333-8.533 0-12.8 4.267-17.067 8.533s-8.533 12.8-8.533 17.067v17.067c0 8.533 4.267 12.8 8.533 17.067s8.533 8.533 17.067 8.533c8.533 0 12.8-4.267 17.067-8.533zM768 448c-140.8 0-256-115.2-256-256s115.2-256 256-256 256 115.2 256 256-115.2 256-256 256zM635.733 273.067v17.067c0 21.333 4.267 34.133 17.067 46.933s29.867 17.067 51.2 17.067c21.333 0 38.4-4.267 51.2-17.067s17.067-29.867 17.067-46.933v-17.067c0-21.333-4.267-34.133-17.067-46.933s-29.867-17.067-51.2-17.067-38.4 4.267-51.2 17.067c-8.533 12.8-17.067 29.867-17.067 46.933zM721.067 59.733l-34.133 17.067 153.6 243.2 34.133-17.067-153.6-243.2zM925.867 98.133c0-21.333-4.267-34.133-17.067-46.933s-29.867-17.067-51.2-17.067-38.4 4.267-51.2 17.067c-12.8 12.8-21.333 25.6-21.333 46.933v17.067c0 21.333 4.267 34.133 17.067 46.933s29.867 17.067 51.2 17.067 38.4-4.267 51.2-17.067c12.8-12.8 17.067-29.867 17.067-46.933v-17.067h4.267z" />
|
||||
<glyph unicode="" glyph-name="thermometer" d="M641.567 326.792v35.527h64.784v25.078h-64.784v119.118h64.784v25.078h-64.784v119.118h64.784v25.078h-64.784v121.208h64.784v25.078h-64.784v8.359c0 71.053-58.514 129.567-129.567 129.567s-129.567-58.514-129.567-129.567v-503.641c-54.335-39.706-87.771-104.49-87.771-173.453 0-119.118 96.131-217.339 217.339-217.339 119.118 0 217.339 96.131 217.339 217.339 0 66.873-33.437 131.657-87.771 173.453zM512-28.473c-100.31 0-179.722 81.502-179.722 179.722 0 64.784 33.437 123.298 87.771 154.645v524.539c0 50.155 41.796 91.951 91.951 91.951s91.951-41.796 91.951-91.951v-522.449c54.335-31.347 87.771-89.861 87.771-154.645 0-100.31-79.412-181.812-179.722-181.812zM652.016 435.461v25.078h35.527v-25.078h-35.527zM652.016 579.657v25.078h35.527v-25.078h-35.527zM652.016 723.853v25.078h35.527v-25.078h-35.527zM568.424 284.996v543.347c0 0 0 0 0 0s0 0 0 0v0 0c0 31.347-25.078 56.424-56.424 56.424s-56.424-25.078-56.424-56.424v0-543.347c-52.245-20.898-87.771-73.143-87.771-131.657 0-79.412 64.784-144.196 144.196-144.196s144.196 64.784 144.196 144.196c0 58.514-35.527 108.669-87.771 131.657zM470.204 824.163v4.18c0 22.988 18.808 41.796 41.796 41.796s41.796-18.808 41.796-41.796v-219.429h-85.682v215.249z" />
|
||||
|
|
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 185 KiB |
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue