diff --git a/.cspell.json b/.cspell.json index be2df850e..9c47d01cc 100644 --- a/.cspell.json +++ b/.cspell.json @@ -372,7 +372,7 @@ "tinvitations", "tnode", "Togger", - "tomorow", + "tomorrow", "Tongatapu", "tota", "TRANSFERT", diff --git a/.vscode/settings.json b/.vscode/settings.json index 06dae99cd..be7e65f57 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,9 +23,7 @@ }, "vsicons.presets.angular": true, "deepscan.enable": true, - "cSpell.words": [ - "Timepicker" - ], + "cSpell.words": ["Timepicker"], "files.exclude": { "**/.git": true, "**/.DS_Store": true, diff --git a/apps/server-web/src/locales/i18n/bg/translation.json b/apps/server-web/src/locales/i18n/bg/translation.json index a9ba889cc..bcec1eff5 100644 --- a/apps/server-web/src/locales/i18n/bg/translation.json +++ b/apps/server-web/src/locales/i18n/bg/translation.json @@ -14,6 +14,21 @@ "OPEN_WEB": "Отворете уеб в браузъра", "SERVER_WINDOW": "Прозорец на сървъра" }, + "MENU_APP": { + "ABOUT": "Относно", + "QUIT": "Изход", + "WINDOW": "Прозорец", + "SUBMENU": { + "SETTING": "Настройки", + "SERVER_WINDOW": "Сървърен прозорец", + "LEARN_MORE": "Научете повече", + "DOC": "Документация", + "SETTING_DEV": "Настройки за разработчици", + "SERVER_DEV": "Сървър за разработчици" + }, + "DEV": "Разработчик", + "HELP": "Помощ" + }, "FORM": { "FIELDS": { "PORT": "ПРИСТАНИЩЕ", diff --git a/apps/server-web/src/locales/i18n/en/translation.json b/apps/server-web/src/locales/i18n/en/translation.json index 48320d87b..c18265dde 100644 --- a/apps/server-web/src/locales/i18n/en/translation.json +++ b/apps/server-web/src/locales/i18n/en/translation.json @@ -14,6 +14,21 @@ "OPEN_WEB": "Open Web In Browser", "SERVER_WINDOW": "Server Window" }, + "MENU_APP": { + "ABOUT": "About", + "QUIT": "Quit", + "WINDOW": "Window", + "SUBMENU": { + "SETTING": "Setting", + "SERVER_WINDOW": "Server Window", + "LEARN_MORE": "Learn More", + "DOC": "Documentation", + "SETTING_DEV": "Setting Dev.", + "SERVER_DEV": "Server Dev." + }, + "DEV": "Developer", + "HELP": "Help" + }, "FORM": { "FIELDS": { "PORT": "PORT", diff --git a/apps/server-web/src/main/helpers/services/libs/desktop-store.ts b/apps/server-web/src/main/helpers/services/libs/desktop-store.ts index 9db836cd9..01c708563 100644 --- a/apps/server-web/src/main/helpers/services/libs/desktop-store.ts +++ b/apps/server-web/src/main/helpers/services/libs/desktop-store.ts @@ -1,6 +1,19 @@ import Store from 'electron-store'; import { WebServer } from '../../interfaces'; const store = new Store(); +const DEFAULT_CONFIG:any = { + server: { + PORT: 3002, + GAUZY_API_SERVER_URL: 'http://localhost:3000', + NEXT_PUBLIC_GAUZY_API_SERVER_URL: 'http://localhost:3000', + DESKTOP_WEB_SERVER_HOSTNAME: '0.0.0.0' + }, + general: { + lang: 'en', + autoUpdate: true, + updateCheckPeriode: '1140' + } +} export const LocalStore = { getStore: (source: string | 'config'): WebServer | any => { return store.get(source); @@ -24,22 +37,15 @@ export const LocalStore = { setDefaultServerConfig: () => { - const defaultConfig: WebServer | any = store.get('config'); - if (!defaultConfig || !defaultConfig.server || !defaultConfig.general) { - const config: WebServer = { - server: { - PORT: 3002, - GAUZY_API_SERVER_URL: 'http://localhost:3000', - NEXT_PUBLIC_GAUZY_API_SERVER_URL: 'http://localhost:3000', - DESKTOP_WEB_SERVER_HOSTNAME: '0.0.0.0' - }, - general: { - lang: 'en', - autoUpdate: true, - updateCheckPeriode: '30' - } - } - store.set({ config }); - } + const defaultConfig: WebServer | any = store.get('config') || {}; + Object.keys(DEFAULT_CONFIG).forEach((key) => { + Object.keys(DEFAULT_CONFIG[key]).forEach((keySub) => { + defaultConfig[key] = defaultConfig[key] || {}; + defaultConfig[key][keySub] = defaultConfig[key][keySub] || DEFAULT_CONFIG[key][keySub]; + }) + }) + store.set({ + config: defaultConfig + }); } }; diff --git a/apps/server-web/src/main/main.ts b/apps/server-web/src/main/main.ts index f88b5cf7b..cba85f718 100644 --- a/apps/server-web/src/main/main.ts +++ b/apps/server-web/src/main/main.ts @@ -1,5 +1,5 @@ import path from 'path'; -import { app, ipcMain, Tray, dialog, BrowserWindow, shell } from 'electron'; +import { app, ipcMain, Tray, dialog, BrowserWindow, shell, Menu } from 'electron'; import { DesktopServer } from './helpers/desktop-server'; import { LocalStore } from './helpers/services/libs/desktop-store'; import { EventEmitter } from 'events'; @@ -23,8 +23,6 @@ Object.assign(console, Log.functions); app.name = config.DESCRIPTION; - - const eventEmitter = new EventEmitter(); const controller = new AbortController(); @@ -43,6 +41,7 @@ let logWindow: BrowserWindow | null = null; let setupWindow: BrowserWindow | any = null; let SettingMenu: any = null; let ServerWindowMenu: any = null; +const appMenu = new MenuBuilder(eventEmitter) Log.hooks.push((message: any, transport) => { if (transport !== Log.transports.file) { @@ -93,6 +92,7 @@ i18nextMainBackend.on('initialized', () => { }); let trayMenuItems: any = []; +let appMenuItems: any = []; const RESOURCES_PATH = app.isPackaged ? path.join(process.resourcesPath, 'assets') @@ -182,10 +182,7 @@ const createWindow = async (type: 'SETTING_WINDOW' | 'LOG_WINDOW' | 'SETUP_WINDO settingWindow = null; SettingMenu = null }); - if (!SettingMenu) { - SettingMenu = new MenuBuilder(settingWindow); - } - SettingMenu.buildMenu(); + Menu.setApplicationMenu(appMenu.buildDefaultTemplate(appMenuItems, i18nextMainBackend)) break; case 'LOG_WINDOW': logWindow = new BrowserWindow(defaultOptionWindow); @@ -196,10 +193,7 @@ const createWindow = async (type: 'SETTING_WINDOW' | 'LOG_WINDOW' | 'SETUP_WINDO logWindow = null; ServerWindowMenu = null }) - if (!ServerWindowMenu) { - ServerWindowMenu = new MenuBuilder(logWindow); - } - ServerWindowMenu.buildMenu(); + Menu.setApplicationMenu(appMenu.buildDefaultTemplate(appMenuItems, i18nextMainBackend)) break; case 'SETUP_WINDOW': setupWindow = new BrowserWindow(defaultOptionWindow); @@ -218,7 +212,7 @@ const createWindow = async (type: 'SETTING_WINDOW' | 'LOG_WINDOW' | 'SETUP_WINDO const runServer = async () => { console.log('Run the Server...'); try { - const envVal = getEnvApi(); + const envVal: any = getEnvApi(); // Instantiate API and UI servers await desktopServer.start( @@ -262,14 +256,18 @@ const SendMessageToSettingWindow = (type: string, data: any) => { } const onInitApplication = () => { - LocalStore.setDefaultServerConfig(); // check and set default config + // check and set default config + LocalStore.setDefaultServerConfig(); createIntervalAutoUpdate() trayMenuItems = trayMenuItems.length ? trayMenuItems : defaultTrayMenuItem(eventEmitter); + appMenuItems = appMenuItems.length ? appMenuItems : appMenu.defaultMenu(); tray = _initTray(trayMenuItems, getAssetPath('icons/icon.png')); i18nextMainBackend.on('languageChanged', (lng) => { if (i18nextMainBackend.isInitialized) { + trayMenuItems = trayMenuItems.length ? trayMenuItems : defaultTrayMenuItem(eventEmitter); updateTrayMenu('none', {}, eventEmitter, tray, trayMenuItems, i18nextMainBackend); + Menu.setApplicationMenu(appMenu.buildDefaultTemplate(appMenuItems, i18nextMainBackend)) } }); eventEmitter.on(EventLists.webServerStart, async () => { @@ -425,6 +423,16 @@ const onInitApplication = () => { const url = `http://127.0.0.1:${envConfig?.PORT}` shell.openExternal(url) }) + + eventEmitter.on(EventLists.SETTING_WINDOW_DEV, () => { + settingWindow?.webContents.toggleDevTools(); + }) + + eventEmitter.on(EventLists.SERVER_WINDOW_DEV, () => { + logWindow?.webContents.toggleDevTools(); + }) + + eventEmitter.emit(EventLists.SERVER_WINDOW); } (async () => { @@ -452,7 +460,6 @@ ipcMain.on('message', async (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(); @@ -512,13 +519,6 @@ ipcMain.on(IPC_TYPES.SETTING_PAGE, async (event, arg) => { case SettingPageTypeMessage.themeChange: eventEmitter.emit(EventLists.CHANGE_THEME, arg.data) break; - default: - break; - } -}) - -ipcMain.on(IPC_TYPES.UPDATER_PAGE, (event, arg) => { - switch (arg.type) { case SettingPageTypeMessage.updateSetting: LocalStore.updateConfigSetting({ general: { @@ -529,7 +529,6 @@ ipcMain.on(IPC_TYPES.UPDATER_PAGE, (event, arg) => { createIntervalAutoUpdate() event.sender.send(IPC_TYPES.UPDATER_PAGE, { type: SettingPageTypeMessage.updateSettingResponse, data: true }) break; - default: break; } diff --git a/apps/server-web/src/main/main_.ts b/apps/server-web/src/main/main_.ts deleted file mode 100644 index 10c8220ed..000000000 --- a/apps/server-web/src/main/main_.ts +++ /dev/null @@ -1,137 +0,0 @@ -/* eslint global-require: off, no-console: off, promise/always-return: off */ - -/** - * This module executes inside of electron's main process. You can start - * electron renderer process from here and communicate with the other processes - * through IPC. - * - * When running `npm run build` or `npm run build:main`, this file is compiled to - * `./src/main.js` using webpack. This gives us some performance wins. - */ -import path from 'path'; -import { app, BrowserWindow, shell, ipcMain } from 'electron'; -import { autoUpdater } from 'electron-updater'; -import log from 'electron-log'; -import MenuBuilder from './menu'; -import { resolveHtmlPath } from './util'; - -class AppUpdater { - constructor() { - log.transports.file.level = 'info'; - autoUpdater.logger = log; - autoUpdater.checkForUpdatesAndNotify(); - } -} - -let mainWindow: BrowserWindow | null = null; - -ipcMain.on('ipc-example', async (event, arg) => { - const msgTemplate = (pingPong: string) => `IPC test: ${pingPong}`; - console.log(msgTemplate(arg)); - event.reply('ipc-example', msgTemplate('pong')); -}); - -if (process.env.NODE_ENV === 'production') { - const sourceMapSupport = require('source-map-support'); - sourceMapSupport.install(); -} - -const isDebug = - process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true'; - -if (isDebug) { - require('electron-debug')(); -} - -const installExtensions = async () => { - const installer = require('electron-devtools-installer'); - const forceDownload = !!process.env.UPGRADE_EXTENSIONS; - const extensions = ['REACT_DEVELOPER_TOOLS']; - - return installer - .default( - extensions.map((name) => installer[name]), - forceDownload, - ) - .catch(console.log); -}; - -const createWindow = async () => { - if (isDebug) { - await installExtensions(); - } - - const RESOURCES_PATH = app.isPackaged - ? path.join(process.resourcesPath, 'assets') - : path.join(__dirname, '../../assets'); - - const getAssetPath = (...paths: string[]): string => { - return path.join(RESOURCES_PATH, ...paths); - }; - - mainWindow = new BrowserWindow({ - show: false, - width: 1024, - height: 728, - icon: getAssetPath('icon.png'), - webPreferences: { - preload: app.isPackaged - ? path.join(__dirname, 'preload.js') - : path.join(__dirname, '../../.erb/dll/preload.js'), - }, - }); - - mainWindow.loadURL(resolveHtmlPath('index.html')); - - mainWindow.on('ready-to-show', () => { - if (!mainWindow) { - throw new Error('"mainWindow" is not defined'); - } - if (process.env.START_MINIMIZED) { - mainWindow.minimize(); - } else { - mainWindow.show(); - } - }); - - mainWindow.on('closed', () => { - mainWindow = null; - }); - - const menuBuilder = new MenuBuilder(mainWindow); - menuBuilder.buildMenu(); - - // Open urls in the user's browser - mainWindow.webContents.setWindowOpenHandler((eData) => { - shell.openExternal(eData.url); - return { action: 'deny' }; - }); - - // Remove this if your app does not use auto updates - // eslint-disable-next-line - new AppUpdater(); -}; - -/** - * Add event listeners... - */ - -app.on('window-all-closed', () => { - // Respect the OSX convention of having the application in memory even - // after all windows have been closed - if (process.platform !== 'darwin') { - app.quit(); - } -}); - -app - .whenReady() - .then(() => { - createWindow(); - app.on('activate', () => { - // On macOS it's common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (mainWindow === null) createWindow(); - }); - }) - .catch(console.log); diff --git a/apps/server-web/src/main/menu.ts b/apps/server-web/src/main/menu.ts index be4348ceb..cec72f148 100644 --- a/apps/server-web/src/main/menu.ts +++ b/apps/server-web/src/main/menu.ts @@ -2,216 +2,79 @@ import { app, Menu, shell, - BrowserWindow, - MenuItemConstructorOptions, } from 'electron'; import { config } from '../configs/config'; - -interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions { - selector?: string; - submenu?: DarwinMenuItemConstructorOptions[] | Menu; -} +import { EventEmitter } from 'events'; +import { EventLists } from './helpers/constant'; +import i18n from 'i18next'; export default class MenuBuilder { - mainWindow: BrowserWindow; - - constructor(mainWindow: BrowserWindow) { - this.mainWindow = mainWindow; - } - - buildMenu(): Menu { - if ( - process.env.NODE_ENV === 'development' || - process.env.DEBUG_PROD === 'true' - ) { - this.setupDevelopmentEnvironment(); - } - - const template = - process.platform === 'darwin' - ? this.buildDarwinTemplate() - : this.buildDefaultTemplate(); - - const menu = Menu.buildFromTemplate(template); - Menu.setApplicationMenu(menu); - - return menu; - } - - setupDevelopmentEnvironment(): void { - this.mainWindow.webContents.on('context-menu', (_, props) => { - const { x, y } = props; - - Menu.buildFromTemplate([ - { - label: 'Inspect element', - click: () => { - this.mainWindow.webContents.inspectElement(x, y); - }, - }, - ]).popup({ window: this.mainWindow }); - }); - } + eventEmitter: EventEmitter - buildDarwinTemplate(): MenuItemConstructorOptions[] { - const subMenuAbout: DarwinMenuItemConstructorOptions = { - label: config.DESCRIPTION || app.getName(), - submenu: [ - { - label: `About ${config.DESCRIPTION || app.getName()}`, - selector: 'orderFrontStandardAboutPanel:', - }, - { type: 'separator' }, - { - label: 'Quit', - accelerator: 'Command+Q', - click: () => { - app.quit(); - }, - }, - ], - }; - const subMenuViewDev: MenuItemConstructorOptions = { - label: 'View', - submenu: [ - { - label: 'Reload', - accelerator: 'Command+R', - click: () => { - this.mainWindow.webContents.reload(); - }, - }, - { - label: 'Toggle Developer Tools', - accelerator: 'Alt+Command+I', - click: () => { - this.mainWindow.webContents.toggleDevTools(); - }, - }, - ], - }; - const subMenuViewProd: MenuItemConstructorOptions = { - label: 'View', - submenu: [ - { - label: 'Reload', - accelerator: 'Command+R', - click: () => { - this.mainWindow.webContents.reload(); - }, - }, - { - label: 'Toggle Developer Tools', - accelerator: 'Alt+Command+I', - click: () => { - this.mainWindow.webContents.toggleDevTools(); - }, - }, - ], - }; - const subMenuWindow: DarwinMenuItemConstructorOptions = { - label: 'Window', - submenu: [ - { - label: 'Minimize', - accelerator: 'Command+M', - selector: 'performMiniaturize:', - }, - { label: 'Close', accelerator: 'Command+W', selector: 'performClose:' }, - { type: 'separator' }, - ], - }; - const subMenuHelp: MenuItemConstructorOptions = { - label: 'Help', - submenu: [ - { - label: 'Learn More', - click() { - shell.openExternal('https://ever.team/'); - }, - }, - { - label: 'Documentation', - click() { - shell.openExternal( - 'https://github.com/ever-co/ever-teams/blob/develop/README.md', - ); - }, - }, - { - label: 'Search Issues', - click() { - shell.openExternal('https://github.com/ever-co/ever-teams/issues'); - }, - }, - ], - }; - - const subMenuView = - process.env.NODE_ENV === 'development' || - process.env.DEBUG_PROD === 'true' - ? subMenuViewDev - : subMenuViewProd; - - return [subMenuAbout, subMenuView, subMenuWindow, subMenuHelp]; + constructor(eventEmitter: EventEmitter) { + this.eventEmitter = eventEmitter } - buildDefaultTemplate() { - const templateDefault = [ + defaultMenu() { + const isDarwin = process.platform === 'darwin'; + return [ { - label: '&File', + id: 'MENU_APP', + label: config.DESCRIPTION || app.getName(), submenu: [ { - label: '&Close', - accelerator: 'Ctrl+W', + id: 'MENU_APP_ABOUT', + label: `MENU_APP.ABOUT`, + selector: 'orderFrontStandardAboutPanel:', + click: () => { + this.eventEmitter.emit(EventLists.gotoAbout) + } + }, + { type: 'separator' }, + { + id: 'MENU_APP_QUIT', + label: 'MENU_APP.QUIT', + accelerator: isDarwin ? 'Command+Q' : 'Alt+F4', click: () => { - this.mainWindow.close(); + app.quit(); }, }, ], }, { - label: '&View', - submenu: - process.env.NODE_ENV === 'development' || - process.env.DEBUG_PROD === 'true' - ? [ - { - label: '&Reload', - accelerator: 'Ctrl+R', - click: () => { - this.mainWindow.webContents.reload(); - }, - }, - { - label: 'Toggle &Developer Tools', - accelerator: 'Alt+Ctrl+I', - click: () => { - this.mainWindow.webContents.toggleDevTools(); - }, - }, - ] - : [ - { - label: 'Toggle &Developer Tools', - accelerator: 'Alt+Ctrl+I', - click: () => { - this.mainWindow.webContents.toggleDevTools(); - }, - }, - ], + id: 'MENU_APP_WINDOW', + label: 'MENU_APP.WINDOW', + submenu: [ + { + id: 'SUBMENU_SETTING', + label: 'MENU_APP.SUBMENU.SETTING', + click: () => { + this.eventEmitter.emit(EventLists.gotoSetting); + } + }, + { + id: 'SUBMENU_SERVER', + label: 'MENU_APP.SUBMENU.SERVER_WINDOW', + click: () => { + this.eventEmitter.emit(EventLists.SERVER_WINDOW); + } + } + ] }, { - label: 'Help', + id: 'MENU_APP_HELP', + label: 'MENU_APP.HELP', submenu: [ { - label: 'Learn More', + id: 'SUBMENU_LEARN_MORE', + label: 'MENU_APP.SUBMENU.LEARN_MORE', click() { shell.openExternal(config.COMPANY_SITE_LINK); }, }, { - label: 'Documentation', + id: 'SUBMENU_DOC', + label: 'MENU_APP.SUBMENU.DOC', click() { shell.openExternal( config.COMPANY_GITHUB_LINK @@ -220,8 +83,61 @@ export default class MenuBuilder { }, ], }, - ]; + { + id: 'MENU_APP_DEV', + label: 'MENU_APP.DEV', + submenu: [ + { + id: 'SUBMENU_SETTING_DEV', + label: 'MENU_APP.SUBMENU.SETTING_DEV', + click: () => { + this.eventEmitter.emit(EventLists.SETTING_WINDOW_DEV); + }, + }, + { + id: 'SUBMENU_SERVER_DEV', + label: 'MENU_APP.SUBMENU.SERVER_DEV', + click: () => { + this.eventEmitter.emit(EventLists.SERVER_WINDOW_DEV); + }, + }, + ] + }, + ] + } - return templateDefault; + buildDefaultTemplate(menuItems: any, i18nextMainBackend: typeof i18n) { + return Menu.buildFromTemplate(this.translateAppMenu(i18nextMainBackend, menuItems)); } + + updateAppMenu(menuItem: string, context: { label?: string, enabled?: boolean}, contextMenuItems: any, i18nextMainBackend: typeof i18n) { + const menuIdx:number = contextMenuItems.findIndex((item: any) => item.id === menuItem); + if (menuIdx > -1) { + contextMenuItems[menuIdx] = {...contextMenuItems[menuIdx], ...context}; + const newMenu = [...contextMenuItems]; + Menu.setApplicationMenu(Menu.buildFromTemplate(this.translateAppMenu(i18nextMainBackend, newMenu))); + } else { + const newMenu = [...contextMenuItems]; + Menu.setApplicationMenu(Menu.buildFromTemplate(this.translateAppMenu(i18nextMainBackend, newMenu))) + } +} + +translateAppMenu(i18nextMainBackend: typeof i18n, contextMenu: any) { + return contextMenu.map((menu: any) => { + const menuCopied = {...menu}; + if (menuCopied.label) { + menuCopied.label = i18nextMainBackend.t(menuCopied.label); + } + if (menuCopied.submenu && menuCopied.submenu.length) { + menuCopied.submenu = menuCopied.submenu.map((sm: any) => { + const submenu = {...sm}; + if (submenu.label) { + submenu.label = i18nextMainBackend.t(submenu.label) + } + return submenu; + }) + } + return menuCopied; + }) +} } diff --git a/apps/server-web/src/renderer/components/svgs/EverTeamsLogo.tsx b/apps/server-web/src/renderer/components/svgs/EverTeamsLogo.tsx index 2a123bf40..3df194cda 100644 --- a/apps/server-web/src/renderer/components/svgs/EverTeamsLogo.tsx +++ b/apps/server-web/src/renderer/components/svgs/EverTeamsLogo.tsx @@ -1,6 +1,12 @@ import Logo from '../../../resources/icons/platform-logo.png'; export const EverTeamsLogo = () => { return ( - EverTeams Logo + EverTeams Logo ); }; diff --git a/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetAction.tsx b/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetAction.tsx index 2bad95b8a..5e1de381d 100644 --- a/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetAction.tsx +++ b/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetAction.tsx @@ -8,12 +8,15 @@ type ITimesheetButton = { title?: string, onClick?: () => void, className?: string, - icon?: ReactNode + icon?: ReactNode, + disabled?: boolean } -export const TimesheetButton = ({ className, icon, onClick, title }: ITimesheetButton) => { +export const TimesheetButton = ({ className, icon, onClick, title, disabled }: ITimesheetButton) => { return ( - + } + > + {/*button confirm*/} + {/*button cancel*/} + - } - > - {/*button confirm*/} - - {/*button cancel*/} - - - - ) : ( - <> - )} - - )} - + + + ) : ( + <> + )} + + )} + + )} ))} 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 29ce71c3e..d4fdf8cd1 100644 --- a/apps/web/lib/features/task/daily-plan/past-tasks.tsx +++ b/apps/web/lib/features/task/daily-plan/past-tasks.tsx @@ -13,6 +13,7 @@ import { useEffect, useState } from 'react'; import { IDailyPlan } from '@app/interfaces'; import { DragDropContext, Draggable, Droppable, DroppableProvided, DroppableStateSnapshot } from 'react-beautiful-dnd'; import { useDateRange } from '@app/hooks/useDateRange'; +import DailyPlanTasksTableView from './table-view'; export function PastTasks({ profile, currentTab = 'Past Tasks' }: { profile: any; currentTab?: FilterTabs }) { const { pastPlans } = useDailyPlan(); @@ -51,79 +52,90 @@ export function PastTasks({ profile, currentTab = 'Past Tasks' }: { profile: any {/* Plan header */} - - {(provided: DroppableProvided, snapshot: DroppableStateSnapshot) => ( -
    - {plan.tasks?.map((task, index) => - view === 'CARDS' ? ( - - {(provided) => ( -
    - -
    - )} -
    - ) : ( - - {(provided) => ( -
    - -
    - )} -
    - ) - )} -
- )} -
+ {view === 'TABLE' ? ( + + ) : ( + + {(provided: DroppableProvided, snapshot: DroppableStateSnapshot) => ( +
    + {plan.tasks?.map((task, index) => + view === 'CARDS' ? ( + + {(provided) => ( +
    + +
    + )} +
    + ) : ( + + {(provided) => ( +
    + +
    + )} +
    + ) + )} +
+ )} +
+ )}
))} diff --git a/apps/web/lib/features/task/daily-plan/table-view/cells/task-action-menu-cell.tsx b/apps/web/lib/features/task/daily-plan/table-view/cells/task-action-menu-cell.tsx new file mode 100644 index 000000000..89643f9df --- /dev/null +++ b/apps/web/lib/features/task/daily-plan/table-view/cells/task-action-menu-cell.tsx @@ -0,0 +1,51 @@ +import { ITeamTask } from '@/app/interfaces'; +import { CellContext } from '@tanstack/react-table'; +import { ActiveTaskStatusDropdown } from '../../../task-status'; +import { useMemo, useState } from 'react'; +import { I_UserProfilePage, useOrganizationTeams, useTeamMemberCard } from '@/app/hooks'; +import { get } from 'lodash'; +import { TaskCardMenu } from '../../../task-card'; + +export default function TaskActionMenuCell(props: CellContext) { + const [loading, setLoading] = useState(false); + const { activeTeam } = useOrganizationTeams(); + const members = useMemo(() => activeTeam?.members || [], [activeTeam?.members]); + const profile = get(props.column, 'columnDef.meta.profile') as unknown as I_UserProfilePage; + const plan = get(props.column, 'columnDef.meta.plan'); + const planMode = get(props.column, 'columnDef.meta.planMode'); + const currentMember = useMemo( + () => + members.find((m) => { + return m.employee.user?.id === profile?.userProfile?.id; + }), + [members, profile?.userProfile?.id] + ); + const memberInfo = useTeamMemberCard(currentMember || undefined); + + return ( +
+ {/* Active Task Status Dropdown (It's a dropdown that allows the user to change the status of the task.)*/} +
+ setLoading(load)} + className="min-w-[10.625rem] text-sm" + /> +
+ {/* TaskCardMenu */} +
+ {props.row.original && currentMember && ( + + )} +
+
+ ); +} diff --git a/apps/web/lib/features/task/daily-plan/table-view/cells/task-estimation-cell.tsx b/apps/web/lib/features/task/daily-plan/table-view/cells/task-estimation-cell.tsx new file mode 100644 index 000000000..51a933455 --- /dev/null +++ b/apps/web/lib/features/task/daily-plan/table-view/cells/task-estimation-cell.tsx @@ -0,0 +1,24 @@ +import { I_UserProfilePage, useOrganizationTeams, useTeamMemberCard, useTMCardTaskEdit } from '@/app/hooks'; +import { ITeamTask } from '@/app/interfaces'; +import { TaskEstimateInfo } from '@/lib/features/team/user-team-card/task-estimate'; +import { CellContext } from '@tanstack/react-table'; +import { get } from 'lodash'; +import { useMemo } from 'react'; + +export default function DailyPlanTaskEstimationCell(props: CellContext) { + const plan = get(props.column, 'columnDef.meta.plan'); + const profile = get(props.column, 'columnDef.meta.profile') as unknown as I_UserProfilePage; + const { activeTeam } = useOrganizationTeams(); + const members = useMemo(() => activeTeam?.members || [], [activeTeam?.members]); + const currentMember = useMemo( + () => + members.find((m) => { + return m.employee.user?.id === profile?.userProfile?.id; + }), + [members, profile?.userProfile?.id] + ); + const memberInfo = useTeamMemberCard(currentMember || undefined); + const taskEdition = useTMCardTaskEdit(props.row.original); + + return ; +} diff --git a/apps/web/lib/features/task/daily-plan/table-view/cells/task-info-cell.tsx b/apps/web/lib/features/task/daily-plan/table-view/cells/task-info-cell.tsx new file mode 100644 index 000000000..d31827ce7 --- /dev/null +++ b/apps/web/lib/features/task/daily-plan/table-view/cells/task-info-cell.tsx @@ -0,0 +1,7 @@ +import { CellContext } from '@tanstack/react-table'; +import { TaskInfo } from '../../../task-card'; +import { ITeamTask } from '@/app/interfaces'; + +export default function DailyPlanTaskInfoCell(props: CellContext) { + return ; +} diff --git a/apps/web/lib/features/task/daily-plan/table-view/cells/task-times-cell.tsx b/apps/web/lib/features/task/daily-plan/table-view/cells/task-times-cell.tsx new file mode 100644 index 000000000..2ba60368f --- /dev/null +++ b/apps/web/lib/features/task/daily-plan/table-view/cells/task-times-cell.tsx @@ -0,0 +1,31 @@ +import { CellContext } from '@tanstack/react-table'; +import { TaskTimes } from '../../../task-times'; +import { ITeamTask } from '@/app/interfaces'; +import { I_UserProfilePage, useOrganizationTeams, useTeamMemberCard } from '@/app/hooks'; +import get from 'lodash/get'; +import { useMemo } from 'react'; + +export default function DailyPlanTaskTimesCell(props: CellContext) { + const profile = get(props.column, 'columnDef.meta.profile') as unknown as I_UserProfilePage; + const { activeTeam } = useOrganizationTeams(); + const members = useMemo(() => activeTeam?.members || [], [activeTeam?.members]); + const currentMember = useMemo( + () => + members.find((m) => { + return m.employee.user?.id === profile?.userProfile?.id; + }), + [members, profile?.userProfile?.id] + ); + const memberInfo = useTeamMemberCard(currentMember || undefined); + + return ( + + ); +} diff --git a/apps/web/lib/features/task/daily-plan/table-view/index.tsx b/apps/web/lib/features/task/daily-plan/table-view/index.tsx new file mode 100644 index 000000000..7adc2dc79 --- /dev/null +++ b/apps/web/lib/features/task/daily-plan/table-view/index.tsx @@ -0,0 +1,75 @@ +import * as React from 'react'; +import DataTable from '@components/ui/data-table'; +import { ColumnDef } from '@tanstack/react-table'; +import { IDailyPlan, ITeamTask } from '@app/interfaces'; +import DailyPlanTaskEstimationCell from './cells/task-estimation-cell'; +import DailyPlanTaskInfoCell from './cells/task-info-cell'; +import DailyPlanTaskTimesCell from './cells/task-times-cell'; +import TaskActionMenuCell from './cells/task-action-menu-cell'; +import { FilterTabs, I_UserProfilePage } from '@/app/hooks'; + +interface IDailyPlanTasksTableViewProps { + data: ITeamTask[]; + plan: IDailyPlan; + profile: I_UserProfilePage; + planMode?: FilterTabs; +} + +/** + * Table view of daily plan tasks + * + * @param {Object} props - THe props object + * @param {ITeamTask[]} props.data - The tasks + * @param {I_UserProfilePage} props.profile - The user profile page + * @param {FilterTabs} props.planMode - The plan mode to display + * + * @returns {JSX.Element} - The table view of daily plan tasks + */ +export default function DailyPlanTasksTableView(props: IDailyPlanTasksTableViewProps) { + const { data, plan, profile, planMode } = props; + + const columns = React.useMemo[]>( + () => [ + { + id: 'task', + header: 'Task', + tooltip: '', + cell: DailyPlanTaskInfoCell + }, + { + id: 'estimate', + header: 'Estimate', + tooltip: '', + cell: DailyPlanTaskEstimationCell, + meta: { + plan, + profile + } + }, + { + id: 'workedOn', + header: 'Worked On', + tooltip: '', + cell: DailyPlanTaskTimesCell, + meta: { + profile + } + }, + { + id: 'action', + header: 'Action', + tooltip: '', + cell: TaskActionMenuCell, + meta: { + plan, + profile, + planMode + } + } + ], + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + + return ; +} diff --git a/apps/web/lib/features/task/task-card.tsx b/apps/web/lib/features/task/task-card.tsx index c4d97f026..181ade9af 100644 --- a/apps/web/lib/features/task/task-card.tsx +++ b/apps/web/lib/features/task/task-card.tsx @@ -480,7 +480,7 @@ function TimerButtonCall({ //* Task Estimate info * //* Task Info FC * -function TaskInfo({ +export function TaskInfo({ className, task, taskBadgeClassName, @@ -526,7 +526,7 @@ function TaskInfo({ /** * It's a dropdown menu that allows the user to remove the task. */ -function TaskCardMenu({ +export function TaskCardMenu({ task, loading, memberInfo, @@ -646,39 +646,39 @@ function TaskCardMenu({ {(viewType == 'default' || (viewType === 'dailyplan' && planMode === 'Outstanding')) && ( - <> - -
- {!taskPlannedToday && ( -
  • - -
  • - )} - {!taskPlannedTomorrow && ( + <> + +
    + {!taskPlannedToday && ( +
  • + +
  • + )} + {!taskPlannedTomorrow && ( +
  • + +
  • + )}
  • - )} -
  • - -
  • -
    - - )} +
    + + )} {viewType === 'dailyplan' && (planMode === 'Today Tasks' || planMode === 'Future Tasks') && ( @@ -812,7 +812,7 @@ export function PlanTask({ )} )} - {planMode === 'tomorow' && !taskPlannedForTomorrow && ( + {planMode === 'tomorrow' && !taskPlannedForTomorrow && ( {isPending || createDailyPlanLoading ? ( diff --git a/apps/web/lib/features/task/task-issue.tsx b/apps/web/lib/features/task/task-issue.tsx index 1b494a2f8..8c13db8ed 100644 --- a/apps/web/lib/features/task/task-issue.tsx +++ b/apps/web/lib/features/task/task-issue.tsx @@ -1,5 +1,5 @@ import { useModal } from '@app/hooks'; -import { IClassName, IssueType, ITaskIssue, ITeamTask, ITimeSheet, Nullable } from '@app/interfaces'; +import { IClassName, IssueType, ITaskIssue, ITeamTask, Nullable } from '@app/interfaces'; import { clsxm } from '@app/utils'; import { BackButton, Button, Card, InputField, Modal, Text } from 'lib/components'; import { NoteIcon, BugIcon, Square4StackIcon, Square4OutlineIcon } from 'assets/svg'; @@ -212,19 +212,3 @@ export function CreateTaskIssueModal({ open, closeModal }: { open: boolean; clos ); } - - -export function TaskIssueStatusTimesheet({ - task, - className, - showIssueLabels -}: { task: Nullable; showIssueLabels?: boolean } & IClassName) { - return ( - - ); -} diff --git a/apps/web/lib/features/task/task-status.tsx b/apps/web/lib/features/task/task-status.tsx index 73096654c..eb9c4e2d7 100644 --- a/apps/web/lib/features/task/task-status.tsx +++ b/apps/web/lib/features/task/task-status.tsx @@ -33,6 +33,7 @@ import { XMarkIcon } from '@heroicons/react/24/outline'; import { readableColor } from 'polished'; import { useTheme } from 'next-themes'; import { Square4OutlineIcon, CircleIcon } from 'assets/svg'; +import { getTextColor } from '@app/helpers'; import { cn } from '@/lib/utils'; export type TStatusItem = { @@ -854,7 +855,8 @@ export function TaskStatus({ className )} style={{ - backgroundColor: active ? backgroundColor : undefined + backgroundColor: active ? backgroundColor : undefined, + color: getTextColor(backgroundColor ?? 'white') }} >
    ({ className={clsxm( `justify-between capitalize`, sidebarUI && ['text-xs'], - !value && ['text-dark dark:text-white dark:bg-dark--theme-light'], + !value && ['!text-dark/40 dark:text-white'], isVersion || (forDetails && !value) ? 'bg-transparent border border-solid border-color-[#F2F2F2]' - : 'bg-[#F2F2F2] ', + : 'bg-white border', 'dark:bg-[#1B1D22] dark:border dark:border-[#FFFFFF33]', taskStatusClassName, isVersion && 'dark:text-white', @@ -1028,6 +1030,7 @@ export function StatusDropdown({ 10} label={capitalize(value?.name) || ''} + className="h-full" > {button} diff --git a/apps/web/lib/features/team-members-table-view.tsx b/apps/web/lib/features/team-members-table-view.tsx index af2d3d471..50df9227d 100644 --- a/apps/web/lib/features/team-members-table-view.tsx +++ b/apps/web/lib/features/team-members-table-view.tsx @@ -72,7 +72,6 @@ const TeamMembersTableView = ({ return ( <> - []} diff --git a/apps/web/lib/features/user-profile-plans.tsx b/apps/web/lib/features/user-profile-plans.tsx index e9c8a5153..754622dd6 100644 --- a/apps/web/lib/features/user-profile-plans.tsx +++ b/apps/web/lib/features/user-profile-plans.tsx @@ -50,6 +50,7 @@ import TaskBlockCard from './task/task-block-card'; import { TaskCard } from './task/task-card'; import moment from 'moment'; import { usePathname } from 'next/navigation'; +import DailyPlanTasksTableView from './task/daily-plan/table-view'; export type FilterTabs = 'Today Tasks' | 'Future Tasks' | 'Past Tasks' | 'All Tasks' | 'Outstanding'; type FilterOutstanding = 'ALL' | 'DATE'; @@ -333,79 +334,95 @@ function AllPlans({ profile, currentTab = 'All Tasks' }: { profile: any; current - - {(provided: DroppableProvided, snapshot: DroppableStateSnapshot) => ( -
      - {plan.tasks?.map((task, index) => - view === 'CARDS' ? ( - - {(provided) => ( -
      - -
      - )} -
      - ) : ( - - {(provided) => ( -
      - -
      - )} -
      - ) - )} - <>{provided.placeholder} -
    - )} -
    + + {view === 'TABLE' ? ( + + ) : ( + + {(provided: DroppableProvided, snapshot: DroppableStateSnapshot) => ( +
      + {plan.tasks?.map((task, index) => + view === 'CARDS' ? ( + + {(provided) => ( +
      + +
      + )} +
      + ) : ( + + {(provided) => ( +
      + +
      + )} +
      + ) + )} + <>{provided.placeholder} +
    + )} +
    + )}
    ))} @@ -455,9 +472,8 @@ export function PlanHeader({ plan, planMode }: { plan: IDailyPlan; planMode: Fil return (
    {/* Planned Time */} diff --git a/apps/web/lib/settings/icon-items.tsx b/apps/web/lib/settings/icon-items.tsx index 5ba87b2db..8c72290e2 100644 --- a/apps/web/lib/settings/icon-items.tsx +++ b/apps/web/lib/settings/icon-items.tsx @@ -2,6 +2,7 @@ import { GAUZY_API_BASE_SERVER_URL } from '@app/constants'; import { IIcon } from '@app/interfaces'; import { clsxm } from '@app/utils'; import { DropdownItem } from 'lib/components'; +import { useTranslations } from 'next-intl'; import Image from 'next/image'; export type IconItem = DropdownItem; @@ -53,6 +54,7 @@ export function IconItem({ url: string; disabled?: boolean; }) { + const t = useTranslations(); return (
    - {url && ( + {url ? (
    + ) : ( + {t('common.ICON')} )}
    - - {title} -
    ); } diff --git a/apps/web/lib/settings/list-card.tsx b/apps/web/lib/settings/list-card.tsx index f07ef63b0..e9ad47a24 100644 --- a/apps/web/lib/settings/list-card.tsx +++ b/apps/web/lib/settings/list-card.tsx @@ -1,11 +1,12 @@ import { EditPenUnderlineIcon, TrashIcon } from 'assets/svg'; import { Button, Text, Tooltip } from 'lib/components'; -import Image from 'next/image'; import { CHARACTER_LIMIT_TO_SHOW } from '@app/constants'; import { IClassName } from '@app/interfaces'; import { clsxm } from '@app/utils'; import { getTextColor } from '@app/helpers'; import { useTranslations } from 'next-intl'; +import { useEffect } from 'react'; +import { svgFetch } from '@app/services/server/fetch'; export const StatusesListCard = ({ statusIcon, @@ -27,6 +28,12 @@ export const StatusesListCard = ({ const textColor = getTextColor(bgColor); const t = useTranslations(); + useEffect(() => { + if (statusIcon) { + loadSVG(statusIcon, 'icon-container' + statusTitle, textColor); + } + }, [statusIcon, statusTitle, textColor]); + return (
    - {statusIcon && ( - {statusTitle} - )} + {statusIcon &&
    } = CHARACTER_LIMIT_TO_SHOW} @@ -84,3 +80,37 @@ export const StatusesListCard = ({
    ); }; + +/** + * A function to load an SVG and gives the ability to + * update its attributes. e.g: fill color + * + * @param {string} url the URL of the SVG file to load + * @param {string} containerId the ID of the container where the SVG will be inserted + * @param {string} color the fill color for the SVG + */ +const loadSVG = async (url: string, containerId: string, color: string): Promise => { + try { + const response = await svgFetch(url); + + if (!response.ok) { + throw new Error(`Failed to fetch SVG: ${response.statusText}`); + } + + let svgContent = await response.text(); + + // Update the fill color in the SVG content + svgContent = svgContent.replace(/stroke="[^"]*"/g, `stroke="${color}"`); + + const container = document.getElementById(containerId); + + if (container) { + console.log(container); + container.innerHTML = svgContent; + } else { + console.error(`Container with ID "${containerId}" not found.`); + } + } catch (error) { + console.error(`Error loading SVG: ${(error as Error).message}`); + } +}; diff --git a/apps/web/lib/settings/task-statuses-form.tsx b/apps/web/lib/settings/task-statuses-form.tsx index 678e9b569..c07e48b7b 100644 --- a/apps/web/lib/settings/task-statuses-form.tsx +++ b/apps/web/lib/settings/task-statuses-form.tsx @@ -6,7 +6,7 @@ import { clsxm } from '@app/utils'; import { Spinner } from '@components/ui/loaders/spinner'; import { PlusIcon } from '@heroicons/react/20/solid'; import { Button, ColorPicker, InputField, Modal, Text } from 'lib/components'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useForm } from 'react-hook-form'; import { useTranslations } from 'next-intl'; import { useAtom } from 'jotai'; @@ -31,6 +31,8 @@ export const TaskStatusesForm = ({ const [createNew, setCreateNew] = useState(formOnly); const [edit, setEdit] = useState(null); const t = useTranslations(); + const [selectedStatusType, setSelectedStatusType] = useState(null); + const [randomColor, setRandomColor] = useState(undefined); const taskStatusIconList: IIcon[] = generateIconList('task-statuses', [ 'open', @@ -38,14 +40,15 @@ export const TaskStatusesForm = ({ 'ready', 'in-review', 'blocked', - 'completed' + 'completed', + 'backlog', ]); const taskSizesIconList: IIcon[] = generateIconList('task-sizes', [ - 'x-large' - // 'large', - // 'medium', - // 'small', - // 'tiny', + 'x-large', + 'large', + 'medium', + 'small', + 'tiny', ]); const taskPrioritiesIconList: IIcon[] = generateIconList('task-priorities', [ 'urgent', @@ -54,11 +57,12 @@ export const TaskStatusesForm = ({ 'low' ]); - const iconList: IIcon[] = [ + const iconList: IIcon[] = useMemo(() => [ ...taskStatusIconList, ...taskSizesIconList, ...taskPrioritiesIconList - ]; + // eslint-disable-next-line react-hooks/exhaustive-deps + ],[]) ; const { loading, @@ -157,6 +161,53 @@ export const TaskStatusesForm = ({ const [statusToDelete, setStatusToDelete] = useState(null) const {tasks} = useTeamTasks() + /** + * Get Icon by status name + * + * @param {string} iconName - Name of the icon + * @returns {IIcon} - Icon of the status + */ + const getIcon = useCallback( + (iconName: string | null) => { + if (!iconName) return null; + + const STATUS_MAPPINGS: Record = { + 'ready-for-review': 'ready' + }; + + const name = STATUS_MAPPINGS[iconName] || iconName; + + const icon = iconList.find((icon) => icon.title === name); + + if (icon) { + setValue('icon', icon.path); + } + return icon; + }, + [iconList, setValue] + ); + + + /** + * Get random color for new status + * + * @returns {string} - Random color + */ + const getRandomColor = useCallback(() => { + const letters = '0123456789ABCDEF'; + let color = '#'; + for (let i = 0; i < 6; i++) { + color += letters[Math.floor(Math.random() * 16)]; + } + return color; + }, []); + + useEffect(() => { + if (!edit && selectedStatusType) { + setRandomColor(getRandomColor()); + } + }, [selectedStatusType, edit, getRandomColor]); + return ( <> @@ -195,7 +246,7 @@ export const TaskStatusesForm = ({ variant="outline" className="rounded-[10px]" > - Sort + {t('common.SORT')}
    {(createNew || edit) && ( @@ -218,22 +269,26 @@ export const TaskStatusesForm = ({ {...register('name')} /> setValue('template', status)} - className=" h-14 shrink-0" + onValueChange={(status) => { + setValue('template', status) + setSelectedStatusType(status) + } } + className="h-14 shrink-0" + defaultValue={edit?.value} /> icon.path === edit.icon - ) as IIcon) - : null + ) as IIcon) : null } /> setValue('color', color)} className=" shrink-0" /> @@ -247,6 +302,9 @@ export const TaskStatusesForm = ({ createTaskStatusLoading || editTaskStatusLoading } loading={createTaskStatusLoading || editTaskStatusLoading} + onClick={() => { + setSelectedStatusType(null); + }} > {edit ? t('common.SAVE') : t('common.CREATE')} @@ -257,6 +315,7 @@ export const TaskStatusesForm = ({ onClick={() => { setCreateNew(false); setEdit(null); + setSelectedStatusType(null); }} > {t('common.CANCEL')} diff --git a/apps/web/locales/ar.json b/apps/web/locales/ar.json index 77d575a46..2b45b804d 100644 --- a/apps/web/locales/ar.json +++ b/apps/web/locales/ar.json @@ -235,6 +235,8 @@ "CHANGE_RELATIONS": "تغيير العلاقات", "SET_AS_NEXT": "تعيين كالتالي", "MOVE_TO": "نقل إلى", + "SORT": "فرز", + "ICON": "أيقونة", "SELECT_DATE": "اختر التاريخ", "SELECT_AND_CLOSE": "اختر وأغلق", "SELECT_ROLE": "يرجى اختيار أي دور", diff --git a/apps/web/locales/bg.json b/apps/web/locales/bg.json index 146962a2e..6a35a5b89 100644 --- a/apps/web/locales/bg.json +++ b/apps/web/locales/bg.json @@ -255,6 +255,8 @@ "MOVE_TO": "Преместете в", "SELECT_DATE": "Изберете дата", "SELECT_AND_CLOSE": "Изберете и затворете", + "SORT": "Подредете", + "ICON": "Икона", "SELECT_ROLE": "Моля, изберете роля", "ADD_TIME": "Добавете време", "VIEW_TIMESHEET": "Преглед на работния час", diff --git a/apps/web/locales/de.json b/apps/web/locales/de.json index c42de6e56..a8cd98b43 100644 --- a/apps/web/locales/de.json +++ b/apps/web/locales/de.json @@ -255,6 +255,8 @@ "MOVE_TO": "Verschieben nach", "SELECT_DATE": "Datum auswählen", "SELECT_AND_CLOSE": "Auswählen und schließen", + "SORT": "Sortieren", + "ICON": "Symbol", "SELECT_ROLE": "Bitte wählen Sie eine Rolle", "ADD_TIME": "Zeit hinzufügen", "VIEW_TIMESHEET": "Zeiterfassung anzeigen", diff --git a/apps/web/locales/en.json b/apps/web/locales/en.json index a35d7ec92..e230c7a31 100644 --- a/apps/web/locales/en.json +++ b/apps/web/locales/en.json @@ -255,6 +255,8 @@ "MOVE_TO": "Move to", "SELECT_DATE": "Select date", "SELECT_AND_CLOSE": "Select & Close", + "SORT": "Sort", + "ICON": "Icon", "SELECT_ROLE": "Please Select any Role", "ADD_TIME": "Add Time", "VIEW_TIMESHEET": "View timesheet", diff --git a/apps/web/locales/es.json b/apps/web/locales/es.json index 8a30a3032..7e8997c94 100644 --- a/apps/web/locales/es.json +++ b/apps/web/locales/es.json @@ -255,6 +255,8 @@ "MOVE_TO": "Mover a", "SELECT_DATE": "Seleccionar fecha", "SELECT_AND_CLOSE": "Seleccionar y cerrar", + "SORT": "Ordenar", + "ICON": "Ícono", "SELECT_ROLE": "Por favor, selecciona un rol", "ADD_TIME": "Agregar tiempo", "VIEW_TIMESHEET": "Ver hoja de tiempo", diff --git a/apps/web/locales/fr.json b/apps/web/locales/fr.json index bbd5f0430..2c70c0917 100644 --- a/apps/web/locales/fr.json +++ b/apps/web/locales/fr.json @@ -255,6 +255,8 @@ "MOVE_TO": "Déplacer vers", "SELECT_DATE": "Sélectionner la date", "SELECT_AND_CLOSE": "Sélectionner et fermer", + "SORT": "Trier", + "ICON": "Icône", "SELECT_ROLE": "Veuillez sélectionner un rôle", "ADD_TIME": "Ajouter du temps", "VIEW_TIMESHEET": "Voir la feuille de temps", diff --git a/apps/web/locales/he.json b/apps/web/locales/he.json index 0cf6d5c9e..e6c81b8fc 100644 --- a/apps/web/locales/he.json +++ b/apps/web/locales/he.json @@ -255,6 +255,8 @@ "MOVE_TO": "העבר אל", "SELECT_DATE": "בחר תאריך", "SELECT_AND_CLOSE": "בחר וסגור", + "SORT": "מיין", + "ICON": "סמל", "SELECT_ROLE": "אנא בחר תפקיד", "ADD_TIME": "הוסף זמן", "VIEW_TIMESHEET": "הצג גיליון זמן", diff --git a/apps/web/locales/it.json b/apps/web/locales/it.json index 576c9af34..945980bd8 100644 --- a/apps/web/locales/it.json +++ b/apps/web/locales/it.json @@ -255,6 +255,8 @@ "MOVE_TO": "Sposta a", "SELECT_DATE": "Seleziona data", "SELECT_AND_CLOSE": "Seleziona e chiudi", + "SORT": "Ordina", + "ICON": "Icona", "SELECT_ROLE": "Seleziona un ruolo", "ADD_TIME": "Aggiungi tempo", "VIEW_TIMESHEET": "Vedi foglio ore", diff --git a/apps/web/locales/nl.json b/apps/web/locales/nl.json index 1c7612d5c..2445311c8 100644 --- a/apps/web/locales/nl.json +++ b/apps/web/locales/nl.json @@ -255,6 +255,8 @@ "MOVE_TO": "Verplaatsen naar", "SELECT_DATE": "Selecteer datum", "SELECT_AND_CLOSE": "Selecteren en sluiten", + "SORT": "Sorteren", + "ICON": "Icoon", "SELECT_ROLE": "Selecteer een rol", "ADD_TIME": "Tijd toevoegen", "VIEW_TIMESHEET": "Bekijk tijdregistratie", diff --git a/apps/web/locales/pl.json b/apps/web/locales/pl.json index 7e45f58ba..198f39004 100644 --- a/apps/web/locales/pl.json +++ b/apps/web/locales/pl.json @@ -255,6 +255,8 @@ "MOVE_TO": "Przenieś do", "SELECT_DATE": "Wybierz datę", "SELECT_AND_CLOSE": "Wybierz i zamknij", + "SORT": "Sortuj", + "ICON": "Ikona", "SELECT_ROLE": "Proszę wybrać rolę", "ADD_TIME": "Dodaj czas", "VIEW_TIMESHEET": "Zobacz Ewidencję czasu", diff --git a/apps/web/locales/pt.json b/apps/web/locales/pt.json index 4a832f9ab..033cc1a79 100644 --- a/apps/web/locales/pt.json +++ b/apps/web/locales/pt.json @@ -255,6 +255,8 @@ "MOVE_TO": "Mover para", "SELECT_DATE": "Selecionar data", "SELECT_AND_CLOSE": "Selecionar e fechar", + "SORT": "Classificar", + "ICON": "Ícone", "SELECT_ROLE": "Por favor, selecione um cargo", "ADD_TIME": "Adicionar tempo", "VIEW_TIMESHEET": "Ver folha de ponto", diff --git a/apps/web/locales/ru.json b/apps/web/locales/ru.json index 20c2605ab..3ab812538 100644 --- a/apps/web/locales/ru.json +++ b/apps/web/locales/ru.json @@ -255,6 +255,8 @@ "MOVE_TO": "Переместить в", "SELECT_DATE": "Выберите дату", "SELECT_AND_CLOSE": "Выбрать и закрыть", + "SORT": "Сортировать", + "ICON": "Иконка", "SELECT_ROLE": "Пожалуйста, выберите роль", "ADD_TIME": "Добавить время", "VIEW_TIMESHEET": "Просмотреть табель", diff --git a/apps/web/locales/zh.json b/apps/web/locales/zh.json index 3735c7f97..50d39d2d2 100644 --- a/apps/web/locales/zh.json +++ b/apps/web/locales/zh.json @@ -255,6 +255,8 @@ "MOVE_TO": "移动到", "SELECT_DATE": "选择日期", "SELECT_AND_CLOSE": "选择并关闭", + "SORT": "排序", + "ICON": "图标", "SELECT_ROLE": "请选择角色", "ADD_TIME": "添加时间", "VIEW_TIMESHEET": "查看工时表", diff --git a/apps/web/package.json b/apps/web/package.json index 7e36362dc..32e4781f6 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -52,6 +52,7 @@ "@opentelemetry/semantic-conventions": "^1.18.1", "@popperjs/core": "^2.11.6", "@radix-ui/react-accordion": "^1.1.2", + "@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-avatar": "^1.1.1", "@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-collapsible": "^1.1.1", @@ -98,7 +99,7 @@ "moment": "^2.29.4", "moment-timezone": "^0.5.42", "nanoid": "5.0.1", - "next": "14.2.17", + "next": "14.2.17", "next-auth": "^5.0.0-beta.18", "next-intl": "^3.3.2", "next-themes": "^0.2.1", @@ -132,9 +133,6 @@ "tailwind-merge": "^1.14.0" }, "devDependencies": { - "tailwindcss-animate": "^1.0.6", - "tailwindcss": "^3.4.1", - "postcss": "^8.4.19", "@svgr/webpack": "^8.1.0", "@tailwindcss/typography": "^0.5.9", "@types/cookie": "^0.5.1", @@ -151,6 +149,9 @@ "eslint": "^8.28.0", "eslint-config-next": "^14.0.4", "eslint-plugin-unused-imports": "^3.0.0", + "postcss": "^8.4.19", + "tailwindcss": "^3.4.1", + "tailwindcss-animate": "^1.0.6", "typescript": "^4.9.4" }, "prettier": { diff --git a/yarn.lock b/yarn.lock index d416b8b87..6e68a578d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6050,6 +6050,18 @@ "@radix-ui/react-primitive" "1.0.3" "@radix-ui/react-use-controllable-state" "1.0.1" +"@radix-ui/react-alert-dialog@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.2.tgz#ac3bb7f71f5cbb595d3d0949bb12b598c2a99981" + integrity sha512-eGSlLzPhKO+TErxkiGcCZGuvbVMnLA1MTnyBksGOeGRGkxHiiJUujsjmNTdWTm4iHVSRaUao9/4Ur671auMghQ== + dependencies: + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.1" + "@radix-ui/react-dialog" "1.1.2" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-slot" "1.1.0" + "@radix-ui/react-arrow@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz#c24f7968996ed934d57fe6cde5d6ec7266e1d25d" @@ -6203,7 +6215,7 @@ aria-hidden "^1.1.1" react-remove-scroll "2.5.4" -"@radix-ui/react-dialog@^1.1.2": +"@radix-ui/react-dialog@1.1.2", "@radix-ui/react-dialog@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz#d9345575211d6f2d13e209e84aec9a8584b54d6c" integrity sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==