diff --git a/.github/workflows/firebase-hosting-merge.yml b/.github/workflows/firebase-hosting-merge.yml deleted file mode 100644 index 6cf9fc9e..00000000 --- a/.github/workflows/firebase-hosting-merge.yml +++ /dev/null @@ -1,16 +0,0 @@ -# This file was auto-generated by the Firebase CLI -# https://github.com/firebase/firebase-tools - -name: Deploy to Firebase Hosting on merge -'on': - push: - branches: - - main -jobs: - call-workflow-in-another-repo: - uses: hotwax/dxp-components/.github/workflows/common-firebase-hosting-merge.yml@main - with: - config-path: .github/labeler.yml - secrets: - envPAT: ${{ secrets.envPAT }} - HOTWAX_PUBLIC_SECRET: ${{ secrets.HOTWAX_PUBLIC_SECRET }} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3ca4f80b..97e6797e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@capacitor/core": "^2.4.7", "@casl/ability": "^6.0.0", "@hotwax/app-version-info": "^1.0.0", + "@hotwax/apps-theme": "^1.2.4", "@hotwax/dxp-components": "^1.11.0", "@hotwax/oms-api": "^1.11.0", "@ionic/core": "^6.7.5", @@ -2841,6 +2842,11 @@ "resolved": "https://registry.npmjs.org/@hotwax/app-version-info/-/app-version-info-1.0.0.tgz", "integrity": "sha512-PnJTqTbFvvl9N23yi1DjL4aNmTkpYFrayyoJyfH1qDJXADFbQ9kB7gJmKcfiPpyYMGR86Yf3Is5ct0+wReUJGQ==" }, + "node_modules/@hotwax/apps-theme": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@hotwax/apps-theme/-/apps-theme-1.2.5.tgz", + "integrity": "sha512-3Vec4pGWJVs6vDbL/pO5hYqoF1zn2w8liXuleqDJ7waI2R5JuF2eaJmYcUQETnk/DZIg47bDdyOSxihQltB2Ow==" + }, "node_modules/@hotwax/dxp-components": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/@hotwax/dxp-components/-/dxp-components-1.11.0.tgz", @@ -7598,6 +7604,15 @@ "node": ">=0.10.0" } }, + "node_modules/@vue/cli-plugin-unit-jest/node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/@vue/cli-plugin-unit-jest/node_modules/istanbul-lib-coverage": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", @@ -8170,6 +8185,28 @@ "node": ">=0.10.0" } }, + "node_modules/@vue/cli-plugin-unit-jest/node_modules/node-notifier": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.5.tgz", + "integrity": "sha512-tVbHs7DyTLtzOiN78izLA85zRqB9NvEXkAf014Vx3jtSvn/xBl6bR8ZYifj+dFcFrKI21huSQgJZ6ZtL3B4HfQ==", + "dev": true, + "dependencies": { + "growly": "^1.3.0", + "is-wsl": "^1.1.0", + "semver": "^5.5.0", + "shellwords": "^0.1.1", + "which": "^1.3.0" + } + }, + "node_modules/@vue/cli-plugin-unit-jest/node_modules/node-notifier/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/@vue/cli-plugin-unit-jest/node_modules/normalize-path": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", @@ -26281,48 +26318,60 @@ "dev": true }, "node_modules/node-notifier": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.5.tgz", - "integrity": "sha512-tVbHs7DyTLtzOiN78izLA85zRqB9NvEXkAf014Vx3jtSvn/xBl6bR8ZYifj+dFcFrKI21huSQgJZ6ZtL3B4HfQ==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-10.0.1.tgz", + "integrity": "sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "growly": "^1.3.0", - "is-wsl": "^1.1.0", - "semver": "^5.5.0", + "is-wsl": "^2.2.0", + "semver": "^7.3.5", "shellwords": "^0.1.1", - "which": "^1.3.0" + "uuid": "^8.3.2", + "which": "^2.0.2" } }, - "node_modules/node-notifier/node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "node_modules/node-notifier/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "yallist": "^4.0.0" + }, "engines": { - "node": ">=4" + "node": ">=10" } }, "node_modules/node-notifier/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/node-notifier/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "isexe": "^2.0.0" + "lru-cache": "^6.0.0" }, "bin": { - "which": "bin/which" + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, + "node_modules/node-notifier/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "optional": true, + "peer": true + }, "node_modules/node-releases": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", @@ -37831,6 +37880,11 @@ "resolved": "https://registry.npmjs.org/@hotwax/app-version-info/-/app-version-info-1.0.0.tgz", "integrity": "sha512-PnJTqTbFvvl9N23yi1DjL4aNmTkpYFrayyoJyfH1qDJXADFbQ9kB7gJmKcfiPpyYMGR86Yf3Is5ct0+wReUJGQ==" }, + "@hotwax/apps-theme": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@hotwax/apps-theme/-/apps-theme-1.2.5.tgz", + "integrity": "sha512-3Vec4pGWJVs6vDbL/pO5hYqoF1zn2w8liXuleqDJ7waI2R5JuF2eaJmYcUQETnk/DZIg47bDdyOSxihQltB2Ow==" + }, "@hotwax/dxp-components": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/@hotwax/dxp-components/-/dxp-components-1.11.0.tgz", @@ -41630,6 +41684,12 @@ "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", "dev": true }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "dev": true + }, "istanbul-lib-coverage": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", @@ -42093,6 +42153,27 @@ } } }, + "node-notifier": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.5.tgz", + "integrity": "sha512-tVbHs7DyTLtzOiN78izLA85zRqB9NvEXkAf014Vx3jtSvn/xBl6bR8ZYifj+dFcFrKI21huSQgJZ6ZtL3B4HfQ==", + "dev": true, + "requires": { + "growly": "^1.3.0", + "is-wsl": "^1.1.0", + "semver": "^5.5.0", + "shellwords": "^0.1.1", + "which": "^1.3.0" + }, + "dependencies": { + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true + } + } + }, "normalize-path": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", @@ -56233,38 +56314,50 @@ } }, "node-notifier": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.5.tgz", - "integrity": "sha512-tVbHs7DyTLtzOiN78izLA85zRqB9NvEXkAf014Vx3jtSvn/xBl6bR8ZYifj+dFcFrKI21huSQgJZ6ZtL3B4HfQ==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-10.0.1.tgz", + "integrity": "sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ==", "dev": true, + "optional": true, + "peer": true, "requires": { "growly": "^1.3.0", - "is-wsl": "^1.1.0", - "semver": "^5.5.0", + "is-wsl": "^2.2.0", + "semver": "^7.3.5", "shellwords": "^0.1.1", - "which": "^1.3.0" + "uuid": "^8.3.2", + "which": "^2.0.2" }, "dependencies": { - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", - "dev": true + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "yallist": "^4.0.0" + } }, "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, + "optional": true, + "peer": true, "requires": { - "isexe": "^2.0.0" + "lru-cache": "^6.0.0" } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "optional": true, + "peer": true } } }, diff --git a/package.json b/package.json index 0a2785ee..a7598718 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@capacitor/core": "^2.4.7", "@casl/ability": "^6.0.0", "@hotwax/app-version-info": "^1.0.0", + "@hotwax/apps-theme": "^1.2.4", "@hotwax/dxp-components": "^1.11.0", "@hotwax/oms-api": "^1.11.0", "@ionic/core": "^6.7.5", diff --git a/src/components/OrderLimitPopover.vue b/src/components/OrderLimitPopover.vue new file mode 100644 index 00000000..a3b64957 --- /dev/null +++ b/src/components/OrderLimitPopover.vue @@ -0,0 +1,118 @@ + + + \ No newline at end of file diff --git a/src/locales/en.json b/src/locales/en.json index 16d81da9..40d9a7f3 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1,4 +1,5 @@ { + "A store represents a company or a unique catalog of products. If your OMS is connected to multiple eCommerce stores selling different collections of products, you may have multiple Product Stores set up in HotWax Commerce.": "A store represents a company or a unique catalog of products. If your OMS is connected to multiple eCommerce stores selling different collections of products, you may have multiple Product Stores set up in HotWax Commerce.", "Add": "Add", "Add locations to facility": "Add locations to facility", "Add staff member to facility": "Add staff member to facility", @@ -8,44 +9,63 @@ "Address line 2": "Address line 2", "aisle": "aisle", "Aisle": "Aisle", + "All": "All", "Allow pickup": "Allow pickup", + "App": "App", + "Apply": "Apply", "area": "area", "Area": "Area", + "Back to Launchpad": "Back to Launchpad", "Built: ": "Built: { builtDateTime }", "Cancel": "Cancel", "Change": "Change", "Change time zone": "Change time zone", "Choose system": "Choose system", + "Choose language": "Choose language", "City": "City", "Click the backdrop to dismiss.": "Click the backdrop to dismiss.", "Closing Time": "Closing Time", "Configure the order fulfillment capacity of your facility.": "Configure the order fulfillment capacity of your facility.", - "Country": "Country", "Custom": "Custom", + "Custom fulfillment capacity": "Custom fulfillment capacity", "Custom mapping": "Custom mapping", + "Country": "Country", "Days to ship": "Days to ship", "Dismiss": "Dismiss", "Edit": "Edit", "Edit location": "Edit location", "External mappings": "External mappings", + "Facilities": "Facilities", + "Facility": "Facility", "Facility details": "Facility details", "Facility ID": "Facility ID", "Facility name": "Facility name", + "Facility Management": "Facility Management", "Fetching TimeZones": "Fetching TimeZones", + "Find Facilities": "Find Facilities", "Friday": "Friday", + "Fulfillment Capacity": "Fulfillment Capacity", + "Fulfillment capacity updated successfully for ": "Fulfillment capacity updated successfully for {facilityName}", "Fulfillment Settings": "Fulfillment Settings", "Generate": "Generate", "Generate shipping labels": "Generate shipping labels", + "Go to Launchpad": "Go to Launchpad", + "Go to OMS": "Go to OMS", + "Groups": "Groups", "Identification": "Identification", "Instance Url": "Instance Url", + "Language": "Language", "Latitude": "Latitude", "Latitude & Longitude": "Latitude & Longitude", "level": "level", "Level": "Level", + "Loading": "Loading", "Location": "Location", "Location details": "Location details", "Locations": "Locations", + "Logging out": "Logging out", "Login": "Login", + "Login failed": "Login failed", "Logout": "Logout", "Longitude": "Longitude", "Make primary": "Make primary", @@ -54,19 +74,30 @@ "Map facility to an external system": "Map facility to an external system", "Monday": "Monday", "Netsuite": "Netsuite", + "No Capacity": "No Capacity", + "No capacity": "No capacity", + "No facilities found": "No facilities found", + "No fulfillment capacity": "No fulfillment capacity", + "No capacity sets the fulfillment capacity to 0, preventing any new orders from being allocated to this facility. Use the \"Reject all orders\" option in the fulfillment pages to clear your facilities fulfillment queue. To add a fulfillment capacity to this facility, use the custom option.": "No capacity sets the fulfillment capacity to 0, preventing any new orders from being allocated to this facility. Use the \"Reject all orders\" option in the fulfillment pages to clear your facilities fulfillment queue. To add a fulfillment capacity to this facility, use the custom option.", "No time zone found": "No time zone found", "OMS": "OMS", + "OMS instance": "OMS instance", "Online Order Fulfillment": "Online Order Fulfillment", "Opening Time": "Opening Time", "Operating hours": "Operating hours", + "Order fulfillment capacity": "Order fulfillment capacity", + "Parking": "Parking", "party id": "party id", "Party Id": "Party Id", - "Password": "Password", + "Password": "Password", + "Please contact the administrator.": "Please contact the administrator.", "primary store": "primary store", + "Product Store": "Product Store", "Product Stores": "Product Stores", + "Reason:": "Reason:", + "Reset": "Reset", "Remove": "Remove", "Remove location": "Remove location", - "Reset": "Reset", "role": "role", "Role": "Role", "Saturday": "Saturday", @@ -74,29 +105,44 @@ "section": "section", "Section": "Section", "Select": "Select", + "Search facilities": "Search facilities", + "Search time zones": "Search time zones", + "Select store": "Select store", "Select time": "Select time", "Select time zone": "Select time zone", - "Search time zones": "Search time zones", + "Select facility": "Select facility", "Select product stores": "Select product stores", + "Select your preferred language.": "Select your preferred language.", + "Sell Online": "Sell Online", "Sell Inventory Online": "Sell Inventory Online", "sequence": "sequence", "Sequence": "Sequence", - "Setting fulfillment capacity to 0 disables new order from being allocated to this facility. Leave this empty if this facility's fulfillment capacity is unrestricted.": "Setting fulfillment capacity to 0 disables new order from being allocated to this facility. Leave this empty if this facility's fulfillment capacity is unrestricted.", "Settings": "Settings", + "Setting fulfillment capacity to 0 disables new order from being allocated to this facility. Leave this empty if this facility's fulfillment capacity is unrestricted.": "Setting fulfillment capacity to 0 disables new order from being allocated to this facility. Leave this empty if this facility's fulfillment capacity is unrestricted.", "Shopify": "Shopify", "Shopify facility": "Shopify facility", "Something went wrong": "Something went wrong", + "Something went wrong while login. Please contact administrator.": "Something went wrong while login. Please contact administrator.", "Sorry, your username or password is incorrect. Please try again.": "Sorry, your username or password is incorrect. Please try again.", + "Specify which facility you want to operate from. Order, inventory and other configuration data will be specific to the facility you select.": "Specify which facility you want to operate from. Order, inventory and other configuration data will be specific to the facility you select.", "Staff": "Staff", "State": "State", "Store": "Store", "store name": "store name", "Sunday": "Sunday", - "Thursday": "Thursday", + "The timezone you select is used to ensure automations you schedule are always accurate to the time you select.": "The timezone you select is used to ensure automations you schedule are always accurate to the time you select.", "These values are used to help customers lookup how close they are to your stores when they are finding nearby stores.": "These values are used to help customers lookup how close they are to your stores when they are finding nearby stores.", + "This is the name of the OMS you are connected to right now. Make sure that you are connected to the right instance before proceeding.": "This is the name of the OMS you are connected to right now. Make sure that you are connected to the right instance before proceeding.", + "threshold consumed": "threshold consumed", + "Thursday": "Thursday", "Time zone updated successfully": "Time zone updated successfully", + "Timezone": "Timezone", "Tuesday": "Tuesday", "Type": "Type", + "Unlimited Capacity": "Unlimited Capacity", + "Unlimited capacity removes the fulfillment capacity limit entirely. To add a fulfillment capacity to this facility, use the custom option.": "Unlimited capacity removes the fulfillment capacity limit entirely. To add a fulfillment capacity to this facility, use the custom option.", + "Unlimited fulfillment capacity": "Unlimited fulfillment capacity", + "Unlimited orders": "Unlimited orders", "Unlink":"Unlink", "Update days to ship": "Update days to ship", "Username": "Username", diff --git a/src/main.ts b/src/main.ts index 9ec8800f..4c6d1a52 100644 --- a/src/main.ts +++ b/src/main.ts @@ -24,6 +24,7 @@ import '@ionic/vue/css/display.css'; /* Theme variables */ import './theme/variables.css'; +import '@hotwax/apps-theme'; import store from './store' import permissionPlugin from '@/authorization'; diff --git a/src/router/index.ts b/src/router/index.ts index 76ce345e..273b9495 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,6 +1,5 @@ import { createRouter, createWebHistory } from '@ionic/vue-router'; import { RouteRecordRaw } from 'vue-router'; -import Settings from "@/views/Settings.vue" import FacilityDetails from '@/views/FacilityDetails.vue'; import store from '@/store' import { hasPermission } from '@/authorization'; @@ -8,6 +7,9 @@ import { showToast } from '@/utils' import 'vue-router' import { useAuthStore, DxpLogin, translate } from '@hotwax/dxp-components' import { loader } from '@/utils/user'; +import FacilityManagement from '@/views/FacilityManagement.vue' +import Settings from '@/views/Settings.vue'; +import FindFacilities from '@/views/FindFacilities.vue'; // Defining types for the meta values declare module 'vue-router' { @@ -39,7 +41,7 @@ const loginGuard = (to: any, from: any, next: any) => { const routes: Array = [ { path: '/', - redirect: '/settings' + redirect: '/facility-management' }, { path: '/login', @@ -47,16 +49,28 @@ const routes: Array = [ component: DxpLogin, beforeEnter: loginGuard }, + { + path: '/facility-management', + name: 'FacilityManagement', + component: FacilityManagement, + beforeEnter: authGuard + }, + { + path: '/find-facilities', + name: 'FindFacilities', + component: FindFacilities, + beforeEnter: authGuard + }, { path: "/facility-details/:facilityId", - props: true, name: "Facility Details", component: FacilityDetails, + props: true, beforeEnter: authGuard }, { - path: "/settings", - name: "Settings", + path: '/settings', + name: 'Settings', component: Settings, beforeEnter: authGuard } diff --git a/src/services/FacilityService.ts b/src/services/FacilityService.ts new file mode 100644 index 00000000..23ed7a45 --- /dev/null +++ b/src/services/FacilityService.ts @@ -0,0 +1,100 @@ +import { api, hasError } from '@/adapter'; +import logger from '@/logger'; +import { DateTime } from 'luxon'; + +const fetchFacilities = async(query: any): Promise => { + return api({ + url: "performFind", + method: "post", + data: query + }); +} + +const fetchFacilityOnlineGroupInformation = async(facilityIds: Array): Promise => { + let facilitiesWithSellOnlineEnabled = [] + + const params = { + inputFields: { + facilityId: facilityIds, + facilityId_op: "in", + facilityGroupTypeId: 'SHOPIFY_GROUP_FAC', + facilityGroupTypeId_op: 'equals' + }, + fieldList: ['facilityId', 'facilityGroupTypeId'], + entityName: "FacilityGroupAndMember", + distinct: 'Y', + filterByDate: 'Y', + viewSize: facilityIds.length + } + + try { + const resp = await api({ + url: "performFind", + method: "post", + data: params + }) as any; + + if(!hasError(resp) && resp.data.count > 0) { + facilitiesWithSellOnlineEnabled = resp.data.docs.map((facility: any) => facility.facilityId) + } else { + throw resp.data; + } + } catch(err) { + logger.error(err) + } + + return facilitiesWithSellOnlineEnabled; +} + +const fetchFacilitiesOrderCount = async(facilityIds: Array): Promise => { + let facilitiesOrderCount = {}, resp: any; + try { + const params = { + entityName: "FacilityOrderCount", + inputFields: { + facilityId: facilityIds, + facilityId_op: "in", + entryDate: DateTime.now().toFormat('yyyy-MM-dd'), + }, + viewSize: facilityIds.length, + fieldList: ["entryDate", "facilityId", "lastOrderCount"], + } + + resp = await api({ + url: "performFind", + method: "post", + data: params + }) + + if (!hasError(resp) && resp.data.count) { + // using index 0 as we will only get a single record + // this.currentFacilityDetails.orderCount = resp.data.docs[0].lastOrderCount + + facilitiesOrderCount = resp.data.docs.reduce((facilitiesCount: any, facilityCount: any) => { + facilitiesCount[facilityCount.facilityId] = facilityCount.lastOrderCount + return facilitiesCount + }, {}) + } else { + throw resp.data + } + } catch(err) { + logger.error("Failed to fetch total orders count", err); + } + + return facilitiesOrderCount; +} + +const updateFacility = async (payload: any): Promise => { + return api({ + url: "service/updateFacility", + method: "post", + data: payload + }) +} + +export const FacilityService = { + fetchFacilityOnlineGroupInformation, + fetchFacilitiesOrderCount, + fetchFacilities, + updateFacility +} \ No newline at end of file diff --git a/src/services/UtilService.ts b/src/services/UtilService.ts new file mode 100644 index 00000000..85365974 --- /dev/null +++ b/src/services/UtilService.ts @@ -0,0 +1,25 @@ +import { api } from '@/adapter'; + +const fetchFacilityTypes = async (payload: any): Promise => { + return api({ + url: "performFind", + method: "POST", + data: payload, + cache: true + }) +} + +const fetchProductStores = async (payload: any): Promise => { + return api({ + url: "performFind", + method: "POST", + data: payload, + cache: true + }) +} + +export const UtilService = { + fetchFacilityTypes, + fetchProductStores +} + diff --git a/src/store/index.ts b/src/store/index.ts index cc2bf60a..9bc4a805 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -6,6 +6,8 @@ import RootState from './RootState' import createPersistedState from "vuex-persistedstate"; import userModule from './modules/user'; import { setPermissions } from '@/authorization' +import facilityModule from "./modules/facility" +import utilModule from "./modules/util" // TODO check how to register it from the components only @@ -30,7 +32,9 @@ const store = createStore({ getters, plugins: [ persistState ], modules: { - 'user': userModule + 'user': userModule, + 'facility': facilityModule, + 'util': utilModule }, }) diff --git a/src/store/modules/facility/FacilityState.ts b/src/store/modules/facility/FacilityState.ts new file mode 100644 index 00000000..416a59aa --- /dev/null +++ b/src/store/modules/facility/FacilityState.ts @@ -0,0 +1,12 @@ +export default interface FacilityState { + query: { + queryString: string, + productStoreId: string, + facilityTypeId: string + }; + selectedFacility: object; + facilities: { + list: Array, + total: number + }; +} \ No newline at end of file diff --git a/src/store/modules/facility/actions.ts b/src/store/modules/facility/actions.ts new file mode 100644 index 00000000..88ee6b76 --- /dev/null +++ b/src/store/modules/facility/actions.ts @@ -0,0 +1,107 @@ +import { ActionTree } from 'vuex' +import RootState from '@/store/RootState' +import FacilityState from './FacilityState' +import emitter from '@/event-bus' +import { FacilityService } from '@/services/FacilityService' +import { hasError } from '@/adapter' +import * as types from './mutation-types' +import logger from '@/logger' + +const actions: ActionTree = { + async fetchFacilities({ commit, state }, payload) { + if (payload.viewIndex === 0) emitter.emit("presentLoader"); + const filters = { + 'parentFacilityTypeId': 'VIRTUAL_FACILITY', + 'parentFacilityTypeId_op': 'notEqual' + } as any + + if(state.query.productStoreId) { + filters['productStoreId'] = state.query.productStoreId + filters['productStoreId_op'] = 'equals' + } + + if(state.query.facilityTypeId) { + filters['facilityTypeId'] = state.query.facilityTypeId + filters['facilityTypeId_op'] = 'equals' + } + + if(state.query.queryString) { + filters['facilityId_value'] = state.query.queryString + filters['facilityId_op'] = 'contains' + filters['facilityId_ic'] = 'Y' + filters['facilityId_grp'] = '1' + filters['facilityName_value'] = state.query.queryString + filters['facilityName_op'] = 'contains' + filters['facilityName_ic'] = 'Y' + filters['facilityName_grp'] = '2' + } + + const params = { + "inputFields": { + ...filters + }, + "entityName": "FacilityAndProductStore", + "noConditionFind": "Y", + "distinct": "Y", + "fieldList": ['facilityId', 'facilityName', 'facilityTypeId', 'maximumOrderLimit'], + ...payload + } + + let facilities = [], total = 0; + + try { + const resp = await FacilityService.fetchFacilities(params) + + if(!hasError(resp) && resp.data.count) { + facilities = resp.data.docs + if(payload.viewIndex && payload.viewIndex > 0) facilities = JSON.parse(JSON.stringify(state.facilities.list)).concat(resp.data.docs) + total = resp.data.count + + // make api calls in parallel + const facilityOnlineGroupInformation = await FacilityService.fetchFacilityOnlineGroupInformation(facilities.map((facility: any) => facility.facilityId)) + const facilitiesOrderCount = await FacilityService.fetchFacilitiesOrderCount(facilities.map((facility: any) => facility.facilityId)) + + facilities.map((facility: any) => { + const fulfillmentOrderLimit = facility.maximumOrderLimit + if (fulfillmentOrderLimit === 0) { + facility.orderLimitType = 'no-capacity' + } else if (fulfillmentOrderLimit) { + facility.orderLimitType = 'custom' + } else { + facility.orderLimitType = 'unlimited' + } + + facility.orderCount = facilitiesOrderCount[facility.facilityId] ? facilitiesOrderCount[facility.facilityId] : 0; + + if(facilityOnlineGroupInformation.includes(facility.facilityId)) { + facility.sellOnline = true + } else { + facility.sellOnline = false + } + }) + } else { + throw resp.data + } + } catch(error) { + logger.error(error) + } + + emitter.emit("dismissLoader"); + commit(types.FACILITY_LIST_UPDATED , { facilities, total }); + }, + + updateQuery({ commit }, query) { + commit(types.FACILITY_QUERY_UPDATED, query) + }, + + clearFacilityState({ commit }) { + commit(types.FACILITY_QUERY_UPDATED, { + queryString: '', + productStoreId: '', + facilityTypeId: '' + }) + commit(types.FACILITY_LIST_UPDATED , { facilities: [], total: 0 }); + } +} + +export default actions; \ No newline at end of file diff --git a/src/store/modules/facility/getters.ts b/src/store/modules/facility/getters.ts new file mode 100644 index 00000000..211686f1 --- /dev/null +++ b/src/store/modules/facility/getters.ts @@ -0,0 +1,18 @@ +import { GetterTree } from 'vuex' +import FacilityState from './FacilityState' +import RootState from '@/store/RootState' + +const getters: GetterTree = { + getFacilities(state) { + return JSON.parse(JSON.stringify(state.facilities.list)) + }, + getQuery(state) { + return JSON.parse(JSON.stringify(state.query)) + }, + isScrollable(state) { + return ( + state.facilities.list?.length > 0 && state.facilities.list?.length < state.facilities.total + ); + }, +} +export default getters; \ No newline at end of file diff --git a/src/store/modules/facility/index.ts b/src/store/modules/facility/index.ts new file mode 100644 index 00000000..87d00f40 --- /dev/null +++ b/src/store/modules/facility/index.ts @@ -0,0 +1,27 @@ +import actions from './actions' +import getters from './getters' +import mutations from './mutations' +import { Module } from 'vuex' +import FacilityState from './FacilityState' +import RootState from '@/store/RootState' + +const facilityModule: Module = { + namespaced: true, + state: { + query: { + queryString: '', + productStoreId: '', + facilityTypeId: '' + }, + selectedFacility: {}, + facilities: { + list: [], + total: 0 + } + }, + getters, + actions, + mutations, +} + +export default facilityModule; \ No newline at end of file diff --git a/src/store/modules/facility/mutation-types.ts b/src/store/modules/facility/mutation-types.ts new file mode 100644 index 00000000..a6cb0882 --- /dev/null +++ b/src/store/modules/facility/mutation-types.ts @@ -0,0 +1,3 @@ +export const SN_FACILITY = 'facility' +export const FACILITY_LIST_UPDATED = SN_FACILITY + '/LIST_UPDATED' +export const FACILITY_QUERY_UPDATED = SN_FACILITY + '/QUERY_UPDATED' \ No newline at end of file diff --git a/src/store/modules/facility/mutations.ts b/src/store/modules/facility/mutations.ts new file mode 100644 index 00000000..157649ad --- /dev/null +++ b/src/store/modules/facility/mutations.ts @@ -0,0 +1,14 @@ +import { MutationTree } from 'vuex' +import FacilityState from './FacilityState' +import * as types from './mutation-types' + +const mutations: MutationTree = { + [types.FACILITY_LIST_UPDATED](state, payload) { + state.facilities.list = payload.facilities + state.facilities.total = payload.total + }, + [types.FACILITY_QUERY_UPDATED](state, payload) { + state.query = payload + } +} +export default mutations; \ No newline at end of file diff --git a/src/store/modules/user/actions.ts b/src/store/modules/user/actions.ts index 1ff3a6b0..bdf6c9e0 100644 --- a/src/store/modules/user/actions.ts +++ b/src/store/modules/user/actions.ts @@ -52,35 +52,41 @@ const actions: ActionTree = { //fetching user facilities const baseURL = store.getters['user/getBaseUrl']; - const facilities = await getUserFacilities(token, baseURL, userProfile?.partyId, ""); - - if (!facilities.length) throw 'Unable to login. User is not assocaited with any facility' - - userProfile.facilities = facilities; + try { + const facilities = await getUserFacilities(token, baseURL, userProfile?.partyId, ""); + + // if (!facilities.length) throw 'Unable to login. User is not assocaited with any facility' + + userProfile.facilities = facilities; + userProfile.facilities.reduce((uniqueFacilities: any, facility: any, index: number) => { + if (uniqueFacilities.includes(facility.facilityId)) userProfile.facilities.splice(index, 1); + else uniqueFacilities.push(facility.facilityId); + return uniqueFacilities + }, []); + const currentFacility = userProfile.facilities[0]; + userProfile.stores = await UserService.getEComStores(token, currentFacility.facilityId); + + // In Job Manager application, we have jobs which may not be associated with any product store + userProfile.stores.push({ + productStoreId: "", + storeName: "None" + }) + let preferredStore = userProfile.stores[0] + + const preferredStoreId = await UserService.getPreferredStore(token); + if (preferredStoreId) { + const store = userProfile.stores.find((store: any) => store.productStoreId === preferredStoreId); + store && (preferredStore = store) + } + commit(types.USER_CURRENT_ECOM_STORE_UPDATED, preferredStore); + commit(types.USER_CURRENT_FACILITY_UPDATED, currentFacility); + } catch(err) { + logger.error(err) + } // Getting unique facilities - userProfile.facilities.reduce((uniqueFacilities: any, facility: any, index: number) => { - if (uniqueFacilities.includes(facility.facilityId)) userProfile.facilities.splice(index, 1); - else uniqueFacilities.push(facility.facilityId); - return uniqueFacilities - }, []); // TODO Use a separate API for getting facilities, this should handle user like admin accessing the app - const currentFacility = userProfile.facilities[0]; - userProfile.stores = await UserService.getEComStores(token, currentFacility.facilityId); - - // In Job Manager application, we have jobs which may not be associated with any product store - userProfile.stores.push({ - productStoreId: "", - storeName: "None" - }) - let preferredStore = userProfile.stores[0] - - const preferredStoreId = await UserService.getPreferredStore(token); - if (preferredStoreId) { - const store = userProfile.stores.find((store: any) => store.productStoreId === preferredStoreId); - store && (preferredStore = store) - } /* ---- Guard clauses ends here --- */ @@ -90,8 +96,6 @@ const actions: ActionTree = { } // TODO user single mutation - commit(types.USER_CURRENT_ECOM_STORE_UPDATED, preferredStore); - commit(types.USER_CURRENT_FACILITY_UPDATED, currentFacility); commit(types.USER_INFO_UPDATED, userProfile); commit(types.USER_PERMISSIONS_UPDATED, appPermissions); commit(types.USER_TOKEN_CHANGED, { newToken: token }) @@ -141,6 +145,9 @@ const actions: ActionTree = { resetConfig(); resetPermissions(); + this.dispatch('util/clearUtilState') + this.dispatch('facility/clearFacilityState') + // reset plugin state on logout authStore.$reset() userStore.$reset() diff --git a/src/store/modules/util/UtilState.ts b/src/store/modules/util/UtilState.ts new file mode 100644 index 00000000..d6f63851 --- /dev/null +++ b/src/store/modules/util/UtilState.ts @@ -0,0 +1,4 @@ +export default interface UtilState { + productStores: any[]; + facilityTypes: object; +} \ No newline at end of file diff --git a/src/store/modules/util/actions.ts b/src/store/modules/util/actions.ts new file mode 100644 index 00000000..5375800b --- /dev/null +++ b/src/store/modules/util/actions.ts @@ -0,0 +1,71 @@ +import { UtilService } from '@/services/UtilService' +import { ActionTree } from 'vuex' +import RootState from '@/store/RootState' +import UtilState from './UtilState' +import * as types from './mutation-types' +import { hasError } from '@/adapter' +import logger from '@/logger' + +const actions: ActionTree = { + async fetchProductStores({ commit }) { + let productStores = [] + const params = { + viewSize: 100, + noConditionFind: 'Y', + entityName: 'ProductStore', + fieldList: ['productStoreId', 'storeName'] + } + + try { + const resp = await UtilService.fetchProductStores(params) + if (!hasError(resp)) { + productStores = resp.data.docs + } else { + throw resp.data + } + } catch (error) { + logger.error(error) + } + commit(types.UTIL_PRODUCT_STORES_UPDATED, productStores) + }, + + async fetchFacilityTypes({ commit, state }, payload = {}) { + const cachedFacilityTypes = JSON.parse(JSON.stringify(state.facilityTypes)) + + // not fetching facility type information again if already present, as it will not be changed so frequently + if(cachedFacilityTypes.length) { + return; + } + + let facilityTypes = [] + const params = { + inputFields: { + ...payload + }, + viewSize: 100, + noConditionFind: 'Y', + entityName: 'FacilityType', + fieldList: ['facilityTypeId', 'description'] + } as any + + try { + const resp = await UtilService.fetchFacilityTypes(params) + if (!hasError(resp)) { + facilityTypes = resp.data.docs + } else { + throw resp.data + } + } catch (error) { + logger.error(error) + } + + commit(types.UTIL_FACILITY_TYPES_UPDATED, facilityTypes) + }, + + clearUtilState({ commit }) { + commit(types.UTIL_PRODUCT_STORES_UPDATED, []) + commit(types.UTIL_FACILITY_TYPES_UPDATED, []) + } +} + +export default actions; \ No newline at end of file diff --git a/src/store/modules/util/getters.ts b/src/store/modules/util/getters.ts new file mode 100644 index 00000000..0b2f9c6d --- /dev/null +++ b/src/store/modules/util/getters.ts @@ -0,0 +1,13 @@ +import { GetterTree } from 'vuex' +import UtilState from './UtilState' +import RootState from '@/store/RootState' + +const getters: GetterTree = { + getProductStores(state) { + return state.productStores; + }, + getFacilityTypes(state) { + return state.facilityTypes + } +} +export default getters; \ No newline at end of file diff --git a/src/store/modules/util/index.ts b/src/store/modules/util/index.ts new file mode 100644 index 00000000..5470f963 --- /dev/null +++ b/src/store/modules/util/index.ts @@ -0,0 +1,19 @@ +import actions from './actions' +import getters from './getters' +import mutations from './mutations' +import { Module } from 'vuex' +import UtilState from './UtilState' +import RootState from '@/store/RootState' + +const utilModule: Module = { + namespaced: true, + state: { + productStores: [], + facilityTypes: {} + }, + getters, + actions, + mutations, +} + +export default utilModule; diff --git a/src/store/modules/util/mutation-types.ts b/src/store/modules/util/mutation-types.ts new file mode 100644 index 00000000..0901fa26 --- /dev/null +++ b/src/store/modules/util/mutation-types.ts @@ -0,0 +1,3 @@ +export const SN_UTIL = 'util' +export const UTIL_PRODUCT_STORES_UPDATED = SN_UTIL + '/PRODUCT_STORES_UPDATED' +export const UTIL_FACILITY_TYPES_UPDATED = SN_UTIL + '/FACILITY_TYPES_UPDATED' \ No newline at end of file diff --git a/src/store/modules/util/mutations.ts b/src/store/modules/util/mutations.ts new file mode 100644 index 00000000..5d84d496 --- /dev/null +++ b/src/store/modules/util/mutations.ts @@ -0,0 +1,13 @@ +import { MutationTree } from 'vuex' +import UtilState from './UtilState' +import * as types from './mutation-types' + +const mutations: MutationTree = { + [types.UTIL_PRODUCT_STORES_UPDATED](state, payload) { + state.productStores = payload + }, + [types.UTIL_FACILITY_TYPES_UPDATED](state, payload) { + state.facilityTypes = payload + } +} +export default mutations; \ No newline at end of file diff --git a/src/theme/variables.css b/src/theme/variables.css index 8553a343..5fbb21f2 100644 --- a/src/theme/variables.css +++ b/src/theme/variables.css @@ -282,10 +282,6 @@ body { padding: 10px; } -hr { - border-top: var(--border-medium); -} - .empty-state > img { object-fit: contain; } @@ -302,6 +298,32 @@ hr { display: unset; } +.find { + display: grid; + grid-template-areas: "search" + "main"; +} + +.find > .filters{ + display: none; +} + +.find > main { + grid-area: main; +} + +.filters { + grid-area: filters; +} + +.search { + grid-area: search; +} + +hr { + border-top: var(--border-medium); +} + .list-item { --columns-mobile: 2; --columns-tablet: 5; @@ -354,4 +376,28 @@ hr { .tablet { display: unset; } -} \ No newline at end of file + + .find { + grid: "search main" min-content + "filters main" 1fr + / 375px; + column-gap: var(--spacer-2xl); + margin: var(--spacer-lg); + } + + .filters { + margin-top: var(--spacer-lg); + } + + .find > .filters{ + display: unset; + } + + .desktop-only { + display: unset; + } + + .mobile-only { + display: none; + } +} diff --git a/src/views/FacilityDetails.vue b/src/views/FacilityDetails.vue index 421e4c00..932745ee 100644 --- a/src/views/FacilityDetails.vue +++ b/src/views/FacilityDetails.vue @@ -2,7 +2,7 @@ - + {{ translate("Facility details") }} diff --git a/src/views/FacilityManagement.vue b/src/views/FacilityManagement.vue new file mode 100644 index 00000000..4ae31bfd --- /dev/null +++ b/src/views/FacilityManagement.vue @@ -0,0 +1,95 @@ + + + + + \ No newline at end of file diff --git a/src/views/FindFacilities.vue b/src/views/FindFacilities.vue new file mode 100644 index 00000000..db2fa67e --- /dev/null +++ b/src/views/FindFacilities.vue @@ -0,0 +1,250 @@ + + + + + \ No newline at end of file