diff --git a/packages/core/src/auth/components/molecules/ProcessHistoryCard/ProcessHistoryCard.tsx b/packages/core/src/auth/components/molecules/ProcessHistoryCard/ProcessHistoryCard.tsx index 2022a8fe5b..1676e71868 100644 --- a/packages/core/src/auth/components/molecules/ProcessHistoryCard/ProcessHistoryCard.tsx +++ b/packages/core/src/auth/components/molecules/ProcessHistoryCard/ProcessHistoryCard.tsx @@ -53,7 +53,7 @@ const ProccessHistoryCard = ({ const I18n = useTranslator(); const borderStyle = useMemo(() => { - return getStyles(ProcessHistory.getStatusColor(status, Colors).background) + return getStyles(ProcessHistory.getStatusColor(status, Colors)?.background) ?.border; }, [Colors, status]); @@ -72,6 +72,7 @@ const ProccessHistoryCard = ({ iconName: 'calendar-event', indicatorText: I18n.t('User_ProcessHistory_StartedOn'), displayText: formatDateTime(startedDate, I18n.t('Base_DateFormat')), + hideIf: startedDate == null, }, { iconName: 'calendar-check', diff --git a/packages/core/src/auth/screens/ProcessHistoryListScreen.js b/packages/core/src/auth/screens/ProcessHistoryListScreen.js index 1ab69ddca4..aadb9a09b5 100644 --- a/packages/core/src/auth/screens/ProcessHistoryListScreen.js +++ b/packages/core/src/auth/screens/ProcessHistoryListScreen.js @@ -15,6 +15,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ + import React, {useCallback, useEffect, useState} from 'react'; import {StyleSheet} from 'react-native'; import { @@ -32,13 +33,14 @@ import {useTranslator} from '../../i18n'; import {ProcessHistory} from '../types'; import {isToday} from './process-history-list-screen-helper'; +// TODO: add refresh method to the list const ProcessHistoryListScreen = () => { const I18n = useTranslator(); const Colors = useThemeColor(); - const {allProcessList} = useLoaderListener(); + const {processList} = useLoaderListener(); - const [filteredList, setFilteredList] = useState(allProcessList); + const [filteredList, setFilteredList] = useState(processList); const [today, setToday] = useState(true); const [selectedStatus, setSelectedStatus] = useState([]); @@ -48,7 +50,11 @@ const ProcessHistoryListScreen = () => { return []; } else { if (today) { - return list?.filter(item => isToday(item?.startedDate)); + return list?.filter( + item => + isToday(item?.startedDate) || + item?.status === ProcessStatus.PENDING, + ); } else { return list; } @@ -65,8 +71,8 @@ const ProcessHistoryListScreen = () => { ); useEffect(() => { - setFilteredList(filterOnStatus(filterOnDate(allProcessList))); - }, [filterOnDate, filterOnStatus, allProcessList]); + setFilteredList(filterOnStatus(filterOnDate(processList))); + }, [filterOnDate, filterOnStatus, processList]); return ( diff --git a/packages/core/src/auth/storage/ProcessStorage.ts b/packages/core/src/auth/storage/ProcessStorage.ts index fe8d97605f..98492ad413 100644 --- a/packages/core/src/auth/storage/ProcessStorage.ts +++ b/packages/core/src/auth/storage/ProcessStorage.ts @@ -18,24 +18,35 @@ import {Storage, storage} from '../../storage/Storage'; import {ProcessItem} from '../../components'; +import {deserialize, serialize} from './process-storage.helper'; const PROCESS_HISTORY_KEY = 'PROCESS_HISTORY'; class ProcessStorage { private processList: ProcessItem[]; - private refreshCallBack: (sessionList: ProcessItem[]) => void; + private refreshCallBack: (processList: ProcessItem[]) => void; constructor(private localStorage: Storage) { this.processList = []; } + private updateState() { + if (this.refreshCallBack == null) { + return; + } + + this.refreshCallBack(this.processList); + } + register(callBack) { this.refreshCallBack = callBack; } getProcessList(): ProcessItem[] { if (this.processList == null || this.processList.length === 0) { - this.processList = this.localStorage.getItem(PROCESS_HISTORY_KEY) || []; + this.processList = deserialize( + this.localStorage.getItem(PROCESS_HISTORY_KEY), + ); } return this.processList; } @@ -45,7 +56,7 @@ class ProcessStorage { return; } - if (this.processList.find(_item => _item.key === process.key)) { + if (this.processList.find(_item => _item.key === process.key) != null) { this.processList = this.processList.map(_item => { if (_item.key === process.key) { return {..._item, ...process}; @@ -57,7 +68,7 @@ class ProcessStorage { this.processList.push(process); } - this.localStorage.setItem(PROCESS_HISTORY_KEY, this.processList); + this.localStorage.setItem(PROCESS_HISTORY_KEY, serialize(this.processList)); this.updateState(); } @@ -77,14 +88,6 @@ class ProcessStorage { this.localStorage.setItem(PROCESS_HISTORY_KEY, processList); this.updateState(); } - - private updateState() { - if (this.refreshCallBack == null) { - return; - } - - this.refreshCallBack(this.processList); - } } export const processStorage = new ProcessStorage(storage); diff --git a/packages/core/src/auth/storage/process-storage.helper.ts b/packages/core/src/auth/storage/process-storage.helper.ts new file mode 100644 index 0000000000..f8f106e10a --- /dev/null +++ b/packages/core/src/auth/storage/process-storage.helper.ts @@ -0,0 +1,66 @@ +/* + * Axelor Business Solutions + * + * Copyright (C) 2024 Axelor (). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import {ProcessItem} from '../../components'; + +const serializeItem = (p: ProcessItem) => { + return JSON.stringify(p, (k, v) => { + if (typeof v === 'function') { + return v.toString(); + } + + return v; + }); +}; + +const deserializeItem = (data: string) => { + return JSON.parse(data, (k, v) => { + if (typeof v === 'string') { + if (v.startsWith('function') || v.startsWith('() =>')) { + // eslint-disable-next-line no-eval + return eval(`(${v})`); + } + } + return v; + }); +}; + +export const serialize = (list: ProcessItem[]): string => { + if (!Array.isArray(list) || list.length === 0) { + return '[]'; + } + + return JSON.stringify(list.map(p => serializeItem(p))); +}; + +export const deserialize = (data: string) => { + if (data == null || data.length === 0) { + return []; + } + + try { + if (!Array.isArray(data)) { + return []; + } + + return data.map(i => deserializeItem(i)); + } catch (e) { + console.error('Deserialization error:', e); + return []; + } +}; diff --git a/packages/core/src/auth/types/process-history.tsx b/packages/core/src/auth/types/process-history.tsx index ca1d4ed43c..b59525023f 100644 --- a/packages/core/src/auth/types/process-history.tsx +++ b/packages/core/src/auth/types/process-history.tsx @@ -25,6 +25,8 @@ class ProcessHistory { Colors: ThemeColors, ): Color => { switch (status) { + case ProcessStatus.PENDING: + return Colors.plannedColor; case ProcessStatus.RUNNING: return Colors.progressColor; case ProcessStatus.COMPLETED: diff --git a/packages/core/src/components/templates/Loader/ProcessProvider.ts b/packages/core/src/components/templates/Loader/ProcessProvider.ts index 7eb9429dd6..713d805222 100644 --- a/packages/core/src/components/templates/Loader/ProcessProvider.ts +++ b/packages/core/src/components/templates/Loader/ProcessProvider.ts @@ -28,6 +28,7 @@ import { Event, } from './types'; +//TODO: resolve toast press class ProcessProvider { private _events: Map; private _processMap: Map; @@ -40,7 +41,8 @@ class ProcessProvider { this._processMap = new Map(this._processList.map(p => [p.key, p])); this._numberRunningProcess = 0; - processStorage.register(this.setProcessList); + this.onCompleted = this.onCompleted.bind(this); + this.onFailed = this.onFailed.bind(this); } get numberRunningProcess() { @@ -51,14 +53,6 @@ class ProcessProvider { return this._processList.filter(p => p.completed && !p.resolved).length; } - get allProcessList() { - return this._processList; - } - - setProcessList(processList: ProcessItem[]) { - this._processList = processList; - } - on(key: string, e: EventType, c: callBack) { const event = this._events.has(key) ? this._events.get(key) : ({} as Event); @@ -87,7 +81,7 @@ class ProcessProvider { failedDate: null, notifyMe: false, response: null, - status: null, + status: ProcessStatus.PENDING, completed: false, resolved: false, }; @@ -118,8 +112,8 @@ class ProcessProvider { throw new Error(`Process with key ${p.key} not found.`); } - this.onStart(p); - this.executeProcess(p, I18n); + const _p = this.onStart(p); + this.executeProcess(_p, I18n); } private onFinish( @@ -136,15 +130,15 @@ class ProcessProvider { completed: true, completedDate: status === ProcessStatus.COMPLETED && new Date().toISOString(), - failedDate: status === ProcessStatus.FAILED && new Date().toISOString(), + failedDate: + status === ProcessStatus.FAILED ? new Date().toISOString() : null, }; - this._processMap.set(p.key, _p); - processStorage.saveProcess(_p); - this.decrementNumberOfRunningProcess(); - this.resolveProcess(p.key); + this.resolveProcess(_p); + this._processMap.set(p.key, _p); + processStorage.saveProcess(_p); this.emit( p.key, status === ProcessStatus.COMPLETED @@ -157,8 +151,10 @@ class ProcessProvider { private onCompleted(p: ProcessItem, I18n: TranslatorProps) { const {notifyMe, response, disabled, onSuccess} = p; + if (!notifyMe) { - this.onFinishCallBack(p.key, onSuccess, response); + onSuccess(response); + this.resolveProcess(p); } else { showToastMessage({ type: 'success', @@ -169,16 +165,17 @@ class ProcessProvider { typeof response === 'string' ? response : I18n.t('Base_Loader_ProccessSuccessMessage'), - onPress: () => - !disabled && this.onFinishCallBack(p.key, onSuccess, response), + onPress: () => !disabled && onSuccess(response), }); } } private onFailed(p: ProcessItem, I18n: TranslatorProps) { const {notifyMe, response, disabled, onError} = p; + if (!notifyMe) { - this.onFinishCallBack(p.key, onError, response); + onError(response); + this.resolveProcess(p); } else { showToastMessage({ type: 'error', @@ -189,8 +186,7 @@ class ProcessProvider { typeof response === 'string' ? response : I18n.t('Base_Loader_ProccessErrorMessage'), - onPress: () => - !disabled && this.onFinishCallBack(p.key, onError, response), + onPress: () => !disabled && onError(response), }); } } @@ -203,27 +199,21 @@ class ProcessProvider { this._numberRunningProcess = Math.max(0, this._numberRunningProcess - 1); } - private resolveProcess(key: string) { - if (!this._processMap.has(key)) { - throw new Error(`Process with key ${key} not found.`); + private resolveProcess(p: ProcessItem) { + if (!this._processMap.has(p?.key)) { + throw new Error(`Process with key ${p?.key} not found.`); } - const p = this._processMap.get(key); if (!p.completed) { - throw new Error(`Could not resolve uncompleted process ${key}`); + throw new Error(`Could not resolve uncompleted process ${p.key}`); } const _p = {...p, resolved: true}; - this._processMap.set(key, _p); + this._processMap.set(p.key, _p); processStorage.saveProcess(_p); } - private onFinishCallBack(key: string, cb: callBack, response: any) { - this.resolveProcess(key); - cb(response); - } - - private onStart(p: ProcessItem) { + private onStart(p: ProcessItem): ProcessItem { const _p = { ...p, loading: true, @@ -238,6 +228,8 @@ class ProcessProvider { this.on(p.key, EventType.COMPLETED, this.onCompleted); this.on(p.key, EventType.FAILED, this.onFailed); this.emit(p.key, EventType.STARTED); + + return _p; } private async executeProcess(p: ProcessItem, I18n: TranslatorProps) { diff --git a/packages/core/src/components/templates/Loader/types.ts b/packages/core/src/components/templates/Loader/types.ts index d6392005a8..ac0ed91f85 100644 --- a/packages/core/src/components/templates/Loader/types.ts +++ b/packages/core/src/components/templates/Loader/types.ts @@ -29,6 +29,7 @@ export type Event = { }; export enum ProcessStatus { + PENDING = 'PENDING', RUNNING = 'RUNNING', COMPLETED = 'COMPLETED', FAILED = 'FAILED', diff --git a/packages/core/src/components/templates/Loader/use-loader-listener.ts b/packages/core/src/components/templates/Loader/use-loader-listener.ts index c99be044c1..c9bd4757dc 100644 --- a/packages/core/src/components/templates/Loader/use-loader-listener.ts +++ b/packages/core/src/components/templates/Loader/use-loader-listener.ts @@ -17,13 +17,14 @@ */ import {useEffect, useMemo, useState} from 'react'; +import {processStorage} from '../../../auth/storage/ProcessStorage'; import {processProvider} from './ProcessProvider'; import {ProcessItem} from './types'; const useLoaderListener = () => { const [numberProcesses, setNumberProcesses] = useState(0); const [numberUnresolvedProcess, setUnresolvedProcess] = useState(0); - const [allProcessList, setAllProcessList] = useState([]); + const [processList, setProcessList] = useState([]); useEffect(() => { setNumberProcesses(processProvider.numberRunningProcess); @@ -36,13 +37,17 @@ const useLoaderListener = () => { }, [processProvider.numberUnresolvedProcess]); useEffect(() => { - setAllProcessList(processProvider.allProcessList); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [processProvider.allProcessList]); + setProcessList(processStorage.getProcessList()); + processStorage.register(setProcessList); + }, [setProcessList]); return useMemo( - () => ({numberProcesses, numberUnresolvedProcess, allProcessList}), - [numberProcesses, numberUnresolvedProcess, allProcessList], + () => ({ + numberProcesses, + numberUnresolvedProcess, + processList, + }), + [numberProcesses, numberUnresolvedProcess, processList], ); };