From 080a1ae7942233dcd3660576edd6fed14eb511ba Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Mon, 10 Feb 2020 19:58:06 +0000 Subject: [PATCH] --- ayanova/src/api/enums.js | 445 +++++++++++++++++++++++ ayanova/src/components/gz-data-table.vue | 35 ++ ayanova/src/store.js | 5 + 3 files changed, 485 insertions(+) create mode 100644 ayanova/src/api/enums.js diff --git a/ayanova/src/api/enums.js b/ayanova/src/api/enums.js new file mode 100644 index 00000000..f2c0021e --- /dev/null +++ b/ayanova/src/api/enums.js @@ -0,0 +1,445 @@ +/* ZZeslint-disable */ +//TODO: Need enum translator +//AuthorizationRoles.128 +//window.$gz.local.enumLocalized(enumType, enumValue) +//(in locale) check if have that type already featched, if not fetches the whole list and caches it locally +//also a method to return the list of enumerated types in ID order with localized names +//window.$gz.local.enumList(enumType) - returns a list usable on edit forms +/* enums: { + AuthorizationRoles0:"No role", + AuthorizationRoles1:"Business admin limited" + etc + + to get all authorization roles iterate list looking for start of key that is AuthorizationRoles + To get individual one same but only need to fetch actual value i.e. AuthorizationRoles1 + if not one item starts with AuthorizationRoles then list needs to be fetched, maybe can use the enumpicklist route for that + This way entirely bypasses locale stuff + //big object so maybe it's own thing, not part of locale at all or locale fronts for it?? + */ +export default { + getEnumDisplay(enumKey, enumValue) { + // debugger; + if (!window.$gz._.has(window.$gz.store.state.localeText, key)) { + return "??" + key; + } + return window.$gz.store.state.localeText[key]; + }, + /////////////////////////////////// + // + // Returns an array of enum value and display objects + // for enum key specified + // e.g. [{0:"no role"},{1:"Limited role"}] + // + getEnumList(enumKey) { + // debugger; + if (!window.$gz._.has(window.$gz.store.state.enums, enumKey)) { + return "??" + key; + } + return window.$gz.store.state.localeText[key]; + }, + fetch(keys) { + return new Promise(function fetchLocaleKeysFromServer(resolve) { + // + //step 1: build an array of keys that we don't have already + //Note: this will ensure only unique keys go into the store so it's safe to call this with dupes as can happen + //for example datatables have dynamic column names so they need to fetch on demand + var needIt = []; + for (var i = 0; i < keys.length; i++) { + if (!window.$gz._.has(window.$gz.store.state.localeText, keys[i])) { + needIt.push(keys[i]); + } + } + + if (needIt.length == 0) { + resolve(); + return; + } + + //step 2: get it + fetch( + window.$gz.api.APIUrl("locale/subset"), + window.$gz.api.fetchPostOptions(needIt) + ) + .then(window.$gz.api.status) + .then(window.$gz.api.json) + .then(response => { + window.$gz._.forEach( + response.data, + function commitFetchedLTItemToStore(item) { + window.$gz.store.commit("addLocaleText", item); + } + ); + + resolve(); + }); + }); + }, + //Keys that will always be required for any AyaNova work for any user + coreKeys: [ + //main nav options + "Home", + "Dashboard", + "Schedule", + "MemoList", + "UserSettings", + "Locale", + "SetLoginPassword", + "NotifySubscriptionList", + "UserPreferences", + "Service", + "ClientList", + "HeadOfficeList", + "WorkorderServiceList", + "WorkorderServiceTemplate", + "WorkorderQuoteList", + "WorkorderQuoteTemplate", + "WorkorderPreventiveMaintenanceList", + "WorkorderPreventiveMaintenanceTemplate", + "UnitList", + "UnitModels", + "ContractList", + "ClientServiceRequestList", + "LoanItemList", + "PartList", + "PartByWarehouseInventoryList", + "WorkorderItemPartRequestList", + "InventoryPurchaseOrders", + "InventoryPurchaseOrderReceipts", + "InventoryPartInventoryAdjustments", + "WidgetList", + "VendorList", + "AdministrationGlobalSettings", + "HelpLicense", + "UserList", + "LocalizedTextDesign", + "ReportList", + "ReminderList", + "Inventory", + "Accounting", + "Administration", + "Operations", + "Attachments", + "Review", + "History", + "Statistics", + "Backup", + "ServerJobs", + "ServerLog", + "ServerMetrics", + "NotificationSettings", + "HelpAboutAyaNova", + "MenuHelp", + "More", + "Logout", + //form required options + "Active", + "Add", + "Cancel", + "Close", + "Save", + "Delete", + "OK", + "Print", + "Report", + "WikiPage", + "Duplicate", + "RecordHistory", + "Search", + "TypeToSearchOrAdd", + "NoData", + "ErrorFieldLengthExceeded", + "ErrorStartDateAfterEndDate", + "ErrorRequiredFieldEmpty", + "ErrorFieldValueNotInteger", + "ErrorFieldValueNotDecimal", + "ErrorAPI2000", + "ErrorAPI2001", + "ErrorAPI2002", + "ErrorAPI2003", + "ErrorAPI2004", + "ErrorAPI2005", + "ErrorAPI2010", + "ErrorAPI2020", + "ErrorAPI2030", + "ErrorAPI2200", + "ErrorAPI2201", + "ErrorAPI2202", + "ErrorAPI2203", + "ErrorAPI2204", + "ErrorAPI2205", + "ErrorAPI2206", + "ErrorAPI2207", + "ErrorAPI2208", + "ErrorAPI2209", + "ErrorServerUnresponsive", + "ErrorUserNotAuthenticated", + "ErrorUserNotAuthorized", + "DeletePrompt", + "AreYouSureUnsavedChanges", + "Leave", + "Copy", + "Tags", + "Customize", + "ObjectCustomFieldCustomGrid", + "RowsPerPage", + "PageOfPageText", + "Loading", + "AM", + "PM" + ], + + //////////////////////////////////////////////////////// + // Take in a string that contains one or more + //locale keys between square brackets + //translate each and return the string translated + // + translateString(s) { + var ret = s; + var pattern = /\[(.*?)\]/g; + var match; + while ((match = pattern.exec(s)) != null) { + var foundMatch = match[0]; + var ltKey = match[1]; + var newValue = this.get(ltKey); + ret = ret.replace(foundMatch, newValue); + } + return ret; + }, + //////////////////////////////////////////////////////// + // attempt to determine user's preferred language settings + // As of Jan 2020 all major browsers support + // navigator.languages + // but some use navigator.language (singular) to denote UI language preference + // not browsing language preference + // so the ideal way to do this is to use navigator.languages[0] for the preferred language + // and ignore the singular property since we don't care about the actual browser UI language + // only how the user expects to see the page itself + // + // also for sake of future proofing and edge cases need to have it be manually settable as well + // ############### TODO: modify all of these to put the user's manual override first in line (if there is one) + //https://appmakers.dev/bcp-47-language-codes-list/ + getBrowserLanguages() { + return window.navigator.languages; + }, + getBrowserFirstLanguage() { + return window.navigator.languages[0]; + }, + /////////////////////////////////////////// + // Get users default time zone + //https://www.iana.org/time-zones + //https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + getTimeZoneName() { + return Intl.DateTimeFormat().resolvedOptions().timeZone; + }, + ////////////////////////////////////////////////// + // Get the user's chosen currency name + //https://en.wikipedia.org/wiki/ISO_4217 + getCurrencyName() { + return window.$gz.store.state.locale.currencyName; + }, + ////////////////////////////////////////////////// + // Get the user's chosen 12hr clock + // + getHour12() { + return window.$gz.store.state.locale.hour12; + }, + /////////////////////////////////////////// + // Turn a utc date into a displayable + // short date and time + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString + // + utcDateToShortDateAndTimeLocalized( + value, + timeZoneName, + languageName, + hour12 + ) { + if (!value) { + return ""; + } + if (!timeZoneName) { + timeZoneName = this.getTimeZoneName(); + } + if (!languageName) { + languageName = this.getBrowserLanguages(); + } + + if (!hour12) { + hour12 = this.getHour12(); + } + + //parse the date which is identified as utc ("2020-02-06T18:18:49.148011Z") + var parsedDate = new Date(value); + + //is it a valid date? + if (!(parsedDate instanceof Date && !isNaN(parsedDate))) { + return "not valid"; + } + + return parsedDate.toLocaleString(languageName, { + timeZone: timeZoneName, + dateStyle: "short", + timeStyle: "short", + hour12: hour12 + }); + }, /////////////////////////////////////////// + // Turn a utc date into a displayable + // short date + // + utcDateToShortDateLocalized(value, timeZoneName, languageName) { + if (!value) { + return ""; + } + if (!timeZoneName) { + timeZoneName = this.getTimeZoneName(); + } + if (!languageName) { + languageName = this.getBrowserLanguages(); + } + + //parse the date which is identified as utc ("2020-02-06T18:18:49.148011Z") + var parsedDate = new Date(value); + + //is it a valid date? + if (!(parsedDate instanceof Date && !isNaN(parsedDate))) { + return "not valid"; + } + + return parsedDate.toLocaleDateString(languageName, { + timeZone: timeZoneName, + dateStyle: "short" + }); + }, /////////////////////////////////////////// + // Turn a utc date into a displayable + // short time + // + utcDateToShortTimeLocalized(value, timeZoneName, languageName, hour12) { + if (!value) { + return ""; + } + if (!timeZoneName) { + timeZoneName = this.getTimeZoneName(); + } + if (!languageName) { + languageName = this.getBrowserLanguages(); + } + + if (!hour12) { + hour12 = this.getHour12(); + } + + //parse the date which is identified as utc ("2020-02-06T18:18:49.148011Z") + var parsedDate = new Date(value); + + //is it a valid date? + if (!(parsedDate instanceof Date && !isNaN(parsedDate))) { + return "not valid"; + } + + return parsedDate.toLocaleTimeString(languageName, { + timeZone: timeZoneName, + timeStyle: "short", + hour12: hour12 + }); + }, + /////////////////////////////////////////////// + // Convert a utc date to local time zone + // and return time portion only in iso 8601 + // format (used by time and date picker components) + // + utcDateStringToLocal8601TimeOnlyString(value, timeZoneName) { + if (!value) { + //if no value, return the current time as expected by the time picker + } else { + //ok, the reason for sv-SE is that it's a locale that returns the time already in ISO format and 24hr by default + //that can change over time so if this breaks that's why + //also fr-CA does as well as possibly en-CA + //https://stackoverflow.com/a/58633686/8939 + if (!timeZoneName) { + timeZoneName = this.getTimeZoneName(); + } + return new Date(value).toLocaleTimeString("sv-SE", { + timeZone: timeZoneName + }); + } + }, + /////////////////////////////////////////////// + // Convert a local time only string with date string + // to UTC and output as ISO 8601 + // (used by time and date picker components) + // + localTimeDateStringToUTC8601String(value, timeZoneName) { + //https://moment.github.io/luxon/docs/manual/zones.html#creating-datetimes-in-a-zone + if (!timeZoneName) { + timeZoneName = this.getTimeZoneName(); + } + //parse in the time in the currently used timezone + return window.$gz.DateTime.fromISO(value, { + zone: this.timeZoneName + }) + .setZone("utc") //convert to UTC + .toISO(); //output as ISO 8601 + }, + /////////////////////////////////////////////// + // Convert a utc date to local time zone + // and return date only portion only in iso 8601 + // format (used by time and date picker components) + // + utcDateStringToLocal8601DateOnlyString(value, timeZoneName) { + if (!value) { + //if no value, return the current time as expected by the time picker + } else { + //ok, the reason for sv-SE is that it's a locale that returns the time already in ISO format and 24hr by default + //that can change over time so if this breaks that's why + //also fr-CA does as well as possibly en-CA + //https://stackoverflow.com/a/58633686/8939 + if (!timeZoneName) { + timeZoneName = this.getTimeZoneName(); + } + return new Date(value).toLocaleDateString("sv-SE", { + timeZone: timeZoneName + }); + } + }, + /////////////////////////////////////////// + // Turn a decimal number into a local + // currency display + // + currencyLocalized(value, languageName, currencyName) { + if (!value) return ""; + if (!languageName) { + languageName = this.getBrowserLanguages(); + } + if (!currencyName) { + currencyName = this.getCurrencyName(); + } + + return new Intl.NumberFormat(languageName, { + style: "currency", + currency: currencyName + }).format(value); + }, + /////////////////////////////////////////// + // Turn a decimal number into a local + // decimal format display + // + decimalLocalized(value, languageName) { + if (!value) return ""; + if (!languageName) { + languageName = this.getBrowserLanguages(); + } + return new Intl.NumberFormat(languageName, { + minimumFractionDigits: 2 + }).format(value); + }, + //////////////////////////////////////////////////////// + // dynamically set the vuetify language elements from + // users localized text (am/pm etc) + // Keeping vuetify using en locale and just adjusting on top of that + // + setVuetifyDefaultLanguageElements(vm) { + vm.$vuetify.lang.locales.en.close = this.get("OK"); + vm.$vuetify.lang.locales.en.timePicker.am = this.get("AM"); + vm.$vuetify.lang.locales.en.timePicker.pm = this.get("PM"); + } +}; diff --git a/ayanova/src/components/gz-data-table.vue b/ayanova/src/components/gz-data-table.vue index cef748ab..556b4628 100644 --- a/ayanova/src/components/gz-data-table.vue +++ b/ayanova/src/components/gz-data-table.vue @@ -520,6 +520,22 @@ function buildRecords(listData, columndefinitions, filters) { break; case 10: //enum //TODO: Need enum translator +//AuthorizationRoles.128 +//window.$gz.local.enumLocalized(enumType, enumValue) +//(in locale) check if have that type already featched, if not fetches the whole list and caches it locally +//also a method to return the list of enumerated types in ID order with localized names +//window.$gz.local.enumList(enumType) - returns a list usable on edit forms +/* enums: { + AuthorizationRoles0:"No role", + AuthorizationRoles1:"Business admin limited" + etc + + to get all authorization roles iterate list looking for start of key that is AuthorizationRoles + To get individual one same but only need to fetch actual value i.e. AuthorizationRoles1 + if not one item starts with AuthorizationRoles then list needs to be fetched, maybe can use the enumpicklist route for that + This way entirely bypasses locale stuff + //big object so maybe it's own thing, not part of locale at all or locale fronts for it?? + */ display = columndefinitions[iColumn].et + "." + display; break; default: @@ -575,4 +591,23 @@ async function fetchLocalizedHeaderNames(columnData) { window.$gz.errorHandler.handleFormError(err); }); } + +//CURRENTLY THINKING NOT TO CACHE THIS AS +//users might only ever view the list and +//often it might not contain the whole range of options +//so would be unnecessary fetching +// `////////////////////// +// // +// // +// function preFetchEnumListNames(vm, enumListTypes) { +// //enumlisttypes is an array of enum lists to fetch names for +// return window.$gz.api +// .get("EnumPickList/list/authorizationroles") +// .then(res => { +// if (res.error) { +// throw res.error; +// } +// vm.pickLists.roles = res.data; +// }); +// }` diff --git a/ayanova/src/store.js b/ayanova/src/store.js index f7569482..969e7242 100644 --- a/ayanova/src/store.js +++ b/ayanova/src/store.js @@ -20,6 +20,7 @@ export default new Vuex.Store({ userType: 0, homePage: undefined, localeText: {}, + enums: {}, //all enum values with localized text to match stored as key e.g. enums:{AuthorizationRoles:[{0:"no role"},{1:"Limited role"}],UserTypes:[{0:"Technician"},{1:"Client user"}]} locale: { languageOverride: "en-US", timeZoneOverride: "America/New_York", @@ -52,6 +53,7 @@ export default new Vuex.Store({ state.homePage = undefined; state.navItems = []; state.localeText = {}; + state.enums = {}; state.formCustomTemplate = {}; state.apiUrl = ""; state.locale.languageOverride = "en-US"; @@ -77,6 +79,9 @@ export default new Vuex.Store({ state.locale.hour12 = data.hour12; state.locale.timeZoneOverride = data.timeZoneOverride; }, + setEnum(state, data) { + state.enums[data.enumKey] = data.items; //{enumKey:"AuthorizationRoles",items:[{0:"no role"},{1:"Limited role"}]} + }, setAPIURL(state, data) { state.apiUrl = data; },