This commit is contained in:
184
src/api/authorizationroles.js
Normal file
184
src/api/authorizationroles.js
Normal file
@@ -0,0 +1,184 @@
|
||||
import bizrolerights from "./biz-role-rights";
|
||||
|
||||
export default {
|
||||
ROLE_RIGHTS: bizrolerights,
|
||||
AUTHORIZATION_ROLES: {
|
||||
///<summary>No role set</summary>
|
||||
NoRole: 0,
|
||||
///<summary>BizAdminRestricted</summary>
|
||||
BizAdminRestricted: 1,
|
||||
///<summary>BizAdmin</summary>
|
||||
BizAdmin: 2,
|
||||
///<summary>ServiceRestricted</summary>
|
||||
ServiceRestricted: 4,
|
||||
///<summary>Service</summary>
|
||||
Service: 8,
|
||||
///<summary>InventoryRestricted</summary>
|
||||
InventoryRestricted: 16,
|
||||
///<summary>Inventory</summary>
|
||||
Inventory: 32,
|
||||
///<summary>Accounting</summary>
|
||||
Accounting: 64, //No restricted role, not sure if there is a need
|
||||
///<summary>TechRestricted</summary>
|
||||
TechRestricted: 128,
|
||||
///<summary>Tech</summary>
|
||||
Tech: 256,
|
||||
///<summary>SubContractorRestricted</summary>
|
||||
SubContractorRestricted: 512,
|
||||
///<summary>SubContractor</summary>
|
||||
SubContractor: 1024,
|
||||
///<summary>CustomerRestricted</summary>
|
||||
CustomerRestricted: 2048,
|
||||
///<summary>Customer</summary>
|
||||
Customer: 4096,
|
||||
///<summary>OpsAdminRestricted</summary>
|
||||
OpsAdminRestricted: 8192,
|
||||
///<summary>OpsAdmin</summary>
|
||||
OpsAdmin: 16384,
|
||||
///<summary>Sales</summary>
|
||||
Sales: 32768,
|
||||
///<summary>SalesRestricted</summary>
|
||||
SalesRestricted: 65536
|
||||
},
|
||||
//////////////////////////////////////////////////////////
|
||||
// Does current logged in user have role?
|
||||
// (Can be an array of roles or a single role, if array returns true if any of the array roles are present for this user)
|
||||
//
|
||||
hasRole(desiredRole) {
|
||||
if (!window.$gz.store.state.roles || window.$gz.store.state.roles === 0) {
|
||||
return false;
|
||||
}
|
||||
//array form?
|
||||
|
||||
if (Array.isArray(desiredRole)) {
|
||||
//it's an array of roles, iterate and if any are present then return true
|
||||
for (let i = 0; i < desiredRole.length; i++) {
|
||||
if ((window.$gz.store.state.roles & desiredRole[i]) != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return (window.$gz.store.state.roles & desiredRole) != 0;
|
||||
}
|
||||
},
|
||||
//////////////////////////////////////////////////////////
|
||||
// Does current logged in user have *ANY* role?
|
||||
//
|
||||
//
|
||||
hasAnyRole() {
|
||||
if (!window.$gz.store.state.roles || window.$gz.store.state.roles === 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Get a default empty rights object so that it can be present when a
|
||||
// form first loads
|
||||
//
|
||||
defaultRightsObject() {
|
||||
return {
|
||||
change: false,
|
||||
read: false,
|
||||
delete: false
|
||||
};
|
||||
},
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Get a default FULL rights object for forms that don't really need
|
||||
// to check rights but fits into system for forms in place (e.g. change password)
|
||||
//
|
||||
fullRightsObject() {
|
||||
return {
|
||||
change: true,
|
||||
read: true,
|
||||
delete: true
|
||||
};
|
||||
},
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Get a read only rights object (customer workorder for example)
|
||||
//
|
||||
readOnlyRightsObject() {
|
||||
return {
|
||||
change: false,
|
||||
read: true,
|
||||
delete: false
|
||||
};
|
||||
},
|
||||
/////////////////////////////////
|
||||
// aType is the name of the object type as defined in socktype.js
|
||||
//
|
||||
getRights(aType) {
|
||||
//from bizroles.cs:
|
||||
//HOW THIS WORKS / WHATS EXPECTED
|
||||
//Change = CREATE, RETRIEVE, UPDATE, DELETE - Full rights
|
||||
//
|
||||
//ReadFullRecord = You can read *all* the fields of the record, but can't modify it. Change is automatically checked for so only add different roles from change
|
||||
//PICKLIST NOTE: this does not control getting a list of names for selection which is role independent because it's required for so much indirectly
|
||||
//DELETE = SAME AS CHANGE FOR NOW (There is no specific delete right for now though it's checked for by routes in Authorized.cs in case we want to add it in future as a separate right from create.)
|
||||
//NOTE: biz rules can supersede this, this is just for general rights purposes, if an object has restrictive business rules they will take precedence every time.
|
||||
|
||||
const ret = this.defaultRightsObject();
|
||||
|
||||
//Get the type name from the type enum value
|
||||
let typeName = undefined;
|
||||
for (const [key, value] of Object.entries(window.$gz.type)) {
|
||||
if (value == aType) {
|
||||
typeName = key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Get the Sockeye stock REQUIRED role rights for that object
|
||||
const objectRoleRights = this.ROLE_RIGHTS[typeName];
|
||||
if (!objectRoleRights) {
|
||||
throw new Error(
|
||||
`authorizationroles::getRights type ${aType} not found in roles collection`
|
||||
);
|
||||
}
|
||||
|
||||
//get the logged in user's role
|
||||
const userRole = window.$gz.store.state.roles;
|
||||
//calculate the effective rights
|
||||
//a non zero result of the bitwise calculation means true and zero means false so using !! to force it into a boolean value
|
||||
//(contrary to some style guides that say !! is obscure but I say it saves a lot of typing)
|
||||
|
||||
const canChange = !!(userRole & objectRoleRights.Change);
|
||||
|
||||
//sometimes rights to read are false if change is true since change trumps read anyway so accordingly:
|
||||
let canReadFullRecord = canChange;
|
||||
if (!canReadFullRecord) {
|
||||
//can't change but might have special rights to full record:
|
||||
canReadFullRecord = !!(userRole & objectRoleRights.ReadFullRecord);
|
||||
}
|
||||
|
||||
ret.change = canChange;
|
||||
ret.delete = ret.change; //FOR NOW
|
||||
ret.read = canReadFullRecord;
|
||||
|
||||
// console.log("authorizationroles::canOpen", {
|
||||
// typeName: typeName,
|
||||
// userRole: userRole,
|
||||
// objectRoleRights: objectRoleRights,
|
||||
// retResultIs: ret
|
||||
// });
|
||||
|
||||
return ret;
|
||||
},
|
||||
/////////////////////////////////
|
||||
// convenience method for forms that deal with multiple object types
|
||||
// (i.e. grids, history etc, initialization of main menu etc)
|
||||
//
|
||||
canOpen(aType) {
|
||||
const r = this.getRights(aType);
|
||||
//convention is change might be defined but not read so canOpen is true eitehr way
|
||||
return r.change == true || r.read == true;
|
||||
},
|
||||
/////////////////////////////////
|
||||
// convenience method for forms that deal with multiple object types
|
||||
// (i.e. grids, history etc, initialization of main menu etc)
|
||||
//
|
||||
canChange(aType) {
|
||||
const r = this.getRights(aType);
|
||||
return r.change == true;
|
||||
}
|
||||
};
|
||||
124
src/api/authutil.js
Normal file
124
src/api/authutil.js
Normal file
@@ -0,0 +1,124 @@
|
||||
import jwt_decode from "jwt-decode";
|
||||
import initialize from "./initialize";
|
||||
import notifypoll from "./notifypoll";
|
||||
|
||||
export function processLogin(authResponse, loggedInWithKnownPassword) {
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async function(resolve, reject) {
|
||||
try {
|
||||
//check there is a response of some kind
|
||||
if (!authResponse) {
|
||||
window.$gz.store.commit("logItem", "auth::processLogin -> no response");
|
||||
return reject();
|
||||
}
|
||||
|
||||
//is token present?
|
||||
if (!authResponse || !authResponse.token) {
|
||||
window.$gz.store.commit(
|
||||
"logItem",
|
||||
"auth::processLogin -> response contains no data"
|
||||
);
|
||||
return reject();
|
||||
}
|
||||
const token = jwt_decode(authResponse.token);
|
||||
|
||||
if (!token || !token.iss) {
|
||||
window.$gz.store.commit(
|
||||
"logItem",
|
||||
"auth::processLogin -> response token empty"
|
||||
);
|
||||
return reject();
|
||||
}
|
||||
|
||||
if (token.iss != "rockfish.ayanova.com") {
|
||||
window.$gz.store.commit(
|
||||
"logItem",
|
||||
"auth::processLogin -> token invalid (iss): " + token.iss
|
||||
);
|
||||
return reject();
|
||||
}
|
||||
|
||||
//ensure the store is clean first in case we didn't come here from a clean logout
|
||||
window.$gz.store.commit("logout");
|
||||
sessionStorage.clear(); //clear all temporary session storage data
|
||||
|
||||
//encourage password changing if a purchased license
|
||||
if (loggedInWithKnownPassword)
|
||||
window.$gz.store.commit("setKnownPassword", true);
|
||||
|
||||
//Put app relevant items into vuex store so app can use them
|
||||
window.$gz.store.commit("login", {
|
||||
apiToken: authResponse.token,
|
||||
authenticated: true,
|
||||
userId: Number(token.id),
|
||||
translationId: authResponse.tid,
|
||||
userName: authResponse.name,
|
||||
roles: authResponse.roles,
|
||||
userType: authResponse.usertype,
|
||||
dlt: authResponse.dlt,
|
||||
l: authResponse.l,
|
||||
tfaEnabled: authResponse.tfa,
|
||||
customerRights: authResponse.customerRights
|
||||
});
|
||||
|
||||
//decided to remove this as it is not an out of the ordinary scenario to log
|
||||
// however left this block here in case in future becomes necessary for some common issue
|
||||
// //log the login
|
||||
// window.$gz.store.commit(
|
||||
// "logItem",
|
||||
// "auth::processLogin -> User " + token.id + " logged in"
|
||||
// );
|
||||
|
||||
//Get global settings
|
||||
const gsets = await window.$gz.api.get("global-biz-setting/client");
|
||||
if (gsets.error) {
|
||||
//In a form this would trigger a bunch of validation or error display code but for here and now:
|
||||
//convert error to human readable string for display and popup a notification to user
|
||||
const msg = window.$gz.api.apiErrorToHumanString(gsets.error);
|
||||
window.$gz.eventBus.$emit("notify-error", msg);
|
||||
} else {
|
||||
//Check if overrides and use them here
|
||||
//or else use browser defaults
|
||||
window.$gz.store.commit("setGlobalSettings", gsets.data);
|
||||
}
|
||||
await initialize();
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
//start notification polling
|
||||
notifypoll.startPolling();
|
||||
resolve();
|
||||
//-------------------------------------------------
|
||||
});
|
||||
}
|
||||
|
||||
export function processLogout() {
|
||||
notifypoll.stopPolling();
|
||||
window.$gz.store.commit("logout");
|
||||
sessionStorage.clear(); //clear all temporary session storage data
|
||||
}
|
||||
|
||||
export function isLoggedIn() {
|
||||
return (
|
||||
!!window.$gz.store.state.apiToken &&
|
||||
!isTokenExpired(window.$gz.store.state.apiToken)
|
||||
);
|
||||
}
|
||||
|
||||
function getTokenExpirationDate(encodedToken) {
|
||||
const token = jwt_decode(encodedToken);
|
||||
if (!token.exp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const date = new Date(0);
|
||||
date.setUTCSeconds(token.exp);
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
function isTokenExpired(token) {
|
||||
const expirationDate = getTokenExpirationDate(token);
|
||||
return expirationDate < new Date();
|
||||
}
|
||||
47
src/api/biz-role-rights.js
Normal file
47
src/api/biz-role-rights.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
*
|
||||
* Auto generated by BizRoles.cs in server project, update here whenever that changes
|
||||
*
|
||||
*
|
||||
*/
|
||||
export default {
|
||||
Customer: { Change: 32842, ReadFullRecord: 65797, Select: 131071 },
|
||||
CustomerNote: { Change: 32842, ReadFullRecord: 65797, Select: 131071 },
|
||||
CustomerNotifySubscription: {
|
||||
Change: 10,
|
||||
ReadFullRecord: 65797,
|
||||
Select: 131071
|
||||
},
|
||||
HeadOffice: { Change: 32842, ReadFullRecord: 65797, Select: 131071 },
|
||||
Global: { Change: 2, ReadFullRecord: 1, Select: 0 },
|
||||
GlobalOps: { Change: 16384, ReadFullRecord: 8192, Select: 0 },
|
||||
User: { Change: 2, ReadFullRecord: 1, Select: 131071 },
|
||||
UserOptions: { Change: 2, ReadFullRecord: 1, Select: 0 },
|
||||
Vendor: { Change: 106, ReadFullRecord: 98565, Select: 131071 },
|
||||
ServerState: { Change: 16384, ReadFullRecord: 131071, Select: 0 },
|
||||
LogFile: { Change: 0, ReadFullRecord: 24576, Select: 0 },
|
||||
Backup: { Change: 16384, ReadFullRecord: 8195, Select: 0 },
|
||||
FileAttachment: { Change: 2, ReadFullRecord: 3, Select: 0 },
|
||||
ServerJob: { Change: 16384, ReadFullRecord: 8195, Select: 0 },
|
||||
OpsNotificationSettings: { Change: 16384, ReadFullRecord: 8195, Select: 0 },
|
||||
ServerMetrics: { Change: 16384, ReadFullRecord: 24576, Select: 0 },
|
||||
Translation: { Change: 2, ReadFullRecord: 1, Select: 131071 },
|
||||
DataListSavedFilter: { Change: 2, ReadFullRecord: 131071, Select: 0 },
|
||||
FormUserOptions: { Change: 131071, ReadFullRecord: 131071, Select: 0 },
|
||||
FormCustom: { Change: 2, ReadFullRecord: 131071, Select: 0 },
|
||||
PickListTemplate: { Change: 2, ReadFullRecord: 131071, Select: 0 },
|
||||
BizMetrics: { Change: 2, ReadFullRecord: 98369, Select: 0 },
|
||||
Notification: { Change: 131071, ReadFullRecord: 131071, Select: 0 },
|
||||
NotifySubscription: { Change: 131071, ReadFullRecord: 131071, Select: 0 },
|
||||
Report: { Change: 3, ReadFullRecord: 131071, Select: 131071 },
|
||||
Memo: { Change: 124927, ReadFullRecord: 124927, Select: 124927 },
|
||||
Reminder: { Change: 124927, ReadFullRecord: 124927, Select: 124927 },
|
||||
Review: { Change: 124927, ReadFullRecord: 124927, Select: 124927 },
|
||||
Integration: { Change: 49514, ReadFullRecord: 49514, Select: 49514 },
|
||||
License: { Change: 32842, ReadFullRecord: 65797, Select: 131071 },
|
||||
TrialLicenseRequest: { Change: 32842, ReadFullRecord: 65797, Select: 131071 },
|
||||
SubscriptionServer: { Change: 32842, ReadFullRecord: 65797, Select: 131071 },
|
||||
Purchase: { Change: 32842, ReadFullRecord: 65797, Select: 131071 },
|
||||
Product: { Change: 32842, ReadFullRecord: 65797, Select: 131071 },
|
||||
GZCase: { Change: 32842, ReadFullRecord: 65797, Select: 131071 }
|
||||
};
|
||||
400
src/api/dash-registry.js
Normal file
400
src/api/dash-registry.js
Normal file
@@ -0,0 +1,400 @@
|
||||
import authorizationroles from "./authorizationroles";
|
||||
const role = authorizationroles.AUTHORIZATION_ROLES;
|
||||
/*
|
||||
|
||||
*/
|
||||
export default {
|
||||
registry: [
|
||||
{
|
||||
roles: [
|
||||
role.BizAdmin,
|
||||
role.BizAdminRestricted,
|
||||
role.ServiceRestricted,
|
||||
role.Service,
|
||||
role.Accounting,
|
||||
role.Tech,
|
||||
role.TechRestricted
|
||||
],
|
||||
title: "DashboardWorkOrderByStatusList",
|
||||
icon: "$sockiListAlt",
|
||||
type: "GzDashWorkorderByStatusList",
|
||||
singleOnly: false,
|
||||
settings: {
|
||||
customTitle: null,
|
||||
timeSpan: "*thisyear*",
|
||||
interval: "month",
|
||||
wostatus: null,
|
||||
wotags: [],
|
||||
wotagsany: true,
|
||||
woitemtags: [],
|
||||
woitemtagsany: true
|
||||
}
|
||||
},
|
||||
{
|
||||
roles: [
|
||||
role.BizAdmin,
|
||||
role.BizAdminRestricted,
|
||||
role.ServiceRestricted,
|
||||
role.Service,
|
||||
role.Accounting
|
||||
],
|
||||
title: "DashboardWorkOrderStatusCount",
|
||||
icon: "$sockiChartBar",
|
||||
type: "GzDashWorkOrderStatusCount",
|
||||
scheduleableUserOnly: false,
|
||||
singleOnly: false,
|
||||
settings: {
|
||||
customTitle: null,
|
||||
timeSpan: "*thisyear*",
|
||||
interval: "month",
|
||||
wotags: [],
|
||||
wotagsany: true
|
||||
}
|
||||
},
|
||||
{
|
||||
roles: [
|
||||
role.BizAdmin,
|
||||
role.BizAdminRestricted,
|
||||
role.ServiceRestricted,
|
||||
role.Service,
|
||||
role.Accounting
|
||||
],
|
||||
title: "DashboardWorkOrderStatusPct",
|
||||
icon: "$sockiChartBar",
|
||||
type: "GzDashWorkOrderStatusPct",
|
||||
scheduleableUserOnly: false,
|
||||
singleOnly: false,
|
||||
settings: {
|
||||
customTitle: null,
|
||||
timeSpan: "*thisyear*",
|
||||
interval: "month",
|
||||
wotags: [],
|
||||
wotagsany: true
|
||||
}
|
||||
},
|
||||
{
|
||||
roles: [
|
||||
role.BizAdmin,
|
||||
role.BizAdminRestricted,
|
||||
role.ServiceRestricted,
|
||||
role.Service,
|
||||
role.Accounting
|
||||
],
|
||||
title: "DashboardPctWorkOrderCompletedOnTime",
|
||||
icon: "$sockiChartBar",
|
||||
type: "GzDashPctWorkOrderCompletedOnTimeBar",
|
||||
scheduleableUserOnly: false,
|
||||
singleOnly: false,
|
||||
settings: {
|
||||
customTitle: null,
|
||||
timeSpan: "*thisyear*",
|
||||
interval: "month",
|
||||
wotags: [],
|
||||
wotagsany: true,
|
||||
color: "#00205BFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
roles: [
|
||||
role.BizAdmin,
|
||||
role.BizAdminRestricted,
|
||||
role.ServiceRestricted,
|
||||
role.Service,
|
||||
role.Accounting
|
||||
],
|
||||
title: "DashboardCountWorkOrdersCreated",
|
||||
icon: "$sockiChartLine",
|
||||
type: "GzDashWorkOrderCreatedCountLine",
|
||||
scheduleableUserOnly: false,
|
||||
singleOnly: false,
|
||||
settings: {
|
||||
customTitle: null,
|
||||
timeSpan: "*thisyear*",
|
||||
interval: "day",
|
||||
wotags: [],
|
||||
wotagsany: true,
|
||||
woitemtags: [],
|
||||
woitemtagsany: true,
|
||||
color: "#00205BFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
roles: [
|
||||
role.BizAdmin,
|
||||
role.BizAdminRestricted,
|
||||
role.ServiceRestricted,
|
||||
role.Service,
|
||||
role.Accounting
|
||||
],
|
||||
title: "DashboardCountWorkOrdersCreated",
|
||||
icon: "$sockiChartBar",
|
||||
type: "GzDashWorkOrderCreatedCountBar",
|
||||
scheduleableUserOnly: false,
|
||||
singleOnly: false,
|
||||
settings: {
|
||||
customTitle: null,
|
||||
timeSpan: "*thisyear*",
|
||||
wotags: [],
|
||||
wotagsany: true,
|
||||
woitemtags: [],
|
||||
woitemtagsany: true,
|
||||
interval: "month",
|
||||
color: "#00205BFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
roles: [
|
||||
role.BizAdmin,
|
||||
role.BizAdminRestricted,
|
||||
role.ServiceRestricted,
|
||||
role.Service,
|
||||
role.Accounting
|
||||
],
|
||||
title: "DashboardOverdueAll",
|
||||
icon: "$sockiListAlt",
|
||||
type: "GzDashWorkorderOverdueAllList",
|
||||
scheduleableUserOnly: false,
|
||||
singleOnly: false,
|
||||
settings: {
|
||||
customTitle: null,
|
||||
wotags: [],
|
||||
wotagsany: true,
|
||||
woitemtags: [],
|
||||
woitemtagsany: true
|
||||
}
|
||||
},
|
||||
{
|
||||
roles: [role.Tech, role.TechRestricted],
|
||||
title: "DashboardOverdue",
|
||||
icon: "$sockiListAlt",
|
||||
type: "GzDashWorkorderOverduePersonalList",
|
||||
scheduleableUserOnly: true,
|
||||
singleOnly: true,
|
||||
settings: {
|
||||
customTitle: null,
|
||||
wotags: [],
|
||||
wotagsany: true,
|
||||
woitemtags: [],
|
||||
woitemtagsany: true
|
||||
}
|
||||
},
|
||||
{
|
||||
roles: [
|
||||
role.BizAdmin,
|
||||
role.BizAdminRestricted,
|
||||
role.ServiceRestricted,
|
||||
role.Service,
|
||||
role.Tech,
|
||||
role.TechRestricted
|
||||
],
|
||||
title: "DashboardOpenCSR",
|
||||
icon: "$sockiListAlt",
|
||||
type: "GzDashCSROpenList",
|
||||
singleOnly: false,
|
||||
settings: {
|
||||
customTitle: null,
|
||||
custtags: [],
|
||||
custtagsany: true
|
||||
}
|
||||
},
|
||||
{
|
||||
roles: [
|
||||
role.BizAdmin,
|
||||
role.BizAdminRestricted,
|
||||
role.ServiceRestricted,
|
||||
role.Service,
|
||||
role.Accounting,
|
||||
role.Tech,
|
||||
role.TechRestricted
|
||||
],
|
||||
title: "DashboardNotScheduled",
|
||||
icon: "$sockiListAlt",
|
||||
type: "GzDashWorkorderUnscheduledOpenList",
|
||||
singleOnly: false,
|
||||
settings: {
|
||||
customTitle: null,
|
||||
wostatus: null,
|
||||
wotags: [],
|
||||
wotagsany: true,
|
||||
woitemtags: [],
|
||||
woitemtagsany: true
|
||||
}
|
||||
},
|
||||
{
|
||||
roles: [
|
||||
role.BizAdmin,
|
||||
role.BizAdminRestricted,
|
||||
role.ServiceRestricted,
|
||||
role.Service,
|
||||
role.InventoryRestricted,
|
||||
role.Inventory,
|
||||
role.Accounting,
|
||||
role.Tech,
|
||||
role.TechRestricted,
|
||||
role.OpsAdmin,
|
||||
role.OpsAdminRestricted,
|
||||
role.Sales,
|
||||
role.SalesRestricted
|
||||
],
|
||||
title: "ReminderList",
|
||||
icon: "$sockiCalendarDay",
|
||||
type: "GzDashTodayReminders",
|
||||
singleOnly: true,
|
||||
settings: {}
|
||||
},
|
||||
{
|
||||
roles: [
|
||||
role.BizAdmin,
|
||||
role.BizAdminRestricted,
|
||||
role.ServiceRestricted,
|
||||
role.Service,
|
||||
role.InventoryRestricted,
|
||||
role.Inventory,
|
||||
role.Accounting,
|
||||
role.Tech,
|
||||
role.TechRestricted,
|
||||
role.OpsAdmin,
|
||||
role.OpsAdminRestricted,
|
||||
role.Sales,
|
||||
role.SalesRestricted
|
||||
],
|
||||
title: "ReviewList",
|
||||
icon: "$sockiCalendarDay",
|
||||
type: "GzDashTodayReviews",
|
||||
singleOnly: true,
|
||||
settings: {}
|
||||
},
|
||||
{
|
||||
roles: [role.Tech, role.TechRestricted],
|
||||
title: "DashboardScheduled",
|
||||
icon: "$sockiCalendarDay",
|
||||
type: "GzDashTodayScheduledWo",
|
||||
scheduleableUserOnly: true,
|
||||
singleOnly: true,
|
||||
settings: {}
|
||||
},
|
||||
{
|
||||
roles: [role.Tech, role.TechRestricted],
|
||||
title: "WorkOrderItemLaborServiceRateQuantity",
|
||||
icon: "$sockiChartLine",
|
||||
type: "GzDashLaborHoursPersonalLine",
|
||||
scheduleableUserOnly: true,
|
||||
singleOnly: false,
|
||||
settings: {
|
||||
customTitle: null,
|
||||
timeSpan: "*thisyear*",
|
||||
interval: "day",
|
||||
wotags: [],
|
||||
wotagsany: true,
|
||||
woitemtags: [],
|
||||
woitemtagsany: true,
|
||||
color: "#00205BFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
roles: [role.Tech, role.TechRestricted],
|
||||
title: "WorkOrderItemLaborServiceRateQuantity",
|
||||
icon: "$sockiChartBar",
|
||||
type: "GzDashLaborHoursPersonalBar",
|
||||
scheduleableUserOnly: true,
|
||||
singleOnly: false,
|
||||
settings: {
|
||||
customTitle: null,
|
||||
timeSpan: "*thisyear*",
|
||||
wotags: [],
|
||||
wotagsany: true,
|
||||
woitemtags: [],
|
||||
woitemtagsany: true,
|
||||
interval: "month",
|
||||
color: "#00205BFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
roles: [
|
||||
role.BizAdmin,
|
||||
role.BizAdminRestricted,
|
||||
role.ServiceRestricted,
|
||||
role.Service,
|
||||
role.Accounting
|
||||
],
|
||||
title: "DashboardServiceRateQuantityAllUsers",
|
||||
icon: "$sockiChartLine",
|
||||
type: "GzDashLaborHoursEveryoneLine",
|
||||
scheduleableUserOnly: false,
|
||||
singleOnly: false,
|
||||
settings: {
|
||||
customTitle: null,
|
||||
timeSpan: "*thisyear*",
|
||||
interval: "month",
|
||||
wotags: [],
|
||||
wotagsany: true,
|
||||
woitemtags: [],
|
||||
woitemtagsany: true,
|
||||
techtags: [],
|
||||
techtagsany: true,
|
||||
userid: null,
|
||||
color: "#00205BFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
roles: [
|
||||
role.BizAdmin,
|
||||
role.BizAdminRestricted,
|
||||
role.ServiceRestricted,
|
||||
role.Service,
|
||||
role.Accounting
|
||||
],
|
||||
title: "DashboardServiceRateQuantityAllUsers",
|
||||
icon: "$sockiChartBar",
|
||||
type: "GzDashLaborHoursEveryoneBar",
|
||||
scheduleableUserOnly: false,
|
||||
singleOnly: false,
|
||||
settings: {
|
||||
customTitle: null,
|
||||
timeSpan: "*thisyear*",
|
||||
wotags: [],
|
||||
wotagsany: true,
|
||||
woitemtags: [],
|
||||
woitemtagsany: true,
|
||||
techtags: [],
|
||||
techtagsany: true,
|
||||
userid: null,
|
||||
interval: "month",
|
||||
color: "#00205BFF"
|
||||
}
|
||||
}
|
||||
],
|
||||
availableItems() {
|
||||
const ret = [];
|
||||
for (let i = 0; i < this.registry.length; i++) {
|
||||
const item = this.registry[i];
|
||||
|
||||
if (authorizationroles.hasRole(item.roles)) {
|
||||
//if it's only for sched users and not then skip
|
||||
if (
|
||||
item.scheduleableUserOnly &&
|
||||
!window.$gz.store.getters.isScheduleableUser
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
ret.push({
|
||||
id: i,
|
||||
title: item.title,
|
||||
icon: item.icon,
|
||||
type: item.type,
|
||||
singleOnly: item.singleOnly,
|
||||
settings: item.settings
|
||||
});
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
async cacheTranslationsForAvailableItems() {
|
||||
const items = this.availableItems();
|
||||
|
||||
//await window.$gz.translation.cacheTranslations(items.map(z => z.title));
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
...new Set(items.map(z => z.title))
|
||||
]);
|
||||
}
|
||||
};
|
||||
101
src/api/enums.js
Normal file
101
src/api/enums.js
Normal file
@@ -0,0 +1,101 @@
|
||||
export default {
|
||||
get(enumKey, enumValue) {
|
||||
enumKey = enumKey.toLowerCase();
|
||||
if (enumKey != "authorizationroles") {
|
||||
if (window.$gz.store.state.enums[enumKey] == undefined) {
|
||||
throw new Error(
|
||||
"ERROR enums::get -> enumKey " + enumKey + " is missing from store"
|
||||
);
|
||||
}
|
||||
|
||||
const ret = window.$gz.store.state.enums[enumKey][enumValue];
|
||||
if (ret == undefined) {
|
||||
return "";
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
const ret = [];
|
||||
if (enumValue == null || enumValue == 0) {
|
||||
return "";
|
||||
}
|
||||
const availableRoles = this.getSelectionList("AuthorizationRoles");
|
||||
for (let i = 0; i < availableRoles.length; i++) {
|
||||
const role = availableRoles[i];
|
||||
if (enumValue & role.id) {
|
||||
ret.push(role.name);
|
||||
}
|
||||
}
|
||||
return ret.join(", ");
|
||||
}
|
||||
},
|
||||
//////////////////////////////////
|
||||
//
|
||||
// Used by forms to fetch selection list data
|
||||
// Sorts alphabetically by default but can be turned off with do not sort
|
||||
//
|
||||
getSelectionList(enumKey, noSort) {
|
||||
enumKey = enumKey.toLowerCase();
|
||||
const e = window.$gz.store.state.enums[enumKey];
|
||||
if (!e) {
|
||||
throw new Error(
|
||||
"ERROR enums::getSelectionList -> enumKey " +
|
||||
enumKey +
|
||||
" is missing from store"
|
||||
);
|
||||
}
|
||||
const ret = [];
|
||||
|
||||
//turn it into an array suitable for selection lists
|
||||
for (const [key, value] of Object.entries(e)) {
|
||||
ret.push({ id: Number(key), name: value });
|
||||
}
|
||||
//sort by name
|
||||
if (!noSort) {
|
||||
ret.sort(window.$gz.util.sortByKey("name"));
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
///////////////////////////////////
|
||||
//
|
||||
// Fetches enum list from server
|
||||
// and puts in store. if necessary
|
||||
// ACCEPTS an ARRAY or a single STRING KEY
|
||||
//
|
||||
async fetchEnumList(enumKey) {
|
||||
if (!Array.isArray(enumKey)) {
|
||||
enumKey = [enumKey];
|
||||
}
|
||||
for (let i = 0; i < enumKey.length; i++) {
|
||||
//check if list
|
||||
//if not then fetch it and store it
|
||||
const k = enumKey[i].toLowerCase();
|
||||
|
||||
//de-lodash
|
||||
// if (!window.$gz. _.has(window.$gz.store.state.enums, k)) {
|
||||
//enums is an object this is checking if that object has a key with the name in k
|
||||
if (!window.$gz.util.has(window.$gz.store.state.enums, k)) {
|
||||
const that = this;
|
||||
|
||||
const dat = await that.fetchEnumKey(k);
|
||||
//massage the data as necessary
|
||||
const e = { enumKey: k, items: {} };
|
||||
for (let i = 0; i < dat.length; i++) {
|
||||
const o = dat[i];
|
||||
e.items[o.id] = o.name;
|
||||
}
|
||||
//stuff the data into the store
|
||||
window.$gz.store.commit("setEnum", e);
|
||||
}
|
||||
}
|
||||
},
|
||||
async fetchEnumKey(enumKey) {
|
||||
const res = await window.$gz.api.get("enum-list/list/" + enumKey);
|
||||
//We never expect there to be no data here
|
||||
//if (!Object.prototype.hasOwnProperty.call(res, "data")) {
|
||||
if (!Object.prototype.hasOwnProperty.call(res, "data")) {
|
||||
return Promise.reject(res);
|
||||
}
|
||||
return res.data;
|
||||
}
|
||||
};
|
||||
230
src/api/errorhandler.js
Normal file
230
src/api/errorhandler.js
Normal file
@@ -0,0 +1,230 @@
|
||||
let lastMessageHash = 0;
|
||||
let lastMessageTimeStamp = new Date();
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// translate, Log and optionally display errors
|
||||
// return translated message in case caller needs it
|
||||
async function dealWithError(msg, vm) {
|
||||
//Check if this is the same message again as last time within a short time span to avoid endless looping errors of same message
|
||||
//but still allow for user to repeat operation that causes error so they can view it
|
||||
const newHash = window.$gz.util.quickHash(msg);
|
||||
if (newHash == lastMessageHash) {
|
||||
const tsnow = new Date();
|
||||
//don't show the same exact message if it was just shown less than 1 second ago
|
||||
if (tsnow - lastMessageTimeStamp < 1000) return;
|
||||
}
|
||||
lastMessageHash = newHash;
|
||||
lastMessageTimeStamp = new Date();
|
||||
|
||||
//translate as necessary
|
||||
msg = await window.$gz.translation.translateStringWithMultipleKeysAsync(msg);
|
||||
|
||||
//In some cases the error may not be translatable, if this is not a debug run then it should show without the ?? that translating puts in keys not found
|
||||
//so it's not as weird looking to the user
|
||||
//vm may be null here so check window gz for dev
|
||||
if (!window.$gz.dev && msg.includes("??")) {
|
||||
msg = msg.replace("??", "");
|
||||
}
|
||||
window.$gz.store.commit("logItem", msg);
|
||||
if (window.$gz.dev) {
|
||||
const errMsg = "DEV MODE errorHandler.js:: Unexpected error: \r\n" + msg;
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(errMsg);
|
||||
|
||||
// eslint-disable-next-line no-debugger
|
||||
debugger;
|
||||
}
|
||||
|
||||
//If a form instance was provided (vue instance)
|
||||
//and it can display and error then put the error into it
|
||||
if (!vm || vm.formState == undefined) {
|
||||
//Special work around to not redundantly display errors when Sockeye job fails
|
||||
// and Vue decides to throw it's own error into the mix when we've already displayed appropriate message
|
||||
if (msg.includes("Vue error") && msg.includes("Job failed")) {
|
||||
return;
|
||||
}
|
||||
|
||||
//popup if no place to display it elsewise
|
||||
window.$gz.eventBus.$emit("notify-error", msg);
|
||||
return;
|
||||
}
|
||||
|
||||
//should be able to display in form...
|
||||
if (vm.$sock.dev) {
|
||||
//make sure formState.appError is defined on data
|
||||
if (!window.$gz.util.has(vm, "formState.appError")) {
|
||||
throw new Error(
|
||||
"DEV ERROR errorHandler::dealWithError -> formState.appError seems to be missing from form's vue data object"
|
||||
);
|
||||
}
|
||||
}
|
||||
vm.formState.appError = msg;
|
||||
|
||||
//TODO: What is this doing exactly?
|
||||
//it's related to server errors but I'm setting appError above
|
||||
//why two error properties?
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// DECODE ERROR TO TEXT
|
||||
// accept an unknown type of error variable
|
||||
// and return human readable text
|
||||
//
|
||||
function decodeError(e, vm) {
|
||||
// console.log("decodeError full e object as is: ");
|
||||
// console.log(e);
|
||||
// console.log("decodeError full e object stringified: ", JSON.stringify(e));
|
||||
// console.log("decodeError is typeof:", typeof e);
|
||||
// console.log("decodeError e is instanceof Error ", e instanceof Error);
|
||||
// console.log(
|
||||
// "decodeError e is a string already: ",
|
||||
// window.$gz.util.isString(e)
|
||||
// );
|
||||
|
||||
//already a string?
|
||||
if (window.$gz.util.isString(e)) {
|
||||
return e; //nothing to do here, already a string
|
||||
}
|
||||
|
||||
if (e instanceof Error) {
|
||||
//an Error object?
|
||||
return `Error - Name:${e.name}, Message:${e.message}`;
|
||||
}
|
||||
|
||||
if (
|
||||
e == null ||
|
||||
e == "" ||
|
||||
(typeof e === "object" && Object.keys(e).length === 0)
|
||||
) {
|
||||
return `errorHandler::decodeError - Error is unknown / empty (e:${e})`;
|
||||
}
|
||||
|
||||
//API error object or error RESPONSE object?
|
||||
if (e.error || e.code) {
|
||||
let err = null;
|
||||
//could be the error RESPONSE or it could be the error object *inside* the error response so sort out here
|
||||
if (e.error) {
|
||||
//it's the entire resopnse object
|
||||
err = e.error;
|
||||
} else {
|
||||
//it's the inner error object only
|
||||
err = e;
|
||||
}
|
||||
let msg = "";
|
||||
if (err.code) {
|
||||
msg += err.code;
|
||||
msg += " - ";
|
||||
if (vm) {
|
||||
msg += vm.$sock.t("ErrorAPI" + err.code);
|
||||
}
|
||||
msg += "\n";
|
||||
}
|
||||
if (err.target) {
|
||||
msg += err.target;
|
||||
msg += "\n";
|
||||
}
|
||||
|
||||
if (err.message && !err.message.startsWith("ErrorAPI")) {
|
||||
//errapi already dealt with above no need to repeat it here
|
||||
msg += err.message;
|
||||
msg += "\n";
|
||||
}
|
||||
|
||||
if (err.details) {
|
||||
err.details.forEach(z => {
|
||||
let zerror = null;
|
||||
if (z.error) {
|
||||
zerror = z.error + " - ";
|
||||
}
|
||||
msg += `${zerror}${z.message}\n`;
|
||||
});
|
||||
}
|
||||
|
||||
//console.log("errorhandler:decodeError returning message:", msg);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
//Javascript Fetch API Response object?
|
||||
if (e instanceof Response) {
|
||||
return `http error: ${e.statusText} - ${e.status} Url: ${e.url}`;
|
||||
}
|
||||
|
||||
//last resort
|
||||
return JSON.stringify(e);
|
||||
}
|
||||
export default {
|
||||
handleGeneralError(message, source, lineno, colno, error) {
|
||||
let msg = "General error: \n" + message;
|
||||
if (source) {
|
||||
msg += "\nsource: " + source;
|
||||
}
|
||||
if (lineno) {
|
||||
msg += "\nlineno: " + lineno;
|
||||
}
|
||||
if (colno) {
|
||||
msg += "\ncolno: " + colno;
|
||||
}
|
||||
if (error) {
|
||||
if (typeof error === "object") {
|
||||
error = JSON.stringify(error);
|
||||
}
|
||||
msg += "\nerror: " + error;
|
||||
}
|
||||
dealWithError(msg);
|
||||
},
|
||||
handleVueError(err, vm, info) {
|
||||
let msg = "Vue error: \n" + decodeError(err, vm);
|
||||
if (err.fileName) {
|
||||
msg += "\nfilename: " + err.fileName;
|
||||
}
|
||||
if (err.lineNumber) {
|
||||
msg += "\nlineNumber: " + err.lineNumber;
|
||||
}
|
||||
if (info) {
|
||||
msg += "\ninfo: " + info;
|
||||
}
|
||||
if (err.stack) {
|
||||
msg += "\nSTACK:\n " + err.stack;
|
||||
}
|
||||
dealWithError(msg, vm);
|
||||
},
|
||||
handleVueWarning(wmsg, vm, trace) {
|
||||
let msg = "Vue warning: \n" + decodeError(wmsg, vm);
|
||||
if (trace) {
|
||||
msg += "\ntrace: " + trace;
|
||||
}
|
||||
dealWithError(msg, vm);
|
||||
},
|
||||
/////////////////////////////////////////////////
|
||||
// translate, log and return error
|
||||
//
|
||||
handleFormError(err, vm) {
|
||||
if (window.$gz.dev) {
|
||||
console.trace(err);
|
||||
}
|
||||
//called inside forms when things go unexpectedly wrong
|
||||
dealWithError(decodeError(err, vm), vm);
|
||||
},
|
||||
/////////////////////////////////////////////////
|
||||
// decode error into string suitable to display
|
||||
//
|
||||
errorToString(err, vm) {
|
||||
//called inside forms when things go unexpectedly wrong
|
||||
return decodeError(err, vm);
|
||||
}
|
||||
};
|
||||
/*
|
||||
ERROR CODES USED:
|
||||
Client error codes are all in the range of E16 to E999
|
||||
Server error codes are all in the range of E1000 to E1999
|
||||
API specific (logic) error codes are all in the range of 2000 to 3000
|
||||
|
||||
CLIENT ERROR CODES:
|
||||
E16 - ErrorUserNotAuthenticated
|
||||
E17 - ErrorServerUnresponsive
|
||||
E18 - Misc error without a translation key, unexpected throws etc or api error during server call, details in the message / Any error without a translation key defined basically
|
||||
|
||||
*/
|
||||
2
src/api/eventbus.js
Normal file
2
src/api/eventbus.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import Vue from "vue";
|
||||
export default new Vue();
|
||||
83
src/api/form-custom-template.js
Normal file
83
src/api/form-custom-template.js
Normal file
@@ -0,0 +1,83 @@
|
||||
///Add data key names which make the custom fields control work more easily
|
||||
///Since the names can be inferred from the data that comes from the server it saves bandwidth to do it here at the client
|
||||
function addDataKeyNames(obj) {
|
||||
//iterate the array of objects
|
||||
//if it has a "type" property then it's a custom field so add its data key name
|
||||
|
||||
for (let i = 0; i < obj.length; i++) {
|
||||
if (obj[i].type) {
|
||||
obj[i]["dataKey"] = "c" + parseInt(obj[i].fld.replace(/^\D+/g, ""));
|
||||
}
|
||||
}
|
||||
|
||||
//return the whole thing again now translated
|
||||
return obj;
|
||||
}
|
||||
|
||||
export default {
|
||||
////////////////////////////////
|
||||
// Cache the form customization data if it's not already present
|
||||
// NOTE: FORM KEY **MUST** BE THE AYATYPE NAME WHERE POSSIBLE, IF NO TYPE THEN AN EXCEPTION NEEDS TO BE CODED IN
|
||||
//SERVER FormFieldReference.cs -> public static List<string> FormFieldKeys
|
||||
//
|
||||
async get(formKey, vm, forceRefresh) {
|
||||
if (
|
||||
forceRefresh ||
|
||||
!window.$gz.util.has(window.$gz.store.state.formCustomTemplate, formKey)
|
||||
) {
|
||||
//fetch and populate the store
|
||||
const res = await window.$gz.api.get("form-custom/" + formKey);
|
||||
if (res.error) {
|
||||
throw new Error(window.$gz.errorHandler.errorToString(res, vm));
|
||||
}
|
||||
|
||||
window.$gz.store.commit("setFormCustomTemplateItem", {
|
||||
formKey: formKey,
|
||||
concurrency: res.data.concurrency,
|
||||
value: addDataKeyNames(JSON.parse(res.data.template))
|
||||
});
|
||||
}
|
||||
},
|
||||
set(formKey, token, template) {
|
||||
window.$gz.store.commit("setFormCustomTemplateItem", {
|
||||
formKey: formKey,
|
||||
concurrency: token,
|
||||
value: addDataKeyNames(JSON.parse(template))
|
||||
});
|
||||
},
|
||||
getFieldTemplateValue(formKey, fieldKey) {
|
||||
if (fieldKey === undefined) {
|
||||
throw new Error(
|
||||
"ERROR form-custom-template::getFieldTemplateValue -> fieldKey not specified for template for form [" +
|
||||
formKey +
|
||||
"]"
|
||||
);
|
||||
}
|
||||
|
||||
const template = window.$gz.store.state.formCustomTemplate[formKey];
|
||||
if (template === undefined) {
|
||||
throw new Error(
|
||||
"ERROR form-custom-template::getFieldTemplateValue -> Store is missing form template for [" +
|
||||
formKey +
|
||||
"]"
|
||||
);
|
||||
}
|
||||
|
||||
//Note that not every field being requested will exist so it's valid to return undefined
|
||||
//template is an array of objects that contain a key called "fld"
|
||||
return template.find(z => z.fld == fieldKey);
|
||||
},
|
||||
getTemplateConcurrencyToken(formKey) {
|
||||
const tok =
|
||||
window.$gz.store.state.formCustomTemplate[formKey + "_concurrencyToken"];
|
||||
if (tok === undefined) {
|
||||
throw new Error(
|
||||
"ERROR form-custom-template::getTemplateConcurrencyToken -> Store is missing concurrency token for [" +
|
||||
formKey +
|
||||
"]"
|
||||
);
|
||||
}
|
||||
|
||||
return tok;
|
||||
}
|
||||
};
|
||||
672
src/api/gzapi.js
Normal file
672
src/api/gzapi.js
Normal file
@@ -0,0 +1,672 @@
|
||||
import router from "../router";
|
||||
|
||||
function stringifyPrimitive(v) {
|
||||
switch (typeof v) {
|
||||
case "string":
|
||||
return v;
|
||||
|
||||
case "boolean":
|
||||
return v ? "true" : "false";
|
||||
|
||||
case "number":
|
||||
return isFinite(v) ? v : "";
|
||||
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////
|
||||
// Try to handle an api error
|
||||
// return true if handled or false if not
|
||||
//
|
||||
function handleError(action, error, route) {
|
||||
const errorMessage =
|
||||
"API error: " + action + " route =" + route + ", message =" + error.message;
|
||||
window.$gz.store.commit("logItem", errorMessage);
|
||||
|
||||
//Handle 403 not authorized
|
||||
//popup not authorized, log, then go to HOME
|
||||
//was going to go back one page, but realized most of the time a not authorized is in
|
||||
//reaction to directly entered or opened link, not application logic driving it, so home is safest choice
|
||||
//
|
||||
if (error.message && error.message.includes("NotAuthorized")) {
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
window.$gz.translation.get("ErrorUserNotAuthorized")
|
||||
);
|
||||
router.push(window.$gz.store.state.homePage);
|
||||
|
||||
throw new Error("LT:ErrorUserNotAuthorized");
|
||||
}
|
||||
|
||||
//Handle 401 not authenticated
|
||||
if (error.message && error.message.includes("NotAuthenticated")) {
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-error",
|
||||
window.$gz.translation.get("ErrorUserNotAuthenticated")
|
||||
);
|
||||
|
||||
router.push("/login");
|
||||
|
||||
throw new Error("LT:ErrorUserNotAuthenticated");
|
||||
}
|
||||
|
||||
//is it a network error?
|
||||
//https://medium.com/@vinhlh/how-to-handle-networkerror-when-using-fetch-ff2663220435
|
||||
if (error instanceof TypeError) {
|
||||
if (
|
||||
error.message.includes("Failed to fetch") ||
|
||||
error.message.includes("NetworkError") ||
|
||||
error.message.includes("Network request failed")
|
||||
) {
|
||||
let msg = "";
|
||||
|
||||
if (window.$gz.store.state.authenticated) {
|
||||
msg = window.$gz.translation.get("ErrorServerUnresponsive");
|
||||
} else {
|
||||
msg = "Could not connect to Sockeye server ";
|
||||
}
|
||||
|
||||
msg += window.$gz.api.APIUrl("") + "\r\nError: " + error.message;
|
||||
|
||||
window.$gz.eventBus.$emit("notify-error", msg);
|
||||
//note: using translation key in square brackets
|
||||
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
//Ideally this should never get called because any issue should be addressed above
|
||||
window.$gz.errorHandler.handleFormError(error);
|
||||
}
|
||||
|
||||
export default {
|
||||
status(response) {
|
||||
//Handle expected api errors
|
||||
if (response.status == 401) {
|
||||
throw new Error("LT:ErrorUserNotAuthenticated");
|
||||
}
|
||||
|
||||
if (response.status == 403) {
|
||||
throw new Error("LT:ErrorUserNotAuthorized");
|
||||
}
|
||||
|
||||
//404 not found is an expected status not worth logging allow to bubble up
|
||||
//for client code to deal with
|
||||
if (response.status == 404) {
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
|
||||
if (response.status == 405) {
|
||||
//Probably a development error
|
||||
|
||||
throw new Error("Method Not Allowed (route issue?) " + response.url);
|
||||
}
|
||||
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return Promise.resolve(response);
|
||||
} else {
|
||||
//log unhandled api error
|
||||
window.$gz.store.commit(
|
||||
"logItem",
|
||||
"API error: status=" +
|
||||
response.status +
|
||||
", statusText=" +
|
||||
response.statusText +
|
||||
", url=" +
|
||||
response.url
|
||||
);
|
||||
//let it float up for dealing with by caller(s)
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
},
|
||||
statusEx(response) {
|
||||
//Handle expected api errors
|
||||
if (response.status == 401) {
|
||||
throw new Error("LT:ErrorUserNotAuthenticated");
|
||||
}
|
||||
|
||||
if (response.status == 403) {
|
||||
throw new Error("LT:ErrorUserNotAuthorized");
|
||||
}
|
||||
|
||||
//404 not found is an expected status not worth logging allow to bubble up
|
||||
//for client code to deal with
|
||||
if (response.status == 404) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status == 405) {
|
||||
//Probably a development error
|
||||
|
||||
throw new Error("Method Not Allowed (route issue?) " + response.url);
|
||||
}
|
||||
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return;
|
||||
} else {
|
||||
//log unhandled api error
|
||||
|
||||
window.$gz.store.commit(
|
||||
"logItem",
|
||||
"API error: status=" +
|
||||
response.status +
|
||||
", statusText=" +
|
||||
response.statusText +
|
||||
", url=" +
|
||||
response.url
|
||||
);
|
||||
}
|
||||
},
|
||||
async extractBodyEx(response) {
|
||||
if (response.status == 204) {
|
||||
//no content, nothing to process
|
||||
return response;
|
||||
}
|
||||
const contentType = response.headers.get("content-type");
|
||||
|
||||
if (!contentType) {
|
||||
return response;
|
||||
}
|
||||
if (contentType.includes("json")) {
|
||||
return await response.json();
|
||||
}
|
||||
if (contentType.includes("text/plain")) {
|
||||
return await response.text();
|
||||
}
|
||||
|
||||
if (contentType.includes("application/pdf")) {
|
||||
return await response.blob();
|
||||
}
|
||||
return response;
|
||||
},
|
||||
extractBody(response) {
|
||||
if (response.status == 204) {
|
||||
//no content, nothing to process
|
||||
return response;
|
||||
}
|
||||
const contentType = response.headers.get("content-type");
|
||||
if (!contentType) {
|
||||
return response;
|
||||
}
|
||||
if (contentType.includes("json")) {
|
||||
return response.json();
|
||||
}
|
||||
if (contentType.includes("text/plain")) {
|
||||
return response.text();
|
||||
}
|
||||
return response;
|
||||
},
|
||||
apiErrorToHumanString(apiError) {
|
||||
//empty error object?
|
||||
if (!apiError) {
|
||||
return "(E18) - apiErrorToHumanString():: Empty API eror, unknown";
|
||||
}
|
||||
//convert to readable string
|
||||
return "(E18) - " + JSON.stringify(apiError);
|
||||
},
|
||||
patchAuthorizedHeaders() {
|
||||
return {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json-patch+json",
|
||||
Authorization: "Bearer " + window.$gz.store.state.apiToken
|
||||
};
|
||||
},
|
||||
postAuthorizedHeaders() {
|
||||
return {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + window.$gz.store.state.apiToken
|
||||
//this maybe useful in future like batch ops etc so keeping as a reminder
|
||||
//,"X-AY-Import-Mode": true
|
||||
};
|
||||
},
|
||||
postUnAuthorizedHeaders() {
|
||||
return {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
};
|
||||
},
|
||||
fetchPostNoAuthOptions(data) {
|
||||
return {
|
||||
method: "post",
|
||||
mode: "cors",
|
||||
headers: this.postUnAuthorizedHeaders(),
|
||||
body: JSON.stringify(data)
|
||||
};
|
||||
},
|
||||
fetchPostOptions(data) {
|
||||
return {
|
||||
method: "post",
|
||||
mode: "cors",
|
||||
headers: this.postAuthorizedHeaders(),
|
||||
body: JSON.stringify(data)
|
||||
};
|
||||
},
|
||||
fetchPutOptions(data) {
|
||||
return {
|
||||
method: "put",
|
||||
mode: "cors",
|
||||
headers: this.postAuthorizedHeaders(),
|
||||
body: JSON.stringify(data)
|
||||
};
|
||||
},
|
||||
fetchGetOptions() {
|
||||
/* GET WITH AUTH */
|
||||
return {
|
||||
method: "get",
|
||||
mode: "cors",
|
||||
headers: this.postAuthorizedHeaders()
|
||||
};
|
||||
},
|
||||
fetchRemoveOptions() {
|
||||
/* REMOVE WITH AUTH */
|
||||
return {
|
||||
method: "delete",
|
||||
mode: "cors",
|
||||
headers: this.postAuthorizedHeaders()
|
||||
};
|
||||
},
|
||||
APIUrl(apiPath) {
|
||||
if (
|
||||
window.$gz.dev &&
|
||||
window.location.hostname == "localhost" &&
|
||||
window.location.port == "8080"
|
||||
) {
|
||||
return "http://localhost:7676/api/v8.0/" + apiPath;
|
||||
}
|
||||
|
||||
return (
|
||||
window.location.protocol +
|
||||
"//" +
|
||||
window.location.host +
|
||||
"/api/v8.0/" +
|
||||
apiPath
|
||||
);
|
||||
},
|
||||
helpUrl() {
|
||||
if (
|
||||
window.$gz.dev &&
|
||||
window.location.hostname == "localhost" &&
|
||||
window.location.port == "8080"
|
||||
) {
|
||||
return "http://localhost:7676/docs/";
|
||||
}
|
||||
return window.location.protocol + "//" + window.location.host + "/docs/";
|
||||
},
|
||||
helpUrlCustomer() {
|
||||
if (
|
||||
window.$gz.dev &&
|
||||
window.location.hostname == "localhost" &&
|
||||
window.location.port == "8080"
|
||||
) {
|
||||
return "http://localhost:7676/cust/";
|
||||
}
|
||||
return window.location.protocol + "//" + window.location.host + "/cust/";
|
||||
},
|
||||
/////////////////////////////
|
||||
// Just the server itself
|
||||
// used by profiler etc
|
||||
//
|
||||
ServerBaseUrl() {
|
||||
return this.helpUrl().replace("/docs/", "/");
|
||||
},
|
||||
/////////////////////////////
|
||||
// generic routed download URL
|
||||
//
|
||||
genericDownloadUrl(route) {
|
||||
//http://localhost:7676/api/v8/backup/download/100?t=sssss
|
||||
return this.APIUrl(route + "?t=" + window.$gz.store.state.downloadToken);
|
||||
},
|
||||
/////////////////////////////
|
||||
// report file download URL
|
||||
//
|
||||
reportDownloadUrl(fileName) {
|
||||
//http://localhost:7676/api/v8/report/download/filename.pdf?t=sssss
|
||||
|
||||
return this.APIUrl(
|
||||
"report/download/" +
|
||||
fileName +
|
||||
"?t=" +
|
||||
window.$gz.store.state.downloadToken
|
||||
);
|
||||
},
|
||||
/////////////////////////////
|
||||
// backup file download URL
|
||||
//
|
||||
backupDownloadUrl(fileName) {
|
||||
//http://localhost:7676/api/v8/backup/download/100?t=sssss
|
||||
|
||||
return this.APIUrl(
|
||||
"backup/download/" +
|
||||
fileName +
|
||||
"?t=" +
|
||||
window.$gz.store.state.downloadToken
|
||||
);
|
||||
},
|
||||
/////////////////////////////
|
||||
// attachment download URL
|
||||
//
|
||||
attachmentDownloadUrl(fileId, ctype) {
|
||||
//http://localhost:7676/api/v8/attachment/download/100?t=sssss
|
||||
//Ctype is optional and is the MIME content type, used to detect image urls at client for drag and drop ops
|
||||
//in wiki but ignored by server
|
||||
|
||||
let url =
|
||||
"attachment/download/" +
|
||||
fileId +
|
||||
"?t=" +
|
||||
window.$gz.store.state.downloadToken;
|
||||
|
||||
if (ctype && ctype.includes("image")) {
|
||||
url += "&i=1";
|
||||
}
|
||||
|
||||
return this.APIUrl(url);
|
||||
},
|
||||
/////////////////////////////
|
||||
// logo download URL
|
||||
// (size= 'small', 'medium', 'large')
|
||||
logoUrl(size) {
|
||||
//http://localhost:7676/api/v8/logo/small
|
||||
return this.APIUrl("logo/" + size);
|
||||
},
|
||||
/////////////////////////////
|
||||
// REPLACE END OF URL
|
||||
// (used to change ID in url)
|
||||
replaceAfterLastSlash(theUrl, theReplacement) {
|
||||
return theUrl.substr(0, theUrl.lastIndexOf("\\") + 1) + theReplacement;
|
||||
},
|
||||
/////////////////////////////
|
||||
// ENCODE QUERY STRING
|
||||
//
|
||||
buildQuery(obj, sep, eq, name) {
|
||||
sep = sep || "&";
|
||||
eq = eq || "=";
|
||||
if (obj === null) {
|
||||
obj = undefined;
|
||||
}
|
||||
|
||||
if (typeof obj === "object") {
|
||||
return Object.keys(obj)
|
||||
.map(function(k) {
|
||||
const ks = encodeURIComponent(stringifyPrimitive(k)) + eq;
|
||||
if (Array.isArray(obj[k])) {
|
||||
return obj[k]
|
||||
.map(function(v) {
|
||||
return ks + encodeURIComponent(stringifyPrimitive(v));
|
||||
})
|
||||
.join(sep);
|
||||
} else {
|
||||
return ks + encodeURIComponent(stringifyPrimitive(obj[k]));
|
||||
}
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join(sep);
|
||||
}
|
||||
|
||||
if (!name) return "";
|
||||
return (
|
||||
encodeURIComponent(stringifyPrimitive(name)) +
|
||||
eq +
|
||||
encodeURIComponent(stringifyPrimitive(obj))
|
||||
);
|
||||
},
|
||||
///////////////////////////////////
|
||||
// GET DATA FROM API SERVER
|
||||
//
|
||||
async get(route) {
|
||||
try {
|
||||
const that = this;
|
||||
|
||||
let r = await fetch(that.APIUrl(route), that.fetchGetOptions());
|
||||
that.statusEx(r);
|
||||
r = await that.extractBodyEx(r);
|
||||
return r;
|
||||
} catch (error) {
|
||||
//fundamental error, can't proceed with this call
|
||||
handleError("GET", error, route);
|
||||
}
|
||||
},
|
||||
|
||||
//////////////////////////////////////
|
||||
// Test delay for troubleshooting
|
||||
//
|
||||
doDelayAsync: () => {
|
||||
// eslint-disable-next-line
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => resolve("I did something"), 10000);
|
||||
});
|
||||
},
|
||||
///////////////////////////////////
|
||||
// POST / PUT DATA TO API SERVER
|
||||
//
|
||||
async upsert(route, data, isLogin = false) {
|
||||
try {
|
||||
const that = this;
|
||||
//determine if this is a new or existing record
|
||||
let fetchOptions = undefined;
|
||||
//put?
|
||||
if (data && data.concurrency) {
|
||||
fetchOptions = that.fetchPutOptions(data);
|
||||
} else {
|
||||
//post
|
||||
//ensure the route doesn't end in /0 which will happen if it's a new record
|
||||
//since the edit forms just send the url here with the ID regardless
|
||||
if (route.endsWith("/0")) {
|
||||
route = route.slice(0, -2);
|
||||
}
|
||||
if (isLogin == false) {
|
||||
fetchOptions = that.fetchPostOptions(data);
|
||||
} else {
|
||||
fetchOptions = that.fetchPostNoAuthOptions(data);
|
||||
}
|
||||
}
|
||||
|
||||
let r = await fetch(that.APIUrl(route), fetchOptions);
|
||||
that.statusEx(r);
|
||||
r = await that.extractBodyEx(r);
|
||||
return r;
|
||||
} catch (error) {
|
||||
if (isLogin == false) {
|
||||
handleError("UPSERT", error, route);
|
||||
} else {
|
||||
//specifically this is for the login page
|
||||
console.log("upser error is: ", error);
|
||||
throw new Error(window.$gz.errorHandler.errorToString(error));
|
||||
}
|
||||
}
|
||||
},
|
||||
///////////////////////////////////
|
||||
// DELETE DATA FROM API SERVER
|
||||
//
|
||||
async remove(route) {
|
||||
const that = this;
|
||||
try {
|
||||
let r = await fetch(that.APIUrl(route), that.fetchRemoveOptions());
|
||||
that.statusEx(r);
|
||||
//delete will return a body if there is an error of some kind with the request
|
||||
r = await that.extractBodyEx(r);
|
||||
return r;
|
||||
} catch (error) {
|
||||
//fundamental error, can't proceed with this call
|
||||
handleError("DELETE", error, route);
|
||||
}
|
||||
},
|
||||
///////////////////////////////////
|
||||
// PUT DATA TO API SERVER
|
||||
// (used for puts that can't have a concurrency token like above)
|
||||
async put(route, data) {
|
||||
try {
|
||||
const that = this;
|
||||
let r = await fetch(that.APIUrl(route), that.fetchPutOptions(data));
|
||||
that.statusEx(r);
|
||||
r = await that.extractBodyEx(r);
|
||||
return r;
|
||||
} catch (error) {
|
||||
handleError("PUT", error, route);
|
||||
}
|
||||
},
|
||||
///////////////////////////////////
|
||||
// POST DATA TO API SERVER
|
||||
// (used for post only routes not needing upserts)
|
||||
async post(route, data) {
|
||||
try {
|
||||
const that = this;
|
||||
let r = await fetch(that.APIUrl(route), that.fetchPostOptions(data));
|
||||
that.statusEx(r);
|
||||
r = await that.extractBodyEx(r);
|
||||
return r;
|
||||
} catch (error) {
|
||||
handleError("POST", error, route);
|
||||
}
|
||||
},
|
||||
///////////////////////////////////
|
||||
// POST FILE ATTACHMENTS
|
||||
// @param {sockId:objectid, sockType:aType, files:[array of files]}
|
||||
//
|
||||
async uploadAttachment(at) {
|
||||
const that = this;
|
||||
try {
|
||||
var files = at.files;
|
||||
var data = new FormData();
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
data.append(files[i].name, files[i]);
|
||||
}
|
||||
|
||||
data.append("AttachToAType", at.sockType);
|
||||
data.append("AttachToObjectId", at.sockId);
|
||||
data.append("Notes", at.notes);
|
||||
data.append("FileData", at.fileData);
|
||||
|
||||
//-----------------
|
||||
|
||||
const fetchOptions = {
|
||||
method: "post",
|
||||
mode: "cors",
|
||||
headers: {
|
||||
Authorization: "Bearer " + window.$gz.store.state.apiToken
|
||||
},
|
||||
body: data
|
||||
};
|
||||
|
||||
let r = await fetch(that.APIUrl("attachment"), fetchOptions);
|
||||
that.statusEx(r);
|
||||
r = await that.extractBodyEx(r);
|
||||
return r;
|
||||
} catch (error) {
|
||||
handleError("POSTATTACHMENT", error, "uploadAttachmentRoute");
|
||||
}
|
||||
},
|
||||
//////////////////////////////////////////////
|
||||
// POST (UPLOAD) FILE TO ARBITRARY ROUTE
|
||||
// for various things that require an upload
|
||||
// e.g. translation import etc
|
||||
//
|
||||
//
|
||||
async upload(route, at) {
|
||||
const that = this;
|
||||
try {
|
||||
var files = at.files;
|
||||
var data = new FormData();
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
data.append(files[i].name, files[i]);
|
||||
}
|
||||
if (at.sockType) {
|
||||
data.append("SockType", at.sockType);
|
||||
}
|
||||
if (at.sockId) {
|
||||
data.append("ObjectId", at.sockId);
|
||||
}
|
||||
if (at.notes) {
|
||||
data.append("Notes", at.notes);
|
||||
}
|
||||
data.append("FileData", at.fileData);
|
||||
|
||||
//-----------------
|
||||
|
||||
const fetchOptions = {
|
||||
method: "post",
|
||||
mode: "cors",
|
||||
headers: {
|
||||
Authorization: "Bearer " + window.$gz.store.state.apiToken
|
||||
},
|
||||
body: data
|
||||
};
|
||||
|
||||
let r = await fetch(that.APIUrl(route), fetchOptions);
|
||||
that.statusEx(r);
|
||||
r = await that.extractBodyEx(r);
|
||||
return r;
|
||||
} catch (error) {
|
||||
handleError("POSTATTACHMENT", error, route);
|
||||
}
|
||||
},
|
||||
|
||||
///////////////////////////////////
|
||||
// POST LOGO
|
||||
//
|
||||
//
|
||||
async uploadLogo(fileData, size) {
|
||||
const that = this;
|
||||
try {
|
||||
const data = new FormData();
|
||||
data.append(fileData.name, fileData);
|
||||
|
||||
//-----------------
|
||||
|
||||
const fetchOptions = {
|
||||
method: "post",
|
||||
mode: "cors",
|
||||
headers: {
|
||||
Authorization: "Bearer " + window.$gz.store.state.apiToken
|
||||
},
|
||||
body: data
|
||||
};
|
||||
|
||||
let r = await fetch(that.APIUrl("logo/" + size), fetchOptions);
|
||||
that.statusEx(r);
|
||||
r = await that.extractBodyEx(r);
|
||||
return r;
|
||||
} catch (error) {
|
||||
handleError("uploadLogo", error, "postLogoRoute");
|
||||
}
|
||||
},
|
||||
///////////////////////////////////
|
||||
// REPORT CLIENT META DATA
|
||||
//
|
||||
//
|
||||
reportClientMetaData() {
|
||||
const nowUtc = window.$gz.locale.nowUTC8601String();
|
||||
return {
|
||||
UserName: window.$gz.store.state.userName,
|
||||
UserId: window.$gz.store.state.userId,
|
||||
Authorization: "Bearer " + window.$gz.store.state.apiToken, //api token for using api methods as current user viewing report
|
||||
DownloadToken: window.$gz.store.state.downloadToken,
|
||||
TimeZoneName: window.$gz.locale.getResolvedTimeZoneName(),
|
||||
LanguageName: window.$gz.locale.getResolvedLanguage(),
|
||||
Hour12: window.$gz.locale.getHour12(),
|
||||
CurrencyName: window.$gz.locale.getCurrencyName(),
|
||||
DefaultLocale: window.$gz.locale.getResolvedLanguage().split("-", 1)[0], //kind of suspect, maybe it can be removed
|
||||
PDFDate: window.$gz.locale.utcDateToShortDateLocalized(nowUtc),
|
||||
PDFTime: window.$gz.locale.utcDateToShortTimeLocalized(nowUtc)
|
||||
};
|
||||
},
|
||||
///////////////////////////////////
|
||||
// FETCH BIZ OBJECT NAME
|
||||
//
|
||||
//
|
||||
async fetchBizObjectName(sockType, objectId) {
|
||||
const res = await this.get(`name/${sockType}/${objectId}`);
|
||||
//We never expect there to be no data here
|
||||
if (!Object.prototype.hasOwnProperty.call(res, "data")) {
|
||||
return Promise.reject(res);
|
||||
} else {
|
||||
return res.data;
|
||||
}
|
||||
}
|
||||
//---------------
|
||||
|
||||
//new functions above here
|
||||
};
|
||||
188
src/api/gzdialog.js
Normal file
188
src/api/gzdialog.js
Normal file
@@ -0,0 +1,188 @@
|
||||
let VM_LOCAL = null;
|
||||
|
||||
//Calculate a reasonable time to show the alert based on the size of the message and some sane bounds
|
||||
//https://ux.stackexchange.com/a/85898
|
||||
function CalculateDelay(msg) {
|
||||
//Min 2 seconds max 8 seconds
|
||||
return Math.min(Math.max(msg.length * 50, 3000), 8000);
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
// Dialog, toast, notification
|
||||
// utils and handlers
|
||||
//
|
||||
export default {
|
||||
///////////////////////////////////
|
||||
// WIRE UP DIALOG EVENTS
|
||||
//
|
||||
// called once from app.vue only
|
||||
//
|
||||
wireUpEventHandlers(vm) {
|
||||
//###########################################
|
||||
//Notifications: pops up and slowly disappears
|
||||
//ACTUAL UI IN gznotify.vue
|
||||
//###########################################
|
||||
|
||||
///////////
|
||||
//ERROR
|
||||
window.$gz.eventBus.$on("notify-error", function handleNotifyWarn(
|
||||
msg,
|
||||
helpUrl
|
||||
) {
|
||||
//log full message
|
||||
window.$gz.store.commit("logItem", "notify-error: " + msg);
|
||||
//trim really long message as it's likely useless beyond the first few lines (stack trace etc)
|
||||
msg = msg.substring(0, 600);
|
||||
vm.$root.$gznotify({
|
||||
message: msg,
|
||||
type: "error",
|
||||
timeout: CalculateDelay(msg),
|
||||
helpUrl: helpUrl
|
||||
});
|
||||
});
|
||||
|
||||
///////////
|
||||
//WARNING
|
||||
window.$gz.eventBus.$on("notify-warning", function handleNotifyWarn(
|
||||
msg,
|
||||
helpUrl
|
||||
) {
|
||||
window.$gz.store.commit("logItem", "notify-warning: " + msg);
|
||||
msg = msg.substring(0, 600);
|
||||
vm.$root.$gznotify({
|
||||
message: msg,
|
||||
type: "warning",
|
||||
timeout: CalculateDelay(msg),
|
||||
helpUrl: helpUrl
|
||||
});
|
||||
});
|
||||
|
||||
///////////
|
||||
//INFO
|
||||
window.$gz.eventBus.$on("notify-info", function handleNotifyInfo(
|
||||
msg,
|
||||
helpUrl
|
||||
) {
|
||||
window.$gz.store.commit("logItem", "notify-info: " + msg);
|
||||
msg = msg.substring(0, 600);
|
||||
vm.$root.$gznotify({
|
||||
message: msg,
|
||||
type: "info",
|
||||
timeout: CalculateDelay(msg),
|
||||
helpUrl: helpUrl
|
||||
});
|
||||
});
|
||||
|
||||
///////////
|
||||
//SUCCESS
|
||||
window.$gz.eventBus.$on("notify-success", function handleNotifySuccess(
|
||||
msg,
|
||||
helpUrl
|
||||
) {
|
||||
vm.$root.$gznotify({
|
||||
message: msg,
|
||||
type: "success",
|
||||
timeout: CalculateDelay(msg),
|
||||
helpUrl: helpUrl
|
||||
});
|
||||
});
|
||||
|
||||
VM_LOCAL = vm;
|
||||
},
|
||||
//###########################################
|
||||
//CONFIRMATION DIALOGS
|
||||
//ACTUAL UI IN gzconfirm.vue
|
||||
//###########################################
|
||||
/////////////////////////////////////
|
||||
// Are you sure you want to delete?
|
||||
//
|
||||
confirmDelete() {
|
||||
return VM_LOCAL.$root.$gzconfirm({
|
||||
message: window.$gz.translation.get("DeletePrompt"),
|
||||
yesButtonText: window.$gz.translation.get("Delete"),
|
||||
noButtonText: window.$gz.translation.get("Cancel"),
|
||||
type: "warning"
|
||||
});
|
||||
},
|
||||
/////////////////////////////////////
|
||||
// Are you sure you want to leave unsaved?
|
||||
//
|
||||
confirmLeaveUnsaved() {
|
||||
return VM_LOCAL.$root.$gzconfirm({
|
||||
message: window.$gz.translation.get("AreYouSureUnsavedChanges"),
|
||||
yesButtonText: window.$gz.translation.get("Leave"),
|
||||
noButtonText: window.$gz.translation.get("Cancel"),
|
||||
type: "warning"
|
||||
});
|
||||
},
|
||||
/////////////////////////////////////
|
||||
// Display LT message with wait for ok
|
||||
//
|
||||
displayLTErrorMessage(tKeyText, tKeyTitle = undefined) {
|
||||
return VM_LOCAL.$root.$gzconfirm({
|
||||
message: tKeyText ? window.$gz.translation.get(tKeyText) : "",
|
||||
title: tKeyTitle ? window.$gz.translation.get(tKeyTitle) : "",
|
||||
yesButtonText: window.$gz.translation.get("OK"),
|
||||
type: "error"
|
||||
});
|
||||
},
|
||||
/////////////////////////////////////
|
||||
// Display LT message with wait for ok
|
||||
//
|
||||
displayLTModalNotificationMessage(
|
||||
tKeyText,
|
||||
tKeyTitle = undefined,
|
||||
ttype = "info",
|
||||
tHelpUrl = undefined
|
||||
) {
|
||||
return VM_LOCAL.$root.$gzconfirm({
|
||||
message: tKeyText ? window.$gz.translation.get(tKeyText) : "",
|
||||
title: tKeyTitle ? window.$gz.translation.get(tKeyTitle) : "",
|
||||
yesButtonText: window.$gz.translation.get("OK"),
|
||||
type: ttype,
|
||||
helpUrl: tHelpUrl
|
||||
});
|
||||
},
|
||||
/////////////////////////////////////
|
||||
// Custom confirmation
|
||||
//
|
||||
confirmGeneric(tKey, ttype = "info") {
|
||||
return VM_LOCAL.$root.$gzconfirm({
|
||||
message: window.$gz.translation.get(tKey),
|
||||
yesButtonText: window.$gz.translation.get("OK"),
|
||||
noButtonText: window.$gz.translation.get("Cancel"),
|
||||
type: ttype
|
||||
});
|
||||
},
|
||||
/////////////////////////////////////
|
||||
// Custom confirmation pre-translated
|
||||
//
|
||||
confirmGenericPreTranslated(msg, ttype = "info") {
|
||||
return VM_LOCAL.$root.$gzconfirm({
|
||||
message: msg,
|
||||
yesButtonText: window.$gz.translation.get("OK"),
|
||||
noButtonText: window.$gz.translation.get("Cancel"),
|
||||
type: ttype
|
||||
});
|
||||
},
|
||||
/////////////////////////////////////
|
||||
// Custom confirmation no translation
|
||||
// with all options available
|
||||
//
|
||||
displayNoTranslationModalNotificationMessage(
|
||||
tKeyText,
|
||||
tKeyTitle = undefined,
|
||||
ttype = "info",
|
||||
tHelpUrl = undefined
|
||||
) {
|
||||
return VM_LOCAL.$root.$gzconfirm({
|
||||
message: tKeyText,
|
||||
title: tKeyTitle,
|
||||
yesButtonText: window.$gz.translation.get("OK"),
|
||||
type: ttype,
|
||||
helpUrl: tHelpUrl
|
||||
});
|
||||
}
|
||||
|
||||
//new functions above here
|
||||
};
|
||||
1002
src/api/gzform.js
Normal file
1002
src/api/gzform.js
Normal file
File diff suppressed because it is too large
Load Diff
438
src/api/gzmenu.js
Normal file
438
src/api/gzmenu.js
Normal file
@@ -0,0 +1,438 @@
|
||||
/////////////////////////////////
|
||||
// Menu utils and handlers
|
||||
//
|
||||
export default {
|
||||
///////////////////////////////////////////
|
||||
// TECH SUPPORT / CONTACT FORUM URL
|
||||
//
|
||||
contactSupportUrl() {
|
||||
const dbId = encodeURIComponent(
|
||||
window.$gz.store.state.globalSettings.serverDbId
|
||||
);
|
||||
const company = encodeURIComponent(
|
||||
window.$gz.store.state.globalSettings.company
|
||||
);
|
||||
return `https://contact.ayanova.com/contact?dbid=${dbId}&company=${company}`;
|
||||
},
|
||||
///////////////////////////////
|
||||
// CHANGE HANDLER
|
||||
//
|
||||
// Deal with a menu change request
|
||||
// called from App.vue
|
||||
handleMenuChange(vm, ctx) {
|
||||
const UTILITY_TYPES = [
|
||||
window.$gz.type.NoType,
|
||||
window.$gz.type.Global,
|
||||
window.$gz.type.NoType,
|
||||
window.$gz.type.ServerState,
|
||||
window.$gz.type.License,
|
||||
window.$gz.type.LogFile,
|
||||
window.$gz.type.ServerJob,
|
||||
window.$gz.type.TrialSeeder,
|
||||
window.$gz.type.ServerMetrics,
|
||||
window.$gz.type.UserOptions,
|
||||
window.$gz.type.FormCustom,
|
||||
window.$gz.type.DataListSavedFilter,
|
||||
window.$gz.type.GlobalOps,
|
||||
window.$gz.type.BizMetrics,
|
||||
window.$gz.type.Backup,
|
||||
window.$gz.type.Notification,
|
||||
window.$gz.type.NotifySubscription
|
||||
];
|
||||
|
||||
vm.appBar.isMain = ctx.isMain;
|
||||
vm.appBar.icon = ctx.icon;
|
||||
|
||||
vm.appBar.title = ""; //this prevents fou[translated]c
|
||||
vm.appBar.readOnly = ctx.readOnly;
|
||||
|
||||
if (ctx.readOnly === true) {
|
||||
vm.appBar.color = "readonlybanner";
|
||||
} else {
|
||||
vm.appBar.color = ctx.isMain ? "primary" : "secondary";
|
||||
}
|
||||
|
||||
//ctx.title if set is a Translation key
|
||||
//ctx.formData.recordName is the object name or serial number or whatever identifies it uniquely
|
||||
let recordName = "";
|
||||
if (
|
||||
ctx &&
|
||||
ctx.formData &&
|
||||
ctx.formData.recordName &&
|
||||
ctx.formData.recordName != "null" //some forms (part) present "null" as the record name due to attempts to format a name so if that's the case just turn it into null here to bypass
|
||||
) {
|
||||
recordName = ctx.formData.recordName;
|
||||
}
|
||||
const hasRecordName = !window.$gz.util.stringIsNullOrEmpty(recordName);
|
||||
if (ctx.title) {
|
||||
//it has a title translation key
|
||||
const translatedTitle = vm.$sock.t(ctx.title);
|
||||
if (hasRecordName) {
|
||||
//recordname takes all precedence in AppBar in order to conserve space (narrow view etc)
|
||||
//also it just looks cleaner, the icon is already there to indicate where the user is at
|
||||
vm.appBar.title = recordName;
|
||||
document.title = `${recordName} - ${translatedTitle} Sockeye `;
|
||||
} else {
|
||||
vm.appBar.title = translatedTitle;
|
||||
document.title = `${translatedTitle} ${recordName}`;
|
||||
}
|
||||
} else {
|
||||
if (hasRecordName) {
|
||||
//not title but has record name
|
||||
vm.appBar.title = recordName;
|
||||
document.title = `${recordName} Sockeye`;
|
||||
} else {
|
||||
document.title = "Sockeye";
|
||||
}
|
||||
}
|
||||
|
||||
//Parse the formdata if present
|
||||
//FORMDATA is OPTIONAL and only required for forms that need to allow
|
||||
//viewing object history, attachments, custom fields, etc that kind of thing
|
||||
//usually CORE objects with an id, NOT utility type forms
|
||||
let formSockType = 0;
|
||||
let formRecordId = 0;
|
||||
if (ctx.formData) {
|
||||
if (ctx.formData.sockType != null) {
|
||||
formSockType = ctx.formData.sockType;
|
||||
}
|
||||
if (ctx.formData.recordId != null) {
|
||||
formRecordId = ctx.formData.recordId;
|
||||
}
|
||||
}
|
||||
|
||||
//flag for if it's wikiable, reviewable, attachable, searchable, historical
|
||||
const isCoreBizObject = formSockType != 0 && formRecordId != 0;
|
||||
|
||||
//set the help url if presented or default to the User section intro
|
||||
vm.appBar.helpUrl = ctx.helpUrl ? ctx.helpUrl : "user-intro";
|
||||
|
||||
vm.appBar.menuItems = [];
|
||||
|
||||
//CONTEXT TOP PORTION
|
||||
//populate the context portion of the menu so handle accordingly
|
||||
if (ctx.menuItems) {
|
||||
vm.appBar.menuItems = ctx.menuItems;
|
||||
}
|
||||
|
||||
//STANDARD BIZ OBJECT OPTIONS
|
||||
//NOTE: This applies equally to all core business object types that are basically real world and have an id and a type (all are wikiable, attachable and reviewable)
|
||||
//Not utility type objects like datalist etc
|
||||
//there will be few exceptions so they will be coded in later if needed but assume anything with an id and a type
|
||||
if (isCoreBizObject && !ctx.hideCoreBizStandardOptions) {
|
||||
//"Review" was follow up type of schedule marker
|
||||
//basically it's now a "Reminder" type of object but it's own thing with separate collection
|
||||
|
||||
vm.appBar.menuItems.push({
|
||||
title: "Review",
|
||||
icon: "$sockiCalendarCheck",
|
||||
key: "app:review",
|
||||
data: {
|
||||
sockType: formSockType,
|
||||
recordId: formRecordId,
|
||||
recordName: recordName
|
||||
}
|
||||
});
|
||||
|
||||
//AFAIK right now any item with an id and a type can have a history
|
||||
//anything not would be the exception rather than the rule
|
||||
vm.appBar.menuItems.push({
|
||||
title: "History",
|
||||
icon: "$sockiHistory",
|
||||
key: "app:history",
|
||||
data: { sockType: formSockType, recordId: formRecordId }
|
||||
});
|
||||
}
|
||||
|
||||
//CUSTOMIZE
|
||||
//set custom fields and link to translation text editor
|
||||
|
||||
if (
|
||||
isCoreBizObject &&
|
||||
ctx.formData &&
|
||||
ctx.formData.formCustomTemplateKey != undefined &&
|
||||
window.$gz.role.hasRole([
|
||||
window.$gz.role.AUTHORIZATION_ROLES.BizAdmin,
|
||||
window.$gz.role.AUTHORIZATION_ROLES.BizAdminRestricted
|
||||
])
|
||||
) {
|
||||
//NOTE: BizAdmin can edit, BizAdminRestricted can read only
|
||||
//add customize menu item
|
||||
|
||||
//customize
|
||||
vm.appBar.menuItems.push({
|
||||
title: "Customize",
|
||||
icon: "$sockiCustomize",
|
||||
data: ctx.formData.formCustomTemplateKey,
|
||||
key: "app:customize"
|
||||
});
|
||||
}
|
||||
|
||||
//GLOBAL BOTTOM PORTION
|
||||
|
||||
//SEARCH
|
||||
//all forms except the search form
|
||||
if (!ctx.hideSearch && !UTILITY_TYPES.includes(formSockType)) {
|
||||
//For all forms but not on the search form itself; if this is necessary for others then make a nosearch or something flag controlled by incoming ctx but if not then this should suffice
|
||||
vm.appBar.menuItems.push({
|
||||
title: "Search",
|
||||
icon: "$sockiSearch",
|
||||
key: "app:search",
|
||||
data: formSockType
|
||||
});
|
||||
}
|
||||
|
||||
//HELP
|
||||
vm.appBar.menuItems.push({
|
||||
title: "MenuHelp",
|
||||
icon: "$sockiQuestionCircle",
|
||||
key: "app:help",
|
||||
data: vm.appBar.helpUrl
|
||||
});
|
||||
|
||||
//ABOUT
|
||||
if (!isCoreBizObject && ctx.helpUrl != "sock-about") {
|
||||
vm.appBar.menuItems.push({
|
||||
title: "HelpAboutSockeye",
|
||||
icon: "$sockiInfoCircle",
|
||||
key: "app:nav:abt",
|
||||
data: "sock-about"
|
||||
});
|
||||
}
|
||||
},
|
||||
//Unused to date of beta 0.9
|
||||
// ///////////////////////////////
|
||||
// // CHANGE HANDLER
|
||||
// //
|
||||
// // Deal with a menu item update request
|
||||
// // called from App.vue
|
||||
// handleReplaceMenuItem(vm, newItem) {
|
||||
// if (!vm.appBar.menuItems || !newItem) {
|
||||
// return;
|
||||
// }
|
||||
// //Find the key that is in the collection and replace it
|
||||
// for (let i = 0; i < vm.appBar.menuItems.length; i++) {
|
||||
// if (vm.appBar.menuItems[i].key == newItem.key) {
|
||||
// //NOTE: since we are adding a new object, it has no reactivity in it so we need to use the Vue.Set to set it which
|
||||
// //automatically adds the setters and getters that trigger reactivity
|
||||
// //If it was set directly on the array it wouldn't update the UI
|
||||
// vm.$set(vm.appBar.menuItems, i, newItem);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
//////////////////////////////////////////////
|
||||
// LAST REPORT CHANGE HANDLER
|
||||
// update / add last report menu item
|
||||
//
|
||||
handleUpsertLastReport(vm, newItem) {
|
||||
if (!vm.appBar.menuItems || !newItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
window.$gz.eventBus.$emit("menu-upsert-last-report", {
|
||||
title: reportSelected.name,
|
||||
notrans: true,
|
||||
icon: "$sockiFileAlt",
|
||||
key: formKey + ":report:" + reportSelected.id,
|
||||
vm: vm
|
||||
});
|
||||
*/
|
||||
let key = null;
|
||||
//Find the last report key and update it if present
|
||||
for (let i = 0; i < vm.appBar.menuItems.length; i++) {
|
||||
key = vm.appBar.menuItems[i].key;
|
||||
if (key && key.includes(":report:")) {
|
||||
vm.appBar.menuItems[i].key = newItem.key;
|
||||
vm.appBar.menuItems[i].title = newItem.title;
|
||||
return;
|
||||
}
|
||||
}
|
||||
//No prior last report so slot it in under the report one
|
||||
for (let i = 0; i < vm.appBar.menuItems.length; i++) {
|
||||
key = vm.appBar.menuItems[i].key;
|
||||
if (key && key.endsWith(":report")) {
|
||||
vm.appBar.menuItems.splice(i + 1, 0, newItem);
|
||||
}
|
||||
}
|
||||
},
|
||||
///////////////////////////////
|
||||
// ENABLE / DISABLE HANDLER
|
||||
//
|
||||
// Deal with a menu item enable / disable
|
||||
// called from App.vue
|
||||
handleDisableMenuItem(vm, key, disabled) {
|
||||
if (!vm.appBar.menuItems || !key) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Find the menu item and set it to disabled and recolor it to disabled color and return
|
||||
for (let i = 0; i < vm.appBar.menuItems.length; i++) {
|
||||
const menuItem = vm.appBar.menuItems[i];
|
||||
if (menuItem.key == key) {
|
||||
vm.$set(vm.appBar.menuItems[i], "disabled", disabled);
|
||||
//menuItem.disabled = disabled;
|
||||
vm.$set(vm.appBar.menuItems[i], "color", disabled ? "disabled" : "");
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
///////////////////////////////
|
||||
// CHANGE ICON HANDLER
|
||||
// Change icon dymanically
|
||||
// (note, can pass null for new icon to clear it)
|
||||
//
|
||||
handleChangeMenuItemIcon(vm, key, newIcon) {
|
||||
if (!vm.appBar.menuItems || !key) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Find the menu item and change it's icon
|
||||
for (let i = 0; i < vm.appBar.menuItems.length; i++) {
|
||||
const menuItem = vm.appBar.menuItems[i];
|
||||
if (menuItem.key == key) {
|
||||
vm.$set(vm.appBar.menuItems[i], "icon", newIcon);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
///////////////////////////////
|
||||
// APP (GLOBAL) CLICK HANDLER
|
||||
//
|
||||
// Deal with a menu change request
|
||||
// called from App.vue
|
||||
handleAppClick(vm, menuItem) {
|
||||
//Key will start with the string "app:" if it's a global application command that should be handled here,
|
||||
//otherwise it's a local command for a local form only
|
||||
//If there is any extended information required for the command it will be in the data property of the menu item
|
||||
//split a key into component parts, part one is the responsible party, part two is the command, part three only exists to make it unique if necessary
|
||||
//each part is separated by a colon
|
||||
|
||||
//Handle different items
|
||||
const item = this.parseMenuItem(menuItem);
|
||||
if (!item.disabled && item.owner == "app") {
|
||||
switch (item.key) {
|
||||
case "help":
|
||||
if (item.data.includes("~customer~")) {
|
||||
window.open(
|
||||
window.$gz.api.helpUrlCustomer() +
|
||||
item.data.replace("~customer~", ""),
|
||||
"_blank"
|
||||
);
|
||||
} else {
|
||||
window.open(window.$gz.api.helpUrl() + item.data, "_blank");
|
||||
}
|
||||
break;
|
||||
|
||||
case "search":
|
||||
vm.$router.push({
|
||||
name: "home-search",
|
||||
params: { socktype: item.data }
|
||||
});
|
||||
break;
|
||||
case "review":
|
||||
//go to list
|
||||
// path: "/home-reviews/:aType?/:objectId?",
|
||||
vm.$router.push({
|
||||
name: "home-reviews",
|
||||
params: {
|
||||
aType: window.$gz.util.stringToIntOrNull(item.data.sockType),
|
||||
objectId: window.$gz.util.stringToIntOrNull(item.data.recordId),
|
||||
name: item.data.recordName
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "history":
|
||||
vm.$router.push({
|
||||
name: "sock-history",
|
||||
params: {
|
||||
socktype: item.data.sockType,
|
||||
recordid: item.data.recordId
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "customize":
|
||||
vm.$router.push({
|
||||
name: "sock-customize",
|
||||
params: { formCustomTemplateKey: item.data }
|
||||
});
|
||||
break;
|
||||
case "nav":
|
||||
vm.$router.push({ name: item.data });
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
"gzmenu:handleAppClick - unrecognized command [" +
|
||||
menuItem.key +
|
||||
"]"
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
///////////////////////////////
|
||||
// PARSE MENU ITEM CLICK
|
||||
//
|
||||
// parse out the parts of a
|
||||
// menu item from a click event
|
||||
//
|
||||
parseMenuItem(menuItem) {
|
||||
//format is "AREA:KEY:UNIQUEID"
|
||||
//and data is in data portion
|
||||
const keyparts = menuItem.key.split(":");
|
||||
const ret = {
|
||||
owner: keyparts[0],
|
||||
key: keyparts[1],
|
||||
data: menuItem.data,
|
||||
disabled: menuItem.disabled,
|
||||
vm: menuItem.vm ? menuItem.vm : null
|
||||
};
|
||||
if (keyparts.length > 2) {
|
||||
ret.id = keyparts[2];
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
///////////////////////////////////
|
||||
// WIRE UP MENU EVENTS
|
||||
//
|
||||
// called once from app.vue only
|
||||
//
|
||||
wireUpEventHandlers(vm) {
|
||||
const that = this;
|
||||
window.$gz.eventBus.$on("menu-change", function handleMenuChange(ctx) {
|
||||
that.handleMenuChange(vm, ctx);
|
||||
});
|
||||
|
||||
window.$gz.eventBus.$on(
|
||||
"menu-upsert-last-report",
|
||||
function handleUpsertLastReport(newItem) {
|
||||
that.handleUpsertLastReport(vm, newItem);
|
||||
}
|
||||
);
|
||||
|
||||
window.$gz.eventBus.$on("menu-disable-item", function handleDisableMenuItem(
|
||||
key
|
||||
) {
|
||||
that.handleDisableMenuItem(vm, key, true);
|
||||
});
|
||||
|
||||
window.$gz.eventBus.$on("menu-enable-item", function handleDisableMenuItem(
|
||||
key
|
||||
) {
|
||||
that.handleDisableMenuItem(vm, key, false);
|
||||
});
|
||||
|
||||
window.$gz.eventBus.$on(
|
||||
"menu-change-item-icon",
|
||||
function handleChangeMenuItemIcon(key, newIcon) {
|
||||
that.handleChangeMenuItemIcon(vm, key, newIcon);
|
||||
}
|
||||
);
|
||||
|
||||
window.$gz.eventBus.$on("menu-click", function handleMenuClick(menuitem) {
|
||||
that.handleAppClick(vm, menuitem);
|
||||
});
|
||||
}
|
||||
//new functions above here
|
||||
};
|
||||
931
src/api/gzutil.js
Normal file
931
src/api/gzutil.js
Normal file
@@ -0,0 +1,931 @@
|
||||
/////////////////////////////////
|
||||
// General utility library
|
||||
//
|
||||
|
||||
const icons = {
|
||||
image: "$sockiFileImage",
|
||||
pdf: "$sockiFilePdf",
|
||||
word: "$sockiFileWord",
|
||||
powerpoint: "$sockiFilePowerpoint",
|
||||
excel: "$sockiFileExcel",
|
||||
csv: "$sockiFileCsv",
|
||||
audio: "$sockiFileAudio",
|
||||
video: "$sockiFileVidio",
|
||||
archive: "$sockiFileArchive",
|
||||
code: "$sockiFileCode",
|
||||
text: "$sockiFileAlt",
|
||||
file: "$sockiFile"
|
||||
};
|
||||
const mimeTypes = {
|
||||
"image/gif": icons.image,
|
||||
"image/jpeg": icons.image,
|
||||
"image/png": icons.image,
|
||||
"image/webp": icons.image,
|
||||
|
||||
"application/pdf": icons.pdf,
|
||||
|
||||
"application/msword": icons.word,
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document":
|
||||
icons.word,
|
||||
|
||||
"application/mspowerpoint": icons.powerpoint,
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation":
|
||||
icons.powerpoint,
|
||||
|
||||
"application/msexcel": icons.excel,
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
|
||||
icons.excel,
|
||||
|
||||
"text/csv": icons.csv,
|
||||
|
||||
"audio/aac": icons.audio,
|
||||
"audio/wav": icons.audio,
|
||||
"audio/mpeg": icons.audio,
|
||||
"audio/mp4": icons.audio,
|
||||
"audio/ogg": icons.audio,
|
||||
|
||||
"video/x-msvideo": icons.video,
|
||||
"video/mpeg": icons.video,
|
||||
"video/mp4": icons.video,
|
||||
"video/ogg": icons.video,
|
||||
"video/quicktime": icons.video,
|
||||
"video/webm": icons.video,
|
||||
|
||||
"application/gzip": icons.archive,
|
||||
"application/zip": icons.archive,
|
||||
"application/x-tar": icons.archive,
|
||||
|
||||
"text/css": icons.code,
|
||||
"text/html": icons.code,
|
||||
"text/javascript": icons.code,
|
||||
"application/javascript": icons.code,
|
||||
|
||||
"text/plain": icons.text,
|
||||
"text/richtext": icons.text,
|
||||
"text/rtf": icons.text,
|
||||
"application/rtf": icons.text,
|
||||
"application/json": icons.text
|
||||
};
|
||||
|
||||
const extensions = {
|
||||
gif: icons.image,
|
||||
jpeg: icons.image,
|
||||
jpg: icons.image,
|
||||
png: icons.image,
|
||||
webp: icons.image,
|
||||
|
||||
pdf: icons.pdf,
|
||||
|
||||
doc: icons.word,
|
||||
docx: icons.word,
|
||||
|
||||
ppt: icons.powerpoint,
|
||||
pptx: icons.powerpoint,
|
||||
|
||||
xls: icons.excel,
|
||||
xlsx: icons.excel,
|
||||
|
||||
csv: icons.csv,
|
||||
|
||||
aac: icons.audio,
|
||||
mp3: icons.audio,
|
||||
ogg: icons.audio,
|
||||
|
||||
avi: icons.video,
|
||||
flv: icons.video,
|
||||
mkv: icons.video,
|
||||
mp4: icons.video,
|
||||
|
||||
gz: icons.archive,
|
||||
zip: icons.archive,
|
||||
tar: icons.archive,
|
||||
"7z": icons.archive,
|
||||
|
||||
css: icons.code,
|
||||
html: icons.code,
|
||||
js: icons.code,
|
||||
|
||||
txt: icons.text,
|
||||
json: icons.text,
|
||||
rtf: icons.text
|
||||
};
|
||||
|
||||
export default {
|
||||
///////////////////////////////
|
||||
// CLEAN OBJECT
|
||||
// Clear all properties from object without resorting to assigning a new object (o={})
|
||||
// which can be problematic in some cases (IE bugs, watched data items in forms etc)
|
||||
|
||||
removeAllPropertiesFromObject: function(o) {
|
||||
for (let variableKey in o) {
|
||||
if (Object.prototype.hasOwnProperty.call(o, variableKey)) {
|
||||
delete o[variableKey];
|
||||
}
|
||||
}
|
||||
},
|
||||
///////////////////////////////
|
||||
// DEEP COPY FOR API UPDATE
|
||||
// Deep copy an object skipping all *Viz and named properties from object
|
||||
//
|
||||
deepCopySkip: function(source, skipNames) {
|
||||
if (skipNames == null) {
|
||||
skipNames = [];
|
||||
}
|
||||
let o = {};
|
||||
for (let key in source) {
|
||||
if (
|
||||
!key.endsWith("Viz") &&
|
||||
!skipNames.some(x => x == key) &&
|
||||
Object.prototype.hasOwnProperty.call(source, key)
|
||||
) {
|
||||
o[key] = source[key];
|
||||
}
|
||||
}
|
||||
return o;
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy a string to clipboard
|
||||
* @param {String} string The string to be copied to clipboard
|
||||
* @return {Boolean} returns a boolean correspondent to the success of the copy operation.
|
||||
* Modified from an example here: https://stackoverflow.com/a/53951634/8939
|
||||
* Basically a fallback if navigator.clipboard is not available
|
||||
*/
|
||||
copyToClipboard: function(string) {
|
||||
let textarea;
|
||||
let result;
|
||||
|
||||
if (navigator && navigator.clipboard) {
|
||||
navigator.clipboard.writeText(string);
|
||||
} else {
|
||||
try {
|
||||
textarea = document.createElement("textarea");
|
||||
textarea.setAttribute("readonly", true);
|
||||
textarea.setAttribute("contenteditable", true);
|
||||
textarea.style.position = "fixed"; // prevent scroll from jumping to the bottom when focus is set.
|
||||
textarea.value = string;
|
||||
|
||||
document.body.appendChild(textarea);
|
||||
|
||||
textarea.focus();
|
||||
textarea.select();
|
||||
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(textarea);
|
||||
|
||||
const sel = window.getSelection();
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
|
||||
textarea.setSelectionRange(0, textarea.value.length);
|
||||
result = document.execCommand("copy");
|
||||
} catch (err) {
|
||||
result = null;
|
||||
} finally {
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
|
||||
// manual copy fallback using prompt
|
||||
if (!result) {
|
||||
const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0;
|
||||
const copyHotkey = isMac ? "⌘C" : "CTRL+C";
|
||||
result = prompt(`Press ${copyHotkey}`, string);
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
///////////////////////////////
|
||||
// ROUNDING
|
||||
// //https://medium.com/swlh/how-to-round-to-a-certain-number-of-decimal-places-in-javascript-ed74c471c1b8
|
||||
roundAccurately: function(number, decimalPlaces) {
|
||||
if (!number || number == 0 || Number.isNaN(number)) {
|
||||
return number;
|
||||
}
|
||||
const wasNegative = number < 0;
|
||||
if (wasNegative) {
|
||||
number = Math.abs(number); //make sure it's positive because rounding negative numbers is weird in JS
|
||||
}
|
||||
number = Number(
|
||||
Math.round(number + "e" + decimalPlaces) + "e-" + decimalPlaces
|
||||
);
|
||||
if (wasNegative) {
|
||||
number = 0 - number;
|
||||
}
|
||||
return number;
|
||||
},
|
||||
///////////////////////////////
|
||||
// CLEAN TAG NAME
|
||||
// Clean up a tag with same rules as server
|
||||
//
|
||||
normalizeTag: function(tagName) {
|
||||
if (!tagName || tagName == "") {
|
||||
return null;
|
||||
}
|
||||
tagName = tagName.toLowerCase();
|
||||
|
||||
//spaces to dashes
|
||||
tagName = tagName.replace(/ /gi, "-");
|
||||
|
||||
//multiple dashes to single dashes
|
||||
tagName = tagName.replace(/-+/g, "-");
|
||||
|
||||
//ensure doesn't start or end with a dash
|
||||
tagName = this.trimSpecific(tagName, "-");
|
||||
|
||||
//No longer than 255 characters
|
||||
tagName = tagName.length > 255 ? tagName.substr(0, 255 - 1) : tagName;
|
||||
|
||||
return tagName;
|
||||
},
|
||||
///////////////////////////////
|
||||
// Quick hash for trivial purposes
|
||||
// not cryptographic
|
||||
// https://stackoverflow.com/a/7616484/8939
|
||||
//
|
||||
quickHash: function(theString) {
|
||||
let hash = 0;
|
||||
let i;
|
||||
let chr;
|
||||
if (theString.length === 0) return hash;
|
||||
for (i = 0; i < theString.length; i++) {
|
||||
chr = theString.charCodeAt(i);
|
||||
hash = (hash << 5) - hash + chr;
|
||||
hash |= 0; // Convert to 32bit integer
|
||||
}
|
||||
return hash;
|
||||
},
|
||||
|
||||
////////////////////////////////////////
|
||||
// Random password / login generator
|
||||
// https://stackoverflow.com/a/51540480/8939
|
||||
// using 32 character (128 bit) as default
|
||||
//
|
||||
getRandomPassword: function() {
|
||||
const wishlist = "0123456789abcdefghijkmnopqrstuvwxyz";
|
||||
|
||||
return Array.from(crypto.getRandomValues(new Uint32Array(32)))
|
||||
.map(x => wishlist[x % wishlist.length])
|
||||
.join("");
|
||||
},
|
||||
///////////////////////////////
|
||||
// CONVERT STRING TO BOOLEAN
|
||||
// https://stackoverflow.com/a/1414175/8939
|
||||
//
|
||||
stringToBoolean: function(string) {
|
||||
switch (string.toLowerCase().trim()) {
|
||||
case "true":
|
||||
case "yes":
|
||||
case "1":
|
||||
return true;
|
||||
case "false":
|
||||
case "no":
|
||||
case "0":
|
||||
case null:
|
||||
return false;
|
||||
default:
|
||||
return Boolean(string);
|
||||
}
|
||||
}, ///////////////////////////////
|
||||
// CONVERT STRING TO FLOAT
|
||||
// https://stackoverflow.com/a/9409894/8939
|
||||
//
|
||||
stringToFloat: function(string) {
|
||||
//null or empty then zero
|
||||
if (!string) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
//A number already then parse and return
|
||||
if (this.isNumeric(string)) {
|
||||
if (Number.isNaN(string)) {
|
||||
return 0;
|
||||
}
|
||||
return parseFloat(string);
|
||||
}
|
||||
|
||||
//Not a string at all?
|
||||
if (!this.isString(string)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const ret = parseFloat(string.replace(/[^\d.-]/g, ""));
|
||||
if (Number.isNaN(ret)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
///////////////////////////////
|
||||
// Is negative number
|
||||
//
|
||||
//
|
||||
isNegative: function(v) {
|
||||
//null or empty then zero
|
||||
if (!v || v == 0 || Number.isNaN(v)) {
|
||||
return false;
|
||||
}
|
||||
return parseFloat(v) < 0;
|
||||
},
|
||||
///////////////////////////////
|
||||
// Splice a string
|
||||
//changes the content of a string by removing a range of
|
||||
// characters and/or adding new characters.
|
||||
//
|
||||
// @param {String} source string
|
||||
// @param {number} start Index at which to start changing the string.
|
||||
// @param {number} delCount An integer indicating the number of old chars to remove.
|
||||
// @param {string} newSubStr The String that is spliced in.
|
||||
// @return {string} A new string with the spliced substring.
|
||||
stringSplice: function(source, start, delCount, newSubStr) {
|
||||
if (source == null || source == "") {
|
||||
if (newSubStr) {
|
||||
return newSubStr;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
return (
|
||||
source.slice(0, start) +
|
||||
newSubStr +
|
||||
source.slice(start + Math.abs(delCount))
|
||||
);
|
||||
},
|
||||
///////////////////////////////
|
||||
// Truncate a string
|
||||
//truncates and adds ellipses
|
||||
//
|
||||
// @param {String} source string
|
||||
// @param {number} length desired
|
||||
// @return {string} A new string truncated with ellipses at end
|
||||
truncateString: function(s, len) {
|
||||
if (this.stringIsNullOrEmpty(s)) {
|
||||
return s;
|
||||
}
|
||||
if (s.length > len) {
|
||||
return s.substring(0, len) + "...";
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
},
|
||||
///////////////////////////////
|
||||
// Format tags for display
|
||||
//
|
||||
//
|
||||
// @param {String} tags raw from server
|
||||
// @return {string} A new string with the tags formatted or an empty string if no tags
|
||||
formatTags: function(tags) {
|
||||
if (tags && tags.length > 0) {
|
||||
return tags.join(", ");
|
||||
}
|
||||
return "";
|
||||
},
|
||||
///////////////////////////////
|
||||
// ICON FOR *ALL* OBJECT TYPES
|
||||
//(used for search results and event log / history)
|
||||
//NOTE: Any object type could appear in event log, they all need to be supported where possible
|
||||
//CoreBizObject add here
|
||||
iconForType: function(sockType) {
|
||||
switch (sockType) {
|
||||
case window.$gz.type.NoType:
|
||||
case null:
|
||||
return "$sockiGenderless";
|
||||
case window.$gz.type.Global:
|
||||
return "$sockiGlobe";
|
||||
case window.$gz.type.User:
|
||||
return "$sockiUser";
|
||||
case window.$gz.type.ServerState:
|
||||
return "$sockiDoorOpen";
|
||||
|
||||
case window.$gz.type.LogFile:
|
||||
return "$sockiGlasses";
|
||||
case window.$gz.type.PickListTemplate:
|
||||
return "$sockiPencilRuler";
|
||||
case window.$gz.type.Customer:
|
||||
return "$sockiAddressCard";
|
||||
case window.$gz.type.Vendor:
|
||||
return "$ayiStore";
|
||||
case window.$gz.type.ServerJob:
|
||||
return "$sockiRobot";
|
||||
|
||||
case window.$gz.type.Metrics:
|
||||
return "$sockiFileMedicalAlt";
|
||||
case window.$gz.type.Translation:
|
||||
return "$sockiLanguage";
|
||||
case window.$gz.type.UserOptions:
|
||||
return "$sockiUserCog";
|
||||
case window.$gz.type.HeadOffice:
|
||||
return "$sockiSitemap";
|
||||
|
||||
case window.$gz.type.FileAttachment:
|
||||
return "$sockiPaperclip";
|
||||
case window.$gz.type.DataListSavedFilter:
|
||||
return "$sockiFilter";
|
||||
case window.$gz.type.FormCustom:
|
||||
return "$sockiCustomize";
|
||||
|
||||
case window.$gz.type.Backup:
|
||||
return "$sockiFileArchive";
|
||||
case window.$gz.type.Notification:
|
||||
return "$sockiBell";
|
||||
case window.$gz.type.NotifySubscription:
|
||||
return "$sockiBullhorn";
|
||||
case window.$gz.type.Reminder:
|
||||
return "$sockiStickyNote";
|
||||
|
||||
case window.$gz.type.OpsNotificationSettings:
|
||||
return "$sockiBullhorn";
|
||||
case window.$gz.type.Report:
|
||||
return "$sockiThList";
|
||||
case window.$gz.type.DashboardView:
|
||||
return "$sockiTachometer";
|
||||
case window.$gz.type.CustomerNote:
|
||||
return "$sockiClipboard";
|
||||
case window.$gz.type.Memo:
|
||||
return "$sockiInbox";
|
||||
case window.$gz.type.Review:
|
||||
return "$sockiCalendarCheck";
|
||||
|
||||
case window.$gz.type.License:
|
||||
return "$sockiGem";
|
||||
case window.$gz.type.TrialLicenseRequest:
|
||||
return "$sockiHandHoldingWater";
|
||||
case window.$gz.type.SubscriptionServer:
|
||||
return "$sockiCloud";
|
||||
case window.$gz.type.Purchase:
|
||||
return "$sockiShoppingCart";
|
||||
case window.$gz.type.Product:
|
||||
return "$sockiBarCode";
|
||||
case window.$gz.type.GZCase:
|
||||
return "$sockiCoffee";
|
||||
|
||||
//scroll icon is good one for something
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
//https://gist.github.com/colemanw/9c9a12aae16a4bfe2678de86b661d922
|
||||
iconForFile: function(fileName, mimeType) {
|
||||
// List of official MIME Types: http://www.iana.org/assignments/media-types/media-types.xhtml
|
||||
|
||||
let extension = null;
|
||||
if (fileName && fileName.includes(".")) {
|
||||
extension = fileName.split(".").pop();
|
||||
extension = extension.toLowerCase();
|
||||
}
|
||||
if (!extension && !mimeType) {
|
||||
console.log(
|
||||
"gzutil:iconForFile -> No mime or extension for " +
|
||||
fileName +
|
||||
" " +
|
||||
mimeType
|
||||
);
|
||||
return "$sockiFile";
|
||||
}
|
||||
|
||||
if (!mimeType) {
|
||||
mimeType = "";
|
||||
}
|
||||
mimeType = mimeType.toLowerCase();
|
||||
|
||||
const iconFromExtension = extensions[extension];
|
||||
const iconFromMIME = mimeTypes[mimeType];
|
||||
|
||||
if (iconFromMIME) {
|
||||
return iconFromMIME;
|
||||
}
|
||||
if (iconFromExtension) {
|
||||
return iconFromExtension;
|
||||
}
|
||||
|
||||
return "$sockiFile";
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// attempt to detect image extension name
|
||||
//
|
||||
isImageAttachment: function(fileName, mimeType) {
|
||||
return this.iconForFile(fileName, mimeType) == "$sockiFileImage";
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// Sleep async
|
||||
//
|
||||
sleepAsync: function(milliseconds) {
|
||||
// eslint-disable-next-line
|
||||
return new Promise(resolve => setTimeout(resolve, milliseconds));
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// sortByKey lodash "sortBy" replacement
|
||||
// https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_sortby-and-_orderby
|
||||
//usage:
|
||||
// The native sort modifies the array in place. `_.orderBy` and `_.sortBy` do not, so we use `.concat()` to
|
||||
// copy the array, then sort.
|
||||
// fruits.concat().sort(sortBy("name"));
|
||||
// => [{name:"apple", amount: 4}, {name:"banana", amount: 2}, {name:"mango", amount: 1}, {name:"pineapple", amount: 2}]
|
||||
sortByKey: key => {
|
||||
return (a, b) => {
|
||||
const aaa = a[key].toUpperCase();
|
||||
const bbb = b[key].toUpperCase();
|
||||
return aaa > bbb ? 1 : bbb > aaa ? -1 : 0;
|
||||
//this was the original but it was sorting weird as it was taking case into account with uppercase higher than lowercase
|
||||
//so PMItem came before Part in the object lists
|
||||
//return a[key] > b[key] ? 1 : b[key] > a[key] ? -1 : 0;
|
||||
};
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// "has" lodash replacement
|
||||
// https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_has
|
||||
//
|
||||
has: function(obj, key) {
|
||||
var keyParts = key.split(".");
|
||||
return (
|
||||
!!obj &&
|
||||
(keyParts.length > 1
|
||||
? this.has(obj[key.split(".")[0]], keyParts.slice(1).join("."))
|
||||
: hasOwnProperty.call(obj, key))
|
||||
);
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// Check if object is empty
|
||||
//
|
||||
objectIsEmpty: function(obj) {
|
||||
//https://stackoverflow.com/a/4994265/8939
|
||||
return !obj || Object.keys(obj).length === 0;
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// Trim specific character from start and end
|
||||
// https://stackoverflow.com/a/55292366/8939
|
||||
//
|
||||
trimSpecific: function trim(str, ch) {
|
||||
var start = 0;
|
||||
var end = str.length;
|
||||
while (start < end && str[start] === ch) ++start;
|
||||
while (end > start && str[end - 1] === ch) --end;
|
||||
return start > 0 || end < str.length ? str.substring(start, end) : str;
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// is numeric replacement for lodash
|
||||
// https://stackoverflow.com/a/52986361/8939
|
||||
//
|
||||
isNumeric: function(n) {
|
||||
//lodash isNumber returned false if it's a string and that's what the rest of the code expects even though it's parseable to a number
|
||||
return !this.isString(n) && !isNaN(parseFloat(n)) && isFinite(n);
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// is string replacement for lodash
|
||||
// https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_isString
|
||||
//
|
||||
isString: function(str) {
|
||||
return str != null && typeof str.valueOf() === "string";
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
//
|
||||
//
|
||||
//
|
||||
stringIsNullOrEmpty: function(str) {
|
||||
if (str === null || str === undefined) {
|
||||
return true;
|
||||
}
|
||||
if (this.isString(str)) {
|
||||
if (str.trim() == "") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// is Boolean replacement for lodash
|
||||
// https://stackoverflow.com/a/43718478/8939
|
||||
//
|
||||
isBoolean: function(obj) {
|
||||
return obj === true || obj === false || typeof variable === "boolean";
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// parse to number or null if not a number
|
||||
// used because route params can turn into strings
|
||||
// on their own
|
||||
//
|
||||
stringToIntOrNull: function(n) {
|
||||
const ret = Number.parseInt(n, 10);
|
||||
if (Number.isNaN(ret)) {
|
||||
return null;
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// Simple array equality comparison
|
||||
// (will NOT work on arrays of objects)
|
||||
// Array order is relevant here as they are not sorted
|
||||
// change of order will equal change of array
|
||||
// as this is required for datatable sortby
|
||||
//
|
||||
isEqualArraysOfPrimitives: function(a, b) {
|
||||
if (a === b) return true;
|
||||
if (a == null || b == null) return false;
|
||||
if (a.length !== b.length) return false;
|
||||
|
||||
// If you don't care about the order of the elements inside
|
||||
// the array, you should sort both arrays here.
|
||||
// Please note that calling sort on an array will modify that array.
|
||||
// you might want to clone your array first.
|
||||
|
||||
for (var i = 0; i < a.length; ++i) {
|
||||
if (a[i] !== b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// Use geolocation api to attempt to get current location
|
||||
// try high accuracy first and downgrade if unavailable
|
||||
//https://www.openstreetmap.org/?mlat=48.3911&mlon=-124.7353#map=12/48.3910/-124.7353
|
||||
//https://www.openstreetmap.org/#map=18/49.68155/-125.00435
|
||||
//https://www.openstreetmap.org/?mlat=49.71236&mlon=-124.96961#map=17/49.71236/-124.96961
|
||||
//https://www.google.com/maps/search/?api=1&query=47.5951518,-122.3316393
|
||||
getGeoLocation: async function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
function successHigh(pos) {
|
||||
resolve({
|
||||
latitude: pos.coords.latitude,
|
||||
longitude: pos.coords.longitude
|
||||
});
|
||||
},
|
||||
function error(err) {
|
||||
//if here due to timeout getting high accuracy then try again with low accuracy
|
||||
if (error.code == error.TIMEOUT) {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
function successLow(pos) {
|
||||
resolve({
|
||||
latitude: pos.coords.latitude,
|
||||
longitude: pos.coords.longitude
|
||||
});
|
||||
},
|
||||
function error(err) {
|
||||
reject(
|
||||
new Error(
|
||||
`ERROR getting location(low_accuracy: ${err.code}): ${err.message}`
|
||||
)
|
||||
);
|
||||
},
|
||||
{
|
||||
maximumAge: 600000,
|
||||
timeout: 10000,
|
||||
enableHighAccuracy: false
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
reject(
|
||||
new Error(
|
||||
`ERROR GETTING LOCATION(high_accuracy:${err.code}): ${err.message}`
|
||||
)
|
||||
);
|
||||
},
|
||||
{ maximumAge: 600000, timeout: 5000, enableHighAccuracy: true }
|
||||
);
|
||||
});
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// Open map url
|
||||
//
|
||||
//
|
||||
viewGeoLocation: function(obj) {
|
||||
const hasGeo =
|
||||
obj.latitude != null &&
|
||||
obj.latitude != 0 &&
|
||||
obj.longitude != null &&
|
||||
obj.longitude != 0;
|
||||
|
||||
const hasAddress =
|
||||
!this.stringIsNullOrEmpty(obj.address) &&
|
||||
!this.stringIsNullOrEmpty(obj.city) &&
|
||||
!this.stringIsNullOrEmpty(obj.region) &&
|
||||
!this.stringIsNullOrEmpty(obj.country) &&
|
||||
!this.stringIsNullOrEmpty(obj.postCode);
|
||||
|
||||
if (!hasGeo && !hasAddress) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mapUrl = window.$gz.store.state.userOptions.mapUrlTemplate;
|
||||
|
||||
//No pre-set?
|
||||
if (!mapUrl || mapUrl == "") {
|
||||
mapUrl =
|
||||
"https://www.google.com/maps/search/?api=1&query={ayaddress}<|>https://www.google.com/maps/search/?api=1&query={aylatitude},{aylongitude}";
|
||||
}
|
||||
|
||||
let geoMapUrl = null;
|
||||
let addressMapUrl = null;
|
||||
|
||||
//Parse the map url
|
||||
let mapUrls = [mapUrl];
|
||||
if (mapUrl.includes("<|>")) {
|
||||
mapUrls = mapUrl.split("<|>");
|
||||
}
|
||||
|
||||
mapUrls.forEach(z => {
|
||||
if (!geoMapUrl && z.includes("{aylatitude}")) {
|
||||
geoMapUrl = z;
|
||||
}
|
||||
if (!addressMapUrl && z.includes("{ayaddress}")) {
|
||||
addressMapUrl = z;
|
||||
}
|
||||
});
|
||||
|
||||
//decide which map to use here, favor geocode
|
||||
if (hasGeo && geoMapUrl) {
|
||||
//geo view
|
||||
mapUrl = geoMapUrl;
|
||||
mapUrl = mapUrl.split("{aylatitude}").join(obj.latitude);
|
||||
mapUrl = mapUrl.split("{aylongitude}").join(obj.longitude);
|
||||
} else if (hasAddress && addressMapUrl) {
|
||||
mapUrl = addressMapUrl;
|
||||
//compile address fields together
|
||||
//order street to country seems to be standard
|
||||
//note, if google need plus symbol delimiter, if bing, need comma delimiter
|
||||
//but both might accept one big string space delimited and url encoded so test that on all first
|
||||
const delimiter = " ";
|
||||
let q = "";
|
||||
if (obj.address) {
|
||||
q += obj.address + delimiter;
|
||||
}
|
||||
if (obj.city) {
|
||||
q += obj.city + delimiter;
|
||||
}
|
||||
|
||||
if (obj.region) {
|
||||
q += obj.region + delimiter;
|
||||
}
|
||||
|
||||
if (obj.country) {
|
||||
q += obj.country + delimiter;
|
||||
}
|
||||
|
||||
if (obj.postCode) {
|
||||
q += obj.postCode + delimiter;
|
||||
}
|
||||
|
||||
if (obj.addressPostal) {
|
||||
q += obj.addressPostal + delimiter;
|
||||
}
|
||||
|
||||
if (q.length > 1) {
|
||||
q = q.substring(0, q.length - 1);
|
||||
}
|
||||
//url encode the query
|
||||
q = encodeURIComponent(q);
|
||||
mapUrl = mapUrl.split("{ayaddress}").join(q);
|
||||
} else {
|
||||
throw new Error(
|
||||
"View map: error - no matching mapurl / address / geo coordinates set for display, nothing to view"
|
||||
);
|
||||
}
|
||||
window.open(mapUrl, "map");
|
||||
//This is not valid to do as some platforms don't open a new web browser window
|
||||
//but rather a map application in which case this is null and throws up the exception even though it's working
|
||||
// if (window.open(mapUrl, "map") == null) {
|
||||
// throw new Error(
|
||||
// "Problem displaying map in new window. Browser must allow pop-ups to view maps; check your browser setting"
|
||||
// );
|
||||
// }
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// Online mapping service url formats
|
||||
//
|
||||
//
|
||||
mapProviderUrls: function() {
|
||||
return [
|
||||
{
|
||||
name: "Apple",
|
||||
value:
|
||||
"http://maps.apple.com/?q={ayaddress}<|>http://maps.apple.com/?ll={aylatitude},{aylongitude}"
|
||||
},
|
||||
{
|
||||
name: "Bing",
|
||||
value:
|
||||
"https://bing.com/maps/default.aspx?where1={ayaddress}<|>https://bing.com/maps/default.aspx?cp={aylatitude}~{aylongitude}&lvl=17&style=r&sp=point.{aylatitude}_{aylongitude}"
|
||||
},
|
||||
{
|
||||
name: "Google",
|
||||
value:
|
||||
"https://www.google.com/maps/search/?api=1&query={ayaddress}<|>https://www.google.com/maps/search/?api=1&query={aylatitude},{aylongitude}"
|
||||
},
|
||||
{
|
||||
name: "MapQuest",
|
||||
value:
|
||||
"https://mapquest.com/?center={ayaddress}&zoom=17<|>https://mapquest.com/?center={aylatitude},{aylongitude}&zoom=17"
|
||||
},
|
||||
{
|
||||
name: "Open Street Map",
|
||||
value:
|
||||
"https://www.openstreetmap.org/search?query={ayaddress}<|>https://www.openstreetmap.org/?mlat={aylatitude}&mlon={aylongitude}#map=17/{aylatitude}/{aylongitude}"
|
||||
},
|
||||
{
|
||||
name: "geo URI",
|
||||
value: "geo:{aylatitude},{aylongitude}"
|
||||
},
|
||||
{
|
||||
name: "Waze",
|
||||
value:
|
||||
"https://waze.com/ul?q={ayaddress}<|>https://www.waze.com/ul?ll={aylatitude},{aylongitude}&navigate=yes&zoom=17"
|
||||
},
|
||||
{
|
||||
name: "Yandex",
|
||||
value:
|
||||
"https://yandex.ru/maps/?mode=search&text={ayaddress}&z=17<|>https://yandex.ru/maps/?ll={aylatitude},{aylongitude}&z=12&l=map"
|
||||
}
|
||||
];
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// v-calendar view to Sockeye scheduleview enum
|
||||
//
|
||||
//
|
||||
calendarViewToSockeyeEnum: function(view) {
|
||||
switch (view) {
|
||||
case "day":
|
||||
return 1;
|
||||
case "week":
|
||||
return 2;
|
||||
case "month":
|
||||
return 3;
|
||||
case "4day":
|
||||
return 4;
|
||||
case "category":
|
||||
return 5;
|
||||
|
||||
default:
|
||||
throw new Error(
|
||||
`gzutil->calendarViewtoSockeyeEnum - Unknown view type '${view}'`
|
||||
);
|
||||
}
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// GZDaysOfWeek to VCalendar weekdays
|
||||
//
|
||||
//
|
||||
DaysOfWeekToWeekdays: function(dow) {
|
||||
/*
|
||||
AyaDaysOfWeek
|
||||
Monday = 1,
|
||||
Tuesday = 2,
|
||||
Wednesday = 4,
|
||||
Thursday = 8,
|
||||
Friday = 16,
|
||||
Saturday = 32,
|
||||
Sunday = 64
|
||||
|
||||
vCalendar [
|
||||
0,//sunday
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6//saturday
|
||||
]
|
||||
*/
|
||||
if (dow == null || dow == 0) {
|
||||
return [0, 1, 2, 3, 4, 5, 6]; //all the days
|
||||
}
|
||||
const ret = [];
|
||||
|
||||
//turn EXCLUDE selected gzDaysOfWeek into INCLUDE selected days for vCalendar
|
||||
if (!(dow & 64)) {
|
||||
ret.push(0);
|
||||
}
|
||||
if (!(dow & 1)) {
|
||||
ret.push(1);
|
||||
}
|
||||
if (!(dow & 2)) {
|
||||
ret.push(2);
|
||||
}
|
||||
if (!(dow & 4)) {
|
||||
ret.push(3);
|
||||
}
|
||||
if (!(dow & 8)) {
|
||||
ret.push(4);
|
||||
}
|
||||
if (!(dow & 16)) {
|
||||
ret.push(5);
|
||||
}
|
||||
if (!(dow & 32)) {
|
||||
ret.push(6);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// Random integer from 0 to max
|
||||
//
|
||||
//
|
||||
getRandomInt: function(max) {
|
||||
return Math.floor(Math.random() * max);
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
//new functions above here
|
||||
};
|
||||
614
src/api/initialize.js
Normal file
614
src/api/initialize.js
Normal file
@@ -0,0 +1,614 @@
|
||||
function addNavItem(title, icon, route, navItems, key, testid, color = null) {
|
||||
if (!testid) {
|
||||
testid = route;
|
||||
}
|
||||
|
||||
const o = {
|
||||
title,
|
||||
icon,
|
||||
route,
|
||||
navItems,
|
||||
key: key,
|
||||
testid: testid
|
||||
};
|
||||
if (color != null) {
|
||||
o["color"] = color;
|
||||
}
|
||||
o.navItems.forEach(z => {
|
||||
if (z.testid == null) {
|
||||
z.testid = z.route;
|
||||
}
|
||||
});
|
||||
|
||||
window.$gz.store.commit("addNavItem", o);
|
||||
}
|
||||
|
||||
function initNavPanel() {
|
||||
let key = 0;
|
||||
let sub = [];
|
||||
|
||||
/*Service = 1,
|
||||
NotService = 2,
|
||||
Customer = 3,
|
||||
HeadOffice = 4,
|
||||
ServiceContractor = 5 */
|
||||
|
||||
//########## OUTSIDE USERS GROUP (CUSTOMER / HEADOFFICE) ###
|
||||
if (window.$gz.store.getters.isCustomerUser == true) {
|
||||
//clear sublevel array
|
||||
sub = [];
|
||||
|
||||
//Set homePage in store to customer csr for this user type
|
||||
let CustomerHomePageSet = false;
|
||||
|
||||
//USER SETTINGS
|
||||
//if (window.$gz.store.state.customerRights.userSettings == true) {
|
||||
sub.push({
|
||||
title: "UserSettings",
|
||||
icon: "$sockiUserCog",
|
||||
route: "/home-user-settings",
|
||||
key: key++
|
||||
});
|
||||
|
||||
window.$gz.store.commit("setHomePage", "/home-user-settings");
|
||||
CustomerHomePageSet = true;
|
||||
// }
|
||||
|
||||
if (window.$gz.store.getters.canSubscribeToNotifications) {
|
||||
sub.push({
|
||||
title: "NotifySubscriptionList",
|
||||
icon: "$sockiBullhorn",
|
||||
route: "/home-notify-subscriptions",
|
||||
key: key++
|
||||
});
|
||||
|
||||
window.$gz.store.commit("setHomePage", "/home-notify-subscriptions");
|
||||
CustomerHomePageSet = true;
|
||||
}
|
||||
|
||||
//** CUSTOMER LOGIN HOME (TOP)
|
||||
addNavItem("Home", "$sockiHome", undefined, sub, key++, "homecustomer");
|
||||
|
||||
//last resort home page if nothing else kicked in
|
||||
if (!CustomerHomePageSet) {
|
||||
window.$gz.store.commit("setHomePage", "/no-features-available");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//###### ALL INSIDE USERS FROM HERE DOWN ###############
|
||||
|
||||
//####### HOME GROUP
|
||||
|
||||
//DASHBOARD
|
||||
|
||||
sub.push({
|
||||
title: "Dashboard",
|
||||
icon: "$sockiTachometer",
|
||||
route: "/home-dashboard",
|
||||
key: key++
|
||||
});
|
||||
|
||||
//SEARCH
|
||||
sub.push({
|
||||
title: "Search",
|
||||
icon: "$sockiSearch",
|
||||
route: "/home-search",
|
||||
key: key++
|
||||
});
|
||||
|
||||
//SCHEDULE (personal)
|
||||
sub.push({
|
||||
title: "Schedule",
|
||||
icon: "$sockiCalendarDay",
|
||||
route: "/home-schedule",
|
||||
key: key++
|
||||
});
|
||||
|
||||
//MEMOS
|
||||
sub.push({
|
||||
title: "MemoList",
|
||||
icon: "$sockiInbox",
|
||||
route: "/home-memos",
|
||||
key: key++
|
||||
});
|
||||
|
||||
//REMINDERS
|
||||
sub.push({
|
||||
title: "ReminderList",
|
||||
icon: "$sockiStickyNote",
|
||||
route: "/home-reminders",
|
||||
key: key++
|
||||
});
|
||||
|
||||
//REVIEWS
|
||||
sub.push({
|
||||
title: "ReviewList",
|
||||
icon: "$sockiCalendarCheck",
|
||||
route: "/home-reviews",
|
||||
key: key++
|
||||
});
|
||||
|
||||
//USER SETTINGS
|
||||
sub.push({
|
||||
title: "UserSettings",
|
||||
icon: "$sockiUserCog",
|
||||
route: "/home-user-settings",
|
||||
key: key++
|
||||
});
|
||||
|
||||
//USER NOTIFICATION SUBSCRIPTIONS
|
||||
sub.push({
|
||||
title: "NotifySubscriptionList",
|
||||
icon: "$sockiBullhorn",
|
||||
route: "/home-notify-subscriptions",
|
||||
key: key++
|
||||
});
|
||||
|
||||
//HISTORY / MRU / ACTIVITY (personal)
|
||||
sub.push({
|
||||
title: "History",
|
||||
icon: "$sockiHistory",
|
||||
route: `/history/3/${window.$gz.store.state.userId}/true`,
|
||||
key: key++,
|
||||
testid: "/home-history"
|
||||
});
|
||||
|
||||
//HOME
|
||||
if (sub.length > 0) {
|
||||
//Set homePage in store to dashboard
|
||||
window.$gz.store.commit("setHomePage", "/home-dashboard");
|
||||
addNavItem("Home", "$sockiHome", undefined, sub, key++, "home");
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// BUSINESS GROUP
|
||||
//
|
||||
|
||||
sub = [];
|
||||
|
||||
sub.push({
|
||||
title: "GZCaseList",
|
||||
icon: "$sockiCoffee",
|
||||
route: "/biz-gzcase-list",
|
||||
key: key++
|
||||
});
|
||||
|
||||
sub.push({
|
||||
title: "LicenseList",
|
||||
icon: "$sockiGem",
|
||||
route: "/biz-license-list",
|
||||
key: key++
|
||||
});
|
||||
|
||||
sub.push({
|
||||
title: "TrialLicenseRequestList",
|
||||
icon: "$sockiHandHoldingWater",
|
||||
route: "/biz-trial-request-list",
|
||||
key: key++
|
||||
});
|
||||
|
||||
sub.push({
|
||||
title: "SubscriptionServerList",
|
||||
icon: "$sockiCloud",
|
||||
route: "/biz-subscription-server-list",
|
||||
key: key++
|
||||
});
|
||||
|
||||
sub.push({
|
||||
title: "PurchaseList",
|
||||
icon: "$sockiShoppingCart",
|
||||
route: "/biz-purchase-list",
|
||||
key: key++
|
||||
});
|
||||
|
||||
sub.push({
|
||||
title: "ProductList",
|
||||
icon: "$sockiBarCode",
|
||||
route: "/biz-product-list",
|
||||
key: key++
|
||||
});
|
||||
|
||||
sub.push({
|
||||
title: "VendorList",
|
||||
icon: "$sockiStore",
|
||||
route: "/biz-vendor-list",
|
||||
key: key++
|
||||
});
|
||||
|
||||
if (window.$gz.role.canOpen(window.$gz.type.Customer)) {
|
||||
addNavItem(
|
||||
"BusinessSettings",
|
||||
"$sockiBriefcase",
|
||||
undefined,
|
||||
sub,
|
||||
key++,
|
||||
"biz"
|
||||
);
|
||||
}
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
//######### CUSTOMER GROUP
|
||||
if (window.$gz.role.canOpen(window.$gz.type.Customer)) {
|
||||
//these all require Customer rights so all in the same block
|
||||
|
||||
//clear sublevel array
|
||||
sub = [];
|
||||
|
||||
//CUSTOMERS subitem
|
||||
sub.push({
|
||||
title: "CustomerList",
|
||||
icon: "$sockiAddressCard",
|
||||
route: "/cust-customers",
|
||||
key: key++
|
||||
});
|
||||
|
||||
//HEAD OFFICES subitem
|
||||
sub.push({
|
||||
title: "HeadOfficeList",
|
||||
icon: "$sockiSitemap",
|
||||
route: "/cust-head-offices",
|
||||
key: key++
|
||||
});
|
||||
|
||||
//Customer / Headoffice Users subitem
|
||||
sub.push({
|
||||
title: "Contacts",
|
||||
icon: "$sockiUsers",
|
||||
route: "/cust-users",
|
||||
key: key++
|
||||
});
|
||||
|
||||
sub.push({
|
||||
title: "CustomerNotifySubscriptionList",
|
||||
icon: "$sockiBullhorn",
|
||||
route: "/cust-notify-subscriptions",
|
||||
key: key++
|
||||
});
|
||||
|
||||
// ** CUSTOMER (TOP)
|
||||
addNavItem(
|
||||
"CustomerList",
|
||||
"$sockiAddressBook",
|
||||
undefined,
|
||||
sub,
|
||||
key++,
|
||||
"customer"
|
||||
);
|
||||
}
|
||||
|
||||
// //####### SERVICE GROUP
|
||||
|
||||
// sub = [];
|
||||
|
||||
// //SCHEDULE (shared)
|
||||
|
||||
// sub.push({
|
||||
// title: "Schedule",
|
||||
// icon: "$sockiCalendarAlt",
|
||||
// route: "/svc-schedule",
|
||||
// key: key++
|
||||
// });
|
||||
|
||||
// //**** SHARED (TOP GROUP)
|
||||
// if (
|
||||
// sub.length > 0 &&
|
||||
// !window.$gz.role.hasRole([
|
||||
// window.$gz.role.AUTHORIZATION_ROLES.TechRestricted
|
||||
// ])
|
||||
// ) {
|
||||
// addNavItem("Service", "$sockiToolbox", undefined, sub, key++, "service");
|
||||
// }
|
||||
|
||||
//****************** ACCOUNTING
|
||||
|
||||
//SOCKEYE Keeping this for very likely future accounting functionality
|
||||
sub = [];
|
||||
|
||||
// ** ACCOUNTING (TOP)
|
||||
if (sub.length > 0) {
|
||||
addNavItem(
|
||||
"Accounting",
|
||||
"$sockiCoins",
|
||||
undefined,
|
||||
sub,
|
||||
key++,
|
||||
"accounting"
|
||||
);
|
||||
}
|
||||
|
||||
//############# ADMINISTRATION
|
||||
|
||||
//clear sublevel array
|
||||
sub = [];
|
||||
|
||||
// GLOBAL SETTINGS
|
||||
if (window.$gz.role.canOpen(window.$gz.type.Global)) {
|
||||
sub.push({
|
||||
title: "AdministrationGlobalSettings",
|
||||
icon: "$sockiCogs",
|
||||
route: "/adm-global-settings",
|
||||
key: key++
|
||||
});
|
||||
}
|
||||
|
||||
// USERS
|
||||
if (window.$gz.role.canOpen(window.$gz.type.User)) {
|
||||
sub.push({
|
||||
title: "UserList",
|
||||
icon: "$sockiUsers",
|
||||
route: "/adm-users",
|
||||
key: key++
|
||||
});
|
||||
}
|
||||
|
||||
//TRANSLATION
|
||||
if (window.$gz.role.canOpen(window.$gz.type.Translation)) {
|
||||
sub.push({
|
||||
title: "TranslationList",
|
||||
icon: "$sockiLanguage",
|
||||
route: "/adm-translations",
|
||||
key: key++
|
||||
});
|
||||
}
|
||||
|
||||
//REPORT TEMPLATES
|
||||
if (window.$gz.role.canChange(window.$gz.type.Report)) {
|
||||
sub.push({
|
||||
title: "ReportList",
|
||||
icon: "$sockiThList",
|
||||
route: "/adm-report-templates",
|
||||
key: key++
|
||||
});
|
||||
}
|
||||
|
||||
//FILES IN DATABASE
|
||||
if (window.$gz.role.canOpen(window.$gz.type.FileAttachment)) {
|
||||
sub.push({
|
||||
title: "Attachments",
|
||||
icon: "$sockiPaperclip",
|
||||
route: "/adm-attachments",
|
||||
key: key++
|
||||
});
|
||||
}
|
||||
|
||||
//EVENT LOG / HISTORY
|
||||
if (window.$gz.role.canOpen(window.$gz.type.Global)) {
|
||||
//not really an appropriate object here just guessing
|
||||
sub.push({
|
||||
title: "History",
|
||||
icon: "$sockiHistory",
|
||||
route: "/adm-history",
|
||||
key: key++
|
||||
});
|
||||
}
|
||||
|
||||
//IMPORT
|
||||
if (window.$gz.role.canOpen(window.$gz.type.Global)) {
|
||||
//again, not really an appropriate object type
|
||||
sub.push({
|
||||
title: "Import",
|
||||
icon: "$sockiFileImport",
|
||||
route: "/adm-import",
|
||||
key: key++
|
||||
});
|
||||
}
|
||||
|
||||
//INTEGRATION
|
||||
//decision here is that only teh biz admin can *control* or remove an integration
|
||||
//even though all full role inside users can create or edit integrations (just not through the Sockeye user interface)
|
||||
//this is required to support integrations made for various roles like inventory accounting etc
|
||||
if (window.$gz.role.canOpen(window.$gz.type.Global)) {
|
||||
sub.push({
|
||||
title: "IntegrationList",
|
||||
icon: "$sockiCampground",
|
||||
route: "/adm-integrations",
|
||||
key: key++
|
||||
});
|
||||
}
|
||||
|
||||
// ** ADMINISTRATION (TOP)
|
||||
if (sub.length > 0) {
|
||||
addNavItem(
|
||||
"Administration",
|
||||
"$sockiUserTie",
|
||||
undefined,
|
||||
sub,
|
||||
key++,
|
||||
"administration"
|
||||
);
|
||||
}
|
||||
|
||||
//############ OPERATIONS
|
||||
|
||||
//clear sublevel array
|
||||
sub = [];
|
||||
|
||||
// BACKUP
|
||||
if (window.$gz.role.canOpen(window.$gz.type.Backup)) {
|
||||
sub.push({
|
||||
title: "Backup",
|
||||
icon: "$sockiFileArchive",
|
||||
route: "/ops-backup",
|
||||
key: key++
|
||||
});
|
||||
}
|
||||
|
||||
// SERVER STATE
|
||||
if (window.$gz.role.canChange(window.$gz.type.ServerState)) {
|
||||
sub.push({
|
||||
title: "ServerState",
|
||||
icon: "$sockiDoorOpen",
|
||||
route: "/ops-server-state",
|
||||
key: key++
|
||||
});
|
||||
}
|
||||
|
||||
// JOBS
|
||||
if (window.$gz.role.canOpen(window.$gz.type.ServerJob)) {
|
||||
sub.push({
|
||||
title: "ServerJobs",
|
||||
icon: "$sockiRobot",
|
||||
route: "/ops-jobs",
|
||||
key: key++
|
||||
});
|
||||
}
|
||||
|
||||
// LOGS
|
||||
if (window.$gz.role.canOpen(window.$gz.type.LogFile)) {
|
||||
sub.push({
|
||||
title: "ServerLog",
|
||||
icon: "$sockiHistory",
|
||||
route: "/ops-log",
|
||||
key: key++
|
||||
});
|
||||
}
|
||||
|
||||
//METRICS
|
||||
if (window.$gz.role.canOpen(window.$gz.type.ServerMetrics)) {
|
||||
sub.push({
|
||||
title: "ServerMetrics",
|
||||
icon: "$sockiFileMedicalAlt",
|
||||
route: "/ops-metrics",
|
||||
key: key++
|
||||
});
|
||||
|
||||
// //PROFILE
|
||||
// //metrics rights
|
||||
// sub.push({
|
||||
// title: "ServerProfiler",
|
||||
// icon: "$sockiBinoculars",
|
||||
// route: "/ops-profile",
|
||||
// key: key++
|
||||
// });
|
||||
}
|
||||
|
||||
//NOTIFICATION CONFIG AND HISTORY
|
||||
if (window.$gz.role.canOpen(window.$gz.type.OpsNotificationSettings)) {
|
||||
sub.push({
|
||||
title: "OpsNotificationSettings",
|
||||
icon: "$sockiBullhorn",
|
||||
route: "/ops-notification-settings",
|
||||
key: key++
|
||||
});
|
||||
}
|
||||
|
||||
if (window.$gz.role.canOpen(window.$gz.type.OpsNotificationSettings)) {
|
||||
sub.push({
|
||||
title: "NotificationDeliveryLog",
|
||||
icon: "$sockiHistory",
|
||||
route: "/ops-notify-log",
|
||||
key: key++
|
||||
});
|
||||
}
|
||||
|
||||
if (window.$gz.role.canOpen(window.$gz.type.OpsNotificationSettings)) {
|
||||
sub.push({
|
||||
title: "NotificationCustomerDeliveryLog",
|
||||
icon: "$sockiHistory",
|
||||
route: "/ops-customer-notify-log",
|
||||
key: key++
|
||||
});
|
||||
}
|
||||
|
||||
// OPS VIEW SERVER CONFIGURATION
|
||||
if (window.$gz.role.canOpen(window.$gz.type.GlobalOps)) {
|
||||
sub.push({
|
||||
title: "ViewServerConfiguration",
|
||||
icon: "$sockiInfoCircle",
|
||||
route: "/ops-view-configuration",
|
||||
key: key++
|
||||
});
|
||||
}
|
||||
|
||||
// ** OPERATIONS (TOP)
|
||||
if (sub.length > 0) {
|
||||
addNavItem(
|
||||
"Operations",
|
||||
"$sockiServer",
|
||||
undefined,
|
||||
sub,
|
||||
key++,
|
||||
"operations"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function getUserOptions() {
|
||||
try {
|
||||
const res = await window.$gz.api.get(
|
||||
"user-option/" + window.$gz.store.state.userId
|
||||
);
|
||||
|
||||
if (res.error) {
|
||||
//In a form this would trigger a bunch of validation or error display code but for here and now:
|
||||
//convert error to human readable string for display and popup a notification to user
|
||||
const msg = window.$gz.api.apiErrorToHumanString(res.error);
|
||||
window.$gz.store.commit(
|
||||
"logItem",
|
||||
"Initialize::() fetch useroptions -> error" + msg
|
||||
);
|
||||
window.$gz.eventBus.$emit("notify-error", msg);
|
||||
} else {
|
||||
//Check if overrides and use them here
|
||||
//or else use browser defaults
|
||||
|
||||
const l = {
|
||||
languageOverride: null,
|
||||
timeZoneOverride: null,
|
||||
currencyName: null,
|
||||
hour12: true,
|
||||
//uiColor: "#000000ff",
|
||||
emailAddress: null,
|
||||
mapUrlTemplate: null
|
||||
};
|
||||
|
||||
l.languageOverride = res.data.languageOverride;
|
||||
l.timeZoneOverride = res.data.timeZoneOverride;
|
||||
|
||||
//No browser setting for this so meh
|
||||
l.currencyName = res.data.currencyName;
|
||||
|
||||
if (res.data.hour12 != null) {
|
||||
l.hour12 = res.data.hour12;
|
||||
}
|
||||
|
||||
// l.uiColor = res.data.uiColor || "#000000ff";
|
||||
l.emailAddress = res.data.emailAddress || null;
|
||||
|
||||
l.mapUrlTemplate = res.data.mapUrlTemplate || null;
|
||||
|
||||
window.$gz.store.commit("setUserOptions", l);
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.store.commit(
|
||||
"logItem",
|
||||
"Initialize::() fetch useroptions -> error" + error
|
||||
);
|
||||
throw new Error(window.$gz.errorHandler.errorToString(error));
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
// Initialize the app
|
||||
// on change of authentication status
|
||||
export default function initialize() {
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async function(resolve, reject) {
|
||||
if (!window.$gz.store.state.authenticated) {
|
||||
throw new Error("initialize: Error, called but user not authenticated!");
|
||||
}
|
||||
try {
|
||||
await window.$gz.translation.cacheTranslations(
|
||||
window.$gz.translation.coreKeys
|
||||
);
|
||||
initNavPanel();
|
||||
await getUserOptions();
|
||||
|
||||
resolve();
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
604
src/api/locale.js
Normal file
604
src/api/locale.js
Normal file
@@ -0,0 +1,604 @@
|
||||
//Browser Locale conversion utilities
|
||||
//dates,numbers currency etc
|
||||
export default {
|
||||
////////////////////////////////////////////////////////
|
||||
// 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
|
||||
//
|
||||
//https://appmakers.dev/bcp-47-language-codes-list/
|
||||
///////////////////////////////////////////
|
||||
// Get users default language code
|
||||
// first check if overriden in useroptions
|
||||
// if not then use browsers own setting
|
||||
//if not that then final default of en-US
|
||||
getResolvedLanguage() {
|
||||
let l = window.$gz.store.state.userOptions.languageOverride;
|
||||
if (!window.$gz.util.stringIsNullOrEmpty(l)) {
|
||||
return l;
|
||||
} else {
|
||||
l = window.navigator.languages[0];
|
||||
if (!window.$gz.util.stringIsNullOrEmpty(l)) {
|
||||
return l;
|
||||
} else {
|
||||
return "en-US";
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
///////////////////////////////////////////
|
||||
// Get users default time zone
|
||||
// first check if overriden in useroptions
|
||||
// if not then use browsers own setting
|
||||
// if that is empty then final default of "America/New_York"
|
||||
//https://www.iana.org/time-zones
|
||||
//https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
getResolvedTimeZoneName() {
|
||||
let tz = window.$gz.store.state.userOptions.timeZoneOverride;
|
||||
if (!window.$gz.util.stringIsNullOrEmpty(tz)) {
|
||||
return tz;
|
||||
} else {
|
||||
tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
if (!window.$gz.util.stringIsNullOrEmpty(tz)) {
|
||||
return tz;
|
||||
} else {
|
||||
return "America/New_York";
|
||||
}
|
||||
}
|
||||
},
|
||||
//////////////////////////////////////////////////
|
||||
// Get the user's chosen currency name
|
||||
//https://en.wikipedia.org/wiki/ISO_4217
|
||||
//default to USD if nothing specified
|
||||
getCurrencyName() {
|
||||
const cur = window.$gz.store.state.userOptions.currencyName;
|
||||
if (!window.$gz.util.stringIsNullOrEmpty(cur)) {
|
||||
return cur;
|
||||
} else {
|
||||
return "USD";
|
||||
}
|
||||
},
|
||||
//////////////////////////////////////////////////
|
||||
// Get the user's chosen 12hr clock
|
||||
//
|
||||
getHour12() {
|
||||
return window.$gz.store.state.userOptions.hour12;
|
||||
},
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Turn a utc ISO date from server into a vuetify calendar
|
||||
// schedule control compatible (epoch) format
|
||||
// localized.
|
||||
// For ease of use in schedule the epoch (milliseconds) is the best format
|
||||
// "It must be a Date, number of seconds since Epoch, or a string in the format of YYYY-MM-DD or YYYY-MM-DD hh:mm. Zero-padding is optional and seconds are ignored.""
|
||||
//
|
||||
//
|
||||
utcDateToScheduleCompatibleFormatLocalized(value, timeZoneName) {
|
||||
//This function takes a UTC iso format date string, parses it into a date then converts that date to the User's configured time zone
|
||||
//outputs that in a format close to ISO, fixes the space in the middle of the output to match ISO 8601 format then returns as an
|
||||
//epoch
|
||||
//this is to support controls that are not time zone settable so they are always in local browser time zone of os, however user may be operating
|
||||
//sockeye in another desired time zone so this is all to support that scenario
|
||||
|
||||
if (!value) {
|
||||
if (window.$gz.dev) {
|
||||
throw new Error(
|
||||
`locale::utcDateToScheduleCompatibleFormatLocalized - Value is empty`
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Date(
|
||||
new Date(value) //convert to locale timezone and output in the closest thing to iso-8601 format
|
||||
.toLocaleString("sv-SE", {
|
||||
timeZone: timeZoneName
|
||||
})
|
||||
.replace(" ", "T") //Safari can't parse the date from here because sv-SE puts a space between date and time and Safari will only parse if it has a T between
|
||||
).getTime();
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// Convert a local schedule epoch timestamp
|
||||
// to specified time zone equivalent then
|
||||
// to UTC and output as ISO 8601
|
||||
//
|
||||
//
|
||||
localScheduleFormatToUTC8601String(value, timeZoneName) {
|
||||
if (!timeZoneName) {
|
||||
timeZoneName = this.getResolvedTimeZoneName();
|
||||
}
|
||||
|
||||
//input: epoch in local browser time zone
|
||||
//output: transform to date and time string
|
||||
//convert to desired time zone but at same time and date
|
||||
//(i.e. if it browser is vancouver and 1pm is selected but desired is new york's 1pm
|
||||
// so convert the string as if it was new york then back to iso so that the time is adjusted forward
|
||||
// as if the user was in new york in their browser default)
|
||||
|
||||
//parse in the time in to the specified timezone
|
||||
let ret = window.$gz.DateTime.fromISO(
|
||||
//output the sched epoch as local time string without zone
|
||||
new Date(value).toLocaleString("sv-SE").replace(" ", "T"),
|
||||
{
|
||||
zone: timeZoneName
|
||||
}
|
||||
);
|
||||
|
||||
ret = ret.setZone("utc"); //convert to UTC
|
||||
ret = ret.toISO(); //output as ISO 8601
|
||||
return ret;
|
||||
},
|
||||
///////////////////////////////////////////
|
||||
// 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.getResolvedTimeZoneName();
|
||||
}
|
||||
if (!languageName) {
|
||||
languageName = this.getResolvedLanguage();
|
||||
}
|
||||
|
||||
if (!hour12) {
|
||||
hour12 = this.getHour12();
|
||||
}
|
||||
|
||||
//parse the date which is identified as utc ("2020-02-06T18:18:49.148011Z")
|
||||
const 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
|
||||
// date and time with specific formats
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString
|
||||
//
|
||||
utcDateToSpecifiedDateAndTimeLocalized(
|
||||
value,
|
||||
timeZoneName,
|
||||
languageName,
|
||||
hour12,
|
||||
dateStyle,
|
||||
timeStyle
|
||||
) {
|
||||
if (!value) {
|
||||
return "";
|
||||
}
|
||||
if (!timeZoneName) {
|
||||
timeZoneName = this.getResolvedTimeZoneName();
|
||||
}
|
||||
if (!languageName) {
|
||||
languageName = this.getResolvedLanguage();
|
||||
}
|
||||
|
||||
if (!hour12) {
|
||||
hour12 = this.getHour12();
|
||||
}
|
||||
|
||||
//parse the date which is identified as utc ("2020-02-06T18:18:49.148011Z")
|
||||
const 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: dateStyle,
|
||||
timeStyle: timeStyle,
|
||||
hour12: hour12
|
||||
});
|
||||
},
|
||||
///////////////////////////////////////////
|
||||
// Turn a utc date into a displayable
|
||||
// short date
|
||||
//
|
||||
utcDateToShortDateLocalized(value, timeZoneName, languageName) {
|
||||
if (!value) {
|
||||
return "";
|
||||
}
|
||||
if (!timeZoneName) {
|
||||
timeZoneName = this.getResolvedTimeZoneName();
|
||||
}
|
||||
if (!languageName) {
|
||||
languageName = this.getResolvedLanguage();
|
||||
}
|
||||
|
||||
//parse the date which is identified as utc ("2020-02-06T18:18:49.148011Z")
|
||||
const 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.getResolvedTimeZoneName();
|
||||
}
|
||||
if (!languageName) {
|
||||
languageName = this.getResolvedLanguage();
|
||||
}
|
||||
|
||||
if (!hour12) {
|
||||
hour12 = this.getHour12();
|
||||
}
|
||||
|
||||
//parse the date which is identified as utc ("2020-02-06T18:18:49.148011Z")
|
||||
const 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
|
||||
});
|
||||
},
|
||||
///////////////////////////////////////////
|
||||
// Turn a duration value into a display
|
||||
//
|
||||
durationLocalized(value, hideSeconds) {
|
||||
if (value == null || value == "00:00:00") {
|
||||
return "";
|
||||
}
|
||||
|
||||
let theDays = 0;
|
||||
let theHours = 0;
|
||||
let theMinutes = 0;
|
||||
let theSeconds = 0;
|
||||
let ret = "";
|
||||
|
||||
const work = value.split(":");
|
||||
//has days?
|
||||
if (work[0].includes(".")) {
|
||||
let dh = work[0].split(".");
|
||||
theDays = Number(dh[0]);
|
||||
theHours = Number(dh[1]);
|
||||
} else {
|
||||
theHours = Number(work[0]);
|
||||
}
|
||||
theMinutes = Number(work[1]);
|
||||
//has milliseconds? (ignore them)
|
||||
if (work[2].includes(".")) {
|
||||
let dh = work[2].split(".");
|
||||
theSeconds = Number(dh[0]);
|
||||
} else {
|
||||
theSeconds = Number(work[2]);
|
||||
}
|
||||
|
||||
if (theDays != 0) {
|
||||
ret += theDays + " " + window.$gz.translation.get("TimeSpanDays") + " ";
|
||||
}
|
||||
if (theHours != 0) {
|
||||
ret += theHours + " " + window.$gz.translation.get("TimeSpanHours") + " ";
|
||||
}
|
||||
if (theMinutes != 0) {
|
||||
ret +=
|
||||
theMinutes + " " + window.$gz.translation.get("TimeSpanMinutes") + " ";
|
||||
}
|
||||
if (!hideSeconds && theSeconds != 0) {
|
||||
ret +=
|
||||
theSeconds + " " + window.$gz.translation.get("TimeSpanSeconds") + " ";
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// 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.getResolvedTimeZoneName();
|
||||
}
|
||||
|
||||
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
|
||||
// also converts to time zone specified if diff from browser
|
||||
// (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.getResolvedTimeZoneName();
|
||||
}
|
||||
|
||||
//parse in the time in the currently used timezone
|
||||
let ret = window.$gz.DateTime.fromISO(value, {
|
||||
zone: timeZoneName
|
||||
});
|
||||
|
||||
ret = ret.setZone("utc"); //convert to UTC
|
||||
ret = ret.toISO(); //output as ISO 8601
|
||||
|
||||
return ret;
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// UTC Now in api format
|
||||
// to UTC and output as ISO 8601
|
||||
// (used to set defaults)
|
||||
//
|
||||
nowUTC8601String(timeZoneName) {
|
||||
if (!timeZoneName) {
|
||||
timeZoneName = this.getResolvedTimeZoneName();
|
||||
}
|
||||
const ret = window.$gz.DateTime.local()
|
||||
.setZone(timeZoneName)
|
||||
.toUTC()
|
||||
.toString();
|
||||
return ret;
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// UTC ISO 8601 string add minutes
|
||||
// and return as UTC ISO 8601 string
|
||||
// (used to set automatic / default adjusted times)
|
||||
//
|
||||
addMinutesToUTC8601String(val, minutes) {
|
||||
if (!val || val == "" || minutes == null || minutes == 0) {
|
||||
return val;
|
||||
}
|
||||
//instantiate a luxon date object from val which is assumed to be an iso string
|
||||
let dt = window.$gz.DateTime.fromISO(val);
|
||||
if (!dt.isValid) {
|
||||
console.error("locale::addMinutes, input not valid:", {
|
||||
val: val,
|
||||
dt: dt
|
||||
});
|
||||
return val;
|
||||
}
|
||||
//add minutes
|
||||
dt = dt.plus({ minutes: minutes });
|
||||
return dt.toUTC().toString();
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// UTC ISO 8601 string add arbitrary value based
|
||||
// on luxon duration format
|
||||
// and return as UTC ISO 8601 string
|
||||
//https://moment.github.io/luxon/api-docs/index.html#datetimeplus
|
||||
//
|
||||
addDurationToUTC8601String(val, duration) {
|
||||
if (
|
||||
!val ||
|
||||
val == "" ||
|
||||
duration == null ||
|
||||
!typeof duration === "object"
|
||||
) {
|
||||
return val;
|
||||
}
|
||||
//instantiate a luxon date object from val which is assumed to be an iso string
|
||||
let dt = window.$gz.DateTime.fromISO(val);
|
||||
if (!dt.isValid) {
|
||||
console.error("locale::addDurationToUTC8601String, input not valid:", {
|
||||
val: val,
|
||||
dt: dt
|
||||
});
|
||||
return val;
|
||||
}
|
||||
//add minutes
|
||||
dt = dt.plus(duration);
|
||||
return dt.toUTC().toString();
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// parse UTC ISO 8601 strings, diff, return hours
|
||||
//
|
||||
diffHoursFromUTC8601String(start, stop) {
|
||||
if (!start || start == "" || !stop == null || stop == "") {
|
||||
return 0;
|
||||
}
|
||||
//instantiate a luxon date object from val which is assumed to be an iso string
|
||||
const startDate = window.$gz.DateTime.fromISO(start);
|
||||
if (!startDate.isValid) {
|
||||
console.error("locale::diffHours, start not valid:", {
|
||||
start: start,
|
||||
startDate: startDate
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
const stopDate = window.$gz.DateTime.fromISO(stop);
|
||||
if (!stopDate.isValid) {
|
||||
console.error("locale::diffHours, start not valid:", {
|
||||
stop: stop,
|
||||
stopDate: stopDate
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
|
||||
// console.log(
|
||||
// "locale:diffhours...",
|
||||
// stopDate.diff(startDate, "hours").toObject().hours
|
||||
// );
|
||||
|
||||
// console.log(
|
||||
// "locale:diffhours.. ROUNDED.",
|
||||
// window.$gz.util.roundAccurately(
|
||||
// stopDate.diff(startDate, "hours").toObject().hours,
|
||||
// 2
|
||||
// )
|
||||
// );
|
||||
|
||||
return window.$gz.util.roundAccurately(
|
||||
stopDate.diff(startDate, "hours").toObject().hours,
|
||||
2
|
||||
);
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// Local now timestamp converted to timeZoneName
|
||||
// and output as ISO 8601
|
||||
// (used to inform server of local client time)
|
||||
//
|
||||
clientLocalZoneTimeStamp(timeZoneName) {
|
||||
if (!timeZoneName) {
|
||||
timeZoneName = this.getResolvedTimeZoneName();
|
||||
}
|
||||
const ret = window.$gz.DateTime.local()
|
||||
.setZone(timeZoneName)
|
||||
.toString();
|
||||
return ret;
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// Get default start date time in api format
|
||||
// (this is used to centralize and for future)
|
||||
defaultStartDateTime() {
|
||||
return {
|
||||
start: window.$gz.DateTime.local()
|
||||
.toUTC()
|
||||
.toString(),
|
||||
end: window.$gz.DateTime.local()
|
||||
.plus({ hours: 1 })
|
||||
.toUTC()
|
||||
.toString()
|
||||
};
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// 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.getResolvedTimeZoneName();
|
||||
}
|
||||
return new Date(value).toLocaleDateString("sv-SE", {
|
||||
timeZone: timeZoneName
|
||||
});
|
||||
}
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// Date/time past or future evaluation
|
||||
//
|
||||
dateIsPast(value) {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return new Date(value) < new Date();
|
||||
},
|
||||
///////////////////////////////////////////
|
||||
// Turn a decimal number into a local
|
||||
// currency display
|
||||
//
|
||||
currencyLocalized(value, languageName, currencyName) {
|
||||
if (value == null) return "";
|
||||
if (!languageName) {
|
||||
languageName = this.getResolvedLanguage();
|
||||
}
|
||||
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 == null) return "";
|
||||
if (!languageName) {
|
||||
languageName = this.getResolvedLanguage();
|
||||
}
|
||||
//This forces 2 digits after the decimal
|
||||
// return new Intl.NumberFormat(languageName, {
|
||||
// minimumFractionDigits: 2
|
||||
// }).format(value);
|
||||
//this goes with whatever is the local format which for dev testing turned out to be perfect: 1.00 displays as 1 and 1.75 displays as 1.75
|
||||
//alignment goes out the window but it follows v7 format
|
||||
return new Intl.NumberFormat(languageName).format(value);
|
||||
},
|
||||
///////////////////////////////////////////
|
||||
// Turn a file / memory size number into a local
|
||||
// decimal format display and in reasonable human readable range
|
||||
//
|
||||
humanFileSize(bytes, languageName, si = false, dp = 1) {
|
||||
const thresh = si ? 1000 : 1024;
|
||||
|
||||
if (Math.abs(bytes) < thresh) {
|
||||
return bytes + " B";
|
||||
}
|
||||
|
||||
const units = si
|
||||
? ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
|
||||
: ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
|
||||
let u = -1;
|
||||
const r = 10 ** dp;
|
||||
|
||||
do {
|
||||
bytes /= thresh;
|
||||
++u;
|
||||
} while (
|
||||
Math.round(Math.abs(bytes) * r) / r >= thresh &&
|
||||
u < units.length - 1
|
||||
);
|
||||
|
||||
return (
|
||||
this.decimalLocalized(bytes.toFixed(dp), languageName) + " " + units[u]
|
||||
);
|
||||
}
|
||||
};
|
||||
46
src/api/notifypoll.js
Normal file
46
src/api/notifypoll.js
Normal file
@@ -0,0 +1,46 @@
|
||||
let keepChecking = false;
|
||||
const DEFAULT_POLLING_INTERVAL = 60000;
|
||||
const MAX_POLLING_INTERVAL = 30 * 60 * 1000; //30 minutes maximum wait time
|
||||
export default {
|
||||
async startPolling() {
|
||||
if (keepChecking == true) {
|
||||
return;
|
||||
}
|
||||
keepChecking = true;
|
||||
//initial delay so it fetches "immediately"
|
||||
let pollingInterval = 3000;
|
||||
let status = null;
|
||||
while (keepChecking == true) {
|
||||
try {
|
||||
await window.$gz.util.sleepAsync(pollingInterval);
|
||||
if (keepChecking && window.$gz.store.state.authenticated) {
|
||||
if (window.$gz.erasingDatabase == false) {
|
||||
status = await window.$gz.api.get("notify/new-count");
|
||||
if (status.error) {
|
||||
throw new Error(window.$gz.errorHandler.errorToString(status));
|
||||
// throw new Error(status.error);
|
||||
} else {
|
||||
window.$gz.store.commit("setNewNotificationCount", status.data);
|
||||
//success so go to default in case it was changed by an error
|
||||
pollingInterval = DEFAULT_POLLING_INTERVAL;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
keepChecking = false;
|
||||
}
|
||||
} catch (error) {
|
||||
//fixup if fails on very first iteration with initial short polling interval
|
||||
if (pollingInterval < DEFAULT_POLLING_INTERVAL) {
|
||||
pollingInterval = DEFAULT_POLLING_INTERVAL;
|
||||
}
|
||||
pollingInterval *= 1.5;
|
||||
if (pollingInterval > MAX_POLLING_INTERVAL) {
|
||||
pollingInterval = MAX_POLLING_INTERVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
stopPolling() {
|
||||
keepChecking = false;
|
||||
}
|
||||
};
|
||||
277
src/api/open-object-handler.js
Normal file
277
src/api/open-object-handler.js
Normal file
@@ -0,0 +1,277 @@
|
||||
import socktype from "./socktype";
|
||||
export default {
|
||||
///////////////////////////////
|
||||
// APP (GLOBAL) openobject CLICK HANDLER
|
||||
//
|
||||
// Deal with a request to open an object (from main datatables mainly)
|
||||
// if it's an open object url that triggered here the url would be in the format of {host/open/[socktype integer]/[id integer]}, i.e.
|
||||
// http://localhost:8080/open/2/105
|
||||
// called from App.vue
|
||||
handleOpenObjectClick(vm, tid) {
|
||||
//expects extra data (tid) to be one of { type: [AYATYPE], id: [RECORDID] }
|
||||
//NOTE: for new objects all edit pages assume record ID 0 (or null) means create rather than open
|
||||
|
||||
//for sake of ease of coding I'm going to assume null id also means make a new record intent
|
||||
//so I don't have to parse and decide constantly on forms for every control that has a open record link in it
|
||||
if (tid.id == null) {
|
||||
tid.id = 0;
|
||||
}
|
||||
|
||||
if (tid.type && tid.id != null) {
|
||||
const isCustomerTypeUser =
|
||||
window.$gz.store.state.userType == 3 ||
|
||||
window.$gz.store.state.userType == 4;
|
||||
//if these come from route parameters they may well be strings
|
||||
tid.type = Number.parseInt(tid.type, 10);
|
||||
tid.id = Number.parseInt(tid.id, 10);
|
||||
if (isCustomerTypeUser) {
|
||||
switch (tid.type) {
|
||||
case socktype.NotifySubscription:
|
||||
vm.$router.push({
|
||||
name: "home-notify-subscription",
|
||||
params: { recordid: tid.id }
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
`Customer user: open-object-handler unable to open link - [type:${tid.type}, id:${tid.id}]`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
switch (tid.type) {
|
||||
case socktype.Memo:
|
||||
vm.$router.push({
|
||||
name: "memo-edit",
|
||||
params: { recordid: tid.id }
|
||||
});
|
||||
break;
|
||||
case socktype.Customer:
|
||||
vm.$router.push({
|
||||
name: "customer-edit",
|
||||
params: { recordid: tid.id }
|
||||
});
|
||||
break;
|
||||
case socktype.CustomerNote:
|
||||
vm.$router.push({
|
||||
name: "customer-note-edit",
|
||||
params: { recordid: tid.id }
|
||||
});
|
||||
break;
|
||||
case socktype.HeadOffice:
|
||||
vm.$router.push({
|
||||
name: "head-office-edit",
|
||||
params: { recordid: tid.id }
|
||||
});
|
||||
break;
|
||||
|
||||
case socktype.User:
|
||||
//Is it an "Inside" user (staff or subcontractor)
|
||||
//or an "outside" user (customer or headoffice)
|
||||
//if key doesn't provide this then need to directly find out first before determining which form to redirect to
|
||||
if (tid.id != 0) {
|
||||
//lookup which one to open from server
|
||||
(async () => {
|
||||
try {
|
||||
//shortcut for superuser, always id 1
|
||||
if (tid.inside == undefined && tid.id == 1) {
|
||||
tid.inside = true;
|
||||
}
|
||||
if (tid.inside == undefined) {
|
||||
const res = await window.$gz.api.get(
|
||||
"user/inside-type/" + tid.id
|
||||
);
|
||||
if (res.error) {
|
||||
throw new Error(
|
||||
window.$gz.errorHandler.errorToString(res, vm)
|
||||
);
|
||||
}
|
||||
if (res.data) {
|
||||
tid.inside = res.data;
|
||||
}
|
||||
}
|
||||
if (tid.inside == true) {
|
||||
vm.$router.push({
|
||||
name: "adm-user",
|
||||
params: { recordid: tid.id }
|
||||
});
|
||||
} else {
|
||||
vm.$router.push({
|
||||
name: "cust-user",
|
||||
params: { recordid: tid.id }
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(window.$gz.errorHandler.errorToString(e, vm));
|
||||
//throw new Error(e);
|
||||
}
|
||||
})();
|
||||
}
|
||||
break;
|
||||
case socktype.NotifySubscription:
|
||||
vm.$router.push({
|
||||
name: "home-notify-subscription",
|
||||
params: { recordid: tid.id }
|
||||
});
|
||||
break;
|
||||
case socktype.FileAttachment:
|
||||
//lookup the actual type
|
||||
//then call this method again to do the actual open
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const res = await window.$gz.api.get(
|
||||
"attachment/parent/" + tid.id
|
||||
);
|
||||
|
||||
if (res.error) {
|
||||
throw new Error(
|
||||
window.$gz.errorHandler.errorToString(res, vm)
|
||||
);
|
||||
// throw new Error(res.error);
|
||||
}
|
||||
if (res.data.id && res.data.id != 0) {
|
||||
this.handleOpenObjectClick(vm, res.data);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
//throw new Error(e);
|
||||
throw new Error(window.$gz.errorHandler.errorToString(e, vm));
|
||||
}
|
||||
})();
|
||||
|
||||
break;
|
||||
|
||||
case socktype.Translation:
|
||||
vm.$router.push({
|
||||
name: "adm-translation",
|
||||
params: { recordid: tid.id }
|
||||
});
|
||||
break;
|
||||
case socktype.Report:
|
||||
vm.$router.push({
|
||||
name: "sock-report-edit",
|
||||
params: { recordid: tid.id }
|
||||
});
|
||||
break;
|
||||
case socktype.Backup:
|
||||
vm.$router.push({
|
||||
name: "ops-backup"
|
||||
});
|
||||
break;
|
||||
|
||||
case socktype.FormCustom:
|
||||
//all we have is the id, but need the formkey to open it
|
||||
(async () => {
|
||||
try {
|
||||
const res = await window.$gz.api.get(
|
||||
"form-custom/form-key/" + tid.id
|
||||
);
|
||||
|
||||
if (res.error) {
|
||||
throw new Error(
|
||||
window.$gz.errorHandler.errorToString(res, vm)
|
||||
);
|
||||
}
|
||||
if (res && res.data) {
|
||||
vm.$router.push({
|
||||
name: "sock-customize",
|
||||
params: {
|
||||
formCustomTemplateKey: res.data
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
//throw new Error(e);
|
||||
throw new Error(window.$gz.errorHandler.errorToString(e, vm));
|
||||
}
|
||||
})();
|
||||
break;
|
||||
|
||||
case socktype.Reminder:
|
||||
vm.$router.push({
|
||||
name: "reminder-edit",
|
||||
params: { recordid: tid.id }
|
||||
});
|
||||
break;
|
||||
case socktype.Review:
|
||||
vm.$router.push({
|
||||
name: "review-edit",
|
||||
params: { recordid: tid.id }
|
||||
});
|
||||
break;
|
||||
|
||||
case socktype.CustomerNotifySubscription:
|
||||
vm.$router.push({
|
||||
name: "cust-notify-subscription",
|
||||
params: { recordid: tid.id }
|
||||
});
|
||||
break;
|
||||
case socktype.OpsNotificationSettings:
|
||||
vm.$router.push({
|
||||
name: "ops-notification-settings"
|
||||
});
|
||||
break;
|
||||
case socktype.Integration:
|
||||
vm.$router.push({
|
||||
name: "adm-integration",
|
||||
params: { recordid: tid.id }
|
||||
});
|
||||
break;
|
||||
|
||||
case socktype.Vendor:
|
||||
vm.$router.push({
|
||||
name: "vendor-edit",
|
||||
params: { recordid: tid.id }
|
||||
});
|
||||
break;
|
||||
|
||||
case socktype.Product:
|
||||
vm.$router.push({
|
||||
name: "product-edit",
|
||||
params: { recordid: tid.id }
|
||||
});
|
||||
break;
|
||||
|
||||
case socktype.SubscriptionServer:
|
||||
vm.$router.push({
|
||||
name: "subscription-server-edit",
|
||||
params: { recordid: tid.id }
|
||||
});
|
||||
break;
|
||||
|
||||
case socktype.GZCase:
|
||||
vm.$router.push({
|
||||
name: "gzcase-edit",
|
||||
params: { recordid: tid.id }
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
`open-object-handler: unknown [type:${tid.type}, id:${tid.id}]`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
///////////////////////////////////
|
||||
// WIRE UP MENU EVENTS
|
||||
//
|
||||
// called once from app.vue only
|
||||
//
|
||||
wireUpEventHandlers(vm) {
|
||||
const that = this;
|
||||
//expects extra data (tid) to be { type: [AYATYPE], id: [RECORDID] }
|
||||
window.$gz.eventBus.$on("openobject", function handleOpenObjectClickHandler(
|
||||
tid
|
||||
) {
|
||||
that.handleOpenObjectClick(vm, tid);
|
||||
});
|
||||
}
|
||||
//new functions above here
|
||||
};
|
||||
60
src/api/palette.js
Normal file
60
src/api/palette.js
Normal file
@@ -0,0 +1,60 @@
|
||||
//https://colorpalettes.net
|
||||
export default {
|
||||
color: {
|
||||
blue: "#1f77b4",
|
||||
red: "#d62728",
|
||||
orange: "#fe7f0e",
|
||||
green: "#2ca02c",
|
||||
purple: "#9c27b0",
|
||||
black: "#000000",
|
||||
cyan: "#00BCD4",
|
||||
teal: "#009688",
|
||||
primary: "#00205B", //APP Canucks dark blue
|
||||
secondary: "#00843D", //APP canucks green
|
||||
accent: "#db7022", //APP lighter orangey red, more friendly looking though not as much clarity it seems
|
||||
soft_sand: "#f1d3a1",
|
||||
soft_sand_taupe: "#e3dbd9",
|
||||
soft_pale_blue: "#e6eff6",
|
||||
soft_deep_blue: "#89b4c4",
|
||||
soft_green: "#ccdb86",
|
||||
soft_brown: "#c8bcb1",
|
||||
soft_brown_darker: "#8d7053",
|
||||
soft_gray: "#d2d7db"
|
||||
},
|
||||
getBoldPaletteArray(size) {
|
||||
const palette = [
|
||||
this.color.blue,
|
||||
this.color.red,
|
||||
this.color.green,
|
||||
this.color.orange,
|
||||
this.color.purple,
|
||||
this.color.cyan,
|
||||
this.color.teal,
|
||||
this.color.black
|
||||
];
|
||||
const paletteLength = palette.length;
|
||||
const ret = [];
|
||||
for (let i = 0; i < size; i++) {
|
||||
ret.push(palette[i % paletteLength]);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
getSoftPaletteArray(size) {
|
||||
const palette = [
|
||||
this.color.soft_sand,
|
||||
this.color.soft_pale_blue,
|
||||
this.color.soft_gray,
|
||||
this.color.soft_green,
|
||||
this.color.soft_brown,
|
||||
this.color.soft_deep_blue,
|
||||
this.color.soft_sand_taupe,
|
||||
this.color.soft_brown_darker
|
||||
];
|
||||
const paletteLength = palette.length;
|
||||
const ret = [];
|
||||
for (let i = 0; i < size; i++) {
|
||||
ret.push(palette[i % paletteLength]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
620
src/api/relative-date-filter-calculator.js
Normal file
620
src/api/relative-date-filter-calculator.js
Normal file
@@ -0,0 +1,620 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/////////////////////////////////
|
||||
// Convert a date token to local
|
||||
// date range to UTC for server
|
||||
// dataListView consumption
|
||||
//
|
||||
export default {
|
||||
///////////////////////////////
|
||||
// token to date range
|
||||
//
|
||||
tokenToDates: function(token) {
|
||||
if (token == null || token.length == 0) {
|
||||
throw new Error(
|
||||
"relative-date-filter-calculator: date token is null or empty"
|
||||
);
|
||||
}
|
||||
|
||||
//return object contains the two dates that encompass the time period
|
||||
//the token represents to the local browser time zone but in UTC
|
||||
//and iso8601 format
|
||||
|
||||
//NOTE: it's valid for one of the two ret values might be undefined as it's valid to have a single date for
|
||||
//Past or Future
|
||||
const ret = { after: undefined, before: undefined };
|
||||
const dtNow = window.$gz.DateTime.local();
|
||||
const dtToday = window.$gz.DateTime.local(
|
||||
dtNow.year,
|
||||
dtNow.month,
|
||||
dtNow.day
|
||||
);
|
||||
let dtAfter = null;
|
||||
let dtBefore = null;
|
||||
|
||||
switch (token) {
|
||||
case "*yesterday*":
|
||||
//Between Day before yesterday at midnight and yesterday at midnight
|
||||
ret.after = dtToday
|
||||
.plus({ days: -1, seconds: -1 })
|
||||
.toUTC()
|
||||
.toString();
|
||||
ret.before = dtToday.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*today*":
|
||||
//Between yesterday at midnight and tommorow at midnight
|
||||
ret.after = dtToday
|
||||
.plus({ seconds: -1 })
|
||||
.toUTC()
|
||||
.toString();
|
||||
ret.before = dtToday
|
||||
.plus({ days: 1 })
|
||||
.toUTC()
|
||||
.toString();
|
||||
break;
|
||||
|
||||
case "*tomorrow*":
|
||||
//Between Tonight at midnight and day after tommorow at midnight
|
||||
ret.after = dtToday
|
||||
.plus({ days: 1, seconds: -1 })
|
||||
.toUTC()
|
||||
.toString();
|
||||
ret.before = dtToday
|
||||
.plus({ days: 2 })
|
||||
.toUTC()
|
||||
.toString();
|
||||
break;
|
||||
|
||||
case "*lastweek*":
|
||||
//Between two Sundays ago at midnight and last sunday at midnight
|
||||
|
||||
//go back a week
|
||||
dtAfter = dtToday.plus({ days: -7 });
|
||||
//go backwards to Sunday (In Luxon Monday is 1, Sunday is 7)
|
||||
while (dtAfter.weekday != 7) {
|
||||
dtAfter = dtAfter.plus({ days: -1 });
|
||||
}
|
||||
//go to very start of eighth dayahead
|
||||
dtBefore = dtAfter.plus({ days: 8 });
|
||||
//remove a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*thisweek*":
|
||||
//Between Sunday at midnight and Next sunday at midnight
|
||||
|
||||
//Start with today
|
||||
dtAfter = dtToday;
|
||||
//SET dtAfter to Monday start of this week
|
||||
//go backwards to monday (In Luxon Monday is 1, Sunday is 7)
|
||||
while (dtAfter.weekday != 1) {
|
||||
dtAfter = dtAfter.plus({ days: -1 });
|
||||
}
|
||||
//Now go back to sunday last second
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//Start with today
|
||||
dtBefore = dtToday;
|
||||
|
||||
//SET dtBefore to next monday
|
||||
//is it monday now?
|
||||
if (dtBefore.weekday == 1) {
|
||||
//Monday today? then go to next monday
|
||||
dtBefore = dtBefore.plus({ days: 7 });
|
||||
} else {
|
||||
//Find next monday...
|
||||
while (dtBefore.weekday != 1) {
|
||||
dtBefore = dtBefore.plus({ days: 1 });
|
||||
}
|
||||
}
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*nextweek*":
|
||||
//Between Next Sunday at midnight and Next Next sunday at midnight
|
||||
|
||||
//Start with today
|
||||
dtAfter = dtToday;
|
||||
//If today is monday skip over it first, we're looking for *next* monday, not this one
|
||||
if (dtAfter.weekday == 1) {
|
||||
dtAfter = dtAfter.plus({ days: 1 });
|
||||
}
|
||||
|
||||
//go forwards to next monday 12:00am (In Luxon Monday is 1, Sunday is 7)
|
||||
while (dtAfter.weekday != 1) {
|
||||
dtAfter = dtAfter.plus({ days: 1 });
|
||||
}
|
||||
//Now go back to sunday last second
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set dtBefore 7 days ahead of dtAfter
|
||||
//(sb BEFORE two mondays from now at zero hour so need to add a second due to prior removal of a second to make sunday)
|
||||
dtBefore = dtAfter.plus({ days: 7, seconds: 1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*lastmonth*":
|
||||
//start with the first day of this month
|
||||
dtAfter = window.$gz.DateTime.local(dtNow.year, dtNow.month, 1);
|
||||
//subtract a Month
|
||||
dtAfter = dtAfter.plus({ months: -1 });
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.plus({ months: 1 });
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*thismonth*":
|
||||
//start with the first day of this month
|
||||
dtAfter = window.$gz.DateTime.local(dtNow.year, dtNow.month, 1);
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.plus({ months: 1 });
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*nextmonth*":
|
||||
//start with the first day of this month
|
||||
dtAfter = window.$gz.DateTime.local(dtNow.year, dtNow.month, 1);
|
||||
|
||||
//add a month
|
||||
dtAfter = dtAfter.plus({ months: 1 });
|
||||
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.plus({ months: 1 });
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*14daywindow*":
|
||||
//Start with today
|
||||
dtAfter = dtToday;
|
||||
|
||||
//subtract 7 days
|
||||
dtAfter = dtAfter.plus({ days: -7 });
|
||||
|
||||
//Add 15 days to dtAfter to get end date
|
||||
dtBefore = dtAfter.plus({ days: 15 });
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*past*":
|
||||
//Any time before Now
|
||||
//set return values from calculated values
|
||||
ret.after = undefined;
|
||||
ret.before = dtNow.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*future*":
|
||||
//Any time after Now
|
||||
//set return values from calculated values
|
||||
ret.after = dtNow.toUTC().toString();
|
||||
ret.before = undefined;
|
||||
break;
|
||||
|
||||
case "*lastyear*":
|
||||
//"last year" means prior calendar year from start of january to end of december
|
||||
//start with the first day of this year
|
||||
dtAfter = window.$gz.DateTime.local(dtNow.year);
|
||||
|
||||
//subtract a year
|
||||
dtAfter = dtAfter.plus({ years: -1 });
|
||||
|
||||
//Before zero hour january 1st this year
|
||||
dtBefore = window.$gz.DateTime.local(dtNow.year);
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*thisyear*":
|
||||
//From zero hour january 1 this year (minus a second) to zero hour jan 1 next year
|
||||
//start with the first day of this year
|
||||
dtAfter = window.$gz.DateTime.local(dtNow.year);
|
||||
|
||||
//Before zero hour january 1st next year
|
||||
dtBefore = window.$gz.DateTime.local(dtNow.year);
|
||||
dtBefore = dtBefore.plus({ years: 1 });
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*last3months*":
|
||||
//From Now minus 3 months
|
||||
dtAfter = dtToday.plus({ months: -3 });
|
||||
|
||||
//Before now
|
||||
dtBefore = dtNow;
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*last6months*":
|
||||
//From Now minus 6 months
|
||||
dtAfter = dtToday.plus({ months: -6 });
|
||||
|
||||
//Before now
|
||||
dtBefore = dtNow;
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*pastyear*": //within the prior 365 days before today
|
||||
//From Now minus 365 days
|
||||
dtAfter = dtToday.plus({ days: -365 });
|
||||
|
||||
//Before now
|
||||
dtBefore = dtNow;
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*past90days*":
|
||||
//From Now minus 90 days
|
||||
dtAfter = dtNow.plus({ days: -90 });
|
||||
|
||||
//Before now
|
||||
dtBefore = dtNow;
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*past30days*":
|
||||
//From Now minus 30 days
|
||||
dtAfter = dtNow.plus({ days: -30 });
|
||||
|
||||
//Before now
|
||||
dtBefore = dtNow;
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*past7days*":
|
||||
//From Now minus 7 days
|
||||
dtAfter = dtNow.plus({ days: -7 });
|
||||
|
||||
//Before now
|
||||
dtBefore = dtNow;
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*past24hours*":
|
||||
//From Now minus 24 hours
|
||||
dtAfter = dtNow.plus({ hours: -24 });
|
||||
|
||||
//Before now
|
||||
dtBefore = dtNow;
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*past6hours*":
|
||||
//From Now minus 6 hours
|
||||
dtAfter = dtNow.plus({ hours: -6 });
|
||||
|
||||
//Before now
|
||||
dtBefore = dtNow;
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
case "*january*":
|
||||
//This year specific month (month is 1 based)
|
||||
dtAfter = window.$gz.DateTime.local(dtNow.year, 1, 1);
|
||||
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.plus({ months: 1 });
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*february*":
|
||||
//This year specific month (month is 1 based)
|
||||
dtAfter = window.$gz.DateTime.local(dtNow.year, 2, 1);
|
||||
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.plus({ months: 1 });
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*march*":
|
||||
//This year specific month (month is 1 based)
|
||||
dtAfter = window.$gz.DateTime.local(dtNow.year, 3, 1);
|
||||
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.plus({ months: 1 });
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*april*":
|
||||
//This year specific month (month is 1 based)
|
||||
dtAfter = window.$gz.DateTime.local(dtNow.year, 4, 1);
|
||||
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.plus({ months: 1 });
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*may*":
|
||||
//This year specific month (month is 1 based)
|
||||
dtAfter = window.$gz.DateTime.local(dtNow.year, 5, 1);
|
||||
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.plus({ months: 1 });
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*june*":
|
||||
//This year specific month (month is 1 based)
|
||||
dtAfter = window.$gz.DateTime.local(dtNow.year, 6, 1);
|
||||
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.plus({ months: 1 });
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*july*":
|
||||
//This year specific month (month is 1 based)
|
||||
dtAfter = window.$gz.DateTime.local(dtNow.year, 7, 1);
|
||||
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.plus({ months: 1 });
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*august*":
|
||||
//This year specific month (month is 1 based)
|
||||
dtAfter = window.$gz.DateTime.local(dtNow.year, 8, 1);
|
||||
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.plus({ months: 1 });
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*september*":
|
||||
//This year specific month (month is 1 based)
|
||||
dtAfter = window.$gz.DateTime.local(dtNow.year, 9, 1);
|
||||
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.plus({ months: 1 });
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*october*":
|
||||
//This year specific month (month is 1 based)
|
||||
dtAfter = window.$gz.DateTime.local(dtNow.year, 10, 1);
|
||||
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.plus({ months: 1 });
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*november*":
|
||||
//This year specific month (month is 1 based)
|
||||
dtAfter = window.$gz.DateTime.local(dtNow.year, 11, 1);
|
||||
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.plus({ months: 1 });
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*december*":
|
||||
//This year specific month (month is 1 based)
|
||||
dtAfter = window.$gz.DateTime.local(dtNow.year, 12, 1);
|
||||
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.plus({ months: 1 });
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*lastyearlastmonth*":
|
||||
//start with the first day of this month
|
||||
dtAfter = window.$gz.DateTime.local(dtNow.year, dtNow.month, 1);
|
||||
//subtract a Year and a Month
|
||||
dtAfter = dtAfter.plus({ years: -1, months: -1 });
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.plus({ months: 1 });
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*lastyearthismonth*":
|
||||
//start with the first day of this month
|
||||
dtAfter = window.$gz.DateTime.local(dtNow.year, dtNow.month, 1);
|
||||
//subtract a Year
|
||||
dtAfter = dtAfter.plus({ years: -1 });
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.plus({ months: 1 });
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
case "*lastyearnextmonth*":
|
||||
//start with the first day of this month
|
||||
dtAfter = window.$gz.DateTime.local(dtNow.year, dtNow.month, 1);
|
||||
|
||||
//subtract a year, add a month
|
||||
dtAfter = dtAfter.plus({ years: -1, months: 1 });
|
||||
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.plus({ months: 1 });
|
||||
|
||||
//move after back a second for boundary
|
||||
dtAfter = dtAfter.plus({ seconds: -1 });
|
||||
|
||||
//set return values from calculated values
|
||||
ret.after = dtAfter.toUTC().toString();
|
||||
ret.before = dtBefore.toUTC().toString();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(
|
||||
"relative-date-time-filter-calculater: Date token [" +
|
||||
token +
|
||||
"] was not recognized"
|
||||
);
|
||||
//--------------------------
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
4
src/api/sockeye-version.js
Normal file
4
src/api/sockeye-version.js
Normal file
@@ -0,0 +1,4 @@
|
||||
export default {
|
||||
version: "8.0.28",
|
||||
copyright: "© 1999-2022, Ground Zero Tech-Works Inc."
|
||||
};
|
||||
48
src/api/socktype.js
Normal file
48
src/api/socktype.js
Normal file
@@ -0,0 +1,48 @@
|
||||
export default {
|
||||
NoType: 0,
|
||||
Global: 1,
|
||||
FormUserOptions: 2,
|
||||
User: 3,
|
||||
ServerState: 4,
|
||||
LogFile: 6,
|
||||
PickListTemplate: 7,
|
||||
Customer: 8,
|
||||
ServerJob: 9,
|
||||
ServerMetrics: 12,
|
||||
Translation: 13,
|
||||
UserOptions: 14,
|
||||
HeadOffice: 15,
|
||||
FileAttachment: 17,
|
||||
DataListSavedFilter: 18,
|
||||
FormCustom: 19,
|
||||
Vendor: 33,
|
||||
GlobalOps: 47, //really only used for rights, not an object type of any kind
|
||||
BizMetrics: 48, //deprecate? Not used for anything as of nov 2020
|
||||
Backup: 49,
|
||||
Notification: 50,
|
||||
NotifySubscription: 51,
|
||||
Reminder: 52,
|
||||
OpsNotificationSettings: 56,
|
||||
Report: 57,
|
||||
DashboardView: 58,
|
||||
CustomerNote: 59,
|
||||
Memo: 60,
|
||||
Review: 61,
|
||||
DataListColumnView: 68,
|
||||
CustomerNotifySubscription: 84, //proxy subs for customers
|
||||
Integration: 92, //3rd party or add-on integration data store,
|
||||
|
||||
License: 93,
|
||||
TrialLicenseRequest: 94,
|
||||
SubscriptionServer: 95,
|
||||
Purchase: 96,
|
||||
Product: 97,
|
||||
GZCase: 98
|
||||
};
|
||||
/**
|
||||
*
|
||||
* This is a mirror of SockType.cs in server project
|
||||
* To update just copy the contents of SockType.cs and replace " :" with ":" (without quotes obvsly)
|
||||
*
|
||||
*
|
||||
*/
|
||||
327
src/api/translation.js
Normal file
327
src/api/translation.js
Normal file
@@ -0,0 +1,327 @@
|
||||
export default {
|
||||
////////////////////////////////
|
||||
// Update the local cache
|
||||
//
|
||||
//
|
||||
async updateCache(editedTranslation) {
|
||||
//This function is only called if there is a requirement to refresh the local cache
|
||||
//either they just changed translations and saved it in user settings
|
||||
//or they just edited a translation and saved it in translation editor and it's also their own local translation
|
||||
|
||||
if (editedTranslation) {
|
||||
//iterate the keys that are cached and set them from whatever is in editedTranslation for that key
|
||||
for (const [key] of Object.entries(
|
||||
window.$gz.store.state.translationText
|
||||
)) {
|
||||
const display = editedTranslation.translationItems.find(
|
||||
z => z.key == key
|
||||
).display;
|
||||
|
||||
window.$gz.store.commit("setTranslationText", {
|
||||
key: key,
|
||||
value: display
|
||||
});
|
||||
}
|
||||
} else {
|
||||
//gather up the keys that are cached and fetch the latest and then replace them
|
||||
const needIt = [];
|
||||
Object.keys(window.$gz.store.state.translationText).forEach(z => {
|
||||
needIt.push(z);
|
||||
});
|
||||
//fetch these keys
|
||||
const transData = await window.$gz.api.upsert(
|
||||
"translation/subset",
|
||||
needIt
|
||||
);
|
||||
transData.data.forEach(function commitFetchedTranslationItemToStore(
|
||||
item
|
||||
) {
|
||||
window.$gz.store.commit("setTranslationText", item);
|
||||
});
|
||||
}
|
||||
},
|
||||
get(key) {
|
||||
if (!key) {
|
||||
console.trace("translation.js::get, no translation key was presented");
|
||||
return "";
|
||||
}
|
||||
//no translation for Wiki
|
||||
if (key == "Wiki") {
|
||||
return "Wiki";
|
||||
}
|
||||
if (!window.$gz.util.has(window.$gz.store.state.translationText, key)) {
|
||||
return "??" + key;
|
||||
}
|
||||
return window.$gz.store.state.translationText[key];
|
||||
},
|
||||
async cacheTranslations(keys, forceTranslationId) {
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async function fetchTranslationKeysFromServer(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
|
||||
const needIt = [];
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
if (
|
||||
!window.$gz.util.has(window.$gz.store.state.translationText, keys[i])
|
||||
) {
|
||||
needIt.push(keys[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (needIt.length == 0) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
//step 2: get it
|
||||
let transData = null;
|
||||
|
||||
if (forceTranslationId) {
|
||||
transData = await window.$gz.api.upsert(
|
||||
`translation/subset/${forceTranslationId}`,
|
||||
needIt
|
||||
);
|
||||
} else {
|
||||
transData = await window.$gz.api.upsert("translation/subset", needIt);
|
||||
}
|
||||
|
||||
transData.data.forEach(function commitFetchedTranslationItemToStore(
|
||||
item
|
||||
) {
|
||||
window.$gz.store.commit("setTranslationText", item);
|
||||
});
|
||||
return resolve();
|
||||
});
|
||||
},
|
||||
//Keys that will always be required for any Sockeye work for any user
|
||||
coreKeys: [
|
||||
//main nav options
|
||||
"Home",
|
||||
"Dashboard",
|
||||
"Schedule",
|
||||
"MemoList",
|
||||
"ReviewList",
|
||||
"UserSettings",
|
||||
"SetLoginPassword",
|
||||
"NotifySubscriptionList",
|
||||
"UserPreferences",
|
||||
"Service",
|
||||
"CustomerList",
|
||||
"HeadOfficeList",
|
||||
"VendorList",
|
||||
"CustomerNotifySubscriptionList",
|
||||
"Contacts",
|
||||
"AdministrationGlobalSettings",
|
||||
"HelpLicense",
|
||||
"UserList",
|
||||
"Translation",
|
||||
"TranslationList",
|
||||
"ReportList",
|
||||
"ReminderList",
|
||||
"Accounting",
|
||||
"Administration",
|
||||
"Operations",
|
||||
"Attachments",
|
||||
"Review",
|
||||
"Extensions",
|
||||
"History",
|
||||
"Statistics",
|
||||
"Backup",
|
||||
"ServerState",
|
||||
"ServerJobs",
|
||||
"ServerLog",
|
||||
"ServerMetrics",
|
||||
"ServerProfiler",
|
||||
"OpsNotificationSettings",
|
||||
"ViewServerConfiguration",
|
||||
"NotificationCustomerDeliveryLog",
|
||||
"NotificationDeliveryLog",
|
||||
"HelpAboutSockeye",
|
||||
"MenuHelp",
|
||||
"More",
|
||||
"Logout",
|
||||
"Active",
|
||||
"Copy",
|
||||
"New",
|
||||
"Cancel",
|
||||
"Close",
|
||||
"Save",
|
||||
"SaveACopy",
|
||||
"Delete",
|
||||
"SoftDelete",
|
||||
"SoftDeleteAll",
|
||||
"Undelete",
|
||||
"Add",
|
||||
"Replace",
|
||||
"Remove",
|
||||
"OK",
|
||||
"Open",
|
||||
"Print",
|
||||
"Report",
|
||||
"Refresh",
|
||||
"Sort",
|
||||
"Duplicate",
|
||||
"RecordHistory",
|
||||
"Search",
|
||||
"TypeToSearchOrAdd",
|
||||
"SelectedItems",
|
||||
"AllItemsInList",
|
||||
"NoData",
|
||||
"Errors",
|
||||
"ErrorFieldLengthExceeded",
|
||||
"ErrorStartDateAfterEndDate",
|
||||
"ErrorRequiredFieldEmpty",
|
||||
"ErrorFieldValueNotInteger",
|
||||
"ErrorFieldValueNotDecimal",
|
||||
"ErrorAPI2000",
|
||||
"ErrorAPI2001",
|
||||
"ErrorAPI2002",
|
||||
"ErrorAPI2003",
|
||||
"ErrorAPI2004",
|
||||
"ErrorAPI2005",
|
||||
"ErrorAPI2006",
|
||||
"ErrorAPI2010",
|
||||
"ErrorAPI2020",
|
||||
"ErrorAPI2030",
|
||||
"ErrorAPI2040",
|
||||
"ErrorAPI2200",
|
||||
"ErrorAPI2201",
|
||||
"ErrorAPI2202",
|
||||
"ErrorAPI2203",
|
||||
"ErrorAPI2204",
|
||||
"ErrorAPI2205",
|
||||
"ErrorAPI2206",
|
||||
"ErrorAPI2207",
|
||||
"ErrorAPI2208",
|
||||
"ErrorAPI2209",
|
||||
"ErrorAPI2210",
|
||||
"ErrorAPI2212",
|
||||
"ErrorServerUnresponsive",
|
||||
"ErrorUserNotAuthenticated",
|
||||
"ErrorUserNotAuthorized",
|
||||
"ErrorNoMatch",
|
||||
"ErrorPickListQueryInvalid",
|
||||
"ErrorSecurityUserCapacity",
|
||||
"ErrorDBForeignKeyViolation",
|
||||
"DeletePrompt",
|
||||
"AreYouSureUnsavedChanges",
|
||||
"Leave",
|
||||
"Tags",
|
||||
"Tag",
|
||||
"Customize",
|
||||
"ObjectCustomFieldCustomGrid",
|
||||
"RowsPerPage",
|
||||
"PageOfPageText",
|
||||
"Loading",
|
||||
"Filter",
|
||||
"Heading",
|
||||
"Table",
|
||||
"InsertLink",
|
||||
"LinkUrl",
|
||||
"LinkText",
|
||||
"InsertImage",
|
||||
"ImageUrl",
|
||||
"ImageDescription",
|
||||
"AttachFile",
|
||||
"AttachmentNotes",
|
||||
"Upload",
|
||||
"AttachmentFileName",
|
||||
"FileAttachment",
|
||||
"MaintenanceExpired",
|
||||
"MaintenanceExpiredNote",
|
||||
"Import",
|
||||
"Export",
|
||||
"TimeSpanYears",
|
||||
"TimeSpanMonths",
|
||||
"TimeSpanDays",
|
||||
"TimeSpanHours",
|
||||
"TimeSpanMinutes",
|
||||
"TimeSpanSeconds",
|
||||
"DirectNotification",
|
||||
"UpdateAvailable",
|
||||
"DropFilesHere",
|
||||
"First",
|
||||
"Backward",
|
||||
"Forward",
|
||||
"Last",
|
||||
"GeoCapture",
|
||||
"GeoView",
|
||||
"CopyToClipboard",
|
||||
"SockType",
|
||||
"Now",
|
||||
"DateRangeToday",
|
||||
"ReportRenderTimeOut",
|
||||
"RenderingReport",
|
||||
"Settings",
|
||||
"IntegrationList",
|
||||
"BusinessSettings",
|
||||
"LicenseList",
|
||||
"License",
|
||||
"TrialLicenseRequestList",
|
||||
"SubscriptionServerList",
|
||||
"ProductList",
|
||||
"PurchaseList",
|
||||
"GZCaseList"
|
||||
],
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// Take in a string that contains one or more
|
||||
//translation keys that start with LT:
|
||||
//translate each and replace and return the string translated
|
||||
// (fetch and cache any missing strings)
|
||||
async translateStringWithMultipleKeysAsync(s) {
|
||||
if (s == null) {
|
||||
return s;
|
||||
}
|
||||
let ret = s;
|
||||
|
||||
const found = s.match(/LT:[\w]*/gm);
|
||||
if (found == null) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
//clean up the keys for fetching
|
||||
const keysToCache = found.map(z => z.replace("LT:", ""));
|
||||
//cache / fetch any that are not already present
|
||||
await this.cacheTranslations(keysToCache);
|
||||
|
||||
//replace
|
||||
found.forEach(z => {
|
||||
const translated = this.get(z.replace("LT:", ""));
|
||||
//replace all
|
||||
ret = ret.split(z).join(translated);
|
||||
});
|
||||
|
||||
return ret;
|
||||
},
|
||||
////////////////////////////////////////////////////////
|
||||
// Take in a string that contains one or more
|
||||
//translation keys that start with LT:
|
||||
//translate each and replace and return the string translated
|
||||
// (DOES NOT fetch and cache any missing strings, they must exist)
|
||||
//this is the sync version to be used in non async capable code
|
||||
translateStringWithMultipleKeys(s) {
|
||||
let ret = s;
|
||||
const found = s.match(/LT:[\w]*/gm);
|
||||
if (found == null) {
|
||||
return ret;
|
||||
}
|
||||
//replace
|
||||
found.forEach(z => {
|
||||
const translated = this.get(z.replace("LT:", ""));
|
||||
//replace all
|
||||
ret = ret.split(z).join(translated);
|
||||
});
|
||||
return ret;
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// dynamically set the vuetify language elements from
|
||||
// users translated text
|
||||
// Keeping vuetify using en locale and just adjusting on top of that
|
||||
//
|
||||
setVuetifyDefaultLanguageElements(vm) {
|
||||
vm.$vuetify.lang.locales.en.close = this.get("OK");
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user