Introduce caching and workspace persistence for Edge Gateways, optimize API requests with in-flight request tracking, and implement UI layout adjustments in PosDepartmentStepMobile2Product.vue.
This commit is contained in:
+3
-5
@@ -252,11 +252,7 @@ const noop = () => {};
|
||||
>
|
||||
<i class="fas fa-check"></i>
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="icon vehicle-card__footer-chevron"
|
||||
data-testid="pos-mobile-additional-items-header-icon"
|
||||
>
|
||||
<span v-else class="icon vehicle-card__footer-chevron" data-testid="pos-mobile-additional-items-header-icon">
|
||||
<i class="fas fa-angle-right"></i>
|
||||
</span>
|
||||
</div>
|
||||
@@ -484,6 +480,7 @@ const noop = () => {};
|
||||
}
|
||||
|
||||
.vehicle-card__additional-items {
|
||||
justify-content: flex-start;
|
||||
min-height: 4.1rem;
|
||||
}
|
||||
|
||||
@@ -512,6 +509,7 @@ const noop = () => {};
|
||||
display: flex;
|
||||
flex: 0 0 3.4rem;
|
||||
justify-content: flex-end;
|
||||
margin-left: auto;
|
||||
min-width: 3.4rem;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
+369
-12
@@ -1,21 +1,372 @@
|
||||
import { authenticatedRequest } from "@/components/session/authenticatedRequest.vue";
|
||||
|
||||
export const listEdgeGatewayDepartments = async () => authenticatedRequest("/departments", "GET", {});
|
||||
export const EDGE_GATEWAY_WORKSPACE_CACHE_KEY = "truckwash.edgeGatewayWorkspace.cache.v1";
|
||||
|
||||
export const listEdgeGateways = async ({ departmentId = null, view = "summary" } = {}) =>
|
||||
authenticatedRequest("/edge-gateways", "GET", {
|
||||
...(departmentId ? { department_id: departmentId } : {}),
|
||||
...(view ? { view } : {}),
|
||||
const listRequestsInFlight = new Map();
|
||||
const detailRequestsInFlight = new Map();
|
||||
let departmentsRequestInFlight = null;
|
||||
let edgeGatewayWorkspaceCache = null;
|
||||
|
||||
const cloneJson = (value) => (value === null || value === undefined ? value : JSON.parse(JSON.stringify(value)));
|
||||
const normalizeDepartmentId = (departmentId) => (departmentId === null || departmentId === undefined || departmentId === "" ? null : Number(departmentId));
|
||||
const normalizeGatewayId = (gatewayId) => (gatewayId === null || gatewayId === undefined || gatewayId === "" ? null : String(gatewayId));
|
||||
const listCacheKey = ({ departmentId = null, view = "summary" } = {}) =>
|
||||
JSON.stringify({ departmentId: normalizeDepartmentId(departmentId), view: String(view || "summary") });
|
||||
|
||||
const createEmptyWorkspaceCache = () => ({
|
||||
departments: null,
|
||||
lists: {},
|
||||
details: {},
|
||||
});
|
||||
|
||||
const storage = () => {
|
||||
if (typeof window === "undefined" || !window.localStorage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return window.localStorage;
|
||||
};
|
||||
|
||||
const loadWorkspaceCache = () => {
|
||||
if (edgeGatewayWorkspaceCache !== null) {
|
||||
return edgeGatewayWorkspaceCache;
|
||||
}
|
||||
|
||||
const localStorageHandle = storage();
|
||||
if (!localStorageHandle) {
|
||||
edgeGatewayWorkspaceCache = createEmptyWorkspaceCache();
|
||||
return edgeGatewayWorkspaceCache;
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = JSON.parse(localStorageHandle.getItem(EDGE_GATEWAY_WORKSPACE_CACHE_KEY) || "null");
|
||||
edgeGatewayWorkspaceCache = {
|
||||
...createEmptyWorkspaceCache(),
|
||||
...(decoded && typeof decoded === "object" ? decoded : {}),
|
||||
lists: decoded?.lists && typeof decoded.lists === "object" ? decoded.lists : {},
|
||||
details: decoded?.details && typeof decoded.details === "object" ? decoded.details : {},
|
||||
};
|
||||
} catch (_error) {
|
||||
edgeGatewayWorkspaceCache = createEmptyWorkspaceCache();
|
||||
}
|
||||
|
||||
return edgeGatewayWorkspaceCache;
|
||||
};
|
||||
|
||||
const persistWorkspaceCache = () => {
|
||||
const localStorageHandle = storage();
|
||||
if (!localStorageHandle || edgeGatewayWorkspaceCache === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
localStorageHandle.setItem(EDGE_GATEWAY_WORKSPACE_CACHE_KEY, JSON.stringify(edgeGatewayWorkspaceCache));
|
||||
};
|
||||
|
||||
const mergeGatewaySnapshots = (currentGateway, nextGateway) => {
|
||||
if (!currentGateway) {
|
||||
return cloneJson(nextGateway);
|
||||
}
|
||||
|
||||
return {
|
||||
...cloneJson(currentGateway),
|
||||
...cloneJson(nextGateway),
|
||||
metadata: {
|
||||
...(currentGateway?.metadata || {}),
|
||||
...(nextGateway?.metadata || {}),
|
||||
},
|
||||
inventory: Array.isArray(nextGateway?.inventory)
|
||||
? cloneJson(nextGateway.inventory)
|
||||
: Array.isArray(currentGateway?.inventory)
|
||||
? cloneJson(currentGateway.inventory)
|
||||
: undefined,
|
||||
bindings: Array.isArray(nextGateway?.bindings)
|
||||
? cloneJson(nextGateway.bindings)
|
||||
: Array.isArray(currentGateway?.bindings)
|
||||
? cloneJson(currentGateway.bindings)
|
||||
: undefined,
|
||||
operations: Array.isArray(nextGateway?.operations)
|
||||
? cloneJson(nextGateway.operations)
|
||||
: Array.isArray(currentGateway?.operations)
|
||||
? cloneJson(currentGateway.operations)
|
||||
: undefined,
|
||||
audit_logs: Array.isArray(nextGateway?.audit_logs)
|
||||
? cloneJson(nextGateway.audit_logs)
|
||||
: Array.isArray(currentGateway?.audit_logs)
|
||||
? cloneJson(currentGateway.audit_logs)
|
||||
: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
const cacheGatewaySnapshot = (gateway) => {
|
||||
if (!gateway || !gateway.id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cache = loadWorkspaceCache();
|
||||
const gatewayId = String(gateway.id);
|
||||
const mergedGateway = mergeGatewaySnapshots(cache.details[gatewayId]?.data || null, gateway);
|
||||
cache.details[gatewayId] = {
|
||||
cachedAt: Date.now(),
|
||||
data: mergedGateway,
|
||||
};
|
||||
|
||||
Object.entries(cache.lists || {}).forEach(([cacheKey, entry]) => {
|
||||
const listEntry = entry && typeof entry === "object" ? entry : { params: null, rows: [] };
|
||||
const params = listEntry.params && typeof listEntry.params === "object" ? listEntry.params : {};
|
||||
const rows = Array.isArray(listEntry.rows) ? [...listEntry.rows] : [];
|
||||
const matchesDepartment =
|
||||
normalizeDepartmentId(params.departmentId) === null || Number(params.departmentId) === Number(gateway.department_id);
|
||||
const existingIndex = rows.findIndex((item) => Number(item?.id || 0) === Number(gateway.id));
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
rows[existingIndex] = mergeGatewaySnapshots(rows[existingIndex], gateway);
|
||||
} else if (matchesDepartment) {
|
||||
rows.unshift(cloneJson(gateway));
|
||||
}
|
||||
|
||||
cache.lists[cacheKey] = {
|
||||
...listEntry,
|
||||
rows,
|
||||
cachedAt: Date.now(),
|
||||
};
|
||||
});
|
||||
|
||||
export const getEdgeGateway = async (gatewayId) =>
|
||||
authenticatedRequest(`/edge-gateways/${encodeURIComponent(gatewayId)}`, "GET", {});
|
||||
persistWorkspaceCache();
|
||||
return cloneJson(mergedGateway);
|
||||
};
|
||||
|
||||
const cacheFleetSnapshot = ({ departmentId = null, view = "summary", rows = [], meta = {} } = {}) => {
|
||||
const cache = loadWorkspaceCache();
|
||||
const key = listCacheKey({ departmentId, view });
|
||||
cache.lists[key] = {
|
||||
params: {
|
||||
departmentId: normalizeDepartmentId(departmentId),
|
||||
view: String(view || "summary"),
|
||||
},
|
||||
rows: cloneJson(Array.isArray(rows) ? rows : []),
|
||||
meta: cloneJson(meta || {}),
|
||||
cachedAt: Date.now(),
|
||||
};
|
||||
|
||||
rows.forEach((gateway) => {
|
||||
if (!gateway?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const gatewayId = String(gateway.id);
|
||||
if (!cache.details[gatewayId]) {
|
||||
cache.details[gatewayId] = {
|
||||
cachedAt: Date.now(),
|
||||
data: cloneJson(gateway),
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
cache.details[gatewayId] = {
|
||||
cachedAt: Date.now(),
|
||||
data: mergeGatewaySnapshots(cache.details[gatewayId].data, gateway),
|
||||
};
|
||||
});
|
||||
|
||||
persistWorkspaceCache();
|
||||
};
|
||||
|
||||
const cacheDepartmentsSnapshot = (departments) => {
|
||||
const cache = loadWorkspaceCache();
|
||||
cache.departments = {
|
||||
cachedAt: Date.now(),
|
||||
data: cloneJson(Array.isArray(departments) ? departments : []),
|
||||
};
|
||||
persistWorkspaceCache();
|
||||
};
|
||||
|
||||
const cacheGatewayFromResponse = (response) => {
|
||||
const gateway = response?.data?.data;
|
||||
if (gateway?.id) {
|
||||
cacheGatewaySnapshot(gateway);
|
||||
}
|
||||
return response;
|
||||
};
|
||||
|
||||
export const peekCachedEdgeGatewayDepartments = () => {
|
||||
const departments = loadWorkspaceCache().departments?.data;
|
||||
return Array.isArray(departments) ? cloneJson(departments) : null;
|
||||
};
|
||||
|
||||
export const peekCachedEdgeGatewayList = ({ departmentId = null, view = "summary" } = {}) => {
|
||||
const entry = loadWorkspaceCache().lists[listCacheKey({ departmentId, view })];
|
||||
if (!entry || !Array.isArray(entry.rows)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
cachedAt: Number(entry.cachedAt || 0),
|
||||
rows: cloneJson(entry.rows),
|
||||
meta: cloneJson(entry.meta || {}),
|
||||
};
|
||||
};
|
||||
|
||||
export const peekCachedEdgeGateway = (gatewayId) => {
|
||||
const normalizedGatewayId = normalizeGatewayId(gatewayId);
|
||||
if (!normalizedGatewayId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const entry = loadWorkspaceCache().details[normalizedGatewayId];
|
||||
return entry?.data ? cloneJson(entry.data) : null;
|
||||
};
|
||||
|
||||
export const patchEdgeGatewayCache = (gatewayId, patch) => {
|
||||
const current = peekCachedEdgeGateway(gatewayId);
|
||||
if (!current) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return cacheGatewaySnapshot(mergeGatewaySnapshots(current, patch));
|
||||
};
|
||||
|
||||
export const removeEdgeGatewayCache = (gatewayId) => {
|
||||
const normalizedGatewayId = normalizeGatewayId(gatewayId);
|
||||
if (!normalizedGatewayId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cache = loadWorkspaceCache();
|
||||
delete cache.details[normalizedGatewayId];
|
||||
Object.entries(cache.lists || {}).forEach(([cacheKey, entry]) => {
|
||||
const rows = Array.isArray(entry?.rows) ? entry.rows.filter((item) => String(item?.id || "") !== normalizedGatewayId) : [];
|
||||
cache.lists[cacheKey] = {
|
||||
...(entry || {}),
|
||||
rows,
|
||||
cachedAt: Date.now(),
|
||||
};
|
||||
});
|
||||
persistWorkspaceCache();
|
||||
};
|
||||
|
||||
export const updateDepartmentGatewayCutoverCache = (departmentId, transportMode) => {
|
||||
const normalizedDepartmentId = normalizeDepartmentId(departmentId);
|
||||
if (normalizedDepartmentId === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cache = loadWorkspaceCache();
|
||||
Object.entries(cache.details || {}).forEach(([gatewayId, entry]) => {
|
||||
if (Number(entry?.data?.department_id || 0) !== normalizedDepartmentId) {
|
||||
return;
|
||||
}
|
||||
|
||||
cache.details[gatewayId] = {
|
||||
cachedAt: Date.now(),
|
||||
data: mergeGatewaySnapshots(entry.data, {
|
||||
department_transport_mode: transportMode,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
Object.entries(cache.lists || {}).forEach(([cacheKey, entry]) => {
|
||||
const rows = Array.isArray(entry?.rows)
|
||||
? entry.rows.map((gateway) =>
|
||||
Number(gateway?.department_id || 0) === normalizedDepartmentId
|
||||
? { ...gateway, department_transport_mode: transportMode }
|
||||
: gateway
|
||||
)
|
||||
: [];
|
||||
cache.lists[cacheKey] = {
|
||||
...(entry || {}),
|
||||
rows,
|
||||
cachedAt: Date.now(),
|
||||
};
|
||||
});
|
||||
|
||||
persistWorkspaceCache();
|
||||
};
|
||||
|
||||
export const listEdgeGatewayDepartments = async ({ forceRefresh = false } = {}) => {
|
||||
if (!forceRefresh && departmentsRequestInFlight) {
|
||||
return departmentsRequestInFlight;
|
||||
}
|
||||
|
||||
const request = authenticatedRequest("/departments", "GET", {}).then((response) => {
|
||||
cacheDepartmentsSnapshot(response?.data?.data || []);
|
||||
return response;
|
||||
});
|
||||
|
||||
departmentsRequestInFlight = request.finally(() => {
|
||||
if (departmentsRequestInFlight === request) {
|
||||
departmentsRequestInFlight = null;
|
||||
}
|
||||
});
|
||||
|
||||
return departmentsRequestInFlight;
|
||||
};
|
||||
|
||||
export const listEdgeGateways = async ({ departmentId = null, view = "summary", forceRefresh = false } = {}) => {
|
||||
const requestKey = listCacheKey({ departmentId, view });
|
||||
if (!forceRefresh && listRequestsInFlight.has(requestKey)) {
|
||||
return listRequestsInFlight.get(requestKey);
|
||||
}
|
||||
|
||||
const request = authenticatedRequest("/edge-gateways", "GET", {
|
||||
...(departmentId ? { department_id: departmentId } : {}),
|
||||
...(view ? { view } : {}),
|
||||
}).then((response) => {
|
||||
cacheFleetSnapshot({
|
||||
departmentId,
|
||||
view,
|
||||
rows: response?.data?.data || [],
|
||||
meta: response?.data?.meta || {},
|
||||
});
|
||||
return response;
|
||||
});
|
||||
|
||||
const trackedRequest = request.finally(() => {
|
||||
if (listRequestsInFlight.get(requestKey) === trackedRequest) {
|
||||
listRequestsInFlight.delete(requestKey);
|
||||
}
|
||||
});
|
||||
listRequestsInFlight.set(requestKey, trackedRequest);
|
||||
return trackedRequest;
|
||||
};
|
||||
|
||||
export const getEdgeGateway = async (gatewayId, { forceRefresh = false } = {}) => {
|
||||
const normalizedGatewayId = normalizeGatewayId(gatewayId);
|
||||
if (!normalizedGatewayId) {
|
||||
throw new Error("A gatewayId is required");
|
||||
}
|
||||
|
||||
if (!forceRefresh && detailRequestsInFlight.has(normalizedGatewayId)) {
|
||||
return detailRequestsInFlight.get(normalizedGatewayId);
|
||||
}
|
||||
|
||||
const request = authenticatedRequest(`/edge-gateways/${encodeURIComponent(gatewayId)}`, "GET", {}).then((response) => {
|
||||
const gateway = response?.data?.data;
|
||||
if (gateway?.id) {
|
||||
cacheGatewaySnapshot(gateway);
|
||||
}
|
||||
return response;
|
||||
});
|
||||
|
||||
const trackedRequest = request.finally(() => {
|
||||
if (detailRequestsInFlight.get(normalizedGatewayId) === trackedRequest) {
|
||||
detailRequestsInFlight.delete(normalizedGatewayId);
|
||||
}
|
||||
});
|
||||
detailRequestsInFlight.set(normalizedGatewayId, trackedRequest);
|
||||
return trackedRequest;
|
||||
};
|
||||
|
||||
export const listEdgeGatewayOperations = async (gatewayId) =>
|
||||
authenticatedRequest(`/edge-gateways/${encodeURIComponent(gatewayId)}/operations`, "GET", {});
|
||||
|
||||
export const createEdgeGatewayOperation = async (gatewayId, type, request = {}) =>
|
||||
authenticatedRequest(`/edge-gateways/${encodeURIComponent(gatewayId)}/operations`, "POST", { type, request });
|
||||
authenticatedRequest(`/edge-gateways/${encodeURIComponent(gatewayId)}/operations`, "POST", { type, request }).then((response) => {
|
||||
const gateway = response?.data?.data?.gateway;
|
||||
if (gateway?.id) {
|
||||
cacheGatewaySnapshot(gateway);
|
||||
}
|
||||
return response;
|
||||
});
|
||||
|
||||
export const getEdgeGatewayOperationEvents = async (gatewayId, operationId) =>
|
||||
authenticatedRequest(
|
||||
@@ -31,18 +382,24 @@ export const createEdgeGatewayInstallToken = async ({ department_id, label }) =>
|
||||
authenticatedRequest("/edge-gateways/install-token", "POST", { department_id, label });
|
||||
|
||||
export const updateEdgeGateway = async (gatewayId, payload) =>
|
||||
authenticatedRequest(`/edge-gateways/${encodeURIComponent(gatewayId)}`, "PUT", payload);
|
||||
authenticatedRequest(`/edge-gateways/${encodeURIComponent(gatewayId)}`, "PUT", payload).then(cacheGatewayFromResponse);
|
||||
|
||||
export const triggerEdgeGatewayDiscovery = async (gatewayId) =>
|
||||
authenticatedRequest(`/edge-gateways/${encodeURIComponent(gatewayId)}/discovery`, "POST", {});
|
||||
authenticatedRequest(`/edge-gateways/${encodeURIComponent(gatewayId)}/discovery`, "POST", {}).then(cacheGatewayFromResponse);
|
||||
|
||||
export const saveEdgeGatewayBindings = async (gatewayId, bindings) =>
|
||||
authenticatedRequest(`/edge-gateways/${encodeURIComponent(gatewayId)}/bindings`, "PUT", { bindings });
|
||||
authenticatedRequest(`/edge-gateways/${encodeURIComponent(gatewayId)}/bindings`, "PUT", { bindings }).then(cacheGatewayFromResponse);
|
||||
|
||||
export const deleteEdgeGateway = async (gatewayId) =>
|
||||
authenticatedRequest(`/edge-gateways/${encodeURIComponent(gatewayId)}`, "DELETE", {});
|
||||
authenticatedRequest(`/edge-gateways/${encodeURIComponent(gatewayId)}`, "DELETE", {}).then((response) => {
|
||||
removeEdgeGatewayCache(gatewayId);
|
||||
return response;
|
||||
});
|
||||
|
||||
export const setDepartmentGatewayCutover = async (departmentId, transport_mode) =>
|
||||
authenticatedRequest(`/departments/${encodeURIComponent(departmentId)}/gateway-cutover`, "POST", {
|
||||
transport_mode,
|
||||
}).then((response) => {
|
||||
updateDepartmentGatewayCutoverCache(departmentId, transport_mode);
|
||||
return response;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user