diff --git a/.cspell.json b/.cspell.json
index a1cef8232..7281b6e66 100644
--- a/.cspell.json
+++ b/.cspell.json
@@ -8,8 +8,6 @@
"Accordian",
"adipiscing",
"aliqua",
- "tblr",
- "conver",
"amet",
"Andross",
"animatable",
@@ -23,6 +21,7 @@
"arrowleft",
"asel",
"Authentificate",
+ "authjs",
"barcodes",
"billrate",
"binutils",
@@ -48,6 +47,7 @@
"choos",
"ciphertext",
"classpath",
+ "Clik",
"cloc",
"clockcircleo",
"cloudinary",
@@ -62,13 +62,21 @@
"combx",
"commitlint",
"comparision",
+ "comparization",
"compodoc",
"consectetur",
+ "containerd",
"contaniner",
+ "conver",
"creatoe",
+ "dailyplan",
"Darkmode",
"datas",
+ "dataToDisplay",
+ "dearmor",
+ "deepscan",
"Defaul",
+ "Desabled",
"Descrption",
"deserunt",
"dimesions",
@@ -79,6 +87,7 @@
"domutils",
"DONT",
"dotenv",
+ "dpkg",
"Dropown",
"dropwdown",
"dummyimage",
@@ -90,6 +99,7 @@
"embla",
"Enderbury",
"endregion",
+ "Entra",
"Entypo",
"envalid",
"errr",
@@ -115,6 +125,7 @@
"gauzystage",
"gcloud",
"Gitter",
+ "GlobalSkeleton",
"gradlew",
"graphicsmagick",
"greenkeeper",
@@ -170,9 +181,11 @@
"kanban",
"Kanbanboard",
"kanbandata",
+ "keyrings",
"Kiritimati",
"Kolkata",
"Kosrae",
+ "Koyeb",
"labore",
"Lask",
"lastest",
@@ -191,6 +204,7 @@
"mathieudutour",
"Mazen",
"Metral",
+ "MICROSOFTENTRAID",
"miliseconds",
"millisencods",
"mobx",
@@ -200,7 +214,11 @@
"nextjs",
"nimg",
"nocheck",
+ "nodesource",
+ "nodistro",
"noreferrer",
+ "Northflank",
+ "Notif",
"Opena",
"opentelemetry",
"Ordereds",
@@ -220,10 +238,12 @@
"phraseapp",
"pkill",
"Plaholder",
+ "plan",
"plasmo",
"plasmohq",
"popperjs",
"Pourtcent",
+ "prebuild",
"precommit",
"Pressable",
"Pressible",
@@ -236,6 +256,7 @@
"RECAPTCHA",
"recieve",
"Reconds",
+ "Relationnal",
"Repobeats",
"RESERVERD",
"Rickert",
@@ -249,6 +270,7 @@
"Settingfilter",
"setuptools",
"setwin",
+ "setwork",
"shadcn",
"shandow",
"signin",
@@ -259,9 +281,8 @@
"skey",
"smalltext",
"snyk",
- "Sonner",
"sonner",
- "Notif",
+ "Sonner",
"stackoverflow",
"statsus",
"statut",
@@ -270,8 +291,8 @@
"stylelint",
"stylesheet",
"subsquently",
- "svgs",
"svgr",
+ "svgs",
"Swith",
"Syle",
"Synk",
@@ -281,6 +302,7 @@
"tanstack",
"taskid",
"taskstatus",
+ "tblr",
"teamtask",
"tempor",
"testid",
@@ -290,6 +312,7 @@
"tinvitations",
"tnode",
"Togger",
+ "tomorow",
"Tongatapu",
"tota",
"TRANSFERT",
@@ -326,29 +349,7 @@
"Xlarge",
"xlcard",
"xlight",
- "yellowbox",
- "Desabled",
- "keyrings",
- "dearmor",
- "dpkg",
- "containerd",
- "nodistro",
- "nodesource",
- "Koyeb",
- "Northflank",
- "prebuild",
- "dataToDisplay",
- "GlobalSkeleton",
- "dailyplan",
- "tomorow",
- "comparization",
- "plan",
- "setwork",
- "Clik",
- "Relationnal",
- "authjs",
- "MICROSOFTENTRAID",
- "Entra"
+ "yellowbox"
],
"useGitignore": true,
"ignorePaths": [
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 5561567ae..170edab21 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,43 +1,43 @@
{
- "importSorter.generalConfiguration.sortOnBeforeSave": false,
- "importSorter.sortConfiguration.joinImportPaths": false,
- "cSpell.userWords": [],
- "cSpell.enabled": true,
- "typescript.tsdk": "node_modules/typescript/lib",
- "typescript.enablePromptUseWorkspaceTsdk": true,
- "npm.packageManager": "yarn",
- "prettier.trailingComma": "none",
- "prettier.singleQuote": true,
- "editor.formatOnSave": true,
- "eslint.format.enable": true,
- "editor.tabSize": 4,
- "files.insertFinalNewline": true,
- "files.trimFinalNewlines": true,
- "files.trimTrailingWhitespace": true,
- "editor.codeActionsOnSave": {
- "source.fixAll": "explicit",
- "source.organizeImports": "never",
- "source.sortMembers": "never",
- "organizeImports": "never"
- },
- "vsicons.presets.angular": true,
- "deepscan.enable": true,
- "cSpell.words": [],
- "files.exclude": {
- "**/.git": true,
- "**/.DS_Store": true,
- "**/node_modules": false,
- "**/public/**/*.png": false,
- "**/public/**/*.jpg": false,
- "**/public/**/*.pdf": true
- },
- "search.exclude": {
- "**/node_modules": true,
- "**/.idea": true,
- "**/.idea/**/*.{xml,iml}": true,
- "**/.iml": true,
- "**/.yarn": true,
- "**/yarn.lock": true
- },
- "docwriter.style": "Auto-detect"
+ "importSorter.generalConfiguration.sortOnBeforeSave": false,
+ "importSorter.sortConfiguration.joinImportPaths": false,
+ "cSpell.userWords": [],
+ "cSpell.enabled": true,
+ "typescript.tsdk": "node_modules/typescript/lib",
+ "typescript.enablePromptUseWorkspaceTsdk": true,
+ "npm.packageManager": "yarn",
+ "prettier.trailingComma": "none",
+ "prettier.singleQuote": true,
+ "editor.formatOnSave": true,
+ "eslint.format.enable": true,
+ "editor.tabSize": 4,
+ "files.insertFinalNewline": true,
+ "files.trimFinalNewlines": true,
+ "files.trimTrailingWhitespace": true,
+ "editor.codeActionsOnSave": {
+ "source.fixAll": "explicit",
+ "source.organizeImports": "never",
+ "source.sortMembers": "never",
+ "organizeImports": "never"
+ },
+ "vsicons.presets.angular": true,
+ "deepscan.enable": true,
+ "cSpell.words": ["Timepicker"],
+ "files.exclude": {
+ "**/.git": true,
+ "**/.DS_Store": true,
+ "**/node_modules": false,
+ "**/public/**/*.png": false,
+ "**/public/**/*.jpg": false,
+ "**/public/**/*.pdf": true
+ },
+ "search.exclude": {
+ "**/node_modules": true,
+ "**/.idea": true,
+ "**/.idea/**/*.{xml,iml}": true,
+ "**/.iml": true,
+ "**/.yarn": true,
+ "**/yarn.lock": true
+ },
+ "docwriter.style": "Auto-detect"
}
diff --git a/apps/server-web/package.json b/apps/server-web/package.json
index 626d9bcd5..2c9e42f39 100644
--- a/apps/server-web/package.json
+++ b/apps/server-web/package.json
@@ -51,7 +51,8 @@
"i18next-resources-to-backend": "^1.2.1",
"react-i18next": "^14.1.0",
"@radix-ui/react-switch": "^1.1.0",
- "classnames": "^2.5.1"
+ "classnames": "^2.5.1",
+ "fast-glob": "^3.3.2"
},
"devDependencies": {
"electron": "28.1.0",
diff --git a/apps/server-web/src/locales/i18n/bg/translation.json b/apps/server-web/src/locales/i18n/bg/translation.json
index 1ca895f37..22c0e2529 100644
--- a/apps/server-web/src/locales/i18n/bg/translation.json
+++ b/apps/server-web/src/locales/i18n/bg/translation.json
@@ -63,7 +63,8 @@
"INFO": "Информация",
"UPDATE_AVAILABLE": "Налична е нова актуализация! Моля, щракнете върху бутона Изтегляне сега по-долу.",
"EXIT_MESSAGE": "Мрежата на сървъра все още работи, сигурни ли сте, че ще излезете от приложението?",
- "UPDATE_SUCCESS": "Актуализирайте успешно"
+ "UPDATE_SUCCESS": "Актуализирайте успешно",
+ "SERVER_RUN_DIALOG": "Сървърната мрежа работи в момента, искате ли да рестартирате сървъра?"
},
"LANGUAGES": {
"en": "Английски",
diff --git a/apps/server-web/src/locales/i18n/en/translation.json b/apps/server-web/src/locales/i18n/en/translation.json
index 91ba9f7d5..1d506ae0e 100644
--- a/apps/server-web/src/locales/i18n/en/translation.json
+++ b/apps/server-web/src/locales/i18n/en/translation.json
@@ -63,7 +63,8 @@
"INFO": "Info",
"UPDATE_AVAILABLE": "New Update is available! Please click button Download Now below.",
"EXIT_MESSAGE": "Server web still running, Are you sure to exit the app ?",
- "UPDATE_SUCCESS": "Update Successfully"
+ "UPDATE_SUCCESS": "Update Successfully",
+ "SERVER_RUN_DIALOG": "Server web currently running, You want to restart the server ?"
},
"LANGUAGES": {
"en": "English",
diff --git a/apps/server-web/src/main/helpers/constant.ts b/apps/server-web/src/main/helpers/constant.ts
index 2885630d1..89889dd37 100644
--- a/apps/server-web/src/main/helpers/constant.ts
+++ b/apps/server-web/src/main/helpers/constant.ts
@@ -14,7 +14,8 @@ export const EventLists = {
UPDATE_CANCELLED: 'UPDATE_CANCELLED',
CHANGE_LANGUAGE: 'CHANGE_LANGUAGE',
OPEN_WEB: 'OPEN_WEB',
- SERVER_WINDOW: 'SERVER_WINDOW'
+ SERVER_WINDOW: 'SERVER_WINDOW',
+ RESTART_SERVER: 'RESTART_SERVER'
}
export const SettingPageTypeMessage = {
@@ -33,7 +34,8 @@ export const SettingPageTypeMessage = {
langChange: 'lang',
updateSetting: 'update-setting',
updateSettingResponse: 'update-setting-response',
- updateCancel: 'update-cancel'
+ updateCancel: 'update-cancel',
+ restartServer: 'restart-server'
}
export const ServerPageTypeMessage = {
diff --git a/apps/server-web/src/main/helpers/index.ts b/apps/server-web/src/main/helpers/index.ts
index e1b9aad0a..f1642c4ed 100644
--- a/apps/server-web/src/main/helpers/index.ts
+++ b/apps/server-web/src/main/helpers/index.ts
@@ -1 +1,2 @@
-export * from './create-window'
+export * from './create-window';
+export * from './replace-config';
diff --git a/apps/server-web/src/main/helpers/replace-config.ts b/apps/server-web/src/main/helpers/replace-config.ts
new file mode 100644
index 000000000..98e12f9af
--- /dev/null
+++ b/apps/server-web/src/main/helpers/replace-config.ts
@@ -0,0 +1,44 @@
+import fs from 'fs';
+import path from 'path';
+import fg from 'fast-glob';
+import os from 'os';
+
+type EnvOptions = {
+ before: {
+ NEXT_PUBLIC_GAUZY_API_SERVER_URL?: string
+ },
+ after: {
+ NEXT_PUBLIC_GAUZY_API_SERVER_URL?: string
+ }
+}
+
+const scanAllFiles = async (files: string[], oldConfig: string, newConfig: string) => {
+ files.forEach((file) => {
+ if (path.extname(file) === '.js') {
+ try {
+ const data = fs.readFileSync(file, 'utf-8');
+ const result = data.replace(new RegExp(oldConfig, 'g'), newConfig);
+ fs.writeFileSync(file, result, 'utf8');
+ } catch (error) {
+ console.log('error replace', error);
+ }
+ }
+ });
+}
+export const replaceConfig = async (folderPath: string, envOptions: EnvOptions) => {
+ try {
+ console.log('all files path', folderPath);
+ if (os.platform() === 'win32') {
+ folderPath = folderPath.replace(/\\/g, '/');
+ }
+ console.log('final path', folderPath);
+ const NEXT_PUBLIC_GAUZY_API_SERVER_URL_BEFORE = `"NEXT_PUBLIC_GAUZY_API_SERVER_URL","${envOptions.before.NEXT_PUBLIC_GAUZY_API_SERVER_URL}"`;
+ const NEXT_PUBLIC_GAUZY_API_SERVER_URL_AFTER = `"NEXT_PUBLIC_GAUZY_API_SERVER_URL","${envOptions.after.NEXT_PUBLIC_GAUZY_API_SERVER_URL}"`;
+ const NEXT_PUBLIC_GAUZY_API_SERVER_URL_DEFAULT = `"NEXT_PUBLIC_GAUZY_API_SERVER_URL","https://api.ever.team"`;
+ const files = await fg(`${folderPath}/**/*`, { onlyFiles: true });
+ await scanAllFiles(files, NEXT_PUBLIC_GAUZY_API_SERVER_URL_BEFORE, NEXT_PUBLIC_GAUZY_API_SERVER_URL_AFTER);
+ await scanAllFiles(files, NEXT_PUBLIC_GAUZY_API_SERVER_URL_DEFAULT, NEXT_PUBLIC_GAUZY_API_SERVER_URL_AFTER);
+ } catch (error) {
+ console.log('error on replacing file', error);
+ }
+}
diff --git a/apps/server-web/src/main/main.ts b/apps/server-web/src/main/main.ts
index ebefb4b77..c0249ea0a 100644
--- a/apps/server-web/src/main/main.ts
+++ b/apps/server-web/src/main/main.ts
@@ -11,7 +11,11 @@ import { mainBindings } from 'i18next-electron-fs-backend';
import i18nextMainBackend from '../configs/i18n.mainconfig';
import fs from 'fs';
import { WebServer } from './helpers/interfaces';
+import { replaceConfig } from './helpers';
import Log from 'electron-log';
+import MenuBuilder from './menu';
+
+
console.log = Log.log;
Object.assign(console, Log.functions);
@@ -32,8 +36,10 @@ let intervalUpdate: NodeJS.Timeout;
let tray: Tray;
let settingWindow: BrowserWindow | null = null;
let logWindow: BrowserWindow | null = null;
+let SettingMenu: any = null;
+let ServerWindowMenu: any = null;
-Log.hooks.push((message:any, transport) => {
+Log.hooks.push((message: any, transport) => {
if (transport !== Log.transports.file) {
return message;
}
@@ -167,7 +173,12 @@ const createWindow = async (type: 'SETTING_WINDOW' | 'LOG_WINDOW') => {
mainBindings(ipcMain, settingWindow, fs);
settingWindow.on('closed', () => {
settingWindow = null;
+ SettingMenu = null
});
+ if (!SettingMenu) {
+ SettingMenu = new MenuBuilder(settingWindow);
+ }
+ SettingMenu.buildMenu();
break;
case 'LOG_WINDOW':
logWindow = new BrowserWindow(defaultOptionWindow);
@@ -176,7 +187,12 @@ const createWindow = async (type: 'SETTING_WINDOW' | 'LOG_WINDOW') => {
mainBindings(ipcMain, logWindow, fs);
logWindow.on('closed', () => {
logWindow = null;
+ ServerWindowMenu = null
})
+ if (!ServerWindowMenu) {
+ ServerWindowMenu = new MenuBuilder(logWindow);
+ }
+ ServerWindowMenu.buildMenu();
break;
default:
break;
@@ -207,6 +223,16 @@ const stopServer = async () => {
await desktopServer.stop();
};
+const restartServer = async () => {
+ await desktopServer.stop();
+ const waitingForServerStop = setInterval(async () => {
+ if (!isServerRun) {
+ clearInterval(waitingForServerStop);
+ await runServer()
+ }
+ }, 1000)
+}
+
const getEnvApi = () => {
const setting: WebServer = LocalStore.getStore('config')
return setting.server;
@@ -237,15 +263,19 @@ const onInitApplication = () => {
})
eventEmitter.on(EventLists.webServerStop, async () => {
- isServerRun = false;
await stopServer();
+ isServerRun = false;
+ })
+
+ eventEmitter.on(EventLists.RESTART_SERVER, async () => {
+ await restartServer();
})
eventEmitter.on(EventLists.webServerStarted, () => {
console.log(EventLists.webServerStarted)
updateTrayMenu('SERVER_START', { enabled: false }, eventEmitter, tray, trayMenuItems, i18nextMainBackend);
updateTrayMenu('SERVER_STOP', { enabled: true }, eventEmitter, tray, trayMenuItems, i18nextMainBackend);
- updateTrayMenu('OPEN_WEB', { enabled: true}, eventEmitter, tray, trayMenuItems, i18nextMainBackend);
+ updateTrayMenu('OPEN_WEB', { enabled: true }, eventEmitter, tray, trayMenuItems, i18nextMainBackend);
updateTrayMenu('SERVER_STATUS', { label: 'MENU.SERVER_STATUS_STARTED' }, eventEmitter, tray, trayMenuItems, i18nextMainBackend);
if (logWindow) {
logWindow.webContents.send(IPC_TYPES.SERVER_PAGE, {
@@ -262,7 +292,7 @@ const onInitApplication = () => {
console.log(EventLists.webServerStopped);
updateTrayMenu('SERVER_STOP', { enabled: false }, eventEmitter, tray, trayMenuItems, i18nextMainBackend);
updateTrayMenu('SERVER_START', { enabled: true }, eventEmitter, tray, trayMenuItems, i18nextMainBackend);
- updateTrayMenu('OPEN_WEB', { enabled: false}, eventEmitter, tray, trayMenuItems, i18nextMainBackend);
+ updateTrayMenu('OPEN_WEB', { enabled: false }, eventEmitter, tray, trayMenuItems, i18nextMainBackend);
updateTrayMenu('SERVER_STATUS', { label: 'MENU.SERVER_STATUS_STOPPED' }, eventEmitter, tray, trayMenuItems, i18nextMainBackend);
if (logWindow) {
logWindow.webContents.send(IPC_TYPES.SERVER_PAGE, {
@@ -385,14 +415,35 @@ ipcMain.on('message', async (event, arg) => {
event.reply('message', `${arg} World!`)
})
-ipcMain.on(IPC_TYPES.SETTING_PAGE, (event, arg) => {
+ipcMain.on(IPC_TYPES.SETTING_PAGE, async (event, arg) => {
console.log('main setting page', arg);
switch (arg.type) {
case SettingPageTypeMessage.saveSetting:
+ const existingConfig = getEnvApi();
LocalStore.updateConfigSetting({
server: arg.data
});
- event.sender.send(IPC_TYPES.SETTING_PAGE, { type: SettingPageTypeMessage.mainResponse, data: true });
+ const dirFiles = 'standalone/apps/web/.next';
+ const devDirFilesPath = path.join(__dirname, resourceDir.webServer, dirFiles);
+ const packDirFilesPath = path.join(process.resourcesPath, 'release', 'app', 'dist', dirFiles)
+ const diFilesPath = isPack ? packDirFilesPath : devDirFilesPath;
+ await replaceConfig(
+ diFilesPath,
+ {
+ before: {
+ NEXT_PUBLIC_GAUZY_API_SERVER_URL: existingConfig?.NEXT_PUBLIC_GAUZY_API_SERVER_URL
+ },
+ after: {
+ NEXT_PUBLIC_GAUZY_API_SERVER_URL: arg.data.NEXT_PUBLIC_GAUZY_API_SERVER_URL
+ }
+ }
+ )
+ event.sender.send(IPC_TYPES.SETTING_PAGE, {
+ type: SettingPageTypeMessage.mainResponse, data: {
+ status: true,
+ isServerRun: isServerRun
+ }
+ });
break;
case SettingPageTypeMessage.checkUpdate:
updater.checkUpdate();
@@ -408,6 +459,9 @@ ipcMain.on(IPC_TYPES.SETTING_PAGE, (event, arg) => {
event.sender.send('languageSignal', arg.data);
eventEmitter.emit(EventLists.CHANGE_LANGUAGE, { code: arg.data })
break;
+ case SettingPageTypeMessage.restartServer:
+ eventEmitter.emit(EventLists.RESTART_SERVER)
+ break;
default:
break;
}
diff --git a/apps/server-web/src/main/menu.ts b/apps/server-web/src/main/menu.ts
index ba0fb7709..7e788eef2 100644
--- a/apps/server-web/src/main/menu.ts
+++ b/apps/server-web/src/main/menu.ts
@@ -54,27 +54,13 @@ export default class MenuBuilder {
buildDarwinTemplate(): MenuItemConstructorOptions[] {
const subMenuAbout: DarwinMenuItemConstructorOptions = {
- label: 'Electron',
+ label: app.getName(),
submenu: [
{
- label: 'About ElectronReact',
+ label: `About ${app.getName()}`,
selector: 'orderFrontStandardAboutPanel:',
},
{ type: 'separator' },
- { label: 'Services', submenu: [] },
- { type: 'separator' },
- {
- label: 'Hide ElectronReact',
- accelerator: 'Command+H',
- selector: 'hide:',
- },
- {
- label: 'Hide Others',
- accelerator: 'Command+Shift+H',
- selector: 'hideOtherApplications:',
- },
- { label: 'Show All', selector: 'unhideAllApplications:' },
- { type: 'separator' },
{
label: 'Quit',
accelerator: 'Command+Q',
@@ -84,22 +70,6 @@ export default class MenuBuilder {
},
],
};
- const subMenuEdit: DarwinMenuItemConstructorOptions = {
- label: 'Edit',
- submenu: [
- { label: 'Undo', accelerator: 'Command+Z', selector: 'undo:' },
- { label: 'Redo', accelerator: 'Shift+Command+Z', selector: 'redo:' },
- { type: 'separator' },
- { label: 'Cut', accelerator: 'Command+X', selector: 'cut:' },
- { label: 'Copy', accelerator: 'Command+C', selector: 'copy:' },
- { label: 'Paste', accelerator: 'Command+V', selector: 'paste:' },
- {
- label: 'Select All',
- accelerator: 'Command+A',
- selector: 'selectAll:',
- },
- ],
- };
const subMenuViewDev: MenuItemConstructorOptions = {
label: 'View',
submenu: [
@@ -110,13 +80,6 @@ export default class MenuBuilder {
this.mainWindow.webContents.reload();
},
},
- {
- label: 'Toggle Full Screen',
- accelerator: 'Ctrl+Command+F',
- click: () => {
- this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen());
- },
- },
{
label: 'Toggle Developer Tools',
accelerator: 'Alt+Command+I',
@@ -130,10 +93,17 @@ export default class MenuBuilder {
label: 'View',
submenu: [
{
- label: 'Toggle Full Screen',
- accelerator: 'Ctrl+Command+F',
+ label: 'Reload',
+ accelerator: 'Command+R',
click: () => {
- this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen());
+ this.mainWindow.webContents.reload();
+ },
+ },
+ {
+ label: 'Toggle Developer Tools',
+ accelerator: 'Alt+Command+I',
+ click: () => {
+ this.mainWindow.webContents.toggleDevTools();
},
},
],
@@ -148,7 +118,6 @@ export default class MenuBuilder {
},
{ label: 'Close', accelerator: 'Command+W', selector: 'performClose:' },
{ type: 'separator' },
- { label: 'Bring All to Front', selector: 'arrangeInFront:' },
],
};
const subMenuHelp: MenuItemConstructorOptions = {
@@ -157,27 +126,21 @@ export default class MenuBuilder {
{
label: 'Learn More',
click() {
- shell.openExternal('https://electronjs.org');
+ shell.openExternal('https://ever.team/');
},
},
{
label: 'Documentation',
click() {
shell.openExternal(
- 'https://github.com/electron/electron/tree/main/docs#readme',
+ 'https://github.com/ever-co/ever-teams/blob/develop/README.md',
);
},
},
- {
- label: 'Community Discussions',
- click() {
- shell.openExternal('https://www.electronjs.org/community');
- },
- },
{
label: 'Search Issues',
click() {
- shell.openExternal('https://github.com/electron/electron/issues');
+ shell.openExternal('https://github.com/ever-co/ever-teams/issues');
},
},
],
@@ -189,7 +152,7 @@ export default class MenuBuilder {
? subMenuViewDev
: subMenuViewProd;
- return [subMenuAbout, subMenuEdit, subMenuView, subMenuWindow, subMenuHelp];
+ return [subMenuAbout, subMenuView, subMenuWindow, subMenuHelp];
}
buildDefaultTemplate() {
@@ -197,10 +160,6 @@ export default class MenuBuilder {
{
label: '&File',
submenu: [
- {
- label: '&Open',
- accelerator: 'Ctrl+O',
- },
{
label: '&Close',
accelerator: 'Ctrl+W',
@@ -223,15 +182,6 @@ export default class MenuBuilder {
this.mainWindow.webContents.reload();
},
},
- {
- label: 'Toggle &Full Screen',
- accelerator: 'F11',
- click: () => {
- this.mainWindow.setFullScreen(
- !this.mainWindow.isFullScreen(),
- );
- },
- },
{
label: 'Toggle &Developer Tools',
accelerator: 'Alt+Ctrl+I',
@@ -241,15 +191,13 @@ export default class MenuBuilder {
},
]
: [
- {
- label: 'Toggle &Full Screen',
- accelerator: 'F11',
- click: () => {
- this.mainWindow.setFullScreen(
- !this.mainWindow.isFullScreen(),
- );
- },
+ {
+ label: 'Toggle &Developer Tools',
+ accelerator: 'Alt+Ctrl+I',
+ click: () => {
+ this.mainWindow.webContents.toggleDevTools();
},
+ },
],
},
{
@@ -258,27 +206,21 @@ export default class MenuBuilder {
{
label: 'Learn More',
click() {
- shell.openExternal('https://electronjs.org');
+ shell.openExternal('https://ever.team/');
},
},
{
label: 'Documentation',
click() {
shell.openExternal(
- 'https://github.com/electron/electron/tree/main/docs#readme',
+ 'https://github.com/ever-co/ever-teams/blob/develop/README.md',
);
},
},
- {
- label: 'Community Discussions',
- click() {
- shell.openExternal('https://www.electronjs.org/community');
- },
- },
{
label: 'Search Issues',
click() {
- shell.openExternal('https://github.com/electron/electron/issues');
+ shell.openExternal('https://github.com/ever-co/ever-teams/issues');
},
},
],
diff --git a/apps/server-web/src/renderer/components/Popup.tsx b/apps/server-web/src/renderer/components/Popup.tsx
index f1d51c782..0fdbb9629 100644
--- a/apps/server-web/src/renderer/components/Popup.tsx
+++ b/apps/server-web/src/renderer/components/Popup.tsx
@@ -46,6 +46,26 @@ export function Popup(props: IPopupComponent) {
)}
+ {props.type === 'warning' && (
+
+ )}
{props.type === 'error' && (
@@ -73,7 +93,7 @@ export function Popup(props: IPopupComponent) {
className="text-lg leading-6 font-medium text-gray-900"
id="modal-headline"
>
- {props.type == 'success'
+ {props.type == 'success' || 'warning'
? t('MESSAGE.SUCCESS')
: t('MESSAGE.ERROR')}
@@ -84,13 +104,21 @@ export function Popup(props: IPopupComponent) {
-
+
{t('FORM.BUTTON.OK')}
+ {props.closeAction && (
+
+ {t('FORM.BUTTON.CLOSE')}
+
+ )}
diff --git a/apps/server-web/src/renderer/libs/interfaces/i-components.ts b/apps/server-web/src/renderer/libs/interfaces/i-components.ts
index dbafae505..770bbb72a 100644
--- a/apps/server-web/src/renderer/libs/interfaces/i-components.ts
+++ b/apps/server-web/src/renderer/libs/interfaces/i-components.ts
@@ -12,8 +12,9 @@ type IToastComponent = {
type IPopupComponent = {
isShowPopup: boolean;
modalAction: () => void;
- type: 'success' | 'error' | 'none';
+ type: 'success' | 'error' | 'none' | 'warning';
message: string;
+ closeAction?: () => void;
};
type IProgressComponent = {
diff --git a/apps/server-web/src/renderer/libs/interfaces/i-setting.ts b/apps/server-web/src/renderer/libs/interfaces/i-setting.ts
index 4b47dd5e5..0053f1eda 100644
--- a/apps/server-web/src/renderer/libs/interfaces/i-setting.ts
+++ b/apps/server-web/src/renderer/libs/interfaces/i-setting.ts
@@ -32,8 +32,9 @@ interface IServerSetting {
}
interface IPopup {
- type: 'success' | 'error' | 'none';
+ type: 'success' | 'error' | 'none' | 'warning';
isShow: boolean;
+ isDialog: boolean;
}
interface ILanguages {
diff --git a/apps/server-web/src/renderer/pages/Setting.tsx b/apps/server-web/src/renderer/pages/Setting.tsx
index 87ef415f1..90b699ac7 100644
--- a/apps/server-web/src/renderer/pages/Setting.tsx
+++ b/apps/server-web/src/renderer/pages/Setting.tsx
@@ -16,6 +16,7 @@ import {
ILanguages,
ISideMenu,
} from '../libs/interfaces';
+import { useTranslation } from 'react-i18next';
export function Setting() {
const [menus, setMenu] = useState([
@@ -35,6 +36,7 @@ export function Setting() {
isActive: false,
},
]);
+ const { t } = useTranslation();
const [updateSetting, setUpdateSetting] = useState({
autoUpdate: false,
@@ -64,11 +66,13 @@ export function Setting() {
const [popupServer, setPopupServer] = useState({
isShow: false,
+ isDialog: false,
type: 'none',
});
const [popupUpdater, setPopupUpdater] = useState({
isShow: false,
+ isDialog: false,
type: 'none',
});
@@ -136,6 +140,11 @@ export function Setting() {
setPopupServer((prevData) => ({ ...prevData, isShow: !prevData.isShow }));
};
+ const restartServer = () => {
+ setPopupServer((prevData) => ({ ...prevData, isShow: !prevData.isShow }));
+ sendingMessageToMain({}, SettingPageTypeMessage.restartServer);
+ };
+
const setPopupUpdaterState = () => {
setPopupUpdater((prevData) => ({ ...prevData, isShow: !prevData.isShow }));
};
@@ -147,12 +156,22 @@ export function Setting() {
serverSetting={serverSetting}
saveSetting={saveSetting}
Popup={
-
+ popupServer.isDialog ? (
+
+ ) : (
+
+ )
}
/>
);
@@ -226,6 +245,7 @@ export function Setting() {
setLoading(false);
setPopupUpdater({
isShow: true,
+ isDialog: false,
type: 'error',
});
break;
@@ -252,9 +272,16 @@ export function Setting() {
});
break;
case SettingPageTypeMessage.mainResponse:
+ let typeMessage: any;
+ if (arg.data.status && arg.data.isServerRun) {
+ typeMessage = 'warning';
+ } else {
+ typeMessage = arg.data.status ? 'success' : 'error';
+ }
setPopupServer({
isShow: true,
- type: arg.data ? 'success' : 'error',
+ type: typeMessage,
+ isDialog: arg.data.isServerRun,
});
break;
case SettingPageTypeMessage.showVersion:
diff --git a/apps/web/app/[locale]/kanban/page.tsx b/apps/web/app/[locale]/kanban/page.tsx
index a514c49a4..646b39716 100644
--- a/apps/web/app/[locale]/kanban/page.tsx
+++ b/apps/web/app/[locale]/kanban/page.tsx
@@ -8,9 +8,9 @@ import { withAuthentication } from 'lib/app/authenticator';
import { Breadcrumb, Container, Divider } from 'lib/components';
import { KanbanView } from 'lib/features/team-members-kanban-view';
import { Footer, MainLayout } from 'lib/layout';
-import { useState } from 'react';
+import { useEffect, useMemo, useState } from 'react';
import { useTranslations } from 'next-intl';
-import { useParams } from 'next/navigation';
+import { useParams, useSearchParams } from 'next/navigation';
import ImageComponent, { ImageOverlapperProps } from 'lib/components/image-overlapper';
import Separator from '@components/ui/separator';
import HeaderTabs from '@components/pages/main/header-tabs';
@@ -55,11 +55,15 @@ const Kanban = () => {
const fullWidth = useRecoilValue(fullWidthState);
const currentLocale = params ? params.locale : null;
const [activeTab, setActiveTab] = useState(KanbanTabs.TODAY);
- const breadcrumbPath = [
- { title: JSON.parse(t('pages.home.BREADCRUMB')), href: '/' },
- { title: activeTeam?.name || '', href: '/' },
- { title: t('common.KANBAN'), href: `/${currentLocale}/kanban` }
- ];
+ const employee = useSearchParams().get('employee');
+ const breadcrumbPath = useMemo(
+ () => [
+ { title: JSON.parse(t('pages.home.BREADCRUMB')), href: '/' },
+ { title: activeTeam?.name || '', href: '/' },
+ { title: t('common.KANBAN'), href: `/${currentLocale}/kanban` }
+ ],
+ [activeTeam?.name, currentLocale]
+ );
const activeTeamMembers = activeTeam?.members ? activeTeam.members : [];
@@ -85,6 +89,22 @@ const Kanban = () => {
value: issues as any,
onValueChange: setIssues as any
});
+
+ useEffect(() => {
+ const lastPath = breadcrumbPath.slice(-1)[0];
+ if (employee) {
+ if (lastPath.title == 'Kanban') {
+ breadcrumbPath.push({ title: employee, href: `/${currentLocale}/kanban?employee=${employee}` });
+ } else {
+ breadcrumbPath.pop();
+ breadcrumbPath.push({ title: employee, href: `/${currentLocale}/kanban?employee=${employee}` });
+ }
+ } else {
+ if (lastPath.title !== 'Kanban') {
+ breadcrumbPath.pop();
+ }
+ }
+ }, [breadcrumbPath, currentLocale, employee]);
return (
<>
@@ -99,7 +119,11 @@ const Kanban = () => {
className="h-[calc(100vh-_22px)]"
>
-
+
@@ -122,7 +146,7 @@ const Kanban = () => {
-
+
@@ -250,11 +274,12 @@ const Kanban = () => {
)}
-
+
>
);
diff --git a/apps/web/app/[locale]/page-component.tsx b/apps/web/app/[locale]/page-component.tsx
index dc4e771ad..18ba41fa7 100644
--- a/apps/web/app/[locale]/page-component.tsx
+++ b/apps/web/app/[locale]/page-component.tsx
@@ -3,14 +3,14 @@
'use client';
import React, { useEffect, useState } from 'react';
-import { useOrganizationTeams } from '@app/hooks';
+import { useDailyPlan, useOrganizationTeams, useUserProfilePage } from '@app/hooks';
import { clsxm } from '@app/utils';
import NoTeam from '@components/pages/main/no-team';
import { withAuthentication } from 'lib/app/authenticator';
import { Breadcrumb, Card } from 'lib/components';
import { AuthUserTaskInput, TeamInvitations, TeamMembers, Timer, UnverifiedEmail } from 'lib/features';
import { MainLayout } from 'lib/layout';
-import { IssuesView } from '@app/constants';
+import { DAILY_PLAN_SHOW_MODAL, IssuesView } from '@app/constants';
import { useNetworkState } from '@uidotdev/usehooks';
import Offline from '@components/pages/offline';
import { useTranslations } from 'next-intl';
@@ -31,11 +31,20 @@ import { PeoplesIcon } from 'assets/svg';
import TeamMemberHeader from 'lib/features/team-member-header';
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@components/ui/resizable';
import { TeamOutstandingNotifications } from 'lib/features/team/team-outstanding-notifications';
+import { DailyPlanCompareEstimatedModal } from 'lib/features/daily-plan';
function MainPage() {
const t = useTranslations();
-
+ const [isOpen, setIsOpen] = useState(false)
+ const { todayPlan } = useDailyPlan();
+ const profile = useUserProfilePage();
const [headerSize, setHeaderSize] = useState(10);
+ const plan = todayPlan.find((plan) => plan.date?.toString()?.startsWith(new Date()?.toISOString().split('T')[0]));
+
+ const defaultOpenPopup =
+ typeof window !== 'undefined'
+ ? (window.localStorage.getItem(DAILY_PLAN_SHOW_MODAL)) || null
+ : new Date().toISOString().split('T')[0];
const { isTeamMember, isTrackingEnabled, activeTeam } = useOrganizationTeams();
const [fullWidth, setFullWidth] = useRecoilState(fullWidthState);
@@ -54,6 +63,14 @@ function MainPage() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [path, setView]);
+ useEffect(() => {
+ window.localStorage.setItem(DAILY_PLAN_SHOW_MODAL, new Date().toISOString().split('T')[0]);
+ if (defaultOpenPopup !== new Date().toISOString().split('T')[0] || defaultOpenPopup === null) {
+ setIsOpen(true)
+ }
+ }, [defaultOpenPopup, plan])
+
+
React.useEffect(() => {
window && window?.localStorage.getItem('conf-fullWidth-mode');
setFullWidth(JSON.parse(window?.localStorage.getItem('conf-fullWidth-mode') || 'true'));
@@ -64,6 +81,10 @@ function MainPage() {
}
return (
<>
+
setIsOpen((prev) => {
+ window.localStorage.setItem(DAILY_PLAN_SHOW_MODAL, new Date().toISOString().split('T')[0]);
+ return !prev;
+ })} todayPlan={todayPlan} profile={profile} />
{/*
*/}
diff --git a/apps/web/app/constants.ts b/apps/web/app/constants.ts
index f6a52203e..d58c076eb 100644
--- a/apps/web/app/constants.ts
+++ b/apps/web/app/constants.ts
@@ -267,6 +267,8 @@ export const languagesFlags = [
export const LAST_WORSPACE_AND_TEAM = 'last-workspace-and-team';
export const USER_SAW_OUTSTANDING_NOTIFICATION = 'user-saw-notif';
export const TODAY_PLAN_ALERT_SHOWN_DATE = 'last-today-plan-alert-date';
+export const ESTIMATE_POPUP_SHOWN_DATE = 'last-estimate-popup-date';
+export const DAILY_PLAN_SHOW_MODAL = 'daily-plan-modal';
// OAuth providers keys
diff --git a/apps/web/app/helpers/daily-plan-estimated.ts b/apps/web/app/helpers/daily-plan-estimated.ts
new file mode 100644
index 000000000..498648ed7
--- /dev/null
+++ b/apps/web/app/helpers/daily-plan-estimated.ts
@@ -0,0 +1,29 @@
+"use client"
+
+import { IDailyPlan } from "@app/interfaces";
+
+export const dailyPlanCompareEstimated = (plans: IDailyPlan[]) => {
+
+ const plan = plans.find((plan) => plan.date?.toString()?.startsWith(new Date()?.toISOString().split('T')[0]));
+
+ const times = plan?.tasks?.map((task) => task?.estimate).filter((time): time is number => typeof time === 'number') ?? [];
+ const estimated = plan?.tasks?.map((task) => task.estimate! > 0);
+
+ let estimatedTime = 0;
+ if (times.length > 0) estimatedTime = times.reduce((acc, cur) => acc + cur, 0) ?? 0;
+
+ const workedTimes =
+ plan?.tasks?.map((task) => task.totalWorkedTime).filter((time): time is number => typeof time === 'number') ??
+ [];
+
+ let totalWorkTime = 0;
+ if (workedTimes?.length > 0) totalWorkTime = workedTimes.reduce((acc, cur) => acc + cur, 0) ?? 0;
+
+ const result = estimated?.every(Boolean) ? estimatedTime - totalWorkTime : null;
+
+ return {
+ result,
+ totalWorkTime,
+ estimatedTime
+ }
+}
diff --git a/apps/web/app/hooks/features/useDailyPlan.ts b/apps/web/app/hooks/features/useDailyPlan.ts
index 434da0f59..8aa44d04e 100644
--- a/apps/web/app/hooks/features/useDailyPlan.ts
+++ b/apps/web/app/hooks/features/useDailyPlan.ts
@@ -1,10 +1,9 @@
'use client';
-import { useRecoilState, useRecoilValue } from 'recoil';
-import { useCallback, useEffect, useState } from 'react';
+import { useRecoilState } from 'recoil';
+import { useCallback, useEffect } from 'react';
import { useQuery } from '../useQuery';
import {
- activeTeamState,
dailyPlanFetchingState,
dailyPlanListState,
employeePlansListState,
@@ -27,22 +26,11 @@ import {
import { ICreateDailyPlan, IDailyPlanTasksUpdate, IRemoveTaskFromManyPlans, IUpdateDailyPlan } from '@app/interfaces';
import { useFirstLoad } from '../useFirstLoad';
import { useAuthenticateUser } from './useAuthenticateUser';
-import { TODAY_PLAN_ALERT_SHOWN_DATE } from '@app/constants';
-type TodayPlanNotificationParams = {
- canBeSeen: boolean;
- alreadySeen: boolean;
-};
export type FilterTabs = 'Today Tasks' | 'Future Tasks' | 'Past Tasks' | 'All Tasks' | 'Outstanding';
export function useDailyPlan() {
- const [addTodayPlanTrigger, setAddTodayPlanTrigger] = useState({
- canBeSeen: false,
- alreadySeen: false
- });
-
const { user } = useAuthenticateUser();
- const activeTeam = useRecoilValue(activeTeamState);
const { loading, queryCall } = useQuery(getDayPlansByEmployeeAPI);
const { loading: getAllDayPlansLoading, queryCall: getAllQueryCall } = useQuery(getAllDayPlansAPI);
@@ -66,7 +54,6 @@ export function useDailyPlan() {
const [dailyPlanFetching, setDailyPlanFetching] = useRecoilState(dailyPlanFetchingState);
const { firstLoadData: firstLoadDailyPlanData, firstLoad } = useFirstLoad();
-
useEffect(() => {
if (firstLoad) {
setDailyPlanFetching(loading);
@@ -78,10 +65,8 @@ export function useDailyPlan() {
if (response.data.items.length) {
const { items, total } = response.data;
setDailyPlan({ items, total });
-
}
});
-
}, [getAllQueryCall, setDailyPlan]);
const getMyDailyPlans = useCallback(() => {
@@ -297,37 +282,10 @@ export function useDailyPlan() {
profileDailyPlans.items &&
[...profileDailyPlans.items].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
- const currentUser = activeTeam?.members?.find((member) => member.employee.userId === user?.id);
useEffect(() => {
getMyDailyPlans();
}, [getMyDailyPlans]);
- useEffect(() => {
- const checkAndShowAlert = () => {
- if (activeTeam && currentUser) {
- const lastAlertDate = localStorage.getItem(TODAY_PLAN_ALERT_SHOWN_DATE);
- const today = new Date().toISOString().split('T')[0];
-
- if (currentUser?.totalTodayTasks) {
- const totalMemberWorked = currentUser?.totalTodayTasks.reduce(
- (previousValue, currentValue) => previousValue + currentValue.duration,
- 0
- );
-
- const showTodayPlanTrigger = todayPlan && todayPlan.length > 0 && totalMemberWorked > 0;
- if (lastAlertDate === today) {
- setAddTodayPlanTrigger({ canBeSeen: !!showTodayPlanTrigger, alreadySeen: true });
- }
- }
- }
- };
-
- checkAndShowAlert();
- const intervalId = setInterval(checkAndShowAlert, 24 * 60 * 60 * 1000); // One day check and display
-
- return () => clearInterval(intervalId);
- }, [activeTeam, currentUser, todayPlan]);
-
return {
dailyPlan,
setDailyPlan,
@@ -378,9 +336,6 @@ export function useDailyPlan() {
pastPlans,
outstandingPlans,
todayPlan,
- sortedPlans,
-
- addTodayPlanTrigger,
- setAddTodayPlanTrigger
+ sortedPlans
};
}
diff --git a/apps/web/app/hooks/features/useKanban.ts b/apps/web/app/hooks/features/useKanban.ts
index 42596ba2e..de81ed715 100644
--- a/apps/web/app/hooks/features/useKanban.ts
+++ b/apps/web/app/hooks/features/useKanban.ts
@@ -6,6 +6,7 @@ import { ITaskStatusItemList, ITeamTask } from '@app/interfaces';
import { useTeamTasks } from './useTeamTasks';
import { IKanban } from '@app/interfaces/IKanban';
import { TStatusItem } from 'lib/features';
+import { useSearchParams } from 'next/navigation';
export function useKanban() {
const [loading, setLoading] = useState(true);
const [searchTasks, setSearchTasks] = useState('');
@@ -22,6 +23,7 @@ export function useKanban() {
const { tasks: newTask, tasksFetching, updateTask } = useTeamTasks();
const [priority, setPriority] = useState([]);
const [sizes, setSizes] = useState([]);
+ const employee = useSearchParams().get('employee');
useEffect(() => {
if (!taskStatusHook.loading && !tasksFetching) {
let kanban = {};
@@ -44,6 +46,13 @@ export function useKanban() {
})
.filter((task: ITeamTask) => {
return epics.length ? epics.includes(task.id) : true;
+ })
+ .filter((task: ITeamTask) => {
+ if (employee) {
+ return task.members.map((el) => el.fullName).includes(employee as string);
+ } else {
+ return task;
+ }
});
const getTasksByStatus = (status: string | undefined) => {
@@ -62,7 +71,7 @@ export function useKanban() {
setLoading(false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [taskStatusHook.loading, tasksFetching, newTask, searchTasks, priority, sizes, labels, epics, issues]);
+ }, [taskStatusHook.loading, tasksFetching, newTask, searchTasks, priority, sizes, labels, epics, issues, employee]);
/**
* collapse or show kanban column
diff --git a/apps/web/app/hooks/features/useTimer.ts b/apps/web/app/hooks/features/useTimer.ts
index 6f1b28515..38b298b69 100644
--- a/apps/web/app/hooks/features/useTimer.ts
+++ b/apps/web/app/hooks/features/useTimer.ts
@@ -210,8 +210,11 @@ export function useTimer() {
// If require plan setting is activated,
// check if the today plan has working time planned and all the tasks into the plan are estimated
- const isPlanVerified =
- !!hasPlan?.workTimePlanned && !!hasPlan?.tasks?.every((task) => task.estimate && task.estimate > 0);
+ const isPlanVerified = requirePlan
+ ? hasPlan &&
+ hasPlan?.workTimePlanned > 0 &&
+ !!hasPlan?.tasks?.every((task) => task.estimate && task.estimate > 0)
+ : true;
const canRunTimer =
user?.isEmailVerified &&
diff --git a/apps/web/components/shared/timer/timer-card.tsx b/apps/web/components/shared/timer/timer-card.tsx
index 10f2a9d08..1a100e3f5 100644
--- a/apps/web/components/shared/timer/timer-card.tsx
+++ b/apps/web/components/shared/timer/timer-card.tsx
@@ -1,3 +1,4 @@
+import { ESTIMATE_POPUP_SHOWN_DATE, TODAY_PLAN_ALERT_SHOWN_DATE } from '@app/constants';
import { pad } from '@app/helpers/number';
import { useModal } from '@app/hooks';
import { useTaskStatistics } from '@app/hooks/features/useTaskStatistics';
@@ -27,11 +28,15 @@ const Timer = () => {
const { closeModal, isOpen, openModal } = useModal();
const timerHanlder = () => {
+ const currentDate = new Date().toISOString().split('T')[0];
+ const lastPopupDate = window && window?.localStorage.getItem(TODAY_PLAN_ALERT_SHOWN_DATE);
+ const lastPopupEstimates = window && window?.localStorage.getItem(ESTIMATE_POPUP_SHOWN_DATE);
+
if (timerStatusFetching || !canRunTimer) return;
if (timerStatus?.running) {
stopTimer();
} else {
- if (!isPlanVerified) {
+ if (!isPlanVerified || lastPopupDate !== currentDate || lastPopupEstimates !== currentDate) {
openModal();
} else {
startTimer();
diff --git a/apps/web/components/ui/scroll-area.tsx b/apps/web/components/ui/scroll-area.tsx
new file mode 100644
index 000000000..c43925b00
--- /dev/null
+++ b/apps/web/components/ui/scroll-area.tsx
@@ -0,0 +1,46 @@
+import * as React from "react"
+import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
+
+import { cn } from "lib/utils"
+
+const ScrollArea = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+ {children}
+
+
+
+
+))
+ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
+
+const ScrollBar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, orientation = "vertical", ...props }, ref) => (
+
+
+
+))
+ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
+
+export { ScrollArea, ScrollBar }
diff --git a/apps/web/lib/components/image-overlapper.tsx b/apps/web/lib/components/image-overlapper.tsx
index 7f5223b7b..1f20eb588 100644
--- a/apps/web/lib/components/image-overlapper.tsx
+++ b/apps/web/lib/components/image-overlapper.tsx
@@ -1,4 +1,4 @@
-import { useState } from 'react';
+import { useCallback, useState } from 'react';
import { Popover, PopoverContent, PopoverTrigger } from '@components/ui/popover';
import Image from 'next/image';
import Link from 'next/link';
@@ -17,6 +17,7 @@ import { TaskAvatars } from 'lib/features';
import { FaCheck } from 'react-icons/fa6';
import TeamMember from 'lib/components/team-member';
import { IEmployee } from '@app/interfaces';
+import { Url } from 'next/dist/shared/lib/router/router';
export interface ImageOverlapperProps {
id: string;
@@ -42,7 +43,8 @@ export default function ImageOverlapper({
arrowData = null,
hasActiveMembers = false,
assignTaskButtonCall = false,
- hasInfo = ''
+ hasInfo = '',
+ onAvatarClickRedirectTo = 'profile'
}: {
images: ImageOverlapperProps[];
radius?: number;
@@ -54,6 +56,7 @@ export default function ImageOverlapper({
hasActiveMembers?: boolean;
assignTaskButtonCall?: boolean;
hasInfo?: string;
+ onAvatarClickRedirectTo?: 'kanbanTasks' | 'profile';
}) {
// Split the array into two arrays based on the display number
const firstArray = images?.slice(0, displayImageCount);
@@ -84,6 +87,26 @@ export default function ImageOverlapper({
}
};
+ const onRedirect = useCallback(
+ (image: ImageOverlapperProps): Url => {
+ switch (onAvatarClickRedirectTo) {
+ case 'kanbanTasks':
+ return {
+ pathname: '/kanban',
+ query: {
+ employee: activeTeam?.members.find((el) => el.employee.userId === image.id)?.employee
+ .fullName
+ }
+ };
+ case 'profile':
+ return { pathname: `/profile/${image.id}`, query: { name: image.alt } };
+ default:
+ return {};
+ }
+ },
+ [activeTeam?.members, onAvatarClickRedirectTo]
+ );
+
const onCLickValidate = () => {
setValidate(!validate);
closeModal();
@@ -212,7 +235,7 @@ export default function ImageOverlapper({
className="relative "
>
{firstArray.map((image, index) => (
-
+
{
return (
diff --git a/apps/web/lib/components/index.ts b/apps/web/lib/components/index.ts
index 90dfd5b2b..38faad35d 100644
--- a/apps/web/lib/components/index.ts
+++ b/apps/web/lib/components/index.ts
@@ -21,6 +21,7 @@ export * from './separator';
export * from './color-picker';
export * from './no-data';
export * from './pagination';
+export * from './time-picker'
export * from './inputs/input';
export * from './inputs/auth-code-input';
diff --git a/apps/web/lib/components/inputs/auth-code-input.tsx b/apps/web/lib/components/inputs/auth-code-input.tsx
index a087b2165..7a8a2b4e2 100644
--- a/apps/web/lib/components/inputs/auth-code-input.tsx
+++ b/apps/web/lib/components/inputs/auth-code-input.tsx
@@ -188,26 +188,22 @@ export const AuthCodeInputField = forwardRef
(
};
const handleOnPaste = (e: React.ClipboardEvent) => {
+ e.preventDefault();
const pastedValue = e.clipboardData.getData('Text');
let currentInput = 0;
for (let i = 0; i < pastedValue.length; i++) {
const pastedCharacter = pastedValue.charAt(i);
- const currentValue = inputsRef.current[currentInput].value;
if (pastedCharacter.match(inputProps.pattern)) {
- if (!currentValue) {
- inputsRef.current[currentInput].value = pastedCharacter;
- if (inputsRef.current[currentInput].nextElementSibling !== null) {
- (inputsRef.current[currentInput].nextElementSibling as HTMLInputElement).focus();
- currentInput++;
- }
+ inputsRef.current[currentInput].value = pastedCharacter;
+ if (inputsRef.current[currentInput].nextElementSibling !== null) {
+ (inputsRef.current[currentInput].nextElementSibling as HTMLInputElement).focus();
+ currentInput++;
}
}
}
sendResult();
-
- e.preventDefault();
};
const handleAutoComplete = (code: string) => {
diff --git a/apps/web/lib/components/modal.tsx b/apps/web/lib/components/modal.tsx
index c1af3fc71..33ea9eed9 100644
--- a/apps/web/lib/components/modal.tsx
+++ b/apps/web/lib/components/modal.tsx
@@ -13,6 +13,7 @@ type Props = {
closeModal: () => void;
className?: string;
alignCloseIcon?: boolean;
+ showCloseIcon?: boolean;
} & PropsWithChildren;
export function Modal({
@@ -23,7 +24,8 @@ export function Modal({
titleClass,
description,
className,
- alignCloseIcon
+ alignCloseIcon,
+ showCloseIcon = true
}: Props) {
const refDiv = useRef(null);
@@ -50,20 +52,22 @@ export function Modal({
>
{title && {title} }
{description && {description} }
-
-
-
+ {showCloseIcon && (
+
+
+
+ )}
{children}
diff --git a/apps/web/lib/components/sidebar-accordian.tsx b/apps/web/lib/components/sidebar-accordian.tsx
index 39e6a2e1e..7d63cb34e 100644
--- a/apps/web/lib/components/sidebar-accordian.tsx
+++ b/apps/web/lib/components/sidebar-accordian.tsx
@@ -28,9 +28,8 @@ export const SidebarAccordian = ({ children, title, className, wrapperClassName,
{children && (
diff --git a/apps/web/lib/components/time-picker/index.tsx b/apps/web/lib/components/time-picker/index.tsx
new file mode 100644
index 000000000..dba5217c5
--- /dev/null
+++ b/apps/web/lib/components/time-picker/index.tsx
@@ -0,0 +1,184 @@
+"use client"
+import React, { useCallback, useState } from 'react'
+import { TimerIcon } from "lucide-react"
+import { cn } from "lib/utils"
+import { Button } from "@components/ui/button"
+
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@components/ui/popover"
+import { clsxm } from '@app/utils'
+
+export type TimePickerValue = {
+ hours: string;
+ minute: string;
+ meridiem: 'AM' | 'PM';
+};
+
+interface IPopoverTimePicker {
+ defaultValue?: TimePickerValue;
+ onChange?: (value: TimePickerValue) => void;
+}
+
+export function TimePicker({ onChange, defaultValue }: IPopoverTimePicker) {
+ const [time, setTime] = useState({
+ hours: defaultValue?.hours,
+ minute: defaultValue?.minute,
+ meridiem: defaultValue?.meridiem,
+ });
+
+ const handleTimeChange = (newTime: any) => {
+ setTime(newTime);
+ onChange!(newTime)
+ };
+ return (
+
+
+
+
+ {time.hours !== '--' && time.minute !== '--' ? `${time.hours}:${time.minute} ${time.meridiem}` : Time picker }
+
+
+
+
+
+
+
+
+ );
+}
+
+
+
+const TimePickerInput = ({ onTimeChange }: { onTimeChange: (_: any) => void }) => {
+ const [time, setTime] = useState({
+ hours: 0,
+ minutes: 0,
+ meridiem: true,
+ });
+
+ const handleHoursClick = useCallback((index: number) => {
+ setTime((prev) => {
+ const newTime = { ...prev, hours: index };
+ onTimeChange({
+ ...newTime,
+ hours: String(index + 1).padStart(2, '0'),
+ minute: String(newTime.minutes).padStart(2, '0'),
+ meridiem: newTime.meridiem ? 'PM' : 'AM',
+ });
+ return newTime;
+ });
+ }, [onTimeChange]);
+
+ const handleMinutesClick = useCallback((index: number) => {
+ setTime((prev) => {
+ const newTime = { ...prev, minutes: index };
+ onTimeChange({
+ ...newTime,
+ hours: String(newTime.hours + 1).padStart(2, '0'),
+ minute: String(index).padStart(2, '0'),
+ meridiem: newTime.meridiem ? 'PM' : 'AM',
+ });
+ return newTime;
+ });
+ }, [onTimeChange]);
+
+ const handleMeridiemClick = useCallback((isAM: any) => {
+ setTime((prev) => {
+ const newTime = { ...prev, meridiem: isAM };
+ onTimeChange({
+ ...newTime,
+ hours: String(newTime.hours + 1).padStart(2, '0'),
+ minute: String(newTime.minutes).padStart(2, '0'),
+ meridiem: isAM ? 'PM' : 'AM',
+ });
+ return newTime;
+ });
+ }, [onTimeChange]);
+
+ const renderButtons = (length: number, clickHandler: any, selectedIndex: number) => {
+ return Array.from({ length }, (_, index) => (
+ clickHandler(index)} // deepscan-disable-line
+ variant={selectedIndex === index ? 'default' : 'outline'}
+ />
+ ));
+ };
+
+ return (
+
+
+
+ {renderButtons(12, handleHoursClick, time.hours)}
+
+
+ {renderButtons(60, handleMinutesClick, time.minutes)}
+
+
+ handleMeridiemClick(!time.meridiem)} // deepscan-disable-line
+ variant={time.meridiem ? 'outline' : 'default'}
+ title={'AM'}
+ />
+ handleMeridiemClick(!time.meridiem)} // deepscan-disable-line REACT_INEFFICIENT_PURE_COMPONENT_PROP
+ variant={time.meridiem ? 'default' : 'outline'}
+ title={'PM'}
+ />
+
+
+
+ );
+};
+
+export default TimePickerInput;
+
+
+
+interface TimerPickerButtonProps {
+ title?: string;
+ className?: string;
+ onClick?: () => void;
+ loading?: boolean;
+ variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null;
+}
+
+// eslint-disable-next-line react/display-name
+const TimerPickerButton: React.FC = React.memo(({
+ title = '',
+ className = 'border-none border-gray-100 ',
+ onClick = () => null,
+ loading = false,
+ variant = 'default',
+}) => {
+ return (
+
+ {title}
+
+ );
+});
diff --git a/apps/web/lib/features/daily-plan/daily-plan-compare-estimate-modal.tsx b/apps/web/lib/features/daily-plan/daily-plan-compare-estimate-modal.tsx
new file mode 100644
index 000000000..5e40b0d9d
--- /dev/null
+++ b/apps/web/lib/features/daily-plan/daily-plan-compare-estimate-modal.tsx
@@ -0,0 +1,168 @@
+"use client"
+
+import { Card, Modal, Text, Button, TimePicker, TimePickerValue } from 'lib/components'
+import { PiWarningCircleFill } from "react-icons/pi";
+import React, { useState } from 'react'
+import Separator from '@components/ui/separator';
+import { IDailyPlan, ITeamTask } from '@app/interfaces';
+import { TaskNameInfoDisplay } from '../task/task-displays';
+import { clsxm } from '@app/utils';
+import { TaskEstimateInput } from '../team/user-team-card/task-estimate';
+import { useTeamMemberCard, useTimer, useTMCardTaskEdit } from '@app/hooks';
+import { dailyPlanCompareEstimated } from '@app/helpers/daily-plan-estimated';
+import { secondsToTime } from '@app/helpers';
+
+
+export function DailyPlanCompareEstimatedModal({
+ open,
+ closeModal,
+ todayPlan,
+ profile
+}: { open: boolean, closeModal: () => void, todayPlan: IDailyPlan[], profile: any }) {
+
+ const { estimatedTime } = dailyPlanCompareEstimated(todayPlan);
+ const { h: dh, m: dm } = secondsToTime(estimatedTime || 0);
+ const { startTimer } = useTimer()
+ const hour = dh.toString()?.padStart(2, '0');
+ const minute = dm.toString()?.padStart(2, '0');
+ const [times, setTimes] = useState({
+ hours: '--',
+ meridiem: 'PM',
+ minute: '--'
+ })
+ const onClick = () => {
+ startTimer();
+ window.localStorage.setItem('daily-plan-modal', new Date().toISOString().split('T')[0]);
+ }
+ return (
+
+
+
+
+
+
+
+ {
+ setTimes(value);
+ console.log(times)
+
+ }}
+ />
+
+
+
+ {todayPlan.map((plan, i) => {
+ return
+ {plan.tasks?.map((data, index) => {
+ return
+ })}
+
+ })}
+
+
+
+
+
Please correct planned work hours or re-estimate task(s)
+
+
+
+
+
+
+ )
+}
+export function DailyPlanTask({ task, profile }: { task?: ITeamTask, profile: any }) {
+ const taskEdition = useTMCardTaskEdit(task);
+ const member = task?.selectedTeam?.members.find((member) => {
+ return member?.employee?.user?.id === profile?.userProfile?.id
+ });
+
+ const memberInfo = useTeamMemberCard(member);
+ return (
+
+ );
+}
+
+
+export function DailyPlanCompareActionButton({ closeModal, onClick, loading }: { closeModal?: () => void, onClick?: () => void, loading?: boolean }) {
+ return (
+
+
+ Cancel
+
+
+ Start working
+
+
+ )
+}
+
+
+export function DailyPlanCompareHeader() {
+ return (
+ <>
+
+
+ TODAY'S PLAN
+
+
+
+
+
+ Add planned working hours
+
+ *
+
+
+
+ >
+ )
+}
+
+
+export function DailyPlanWorkTimeInput() {
+ return (
+ <>
+
+
+ Tasks with no time estimations
+
+
+ *
+
+
+ >
+ )
+}
diff --git a/apps/web/lib/features/daily-plan/index.ts b/apps/web/lib/features/daily-plan/index.ts
new file mode 100644
index 000000000..198a4d36b
--- /dev/null
+++ b/apps/web/lib/features/daily-plan/index.ts
@@ -0,0 +1,4 @@
+export * from './plans-work-time-and-estimate';
+export * from './add-task-to-plan';
+export * from './daily-plan-compare-estimate-modal'
+export * from './create-daily-plan-form-modal'
diff --git a/apps/web/lib/features/daily-plan/plans-work-time-and-estimate.tsx b/apps/web/lib/features/daily-plan/plans-work-time-and-estimate.tsx
index f2c4d1dd0..cfd0bb1d5 100644
--- a/apps/web/lib/features/daily-plan/plans-work-time-and-estimate.tsx
+++ b/apps/web/lib/features/daily-plan/plans-work-time-and-estimate.tsx
@@ -1,13 +1,14 @@
import { useCallback, useEffect, useState } from 'react';
import { PiWarningCircleFill } from 'react-icons/pi';
-import { DailyPlanStatusEnum, IDailyPlan, ITeamTask } from '@app/interfaces';
+import { IDailyPlan, ITeamTask, IUser } from '@app/interfaces';
import { Card, InputField, Modal, Text, VerticalSeparator } from 'lib/components';
import { useTranslations } from 'use-intl';
import { TaskNameInfoDisplay } from '../task/task-displays';
import { Button } from '@components/ui/button';
import { TaskEstimate } from '../task/task-estimate';
-import { useAuthenticateUser, useDailyPlan, useTeamTasks } from '@app/hooks';
+import { useAuthenticateUser, useAuthTeamTasks, useDailyPlan, useTeamTasks } from '@app/hooks';
import { ReloadIcon } from '@radix-ui/react-icons';
+import { ESTIMATE_POPUP_SHOWN_DATE, TODAY_PLAN_ALERT_SHOWN_DATE } from '@app/constants';
export function AddWorkTimeAndEstimatesToPlan({
open,
@@ -15,15 +16,12 @@ export function AddWorkTimeAndEstimatesToPlan({
plan,
startTimer,
hasPlan
- // employee
}: {
open: boolean;
closeModal: () => void;
startTimer: () => void;
hasPlan: boolean;
plan?: IDailyPlan;
-
- // employee?: OT_Member;
}) {
const t = useTranslations();
const [workTimePlanned, setworkTimePlanned] = useState(plan?.workTimePlanned);
@@ -31,17 +29,30 @@ export function AddWorkTimeAndEstimatesToPlan({
useEffect(() => {
if (typeof workTimePlanned === 'string') setworkTimePlanned(parseFloat(workTimePlanned));
}, [workTimePlanned]);
+ const { user } = useAuthenticateUser();
- const { updateDailyPlan } = useDailyPlan();
+ const { updateDailyPlan, todayPlan: hasPlanToday } = useDailyPlan();
const { tasks: $tasks, activeTeam } = useTeamTasks();
+ const requirePlan = activeTeam?.requirePlanToTrack;
+ const currentUser = activeTeam?.members?.find((member) => member.employee.userId === user?.id);
+
+ const tasksEstimated = plan?.tasks?.some((t) => typeof t?.estimate === 'number' && t?.estimate <= 0);
+
+ const tasks = $tasks.filter((task) => {
+ return plan?.tasks?.some((t) => task?.id === t.id && typeof task?.estimate === 'number' && task?.estimate <= 0);
+ });
- const tasks = $tasks.filter((task) =>
- plan?.tasks?.some((t) => task?.id === t.id && typeof task?.estimate === 'number' && task?.estimate <= 0)
+ const currentDate = new Date().toISOString().split('T')[0];
+ const lastPopupDate = window && window?.localStorage.getItem(TODAY_PLAN_ALERT_SHOWN_DATE);
+ const lastPopupEstimates = window && window?.localStorage.getItem(ESTIMATE_POPUP_SHOWN_DATE);
+
+ const hasWorkedToday = currentUser?.totalTodayTasks.reduce(
+ (previousValue, currentValue) => previousValue + currentValue.duration,
+ 0
);
const handleSubmit = () => {
- const requirePlan = activeTeam?.requirePlanToTrack;
if (requirePlan) {
if (workTimePlanned === 0 || typeof workTimePlanned !== 'number') return;
if (tasks.some((task) => task.estimate === 0)) return;
@@ -52,77 +63,124 @@ export function AddWorkTimeAndEstimatesToPlan({
closeModal();
};
- return (
-
- {hasPlan ? (
-
-
-
-
- {t('timer.todayPlanSettings.TITLE')}
-
-
-
-
- {t('timer.todayPlanSettings.WORK_TIME_PLANNED')} *
-
- setworkTimePlanned(parseFloat(e.target.value))}
- required
- defaultValue={plan?.workTimePlanned ?? 0}
- />
-
-
- {tasks.length > 0 && (
-
-
-
-
-
-
{t('timer.todayPlanSettings.WARNING_PLAN_ESTIMATION')}
-
+ const handleCloseModal = useCallback(() => {
+ closeModal();
+ // startTimer();
+ localStorage.setItem(ESTIMATE_POPUP_SHOWN_DATE, currentDate);
+ }, [closeModal, currentDate]);
+
+ const Content = () => {
+ if (hasWorkedToday && hasWorkedToday > 0) {
+ if ((!hasPlanToday || hasPlanToday.length === 0) && (!lastPopupDate || lastPopupDate !== currentDate)) {
+ return
;
+ } else {
+ if (
+ (tasksEstimated || !plan?.workTimePlanned || plan?.workTimePlanned <= 0) &&
+ (!lastPopupEstimates || lastPopupEstimates !== currentDate)
+ ) {
+ return (
+
+
+
+ {t('timer.todayPlanSettings.TITLE')}
+
+ {hasPlan && plan?.workTimePlanned && plan?.workTimePlanned <= 0 && (
+
+
+ {t('timer.todayPlanSettings.WORK_TIME_PLANNED')}{' '}
+ *
+
+
+ setworkTimePlanned(parseFloat(e.target.value))}
+ required
+ defaultValue={plan?.workTimePlanned ?? 0}
+ />
+
+ )}
+ {tasksEstimated && (
+
+
+
+
+
+
{t('timer.todayPlanSettings.WARNING_PLAN_ESTIMATION')}
+
+
+ )}
+
+
+
+ {t('common.SKIP_ADD_LATER')}
+
+
+ {t('timer.todayPlanSettings.START_WORKING_BUTTON')}
+
- )}
-
-
-
- {t('common.SKIP_ADD_LATER')}
-
-
- {t('timer.todayPlanSettings.START_WORKING_BUTTON')}
-
-
-
- ) : (
-
- )}
+ );
+ }
+ }
+ }
+ return null;
+ };
+
+ return (
+
+
+
+
);
}
-function UnEstimatedTasks({ dailyPlan }: { dailyPlan?: IDailyPlan }) {
+function UnEstimatedTasks({
+ requirePlan,
+ hasPlan,
+ dailyPlan,
+ user
+}: {
+ requirePlan: boolean;
+ hasPlan: boolean;
+ dailyPlan?: IDailyPlan;
+ user?: IUser;
+}) {
const t = useTranslations();
const { tasks: $tasks } = useTeamTasks();
-
- const tasks = $tasks.filter((task) =>
- dailyPlan?.tasks?.some((t) => task?.id === t.id && typeof task?.estimate === 'number' && task?.estimate <= 0)
- );
+ const { assignedTasks } = useAuthTeamTasks(user);
+
+ let tasks: ITeamTask[] = [];
+ if (hasPlan) {
+ tasks = $tasks.filter((task) =>
+ dailyPlan?.tasks?.some(
+ (t) => task?.id === t.id && typeof task?.estimate === 'number' && task?.estimate <= 0
+ )
+ );
+ } else {
+ if (!requirePlan) {
+ tasks = assignedTasks.filter((task) => typeof task?.estimate === 'number' && task?.estimate <= 0);
+ }
+ }
return (
@@ -158,44 +216,33 @@ export function UnEstimatedTask({ task }: { task: ITeamTask }) {
);
}
-export function CreateTodayPlanPopup({ closeModal }: { closeModal: () => void }) {
- const { createDailyPlan, createDailyPlanLoading } = useDailyPlan();
- const { user } = useAuthenticateUser();
- const { activeTeam } = useTeamTasks();
- const member = activeTeam?.members.find((member) => member.employee.userId === user?.id);
- const onSubmit = useCallback(
- async (values: any) => {
- const toDay = new Date();
- createDailyPlan({
- workTimePlanned: parseInt(values.workTimePlanned) || 0,
- date: toDay,
- status: DailyPlanStatusEnum.OPEN,
- tenantId: user?.tenantId ?? '',
- employeeId: member?.employeeId,
- organizationId: member?.organizationId
- }).then(() => {
- closeModal();
- });
- },
- [closeModal, createDailyPlan, member?.employeeId, member?.organizationId, user?.tenantId]
- );
+export function CreateTodayPlanPopup({ closeModal, currentDate }: { closeModal: () => void; currentDate: string }) {
+ const t = useTranslations();
+ const { createDailyPlanLoading } = useDailyPlan();
+
+ const handleCloseModal = useCallback(() => {
+ closeModal();
+ // startTimer();
+ localStorage.setItem(TODAY_PLAN_ALERT_SHOWN_DATE, currentDate);
+ }, [closeModal, currentDate]);
return (
- CREATE A PLAN FOR TODAY
+ {t('dailyPlan.CREATE_A_PLAN_FOR_TODAY')}
- You are creating a new plan for today
+ {t('dailyPlan.TODAY_PLAN_SUB_TITLE')}
+ {t('dailyPlan.DAILY_PLAN_DESCRIPTION')}
{createDailyPlanLoading && }
OK
diff --git a/apps/web/lib/features/task/daily-plan/future-tasks.tsx b/apps/web/lib/features/task/daily-plan/future-tasks.tsx
index ec453f87f..7b94fa804 100644
--- a/apps/web/lib/features/task/daily-plan/future-tasks.tsx
+++ b/apps/web/lib/features/task/daily-plan/future-tasks.tsx
@@ -55,7 +55,7 @@ export function FutureTasks({ profile }: { profile: any }) {
-
+
{(provided) => (
@@ -79,7 +79,7 @@ export function FutureTasks({ profile }: { profile: any }) {
{...provided.dragHandleProps}
style={{
...provided.draggableProps.style,
- marginBottom: 8
+ marginBottom: 4
}}
>
)}
diff --git a/apps/web/lib/features/task/daily-plan/outstanding-all.tsx b/apps/web/lib/features/task/daily-plan/outstanding-all.tsx
index 80e56fd96..d6722ad5f 100644
--- a/apps/web/lib/features/task/daily-plan/outstanding-all.tsx
+++ b/apps/web/lib/features/task/daily-plan/outstanding-all.tsx
@@ -63,7 +63,7 @@ export function OutstandingAll({ profile }: OutstandingAll) {
{...provided.dragHandleProps}
style={{
...provided.draggableProps.style,
- marginBottom: 8
+ marginBottom: 4
}}
>
)}
diff --git a/apps/web/lib/features/task/daily-plan/outstanding-date.tsx b/apps/web/lib/features/task/daily-plan/outstanding-date.tsx
index 2623e624b..b83ca2b0a 100644
--- a/apps/web/lib/features/task/daily-plan/outstanding-date.tsx
+++ b/apps/web/lib/features/task/daily-plan/outstanding-date.tsx
@@ -40,7 +40,7 @@ export function OutstandingFilterDate({ profile }: IOutstandingFilterDate) {
-
+
{/* Plan header */}
diff --git a/apps/web/lib/features/task/daily-plan/past-tasks.tsx b/apps/web/lib/features/task/daily-plan/past-tasks.tsx
index 6fc9a3983..988266346 100644
--- a/apps/web/lib/features/task/daily-plan/past-tasks.tsx
+++ b/apps/web/lib/features/task/daily-plan/past-tasks.tsx
@@ -47,7 +47,7 @@ export function PastTasks({ profile, currentTab = 'Past Tasks' }: { profile: any
-
+
{/* Plan header */}
)}
diff --git a/apps/web/lib/features/task/task-card.tsx b/apps/web/lib/features/task/task-card.tsx
index 64b03790d..4702cafb6 100644
--- a/apps/web/lib/features/task/task-card.tsx
+++ b/apps/web/lib/features/task/task-card.tsx
@@ -57,6 +57,7 @@ import { CreateDailyPlanFormModal } from '../daily-plan/create-daily-plan-form-m
import { AddTaskToPlan } from '../daily-plan/add-task-to-plan';
import { AddWorkTimeAndEstimatesToPlan } from '../daily-plan/plans-work-time-and-estimate';
import { ReloadIcon } from '@radix-ui/react-icons';
+import { ESTIMATE_POPUP_SHOWN_DATE, TODAY_PLAN_ALERT_SHOWN_DATE } from '@app/constants';
type Props = {
active?: boolean;
@@ -392,11 +393,15 @@ function TimerButtonCall({
]);
const timerHanlderStartStop = useCallback(() => {
+ const currentDate = new Date().toISOString().split('T')[0];
+ const lastPopupDate = window && window?.localStorage.getItem(TODAY_PLAN_ALERT_SHOWN_DATE);
+ const lastPopupEstimates = window && window?.localStorage.getItem(ESTIMATE_POPUP_SHOWN_DATE);
+
if (timerStatusFetching || !canRunTimer) return;
if (timerStatus?.running) {
stopTimer();
} else {
- if (!isPlanVerified) {
+ if (!isPlanVerified || lastPopupDate !== currentDate || lastPopupEstimates !== currentDate) {
openModal();
} else {
startTimer();
diff --git a/apps/web/lib/features/timer/timer.tsx b/apps/web/lib/features/timer/timer.tsx
index fce5dc2ce..32ebe6545 100644
--- a/apps/web/lib/features/timer/timer.tsx
+++ b/apps/web/lib/features/timer/timer.tsx
@@ -16,6 +16,7 @@ import {
import { HotkeysEvent } from 'hotkeys-js';
import { useCallback, useMemo } from 'react';
import { AddWorkTimeAndEstimatesToPlan } from '../daily-plan/plans-work-time-and-estimate';
+import { ESTIMATE_POPUP_SHOWN_DATE, TODAY_PLAN_ALERT_SHOWN_DATE } from '@app/constants';
export function Timer({ className }: IClassName) {
const t = useTranslations();
@@ -38,11 +39,15 @@ export function Timer({ className }: IClassName) {
} = useTimerView();
const timerHanlderStartStop = useCallback(() => {
+ const currentDate = new Date().toISOString().split('T')[0];
+ const lastPopupDate = window && window?.localStorage.getItem(TODAY_PLAN_ALERT_SHOWN_DATE);
+ const lastPopupEstimates = window && window?.localStorage.getItem(ESTIMATE_POPUP_SHOWN_DATE);
+
if (timerStatusFetching || !canRunTimer) return;
if (timerStatus?.running) {
stopTimer();
} else {
- if (!isPlanVerified) {
+ if (!isPlanVerified || lastPopupDate !== currentDate || lastPopupEstimates !== currentDate) {
openModal();
} else {
startTimer();
diff --git a/apps/web/lib/features/user-profile-plans.tsx b/apps/web/lib/features/user-profile-plans.tsx
index 0e6faa0f6..a8d8a78a2 100644
--- a/apps/web/lib/features/user-profile-plans.tsx
+++ b/apps/web/lib/features/user-profile-plans.tsx
@@ -180,8 +180,6 @@ function AllPlans({ profile, currentTab = 'All Tasks' }: { profile: any; current
if (currentTab === 'Today Tasks') filteredPlans = todayPlan;
const canSeeActivity = useCanSeeActivityScreen();
- // const { filteredAllPlanData: filterAllPlanData } = useFilterDateRange(filteredPlans, 'all');
- // const filterPlans: IDailyPlan[] = currentTab === 'All Tasks' ? : filteredPlans;
const view = useRecoilValue(dailyPlanViewHeaderTabs);
const [plans, setPlans] = useState
(filteredPlans);
@@ -215,7 +213,7 @@ function AllPlans({ profile, currentTab = 'All Tasks' }: { profile: any; current
-
+
)}
@@ -381,7 +380,7 @@ export function PlanHeader({ plan, planMode }: { plan: IDailyPlan; planMode: Fil
return (
{/* Planned Time */}
diff --git a/apps/web/locales/ar.json b/apps/web/locales/ar.json
index f240c8951..b66b3e500 100644
--- a/apps/web/locales/ar.json
+++ b/apps/web/locales/ar.json
@@ -579,7 +579,10 @@
"PLAN_FOR_TOMORROW": "خطة الغد",
"PLAN_FOR_SOME_DAY": "خطة ليوم ما",
"ADD_TASK_TO_PLAN": "أضف هذه المهمة إلى الخطة",
- "REMOVE_FROM_THIS_PLAN": "إزالة من هذه الخطة"
+ "REMOVE_FROM_THIS_PLAN": "إزالة من هذه الخطة",
+ "CREATE_A_PLAN_FOR_TODAY": "إنشاء خطة لليوم",
+ "TODAY_PLAN_SUB_TITLE": "لا توجد خطة لليوم",
+ "DAILY_PLAN_DESCRIPTION": "'الخطة اليومية' تساعد في تنظيم عملية العمل لتحقيق أفضل النتائج"
},
"form": {
"NAME_PLACEHOLDER": "أدخل اسمك",
diff --git a/apps/web/locales/bg.json b/apps/web/locales/bg.json
index 98ee52b97..5970bb2ae 100644
--- a/apps/web/locales/bg.json
+++ b/apps/web/locales/bg.json
@@ -579,7 +579,10 @@
"PLAN_FOR_TOMORROW": "План за утре",
"PLAN_FOR_SOME_DAY": "План за някой ден",
"ADD_TASK_TO_PLAN": "Добави тази задача към плана",
- "REMOVE_FROM_THIS_PLAN": "Премахни от този план"
+ "REMOVE_FROM_THIS_PLAN": "Премахни от този план",
+ "CREATE_A_PLAN_FOR_TODAY": "Създайте план за днес",
+ "TODAY_PLAN_SUB_TITLE": "Няма план за днес",
+ "DAILY_PLAN_DESCRIPTION": "'Дневният план' помага да се организира работният процес за постигане на най-добри резултати"
},
"form": {
"NAME_PLACEHOLDER": "Въведете името си",
diff --git a/apps/web/locales/de.json b/apps/web/locales/de.json
index fbf4b6bfa..7349dcf23 100644
--- a/apps/web/locales/de.json
+++ b/apps/web/locales/de.json
@@ -579,7 +579,10 @@
"PLAN_FOR_TOMORROW": "Plan für morgen",
"PLAN_FOR_SOME_DAY": "Plan für irgendeinen Tag",
"ADD_TASK_TO_PLAN": "Diese Aufgabe zum Plan hinzufügen",
- "REMOVE_FROM_THIS_PLAN": "Aus diesem Plan entfernen"
+ "REMOVE_FROM_THIS_PLAN": "Aus diesem Plan entfernen",
+ "CREATE_A_PLAN_FOR_TODAY": "Erstellen Sie einen Plan für heute",
+ "TODAY_PLAN_SUB_TITLE": "Es gibt keinen Plan für heute",
+ "DAILY_PLAN_DESCRIPTION": "'Tagesplan' hilft, den Arbeitsprozess zu organisieren, um die besten Ergebnisse zu erzielen"
},
"form": {
"NAME_PLACEHOLDER": "Ihren Namen eingeben",
diff --git a/apps/web/locales/en.json b/apps/web/locales/en.json
index 81f888ea4..a08dc0bc4 100644
--- a/apps/web/locales/en.json
+++ b/apps/web/locales/en.json
@@ -579,7 +579,10 @@
"PLAN_FOR_TOMORROW": "Plan for tomorrow",
"PLAN_FOR_SOME_DAY": "Plan for some day",
"ADD_TASK_TO_PLAN": "Add this task to a plan",
- "REMOVE_FROM_THIS_PLAN": "Remove from this plan"
+ "REMOVE_FROM_THIS_PLAN": "Remove from this plan",
+ "CREATE_A_PLAN_FOR_TODAY": "Create a Plan for Today",
+ "TODAY_PLAN_SUB_TITLE": " There is no plan for Today",
+ "DAILY_PLAN_DESCRIPTION": "'Daily Plan' helps organize the work process to achieve the best results"
},
"form": {
"NAME_PLACEHOLDER": "Enter your name",
diff --git a/apps/web/locales/es.json b/apps/web/locales/es.json
index bcdfab492..721947be3 100644
--- a/apps/web/locales/es.json
+++ b/apps/web/locales/es.json
@@ -579,7 +579,10 @@
"PLAN_FOR_TOMORROW": "Plan para mañana",
"PLAN_FOR_SOME_DAY": "Plan para algún día",
"ADD_TASK_TO_PLAN": "Agregar esta tarea a un plan",
- "REMOVE_FROM_THIS_PLAN": "Eliminar de este plan"
+ "REMOVE_FROM_THIS_PLAN": "Eliminar de este plan",
+ "CREATE_A_PLAN_FOR_TODAY": "Crear un plan para hoy",
+ "TODAY_PLAN_SUB_TITLE": "No hay plan para hoy",
+ "DAILY_PLAN_DESCRIPTION": "'Plan diario' ayuda a organizar el proceso de trabajo para lograr los mejores resultados"
},
"form": {
"NAME_PLACEHOLDER": "Ingresa tu nombre",
diff --git a/apps/web/locales/fr.json b/apps/web/locales/fr.json
index 04222d68b..be3260e5a 100644
--- a/apps/web/locales/fr.json
+++ b/apps/web/locales/fr.json
@@ -579,7 +579,10 @@
"PLAN_FOR_TOMORROW": "Plan pour demain",
"PLAN_FOR_SOME_DAY": "Plan pour une date",
"ADD_TASK_TO_PLAN": "Ajouter cette tâche au plan",
- "REMOVE_FROM_THIS_PLAN": "Retirer de ce plan"
+ "REMOVE_FROM_THIS_PLAN": "Retirer de ce plan",
+ "CREATE_A_PLAN_FOR_TODAY": "Créer un plan pour aujourd'hui",
+ "TODAY_PLAN_SUB_TITLE": "Il n'y a pas de plan pour aujourd'hui",
+ "DAILY_PLAN_DESCRIPTION": "'Plan quotidien' aide à organiser le processus de travail pour obtenir les meilleurs résultats"
},
"form": {
"NAME_PLACEHOLDER": "Entrez votre nom",
diff --git a/apps/web/locales/he.json b/apps/web/locales/he.json
index 1f75be567..c1ff34b50 100644
--- a/apps/web/locales/he.json
+++ b/apps/web/locales/he.json
@@ -579,7 +579,10 @@
"PLAN_FOR_TOMORROW": "תוכנית למחר",
"PLAN_FOR_SOME_DAY": "תוכנית ליום כלשהו",
"ADD_TASK_TO_PLAN": "הוסף משימה זו לתוכנית",
- "REMOVE_FROM_THIS_PLAN": "הסר מתוכנית זו"
+ "REMOVE_FROM_THIS_PLAN": "הסר מתוכנית זו",
+ "CREATE_A_PLAN_FOR_TODAY": "צור תוכנית להיום",
+ "TODAY_PLAN_SUB_TITLE": "אין תוכנית להיום",
+ "DAILY_PLAN_DESCRIPTION": "'תוכנית יומית' עוזרת לארגן את תהליך העבודה להשגת התוצאות הטובות ביותר"
},
"form": {
"NAME_PLACEHOLDER": "הכנס את השם שלך",
diff --git a/apps/web/locales/it.json b/apps/web/locales/it.json
index 8a332efec..e2f293952 100644
--- a/apps/web/locales/it.json
+++ b/apps/web/locales/it.json
@@ -579,7 +579,10 @@
"PLAN_FOR_TOMORROW": "Piano per domani",
"PLAN_FOR_SOME_DAY": "Piano per un giorno",
"ADD_TASK_TO_PLAN": "Aggiungi questa attività al piano",
- "REMOVE_FROM_THIS_PLAN": "Rimuovi da questo piano"
+ "REMOVE_FROM_THIS_PLAN": "Rimuovi da questo piano",
+ "CREATE_A_PLAN_FOR_TODAY": "Crea un piano per oggi",
+ "TODAY_PLAN_SUB_TITLE": "Non c'è nessun piano per oggi",
+ "DAILY_PLAN_DESCRIPTION": "'Piano giornaliero' aiuta a organizzare il processo di lavoro per ottenere i migliori risultati"
},
"form": {
"NAME_PLACEHOLDER": "Inserisci il tuo nome",
diff --git a/apps/web/locales/nl.json b/apps/web/locales/nl.json
index 557c8e470..09aa6bbd5 100644
--- a/apps/web/locales/nl.json
+++ b/apps/web/locales/nl.json
@@ -579,7 +579,10 @@
"PLAN_FOR_TOMORROW": "Plan voor morgen",
"PLAN_FOR_SOME_DAY": "Plan voor een dag",
"ADD_TASK_TO_PLAN": "Voeg deze taak toe aan een plan",
- "REMOVE_FROM_THIS_PLAN": "Verwijder uit dit plan"
+ "REMOVE_FROM_THIS_PLAN": "Verwijder uit dit plan",
+ "CREATE_A_PLAN_FOR_TODAY": "Maak een plan voor vandaag",
+ "TODAY_PLAN_SUB_TITLE": "Er is geen plan voor vandaag",
+ "DAILY_PLAN_DESCRIPTION": "'Dagelijkse planning' helpt om het werkproces te organiseren om de beste resultaten te behalen"
},
"form": {
"NAME_PLACEHOLDER": "Voer uw naam in",
diff --git a/apps/web/locales/pl.json b/apps/web/locales/pl.json
index 72797e308..b250d0bd4 100644
--- a/apps/web/locales/pl.json
+++ b/apps/web/locales/pl.json
@@ -579,7 +579,10 @@
"PLAN_FOR_TOMORROW": "Plan na jutro",
"PLAN_FOR_SOME_DAY": "Plan na pewien dzień",
"ADD_TASK_TO_PLAN": "Dodaj tę zadanie do planu",
- "REMOVE_FROM_THIS_PLAN": "Usuń z tego planu"
+ "REMOVE_FROM_THIS_PLAN": "Usuń z tego planu",
+ "CREATE_A_PLAN_FOR_TODAY": "Utwórz plan na dziś",
+ "TODAY_PLAN_SUB_TITLE": "Brak planu na dziś",
+ "DAILY_PLAN_DESCRIPTION": "'Plan dnia' pomaga zorganizować proces pracy, aby osiągnąć najlepsze wyniki"
},
"form": {
"NAME_PLACEHOLDER": "Wprowadź swoje imię",
diff --git a/apps/web/locales/pt.json b/apps/web/locales/pt.json
index 34eb0cf9f..c37ed080a 100644
--- a/apps/web/locales/pt.json
+++ b/apps/web/locales/pt.json
@@ -579,7 +579,10 @@
"PLAN_FOR_TOMORROW": "Plano para amanhã",
"PLAN_FOR_SOME_DAY": "Plano para algum dia",
"ADD_TASK_TO_PLAN": "Adicionar esta tarefa ao plano",
- "REMOVE_FROM_THIS_PLAN": "Remover deste plano"
+ "REMOVE_FROM_THIS_PLAN": "Remover deste plano",
+ "CREATE_A_PLAN_FOR_TODAY": "Criar um plano para hoje",
+ "TODAY_PLAN_SUB_TITLE": "Não há plano para hoje",
+ "DAILY_PLAN_DESCRIPTION": "'Plano diário' ajuda a organizar o processo de trabalho para obter os melhores resultados"
},
"form": {
"NAME_PLACEHOLDER": "Digite seu nome",
diff --git a/apps/web/locales/ru.json b/apps/web/locales/ru.json
index 1efca274d..5e7c31c81 100644
--- a/apps/web/locales/ru.json
+++ b/apps/web/locales/ru.json
@@ -579,7 +579,10 @@
"PLAN_FOR_TOMORROW": "План на завтра",
"PLAN_FOR_SOME_DAY": "План на какой-то день",
"ADD_TASK_TO_PLAN": "Добавить эту задачу в план",
- "REMOVE_FROM_THIS_PLAN": "Убрать из этого плана"
+ "REMOVE_FROM_THIS_PLAN": "Убрать из этого плана",
+ "CREATE_A_PLAN_FOR_TODAY": "Создать план на сегодня",
+ "TODAY_PLAN_SUB_TITLE": "На сегодня нет плана",
+ "DAILY_PLAN_DESCRIPTION": "'Ежедневный план' помогает организовать рабочий процесс для достижения наилучших результатов"
},
"form": {
"NAME_PLACEHOLDER": "Введите ваше имя",
diff --git a/apps/web/locales/zh.json b/apps/web/locales/zh.json
index c1ed90f27..b547c5d2f 100644
--- a/apps/web/locales/zh.json
+++ b/apps/web/locales/zh.json
@@ -579,7 +579,10 @@
"PLAN_FOR_TOMORROW": "明日计划",
"PLAN_FOR_SOME_DAY": "未来计划",
"ADD_TASK_TO_PLAN": "添加此任务到计划",
- "REMOVE_FROM_THIS_PLAN": "从计划中移除"
+ "REMOVE_FROM_THIS_PLAN": "从计划中移除",
+ "CREATE_A_PLAN_FOR_TODAY": "为今天制定计划",
+ "TODAY_PLAN_SUB_TITLE": "今天没有计划",
+ "DAILY_PLAN_DESCRIPTION": "'每日计划' 有助于组织工作流程以实现最佳结果"
},
"form": {
"NAME_PLACEHOLDER": "输入您的姓名",
diff --git a/yarn.lock b/yarn.lock
index 6c6e56058..fdabdabc1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9714,14 +9714,6 @@ buffer@~5.2.1:
base64-js "^1.0.2"
ieee754 "^1.1.4"
-builder-util-runtime@9.2.3:
- version "9.2.3"
- resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz#0a82c7aca8eadef46d67b353c638f052c206b83c"
- integrity sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==
- dependencies:
- debug "^4.3.4"
- sax "^1.2.4"
-
builder-util-runtime@9.2.4:
version "9.2.4"
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz#13cd1763da621e53458739a1e63f7fcba673c42a"
@@ -9730,6 +9722,14 @@ builder-util-runtime@9.2.4:
debug "^4.3.4"
sax "^1.2.4"
+builder-util-runtime@9.2.5:
+ version "9.2.5"
+ resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.5.tgz#0afdffa0adb5c84c14926c7dd2cf3c6e96e9be83"
+ integrity sha512-HjIDfhvqx/8B3TDN4GbABQcgpewTU4LMRTQPkVpKYV3lsuxEJoIfvg09GyWTNmfVNSUAYf+fbTN//JX4TH20pg==
+ dependencies:
+ debug "^4.3.4"
+ sax "^1.2.4"
+
builder-util@24.13.1:
version "24.13.1"
resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-24.13.1.tgz#4a4c4f9466b016b85c6990a0ea15aa14edec6816"
@@ -12618,11 +12618,11 @@ electron-to-chromium@^1.4.477:
integrity sha512-FFa8QKjQK/A5QuFr2167myhMesGrhlOBD+3cYNxO9/S4XzHEXesyTD/1/xF644gC8buFPz3ca6G1LOQD0tZrrg==
electron-updater@^6.1.4:
- version "6.1.8"
- resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-6.1.8.tgz#17637bca165322f4e526b13c99165f43e6f697d8"
- integrity sha512-hhOTfaFAd6wRHAfUaBhnAOYc+ymSGCWJLtFkw4xJqOvtpHmIdNHnXDV9m1MHC+A6q08Abx4Ykgyz/R5DGKNAMQ==
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-6.3.0.tgz#13a5c3c3f0b2b114fe33181e24a8270096734b3e"
+ integrity sha512-3Xlezhk+dKaSQrOnkQNqCGiuGSSUPO9BV9TQZ4Iig6AyTJ4FzJONE5gFFc382sY53Sh9dwJfzKsA3DxRHt2btw==
dependencies:
- builder-util-runtime "9.2.3"
+ builder-util-runtime "9.2.5"
fs-extra "^10.1.0"
js-yaml "^4.1.0"
lazy-val "^1.0.5"
@@ -22848,7 +22848,7 @@ semver@7.5.3:
dependencies:
lru-cache "^6.0.0"
-semver@7.5.4, semver@^7.0.0, semver@^7.1.1, semver@^7.1.2, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.6, semver@^7.3.7, semver@^7.3.8, semver@^7.5.1, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4:
+semver@7.5.4:
version "7.5.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
@@ -22860,18 +22860,11 @@ semver@^6.0.0, semver@^6.2.0, semver@^6.3.0, semver@^6.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
-semver@^7.2.1:
+semver@^7.0.0, semver@^7.1.1, semver@^7.1.2, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.6, semver@^7.3.7, semver@^7.3.8, semver@^7.5.1, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0:
version "7.6.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13"
integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==
-semver@^7.6.0:
- version "7.6.0"
- resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d"
- integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==
- dependencies:
- lru-cache "^6.0.0"
-
send@0.18.0, send@latest:
version "0.18.0"
resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
@@ -23710,7 +23703,16 @@ string-to-color@^2.2.2:
lodash.words "^4.2.0"
rgb-hex "^3.0.0"
-"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0":
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -23820,7 +23822,14 @@ stringify-object@^3.3.0:
is-obj "^1.0.1"
is-regexp "^1.0.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -25824,7 +25833,7 @@ wordwrap@^1.0.0:
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -25842,6 +25851,15 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"