From ecd4d29a38136dc62136d7333d3be62ea8347ef3 Mon Sep 17 00:00:00 2001 From: chamiakJ Date: Thu, 3 Jul 2025 01:31:05 +0530 Subject: [PATCH] expand sub tasks --- worklenz-frontend/README.md | 1 + worklenz-frontend/project-report-table.css | 2 +- worklenz-frontend/public/env-config.js | 2 +- .../public/locales/alb/task-management.json | 2 +- .../de/task-drawer/task-drawer-info-tab.json | 2 +- .../public/locales/de/task-management.json | 2 +- .../locales/en/admin-center/current-bill.json | 6 +- .../public/locales/en/phases-drawer.json | 12 +- .../project-view/import-task-templates.json | 18 +- .../project-view/project-member-drawer.json | 13 +- .../en/project-view/project-view-header.json | 32 +- .../locales/en/settings/appearance.json | 2 +- .../en/task-drawer/task-drawer-info-tab.json | 2 +- .../task-drawer-recurring-config.json | 2 +- .../public/locales/en/task-list-table.json | 2 +- .../public/locales/en/task-management.json | 2 +- .../en/tasks/task-table-bulk-actions.json | 80 +- .../public/locales/en/unauthorized.json | 8 +- .../locales/es/admin-center/current-bill.json | 4 +- .../public/locales/es/kanban-board.json | 2 +- .../public/locales/es/phases-drawer.json | 12 +- .../project-view/import-task-templates.json | 20 +- .../project-view/project-member-drawer.json | 13 +- .../es/project-view/project-view-header.json | 32 +- .../es/project-view/save-as-template.json | 2 +- .../locales/es/settings/appearance.json | 2 +- .../es/task-drawer/task-drawer-info-tab.json | 2 +- .../task-drawer-recurring-config.json | 2 +- .../locales/es/task-drawer/task-drawer.json | 2 +- .../public/locales/es/task-list-table.json | 2 +- .../public/locales/es/task-management.json | 2 +- .../es/tasks/task-table-bulk-actions.json | 80 +- .../public/locales/es/unauthorized.json | 8 +- .../locales/pt/admin-center/current-bill.json | 2 +- .../public/locales/pt/kanban-board.json | 2 +- .../public/locales/pt/phases-drawer.json | 12 +- .../project-view/import-task-templates.json | 20 +- .../project-view/project-member-drawer.json | 13 +- .../pt/project-view/project-view-header.json | 32 +- .../pt/project-view/save-as-template.json | 2 +- .../locales/pt/settings/appearance.json | 2 +- .../locales/pt/settings/categories.json | 2 +- .../public/locales/pt/settings/clients.json | 2 +- .../locales/pt/settings/job-titles.json | 2 +- .../public/locales/pt/settings/labels.json | 2 +- .../public/locales/pt/settings/language.json | 2 +- .../locales/pt/settings/notifications.json | 2 +- .../public/locales/pt/settings/profile.json | 2 +- .../pt/settings/project-templates.json | 2 +- .../public/locales/pt/settings/sidebar.json | 2 +- .../locales/pt/settings/task-templates.json | 2 +- .../locales/pt/settings/team-members.json | 2 +- .../pt/task-drawer/task-drawer-info-tab.json | 2 +- .../task-drawer-recurring-config.json | 2 +- .../locales/pt/task-drawer/task-drawer.json | 2 +- .../public/locales/pt/task-list-filters.json | 2 +- .../public/locales/pt/task-list-table.json | 2 +- .../public/locales/pt/task-management.json | 2 +- .../pt/tasks/task-table-bulk-actions.json | 80 +- .../public/locales/pt/unauthorized.json | 8 +- worklenz-frontend/public/unregister-sw.js | 8 +- worklenz-frontend/scripts/copy-tinymce.js | 6 +- worklenz-frontend/src/App.tsx | 18 +- .../admin-center/admin-center.api.service.ts | 65 +- .../api/admin-center/billing.api.service.ts | 15 +- worklenz-frontend/src/api/api-client.ts | 35 +- .../attachments/attachments.api.service.ts | 35 +- .../api/home-page/home-page.api.service.ts | 2 +- .../project-members.api.service.ts | 2 +- .../project-templates.api.service.ts | 20 +- .../src/api/projects/projects.api.service.ts | 10 +- .../api/projects/projects.v1.api.service.ts | 2 +- .../reporting-members.api.service.ts | 11 +- .../reporting-projects.api.service.ts | 14 +- .../reporting.timesheet.api.service.ts | 31 +- .../src/api/schedule/schedule.api.service.ts | 18 +- .../profile/profile-settings.api.service.ts | 7 +- .../task-templates.api.service.ts | 10 +- .../phases/phases.api.service.ts | 2 +- .../status/status.api.service.ts | 12 +- .../src/api/tasks/subtasks.api.service.ts | 11 +- .../api/tasks/task-attachments.api.service.ts | 23 +- .../api/tasks/task-comments.api.service.ts | 27 +- .../tasks/task-dependencies.api.service.ts | 14 +- .../api/tasks/task-recurring.api.service.ts | 29 +- .../api/tasks/task-time-logs.api.service.ts | 12 +- .../api/tasks/tasks-custom-columns.service.ts | 23 +- .../src/api/tasks/tasks.api.service.ts | 46 +- .../team-members/teamMembers.api.service.ts | 4 +- .../src/api/teams/teams.api.service.ts | 14 +- .../src/app/performance-monitor.ts | 21 +- worklenz-frontend/src/app/routes/index.tsx | 149 +- .../src/app/routes/main-routes.tsx | 48 +- .../src/app/routes/settings-routes.tsx | 14 +- worklenz-frontend/src/app/selectors.ts | 18 +- worklenz-frontend/src/app/store.ts | 2 +- .../src/components/AssigneeSelector.tsx | 308 +- worklenz-frontend/src/components/Avatar.tsx | 56 +- .../src/components/AvatarGroup.tsx | 88 +- worklenz-frontend/src/components/Button.tsx | 36 +- worklenz-frontend/src/components/Checkbox.tsx | 42 +- .../src/components/CustomColordLabel.tsx | 12 +- .../src/components/CustomNumberLabel.tsx | 10 +- .../src/components/ErrorBoundary.tsx | 4 +- worklenz-frontend/src/components/HubSpot.tsx | 2 +- .../src/components/LabelsSelector.tsx | 241 +- worklenz-frontend/src/components/Progress.tsx | 34 +- worklenz-frontend/src/components/Tag.tsx | 27 +- worklenz-frontend/src/components/TawkTo.tsx | 8 +- worklenz-frontend/src/components/Tooltip.tsx | 18 +- .../components/account-setup/tasks-step.tsx | 4 +- .../account-storage/account-storage.tsx | 4 +- .../admin-center/billing/current-bill.tsx | 13 +- .../current-plan-details.tsx | 138 +- .../drawers/upgrade-plans/upgrade-plans.tsx | 39 +- .../configuration/configuration.tsx | 9 +- .../teams/settings-drawer/settings-drawer.tsx | 27 +- .../src/components/avatars/avatars.tsx | 57 +- .../board-assignee-selector.tsx | 23 +- .../components/board/custom-avatar-group.tsx | 2 +- .../board/custom-due-date-picker.tsx | 4 +- .../priority-section/priority-section.tsx | 23 +- .../src/components/charts/chart-loader.tsx | 12 +- .../components/collapsible/collapsible.tsx | 4 +- .../invite-team-members.tsx | 7 +- .../template-drawer/template-drawer.css | 1 - .../enhanced-kanban/EnhancedKanbanBoard.css | 2 +- .../enhanced-kanban/EnhancedKanbanBoard.tsx | 142 +- .../EnhancedKanbanCreateSection.tsx | 47 +- .../EnhancedKanbanCreateSubtaskCard.tsx | 41 +- .../EnhancedKanbanCreateTaskCard.tsx | 9 +- .../enhanced-kanban/EnhancedKanbanGroup.css | 9 +- .../enhanced-kanban/EnhancedKanbanGroup.tsx | 939 ++++--- .../EnhancedKanbanTaskCard.css | 7 +- .../EnhancedKanbanTaskCard.tsx | 421 +-- .../enhanced-kanban/PerformanceMonitor.css | 4 +- .../enhanced-kanban/PerformanceMonitor.tsx | 41 +- .../enhanced-kanban/VirtualizedTaskList.css | 2 +- .../enhanced-kanban/VirtualizedTaskList.tsx | 65 +- .../home-tasks-status-dropdown.tsx | 4 +- .../taskDatePicker/home-tasks-date-picker.tsx | 189 +- worklenz-frontend/src/components/index.ts | 2 +- .../SortableKanbanGroup.tsx | 69 +- .../kanbanGroup.tsx | 222 +- .../kanbanTaskCard.tsx | 49 +- .../kanbanTaskListBoard.tsx | 515 ++-- .../notification/invitation-item.tsx | 5 +- .../notification/notfication-drawer.tsx | 6 +- .../notification/notification-template.tsx | 6 +- .../push-notification-template.css | 2 +- .../push-notification-template.tsx | 48 +- .../project-group/project-group-list.tsx | 593 ++-- .../project-list-actions.tsx | 16 +- .../project-list-category.tsx | 6 +- .../project-rate-cell.tsx | 7 +- .../delete-status-drawer.tsx | 230 +- .../column-configuration-modal.tsx | 33 +- .../group-by-filter-dropdown.tsx | 18 +- .../members-filter-dropdown.tsx | 116 +- .../priority-filter-dropdown.tsx | 126 +- .../filter-dropdowns/search-dropdown.tsx | 1 - .../show-fields-filter-dropdown.tsx | 59 +- .../project-create-button.tsx | 8 +- .../project-category-section.tsx | 2 +- .../project-member-invite-drawer.tsx | 36 +- .../reporting-overview-projects-tab.tsx | 1 - .../reporting-overview-projects-table.tsx | 2 +- .../components/reporting/time-wise-filter.tsx | 6 +- .../schedule/grant-chart/grantt-chart.tsx | 10 +- .../grant-chart/grantt-members-table.tsx | 9 +- .../grant-chart/project-timeline-bar.tsx | 13 +- .../settings/update-member-drawer.tsx | 2 +- .../suspense-fallback/suspense-fallback.tsx | 14 +- .../activity-log/task-drawer-activity-log.tsx | 84 +- .../attachments/attachments-preview.tsx | 114 +- .../attachments/attachments-upload.css | 4 +- .../attachments/attachments-upload.tsx | 52 +- .../info-tab/comments/task-comments.css | 4 +- .../info-tab/comments/task-comments.tsx | 8 +- .../comments/task-view-comment-edit.tsx | 16 +- .../shared/info-tab/dependencies-table.css | 2 +- .../shared/info-tab/dependencies-table.tsx | 16 +- .../shared/info-tab/description-editor.tsx | 105 +- .../task-drawer-assignee-selector.tsx | 42 +- .../task-drawer-due-date.tsx | 47 +- .../task-drawer-estimation.tsx | 6 +- .../task-drawer-labels/task-drawer-labels.tsx | 130 +- .../task-drawer-phase-selector.tsx | 4 +- .../task-drawer-priority-selector.tsx | 7 +- .../task-drawer-progress.tsx | 12 +- .../task-drawer-recurring-config.tsx | 38 +- .../shared/info-tab/info-tab-footer.tsx | 37 +- .../info-tab/notify-member-selector.tsx | 9 +- .../shared/info-tab/subtask-table.css | 2 +- .../shared/info-tab/subtask-table.tsx | 174 +- .../shared/time-log/task-drawer-time-log.tsx | 2 +- .../shared/time-log/time-log-form.tsx | 20 +- .../shared/time-log/time-log-item.tsx | 24 +- .../shared/time-log/time-log-list.tsx | 6 +- .../task-drawer-header/task-drawer-header.tsx | 37 +- .../labelsSelector/labels-selector.tsx | 8 +- .../priorityDropdown/priority-dropdown.tsx | 44 +- .../status-dropdown/status-dropdown.tsx | 61 +- .../task-management/drag-drop-optimized.css | 2 +- .../task-management/improved-task-filters.tsx | 678 +++-- .../lazy-assignee-selector.tsx | 49 +- .../optimized-bulk-action-bar.css | 29 +- .../optimized-bulk-action-bar.tsx | 1463 +++++----- .../task-management/performance-analysis.tsx | 125 +- .../components/task-management/task-group.tsx | 605 ++-- .../task-management/task-list-board.tsx | 342 ++- .../task-management/task-phase-dropdown.tsx | 299 +- .../task-priority-dropdown.tsx | 242 +- .../task-management/task-row-optimized.css | 43 +- .../task-management/task-row-utils.ts | 107 +- .../components/task-management/task-row.tsx | 2489 +++++++++-------- .../task-management/task-status-dropdown.tsx | 244 +- .../task-management/virtualized-task-list.tsx | 1332 +++++---- .../task-templates/import-task-template.tsx | 7 +- .../task-templates/task-template-drawer.tsx | 8 +- .../assignee-selector/assignee-selector.tsx | 25 +- .../labelsSelector/CustomColordLabel.tsx | 1 - .../labelsSelector/CustomNumberLabel.tsx | 2 - .../labelsSelector/LabelsSelector.tsx | 129 +- .../phase-dropdown/phase-dropdown.tsx | 100 +- .../components/LabelsDropdown.tsx | 17 +- .../taskListCommon/task-timer/task-timer.tsx | 52 +- worklenz-frontend/src/config/env.ts | 12 +- .../admin-center/admin-center.slice.ts | 26 +- .../src/features/board/board-slice.ts | 13 +- .../enhanced-kanban/enhanced-kanban.slice.ts | 136 +- .../src/features/navbar/navbar.tsx | 27 +- .../features/navbar/timers/timer-button.tsx | 111 +- .../navbar/user-profile/profile-button.tsx | 17 +- .../features/project/project-drawer.slice.ts | 1 - .../features/project/project-view-slice.ts | 10 +- .../src/features/project/project.slice.ts | 4 +- .../src/features/projects/projects.slice.ts | 2 +- .../src/features/projects/projectsSlice.ts | 5 +- .../singleProject/phase/PhaseDrawer.tsx | 2 +- .../singleProject/phase/PhaseOptionItem.tsx | 33 +- .../singleProject/phase/phases.slice.ts | 30 +- .../task-list-custom-columns-slice.ts | 7 +- .../projects/status/DeleteStatusSlice.ts | 13 +- .../update-project/update-project-drawer.tsx | 6 +- .../activity-log-tab/activity-log-card.tsx | 11 +- .../overviewTab/MembersReportsOverviewTab.tsx | 2 +- ...members-overview-projects-stats-drawer.tsx | 9 +- .../members-overview-projects-stats-table.tsx | 60 +- .../taskTab/MembersReportsTasksTable.tsx | 5 +- .../time-log-tab/billable-filter.tsx | 17 +- .../time-log-tab/time-log-card.tsx | 6 +- .../projectReports/project-reports-slice.ts | 13 +- .../project-reports-table-column-slice.ts | 38 +- .../membersTab/ProjectReportsMembersTab.tsx | 2 +- .../membersTab/ProjectReportsMembersTable.tsx | 11 +- .../ProjectReportsMembersTaskDrawer.tsx | 5 +- .../tasksTab/ProjectReportsTaskTable.tsx | 14 +- .../tasksTab/ProjectReportsTasksTab.tsx | 4 +- .../src/features/roadmap/roadmap-slice.ts | 11 +- .../schedule/ProjectTimelineModal.tsx | 49 +- .../schedule/ScheduleSettingsDrawer.tsx | 10 +- .../src/features/schedule/scheduleSlice.ts | 28 +- .../features/settings/member/memberSlice.ts | 7 +- .../features/task-drawer/task-drawer.slice.ts | 28 +- .../task-management/grouping.slice.ts | 79 +- .../task-management/selection.slice.ts | 50 +- .../task-management/task-management.slice.ts | 326 ++- .../task-management/taskListFields.slice.ts | 6 +- .../src/features/tasks/tasks.slice.ts | 48 +- .../src/features/teams/teamSlice.ts | 1 - .../src/features/theme/ThemeWrapper.tsx | 58 +- .../timeReport/projects/timeLogSlice.ts | 3 +- .../src/features/user/userSlice.ts | 2 +- worklenz-frontend/src/hooks/useDragCursor.ts | 4 +- .../src/hooks/useFilterDataLoader.ts | 31 +- .../src/hooks/useIsProjectManager.ts | 10 +- .../src/hooks/useIsomorphicLayoutEffect.ts | 2 +- .../src/hooks/usePerformanceOptimization.ts | 88 +- .../src/hooks/useTabSearchParam.ts | 4 +- .../src/hooks/useTaskDrawerUrlSync.ts | 57 +- .../src/hooks/useTaskSocketHandlers.ts | 520 ++-- worklenz-frontend/src/hooks/useTaskTimer.ts | 18 +- .../src/hooks/useTranslationPreloader.ts | 8 +- worklenz-frontend/src/i18n.ts | 28 +- worklenz-frontend/src/index.css | 7 +- .../src/layouts/AuthenticatedLayout.tsx | 2 +- worklenz-frontend/src/layouts/MainLayout.tsx | 63 +- .../src/layouts/admin-center-layout.tsx | 2 +- .../src/lib/project/project-view-constants.ts | 28 +- .../src/pages/account-setup/account-setup.tsx | 7 +- .../pages/admin-center/billing/billing.tsx | 2 +- .../src/pages/admin-center/users/users.tsx | 4 +- .../src/pages/auth/forgot-password-page.tsx | 5 +- .../src/pages/auth/login-page.tsx | 2 +- .../src/pages/auth/signup-page.tsx | 59 +- .../src/pages/home/home-page.tsx | 34 +- .../recent-and-favourite-project-list.tsx | 8 +- .../home/task-list/add-task-inline-form.tsx | 10 +- .../src/pages/home/task-list/tasks-list.tsx | 66 +- .../pages/license-expired/license-expired.tsx | 26 +- .../src/pages/projects/project-list.css | 2 +- .../src/pages/projects/project-list.tsx | 216 +- .../project-view-1/task-list/table-v2.tsx | 175 +- .../taskList/ProjectViewTaskList.tsx | 5 +- .../taskListFilters/MembersFilterDropdown.tsx | 5 +- .../ShowFieldsFilterDropdown.tsx | 4 +- .../taskList/taskListTable/TaskListTable.tsx | 4 +- .../updates/project-view-updates.tsx | 125 +- .../board-create-section-card.tsx | 46 +- .../board-section-card-header.tsx | 26 +- .../board-section/board-section-container.tsx | 2 +- .../board-create-sub-task-card.tsx | 25 +- .../board-sub-task-card.tsx | 12 +- .../board-view-create-task-card.tsx | 29 +- .../board-task-card/board-view-task-card.tsx | 165 +- .../projectView/board/project-view-board.tsx | 81 +- .../project-view-enhanced-board.tsx | 8 +- .../project-view-enhanced-tasks.tsx | 8 +- .../projectView/files/project-view-files.tsx | 1 - .../tables/tasks-by-members.tsx | 2 +- .../graphs/priority-overview.tsx | 1 - .../graphs/status-overview.tsx | 3 +- .../tables/last-updated-tasks.tsx | 3 +- .../tables/project-deadline.tsx | 2 +- .../tables/over-logged-tasks-table.tsx | 3 +- .../tables/overdue-tasks-table.tsx | 3 +- .../tables/task-completed-early-table.tsx | 1 - .../tables/task-completed-late-table.tsx | 3 +- .../insights/member-stats/member-stats.tsx | 2 +- .../insights/project-stats/project-stats.tsx | 3 +- .../insights/project-view-insights.tsx | 22 +- .../members/project-view-members.tsx | 45 +- .../projectView/project-view-header.tsx | 130 +- .../projects/projectView/project-view.css | 14 +- .../projects/projectView/project-view.tsx | 220 +- .../components/task-group/task-group.tsx | 25 +- .../taskList/groupTables/TaskGroupList.tsx | 30 +- .../taskList/project-view-task-list.tsx | 15 +- .../taskList/task-group-wrapper-optimized.tsx | 15 +- .../task-list-filters/task-list-filters.tsx | 18 +- .../context-menu/task-context-menu.tsx | 31 +- .../custom-column-label-cell.tsx | 14 +- .../custom-column-selection-cell.tsx | 23 +- .../custom-column-modal.tsx | 61 +- .../label-type-column/label-type-column.tsx | 12 +- .../selection-type-column.tsx | 31 +- .../task-group-wrapper/task-group-wrapper.tsx | 43 +- .../task-list-description-cell.tsx | 2 +- .../task-list-estimation-cell.tsx | 2 +- .../task-list-labels-cell.tsx | 10 +- .../task-list-progress-cell.tsx | 11 +- .../add-sub-task-list-row.tsx | 2 +- .../add-task-list-row.tsx | 37 +- .../task-list-table-wrapper.tsx | 45 +- .../task-list-table/task-list-table.tsx | 862 +++--- .../task-list-time-tracker-cell.tsx | 1 - .../members-reports-table.tsx | 4 +- .../tasksProgressCell/TasksProgressCell.tsx | 1 - .../members-reports/members-reports.tsx | 8 +- .../overview-reports/overview-reports.tsx | 33 +- .../overview-reports/overview-stat-card.tsx | 252 +- .../overview-reports/overview-stats.tsx | 256 +- .../overview-table/overview-reports-table.tsx | 84 +- .../project-categories-filter-dropdown.tsx | 36 +- .../project-health-filter-dropdown.tsx | 22 +- .../project-managers-filter-dropdown.tsx | 4 +- .../project-reports-filters.tsx | 51 +- .../project-status-filter-dropdown.tsx | 15 +- .../project-table-show-fields-dropdown.tsx | 6 +- .../projects-reports-table.tsx | 43 +- .../project-category-cell.tsx | 20 +- .../project-dates-cell/project-dates-cell.tsx | 37 +- .../project-health-cell.tsx | 11 +- .../project-status-cell.tsx | 2 +- .../project-update-cell.tsx | 2 +- .../projects-reports/projects-reports.tsx | 43 +- .../sidebar/reporting-collapsed-button.tsx | 3 +- .../estimated-vs-actual-time-sheet.tsx | 89 +- .../members-time-sheet/members-time-sheet.tsx | 26 +- .../project-time-sheet-chart.tsx | 42 +- .../time-sheet-table/time-sheet-table.css | 3 +- .../estimated-vs-actual-time-reports.tsx | 25 +- .../timeReports/members-time-reports.tsx | 4 +- .../timeReports/page-header/categories.tsx | 48 +- .../timeReports/page-header/projects.tsx | 598 ++-- .../timeReports/page-header/team.tsx | 44 +- .../timeReports/projects-time-reports.tsx | 8 +- .../TimeReportingRightHeader.tsx | 12 +- .../appearance/appearance-settings.tsx | 2 +- .../categories/categories-settings.tsx | 4 +- .../change-password/change-password.tsx | 4 +- .../settings/clients/clients-settings.tsx | 7 +- .../language-and-region-settings.tsx | 88 +- .../settings/profile/profile-settings.tsx | 148 +- .../project-templates-settings.tsx | 2 +- .../settings/sidebar/settings-sidebar.tsx | 4 +- .../task-templates-settings.tsx | 4 +- .../team-members/team-members-settings.tsx | 7 +- .../src/pages/unauthorized/unauthorized.tsx | 1 - .../src/services/auth/auth.service.ts | 1 - .../services/task-list/taskList.service.ts | 11 +- worklenz-frontend/src/shared/antd-imports.ts | 8 +- worklenz-frontend/src/shared/constants.ts | 70 +- worklenz-frontend/src/shared/socket-events.ts | 6 +- .../src/socket/socketContext.tsx | 2 +- .../src/styles/task-management.css | 14 +- .../types/admin-center/admin-center.types.ts | 2 +- .../project/groupedProjectsViewModel.types.ts | 2 +- .../types/project/project-view-model.types.ts | 2 +- .../src/types/project/project.types.ts | 4 +- .../src/types/projectMember.types.ts | 2 +- .../reporting/reporting-allocation.types.ts | 2 +- .../src/types/schedule/schedule-v2.types.ts | 29 +- .../types/settings/task-templates.types.ts | 11 +- .../src/types/task-management.types.ts | 2 +- .../types/tasks/task-recurring-schedule.ts | 18 +- .../src/utils/calculate-time-gap.ts | 6 +- .../src/utils/check-task-dependency-status.ts | 22 +- worklenz-frontend/src/utils/colorUtils.ts | 2 +- worklenz-frontend/src/utils/dateUtils.ts | 6 +- .../src/utils/debug-performance.ts | 111 +- .../src/utils/performance-monitor.ts | 84 +- .../src/utils/performance-optimizer.ts | 96 +- worklenz-frontend/src/utils/performance.ts | 17 +- worklenz-frontend/src/utils/project-group.ts | 8 +- worklenz-frontend/src/utils/routePreloader.ts | 29 +- worklenz-frontend/src/utils/sanitizeInput.ts | 12 +- worklenz-frontend/src/utils/schedule.ts | 4 +- .../src/utils/sort-team-members.ts | 16 +- .../src/utils/task-management-mock-data.ts | 22 +- worklenz-frontend/src/utils/timeUtils.ts | 1 - worklenz-frontend/src/utils/validateEmail.ts | 4 +- worklenz-frontend/tailwind.config.js | 9 +- worklenz-frontend/vite.config.ts | 88 +- 435 files changed, 13150 insertions(+), 11087 deletions(-) diff --git a/worklenz-frontend/README.md b/worklenz-frontend/README.md index 46c4a267..15622f91 100644 --- a/worklenz-frontend/README.md +++ b/worklenz-frontend/README.md @@ -3,6 +3,7 @@ Worklenz is a project management application built with React, TypeScript, and Ant Design. The project is bundled using [Vite](https://vitejs.dev/). ## Table of Contents + - [Getting Started](#getting-started) - [Available Scripts](#available-scripts) - [Project Structure](#project-structure) diff --git a/worklenz-frontend/project-report-table.css b/worklenz-frontend/project-report-table.css index 3e67444c..ec1909a8 100644 --- a/worklenz-frontend/project-report-table.css +++ b/worklenz-frontend/project-report-table.css @@ -14,4 +14,4 @@ /* Maintain hover state */ .table-body-row:hover .sticky-column { background-color: var(--background-hover); -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/env-config.js b/worklenz-frontend/public/env-config.js index 2e582288..52a87582 100644 --- a/worklenz-frontend/public/env-config.js +++ b/worklenz-frontend/public/env-config.js @@ -4,4 +4,4 @@ // Set undefined values so the application falls back to build-time env vars window.VITE_API_URL = undefined; -window.VITE_SOCKET_URL = undefined; \ No newline at end of file +window.VITE_SOCKET_URL = undefined; diff --git a/worklenz-frontend/public/locales/alb/task-management.json b/worklenz-frontend/public/locales/alb/task-management.json index 5fe5aef6..9991e559 100644 --- a/worklenz-frontend/public/locales/alb/task-management.json +++ b/worklenz-frontend/public/locales/alb/task-management.json @@ -12,4 +12,4 @@ "enterSubtaskName": "Shkruani emrin e nën-detyrës...", "add": "Shto", "cancel": "Anulo" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/de/task-drawer/task-drawer-info-tab.json b/worklenz-frontend/public/locales/de/task-drawer/task-drawer-info-tab.json index ed79d6bf..aece79f0 100644 --- a/worklenz-frontend/public/locales/de/task-drawer/task-drawer-info-tab.json +++ b/worklenz-frontend/public/locales/de/task-drawer/task-drawer-info-tab.json @@ -26,4 +26,4 @@ "add-sub-task": "+ Unteraufgabe hinzufügen", "refresh-sub-tasks": "Unteraufgaben aktualisieren" } -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/de/task-management.json b/worklenz-frontend/public/locales/de/task-management.json index 45ae2836..720c442f 100644 --- a/worklenz-frontend/public/locales/de/task-management.json +++ b/worklenz-frontend/public/locales/de/task-management.json @@ -12,4 +12,4 @@ "enterSubtaskName": "Unteraufgabenname eingeben...", "add": "Hinzufügen", "cancel": "Abbrechen" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/en/admin-center/current-bill.json b/worklenz-frontend/public/locales/en/admin-center/current-bill.json index a4f39319..fe840789 100644 --- a/worklenz-frontend/public/locales/en/admin-center/current-bill.json +++ b/worklenz-frontend/public/locales/en/admin-center/current-bill.json @@ -25,7 +25,7 @@ "paymentMethod": "Payment Method", "status": "Status", "ltdUsers": "You can add up to {{ltd_users}} users.", - + "totalSeats": "Total seats", "availableSeats": "Available seats", "addMoreSeats": "Add more seats", @@ -103,11 +103,11 @@ "perMonthPerUser": "per user/month", "viewInvoice": "View Invoice", "switchToFreePlan": "Switch to Free Plan", - + "expirestoday": "today", "expirestomorrow": "tomorrow", "expiredDaysAgo": "{{days}} days ago", - + "continueWith": "Continue with {{plan}}", "changeToPlan": "Change to {{plan}}", "creditPlan": "Credit Plan", diff --git a/worklenz-frontend/public/locales/en/phases-drawer.json b/worklenz-frontend/public/locales/en/phases-drawer.json index 51ac7899..ca870b8f 100644 --- a/worklenz-frontend/public/locales/en/phases-drawer.json +++ b/worklenz-frontend/public/locales/en/phases-drawer.json @@ -1,7 +1,7 @@ { - "configurePhases": "Configure Phases", - "phaseLabel": "Phase Label", - "enterPhaseName": "Enter a name for phase label", - "addOption": "Add Option", - "phaseOptions": "Phase Options:" -} \ No newline at end of file + "configurePhases": "Configure Phases", + "phaseLabel": "Phase Label", + "enterPhaseName": "Enter a name for phase label", + "addOption": "Add Option", + "phaseOptions": "Phase Options:" +} diff --git a/worklenz-frontend/public/locales/en/project-view/import-task-templates.json b/worklenz-frontend/public/locales/en/project-view/import-task-templates.json index 6057a524..d732aa08 100644 --- a/worklenz-frontend/public/locales/en/project-view/import-task-templates.json +++ b/worklenz-frontend/public/locales/en/project-view/import-task-templates.json @@ -1,11 +1,11 @@ { - "importTaskTemplate": "Import Task Template", - "templateName": "Template Name", - "templateDescription": "Template Description", - "selectedTasks": "Selected Tasks", - "tasks": "Tasks", - "templates": "Templates", - "remove": "Remove", - "cancel": "Cancel", - "import": "Import" + "importTaskTemplate": "Import Task Template", + "templateName": "Template Name", + "templateDescription": "Template Description", + "selectedTasks": "Selected Tasks", + "tasks": "Tasks", + "templates": "Templates", + "remove": "Remove", + "cancel": "Cancel", + "import": "Import" } diff --git a/worklenz-frontend/public/locales/en/project-view/project-member-drawer.json b/worklenz-frontend/public/locales/en/project-view/project-member-drawer.json index 4b54e2b5..ad2d60c8 100644 --- a/worklenz-frontend/public/locales/en/project-view/project-member-drawer.json +++ b/worklenz-frontend/public/locales/en/project-view/project-member-drawer.json @@ -1,8 +1,7 @@ { - "title": "Project Members", - "searchLabel": "Add members by adding their name or email", - "searchPlaceholder": "Type name or email", - "inviteAsAMember": "Invite as a member", - "inviteNewMemberByEmail": "Invite new member by email" - -} \ No newline at end of file + "title": "Project Members", + "searchLabel": "Add members by adding their name or email", + "searchPlaceholder": "Type name or email", + "inviteAsAMember": "Invite as a member", + "inviteNewMemberByEmail": "Invite new member by email" +} diff --git a/worklenz-frontend/public/locales/en/project-view/project-view-header.json b/worklenz-frontend/public/locales/en/project-view/project-view-header.json index 9a629679..c8467288 100644 --- a/worklenz-frontend/public/locales/en/project-view/project-view-header.json +++ b/worklenz-frontend/public/locales/en/project-view/project-view-header.json @@ -1,17 +1,17 @@ { - "importTasks": "Import tasks", - "importTask": "Import task", - "createTask": "Create task", - "settings": "Settings", - "subscribe": "Subscribe", - "unsubscribe": "Unsubscribe", - "deleteProject": "Delete project", - "startDate": "Start date", - "endDate": "End date", - "projectSettings": "Project settings", - "projectSummary": "Project summary", - "receiveProjectSummary": "Receive a project summary every evening.", - "refreshProject": "Refresh project", - "saveAsTemplate": "Save as template", - "invite": "Invite" -} \ No newline at end of file + "importTasks": "Import tasks", + "importTask": "Import task", + "createTask": "Create task", + "settings": "Settings", + "subscribe": "Subscribe", + "unsubscribe": "Unsubscribe", + "deleteProject": "Delete project", + "startDate": "Start date", + "endDate": "End date", + "projectSettings": "Project settings", + "projectSummary": "Project summary", + "receiveProjectSummary": "Receive a project summary every evening.", + "refreshProject": "Refresh project", + "saveAsTemplate": "Save as template", + "invite": "Invite" +} diff --git a/worklenz-frontend/public/locales/en/settings/appearance.json b/worklenz-frontend/public/locales/en/settings/appearance.json index 76fb246f..9ce8de64 100644 --- a/worklenz-frontend/public/locales/en/settings/appearance.json +++ b/worklenz-frontend/public/locales/en/settings/appearance.json @@ -2,4 +2,4 @@ "title": "Appearance", "darkMode": "Dark Mode", "darkModeDescription": "Switch between light and dark mode to customize your viewing experience." -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/en/task-drawer/task-drawer-info-tab.json b/worklenz-frontend/public/locales/en/task-drawer/task-drawer-info-tab.json index b5caeb72..8592bd8b 100644 --- a/worklenz-frontend/public/locales/en/task-drawer/task-drawer-info-tab.json +++ b/worklenz-frontend/public/locales/en/task-drawer/task-drawer-info-tab.json @@ -27,4 +27,4 @@ "add-sub-task": "+ Add Sub Task", "refresh-sub-tasks": "Refresh Sub Tasks" } -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/en/task-drawer/task-drawer-recurring-config.json b/worklenz-frontend/public/locales/en/task-drawer/task-drawer-recurring-config.json index 10a9db71..1d22e41b 100644 --- a/worklenz-frontend/public/locales/en/task-drawer/task-drawer-recurring-config.json +++ b/worklenz-frontend/public/locales/en/task-drawer/task-drawer-recurring-config.json @@ -31,4 +31,4 @@ "intervalWeeks": "Interval (weeks)", "intervalMonths": "Interval (months)", "saveChanges": "Save Changes" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/en/task-list-table.json b/worklenz-frontend/public/locales/en/task-list-table.json index e05b7790..45ba73f5 100644 --- a/worklenz-frontend/public/locales/en/task-list-table.json +++ b/worklenz-frontend/public/locales/en/task-list-table.json @@ -47,7 +47,7 @@ "searchInputPlaceholder": "Search or create", "assigneeSelectorInviteButton": "Invite a new member by email", "labelInputPlaceholder": "Search or create", - + "pendingInvitation": "Pending Invitation", "contextMenu": { diff --git a/worklenz-frontend/public/locales/en/task-management.json b/worklenz-frontend/public/locales/en/task-management.json index 27df7a05..a9e6814e 100644 --- a/worklenz-frontend/public/locales/en/task-management.json +++ b/worklenz-frontend/public/locales/en/task-management.json @@ -12,4 +12,4 @@ "enterSubtaskName": "Enter subtask name...", "add": "Add", "cancel": "Cancel" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/en/tasks/task-table-bulk-actions.json b/worklenz-frontend/public/locales/en/tasks/task-table-bulk-actions.json index 99eb3178..42fcc024 100644 --- a/worklenz-frontend/public/locales/en/tasks/task-table-bulk-actions.json +++ b/worklenz-frontend/public/locales/en/tasks/task-table-bulk-actions.json @@ -1,41 +1,41 @@ { - "taskSelected": "task selected", - "tasksSelected": "tasks selected", - "changeStatus": "Change Status/ Prioriy/ Phases", - "changeLabel": "Change Label", - "assignToMe": "Assign to me", - "changeAssignees": "Change Assignees", - "archive": "Archive", - "unarchive": "Unarchive", - "delete": "Delete", - "moreOptions": "More options", - "deselectAll": "Deselect all", - "status": "Status", - "priority": "Priority", - "phase": "Phase", - "member": "Member", - "createTaskTemplate": "Create Task Template", - "apply": "Apply", - "createLabel": "+ Create Label", - "searchOrCreateLabel": "Search or create label...", - "hitEnterToCreate": "Press Enter to create", - "labelExists": "Label already exists", - "pendingInvitation": "Pending Invitation", - "noMatchingLabels": "No matching labels", - "noLabels": "No labels", - "CHANGE_STATUS": "Change Status", - "CHANGE_PRIORITY": "Change Priority", - "CHANGE_PHASE": "Change Phase", - "ADD_LABELS": "Add Labels", - "ASSIGN_TO_ME": "Assign to Me", - "ASSIGN_MEMBERS": "Assign Members", - "ARCHIVE": "Archive", - "DELETE": "Delete", - "CANCEL": "Cancel", - "CLEAR_SELECTION": "Clear Selection", - "TASKS_SELECTED": "{{count}} task selected", - "TASKS_SELECTED_plural": "{{count}} tasks selected", - "DELETE_TASKS_CONFIRM": "Delete {{count}} task?", - "DELETE_TASKS_CONFIRM_plural": "Delete {{count}} tasks?", - "DELETE_TASKS_WARNING": "This action cannot be undone." -} \ No newline at end of file + "taskSelected": "task selected", + "tasksSelected": "tasks selected", + "changeStatus": "Change Status/ Prioriy/ Phases", + "changeLabel": "Change Label", + "assignToMe": "Assign to me", + "changeAssignees": "Change Assignees", + "archive": "Archive", + "unarchive": "Unarchive", + "delete": "Delete", + "moreOptions": "More options", + "deselectAll": "Deselect all", + "status": "Status", + "priority": "Priority", + "phase": "Phase", + "member": "Member", + "createTaskTemplate": "Create Task Template", + "apply": "Apply", + "createLabel": "+ Create Label", + "searchOrCreateLabel": "Search or create label...", + "hitEnterToCreate": "Press Enter to create", + "labelExists": "Label already exists", + "pendingInvitation": "Pending Invitation", + "noMatchingLabels": "No matching labels", + "noLabels": "No labels", + "CHANGE_STATUS": "Change Status", + "CHANGE_PRIORITY": "Change Priority", + "CHANGE_PHASE": "Change Phase", + "ADD_LABELS": "Add Labels", + "ASSIGN_TO_ME": "Assign to Me", + "ASSIGN_MEMBERS": "Assign Members", + "ARCHIVE": "Archive", + "DELETE": "Delete", + "CANCEL": "Cancel", + "CLEAR_SELECTION": "Clear Selection", + "TASKS_SELECTED": "{{count}} task selected", + "TASKS_SELECTED_plural": "{{count}} tasks selected", + "DELETE_TASKS_CONFIRM": "Delete {{count}} task?", + "DELETE_TASKS_CONFIRM_plural": "Delete {{count}} tasks?", + "DELETE_TASKS_WARNING": "This action cannot be undone." +} diff --git a/worklenz-frontend/public/locales/en/unauthorized.json b/worklenz-frontend/public/locales/en/unauthorized.json index ad92a7c5..5233250a 100644 --- a/worklenz-frontend/public/locales/en/unauthorized.json +++ b/worklenz-frontend/public/locales/en/unauthorized.json @@ -1,5 +1,5 @@ { - "title": "Unauthorized!", - "subtitle": "You are not authorized to access this page", - "button": "Go to Home" -} \ No newline at end of file + "title": "Unauthorized!", + "subtitle": "You are not authorized to access this page", + "button": "Go to Home" +} diff --git a/worklenz-frontend/public/locales/es/admin-center/current-bill.json b/worklenz-frontend/public/locales/es/admin-center/current-bill.json index 5af54652..52a4bdbb 100644 --- a/worklenz-frontend/public/locales/es/admin-center/current-bill.json +++ b/worklenz-frontend/public/locales/es/admin-center/current-bill.json @@ -24,7 +24,7 @@ "paymentMethod": "Método de Pago", "status": "Estado", "ltdUsers": "Puedes agregar hasta {{ltd_users}} usuarios.", - + "drawerTitle": "Canjear Código", "label": "Canjear Código", "drawerPlaceholder": "Ingrese su código de canje", @@ -98,7 +98,7 @@ "perMonthPerUser": "por usuario / mes", "viewInvoice": "Ver Factura", "switchToFreePlan": "Cambiar a Plan Gratuito", - + "expirestoday": "hoy", "expirestomorrow": "mañana", "expiredDaysAgo": "hace {{days}} días", diff --git a/worklenz-frontend/public/locales/es/kanban-board.json b/worklenz-frontend/public/locales/es/kanban-board.json index 71de992c..c48b6de7 100644 --- a/worklenz-frontend/public/locales/es/kanban-board.json +++ b/worklenz-frontend/public/locales/es/kanban-board.json @@ -20,4 +20,4 @@ "newTaskNamePlaceholder": "Escribe un nombre de tarea", "newSubtaskNamePlaceholder": "Escribe un nombre de subtarea" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/es/phases-drawer.json b/worklenz-frontend/public/locales/es/phases-drawer.json index 0363c69c..6339389a 100644 --- a/worklenz-frontend/public/locales/es/phases-drawer.json +++ b/worklenz-frontend/public/locales/es/phases-drawer.json @@ -1,7 +1,7 @@ { - "configurePhases": "Configurar fases", - "phaseLabel": "Etiqueta de fase", - "enterPhaseName": "Ingrese un nombre para la etiqueta de fase", - "addOption": "Agregar opción", - "phaseOptions": "Opciones de fase:" -} \ No newline at end of file + "configurePhases": "Configurar fases", + "phaseLabel": "Etiqueta de fase", + "enterPhaseName": "Ingrese un nombre para la etiqueta de fase", + "addOption": "Agregar opción", + "phaseOptions": "Opciones de fase:" +} diff --git a/worklenz-frontend/public/locales/es/project-view/import-task-templates.json b/worklenz-frontend/public/locales/es/project-view/import-task-templates.json index c47edc71..7be1539b 100644 --- a/worklenz-frontend/public/locales/es/project-view/import-task-templates.json +++ b/worklenz-frontend/public/locales/es/project-view/import-task-templates.json @@ -1,11 +1,11 @@ { - "importTaskTemplate": "Importar plantilla de tarea", - "templateName": "Nombre de la plantilla", - "templateDescription": "Descripción de la plantilla", - "selectedTasks": "Tareas seleccionadas", - "tasks": "Tareas", - "templates": "Plantillas", - "remove": "Eliminar", - "cancel": "Cancelar", - "import": "Importar" -} \ No newline at end of file + "importTaskTemplate": "Importar plantilla de tarea", + "templateName": "Nombre de la plantilla", + "templateDescription": "Descripción de la plantilla", + "selectedTasks": "Tareas seleccionadas", + "tasks": "Tareas", + "templates": "Plantillas", + "remove": "Eliminar", + "cancel": "Cancelar", + "import": "Importar" +} diff --git a/worklenz-frontend/public/locales/es/project-view/project-member-drawer.json b/worklenz-frontend/public/locales/es/project-view/project-member-drawer.json index 1a90bbd6..ab7570fd 100644 --- a/worklenz-frontend/public/locales/es/project-view/project-member-drawer.json +++ b/worklenz-frontend/public/locales/es/project-view/project-member-drawer.json @@ -1,8 +1,7 @@ { - "title": "Miembros del Proyecto", - "searchLabel": "Agregar miembros ingresando su nombre o correo electrónico", - "searchPlaceholder": "Escriba nombre o correo electrónico", - "inviteAsAMember": "Invitar como miembro", - "inviteNewMemberByEmail": "Invitar nuevo miembro por correo electrónico" - -} \ No newline at end of file + "title": "Miembros del Proyecto", + "searchLabel": "Agregar miembros ingresando su nombre o correo electrónico", + "searchPlaceholder": "Escriba nombre o correo electrónico", + "inviteAsAMember": "Invitar como miembro", + "inviteNewMemberByEmail": "Invitar nuevo miembro por correo electrónico" +} diff --git a/worklenz-frontend/public/locales/es/project-view/project-view-header.json b/worklenz-frontend/public/locales/es/project-view/project-view-header.json index bf42008e..0d9bdf26 100644 --- a/worklenz-frontend/public/locales/es/project-view/project-view-header.json +++ b/worklenz-frontend/public/locales/es/project-view/project-view-header.json @@ -1,17 +1,17 @@ { - "importTasks": "Importar tareas", - "importTask": "Importar tarea", - "createTask": "Crear tarea", - "settings": "Ajustes", - "subscribe": "Suscribirse", - "unsubscribe": "Cancelar suscripción", - "deleteProject": "Eliminar proyecto", - "startDate": "Fecha de inicio", - "endDate": "Fecha de finalización", - "projectSettings": "Ajustes del proyecto", - "projectSummary": "Resumen del proyecto", - "receiveProjectSummary": "Recibir un resumen del proyecto todas las noches.", - "refreshProject": "Actualizar proyecto", - "saveAsTemplate": "Guardar como plantilla", - "invite": "Invitar" -} \ No newline at end of file + "importTasks": "Importar tareas", + "importTask": "Importar tarea", + "createTask": "Crear tarea", + "settings": "Ajustes", + "subscribe": "Suscribirse", + "unsubscribe": "Cancelar suscripción", + "deleteProject": "Eliminar proyecto", + "startDate": "Fecha de inicio", + "endDate": "Fecha de finalización", + "projectSettings": "Ajustes del proyecto", + "projectSummary": "Resumen del proyecto", + "receiveProjectSummary": "Recibir un resumen del proyecto todas las noches.", + "refreshProject": "Actualizar proyecto", + "saveAsTemplate": "Guardar como plantilla", + "invite": "Invitar" +} diff --git a/worklenz-frontend/public/locales/es/project-view/save-as-template.json b/worklenz-frontend/public/locales/es/project-view/save-as-template.json index 6ad67182..4d7e9354 100644 --- a/worklenz-frontend/public/locales/es/project-view/save-as-template.json +++ b/worklenz-frontend/public/locales/es/project-view/save-as-template.json @@ -10,7 +10,7 @@ "taskIncludes": "¿Qué se debe incluir en la plantilla de las tareas?", "taskIncludesOptions": { "statuses": "Estados", - "phases": "Fases", + "phases": "Fases", "labels": "Etiquetas", "name": "Nombre", "priority": "Prioridad", diff --git a/worklenz-frontend/public/locales/es/settings/appearance.json b/worklenz-frontend/public/locales/es/settings/appearance.json index a4c168a4..d6b196da 100644 --- a/worklenz-frontend/public/locales/es/settings/appearance.json +++ b/worklenz-frontend/public/locales/es/settings/appearance.json @@ -2,4 +2,4 @@ "title": "Apariencia", "darkMode": "Modo Oscuro", "darkModeDescription": "Cambia entre el modo claro y oscuro para personalizar tu experiencia visual." -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/es/task-drawer/task-drawer-info-tab.json b/worklenz-frontend/public/locales/es/task-drawer/task-drawer-info-tab.json index cdafd81c..02b3038a 100644 --- a/worklenz-frontend/public/locales/es/task-drawer/task-drawer-info-tab.json +++ b/worklenz-frontend/public/locales/es/task-drawer/task-drawer-info-tab.json @@ -27,4 +27,4 @@ "add-sub-task": "+ Añadir subtarea", "refresh-sub-tasks": "Actualizar subtareas" } -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/es/task-drawer/task-drawer-recurring-config.json b/worklenz-frontend/public/locales/es/task-drawer/task-drawer-recurring-config.json index ecc48c5f..c1ef9e83 100644 --- a/worklenz-frontend/public/locales/es/task-drawer/task-drawer-recurring-config.json +++ b/worklenz-frontend/public/locales/es/task-drawer/task-drawer-recurring-config.json @@ -31,4 +31,4 @@ "intervalWeeks": "Intervalo (semanas)", "intervalMonths": "Intervalo (meses)", "saveChanges": "Guardar cambios" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/es/task-drawer/task-drawer.json b/worklenz-frontend/public/locales/es/task-drawer/task-drawer.json index c3980da8..e1462fbe 100644 --- a/worklenz-frontend/public/locales/es/task-drawer/task-drawer.json +++ b/worklenz-frontend/public/locales/es/task-drawer/task-drawer.json @@ -90,4 +90,4 @@ "cancelMarkAsDone": "No, mantener estado actual", "markAsDoneDescription": "Has establecido el progreso al 100%. ¿Quieres actualizar el estado de la tarea a \"Completada\"?" } -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/es/task-list-table.json b/worklenz-frontend/public/locales/es/task-list-table.json index 659cb8c1..f6ae2339 100644 --- a/worklenz-frontend/public/locales/es/task-list-table.json +++ b/worklenz-frontend/public/locales/es/task-list-table.json @@ -47,7 +47,7 @@ "searchInputPlaceholder": "Buscar o crear", "assigneeSelectorInviteButton": "Invitar a un nuevo miembro por correo", "labelInputPlaceholder": "Buscar o crear", - + "pendingInvitation": "Invitación pendiente", "contextMenu": { diff --git a/worklenz-frontend/public/locales/es/task-management.json b/worklenz-frontend/public/locales/es/task-management.json index 4b916d5b..a1266394 100644 --- a/worklenz-frontend/public/locales/es/task-management.json +++ b/worklenz-frontend/public/locales/es/task-management.json @@ -12,4 +12,4 @@ "enterSubtaskName": "Ingresa el nombre de la subtarea...", "add": "Añadir", "cancel": "Cancelar" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/es/tasks/task-table-bulk-actions.json b/worklenz-frontend/public/locales/es/tasks/task-table-bulk-actions.json index 5ba35bdf..0f98b1a5 100644 --- a/worklenz-frontend/public/locales/es/tasks/task-table-bulk-actions.json +++ b/worklenz-frontend/public/locales/es/tasks/task-table-bulk-actions.json @@ -1,41 +1,41 @@ { - "taskSelected": "Tarea seleccionada", - "tasksSelected": "Tareas seleccionadas", - "changeStatus": "Cambiar estado/ prioridad/ fases", - "changeLabel": "Cambiar etiqueta", - "assignToMe": "Asignar a mí", - "changeAssignees": "Cambiar asignados", - "archive": "Archivar", - "unarchive": "Desarchivar", - "delete": "Eliminar", - "moreOptions": "Más opciones", - "deselectAll": "Deseleccionar todo", - "status": "Estado", - "priority": "Prioridad", - "phase": "Fase", - "member": "Miembro", - "createTaskTemplate": "Crear plantilla de tarea", - "apply": "Aplicar", - "createLabel": "+ Crear etiqueta", - "searchOrCreateLabel": "Buscar o crear etiqueta...", - "hitEnterToCreate": "Presione Enter para crear", - "labelExists": "La etiqueta ya existe", - "pendingInvitation": "Invitación Pendiente", - "noMatchingLabels": "No hay etiquetas coincidentes", - "noLabels": "Sin etiquetas", - "CHANGE_STATUS": "Cambiar Estado", - "CHANGE_PRIORITY": "Cambiar Prioridad", - "CHANGE_PHASE": "Cambiar Fase", - "ADD_LABELS": "Agregar Etiquetas", - "ASSIGN_TO_ME": "Asignar a Mí", - "ASSIGN_MEMBERS": "Asignar Miembros", - "ARCHIVE": "Archivar", - "DELETE": "Eliminar", - "CANCEL": "Cancelar", - "CLEAR_SELECTION": "Limpiar Selección", - "TASKS_SELECTED": "{{count}} tarea seleccionada", - "TASKS_SELECTED_plural": "{{count}} tareas seleccionadas", - "DELETE_TASKS_CONFIRM": "¿Eliminar {{count}} tarea?", - "DELETE_TASKS_CONFIRM_plural": "¿Eliminar {{count}} tareas?", - "DELETE_TASKS_WARNING": "Esta acción no se puede deshacer." -} \ No newline at end of file + "taskSelected": "Tarea seleccionada", + "tasksSelected": "Tareas seleccionadas", + "changeStatus": "Cambiar estado/ prioridad/ fases", + "changeLabel": "Cambiar etiqueta", + "assignToMe": "Asignar a mí", + "changeAssignees": "Cambiar asignados", + "archive": "Archivar", + "unarchive": "Desarchivar", + "delete": "Eliminar", + "moreOptions": "Más opciones", + "deselectAll": "Deseleccionar todo", + "status": "Estado", + "priority": "Prioridad", + "phase": "Fase", + "member": "Miembro", + "createTaskTemplate": "Crear plantilla de tarea", + "apply": "Aplicar", + "createLabel": "+ Crear etiqueta", + "searchOrCreateLabel": "Buscar o crear etiqueta...", + "hitEnterToCreate": "Presione Enter para crear", + "labelExists": "La etiqueta ya existe", + "pendingInvitation": "Invitación Pendiente", + "noMatchingLabels": "No hay etiquetas coincidentes", + "noLabels": "Sin etiquetas", + "CHANGE_STATUS": "Cambiar Estado", + "CHANGE_PRIORITY": "Cambiar Prioridad", + "CHANGE_PHASE": "Cambiar Fase", + "ADD_LABELS": "Agregar Etiquetas", + "ASSIGN_TO_ME": "Asignar a Mí", + "ASSIGN_MEMBERS": "Asignar Miembros", + "ARCHIVE": "Archivar", + "DELETE": "Eliminar", + "CANCEL": "Cancelar", + "CLEAR_SELECTION": "Limpiar Selección", + "TASKS_SELECTED": "{{count}} tarea seleccionada", + "TASKS_SELECTED_plural": "{{count}} tareas seleccionadas", + "DELETE_TASKS_CONFIRM": "¿Eliminar {{count}} tarea?", + "DELETE_TASKS_CONFIRM_plural": "¿Eliminar {{count}} tareas?", + "DELETE_TASKS_WARNING": "Esta acción no se puede deshacer." +} diff --git a/worklenz-frontend/public/locales/es/unauthorized.json b/worklenz-frontend/public/locales/es/unauthorized.json index 586fed6b..e28ce8f4 100644 --- a/worklenz-frontend/public/locales/es/unauthorized.json +++ b/worklenz-frontend/public/locales/es/unauthorized.json @@ -1,5 +1,5 @@ { - "title": "¡No autorizado!", - "subtitle": "No tienes permisos para acceder a esta página", - "button": "Ir a Inicio" -} \ No newline at end of file + "title": "¡No autorizado!", + "subtitle": "No tienes permisos para acceder a esta página", + "button": "Ir a Inicio" +} diff --git a/worklenz-frontend/public/locales/pt/admin-center/current-bill.json b/worklenz-frontend/public/locales/pt/admin-center/current-bill.json index 063fc9c8..2e4b41d7 100644 --- a/worklenz-frontend/public/locales/pt/admin-center/current-bill.json +++ b/worklenz-frontend/public/locales/pt/admin-center/current-bill.json @@ -98,7 +98,7 @@ "perMonthPerUser": "por usuário / mês", "viewInvoice": "Ver Fatura", "switchToFreePlan": "Mudar para Plano Gratuito", - + "expirestoday": "hoje", "expirestomorrow": "amanhã", "expiredDaysAgo": "há {{days}} dias", diff --git a/worklenz-frontend/public/locales/pt/kanban-board.json b/worklenz-frontend/public/locales/pt/kanban-board.json index 0cd9e27b..806e7ae6 100644 --- a/worklenz-frontend/public/locales/pt/kanban-board.json +++ b/worklenz-frontend/public/locales/pt/kanban-board.json @@ -20,4 +20,4 @@ "newTaskNamePlaceholder": "Escreva um nome de tarefa", "newSubtaskNamePlaceholder": "Escreva um nome de subtarefa" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/phases-drawer.json b/worklenz-frontend/public/locales/pt/phases-drawer.json index 0363c69c..6339389a 100644 --- a/worklenz-frontend/public/locales/pt/phases-drawer.json +++ b/worklenz-frontend/public/locales/pt/phases-drawer.json @@ -1,7 +1,7 @@ { - "configurePhases": "Configurar fases", - "phaseLabel": "Etiqueta de fase", - "enterPhaseName": "Ingrese un nombre para la etiqueta de fase", - "addOption": "Agregar opción", - "phaseOptions": "Opciones de fase:" -} \ No newline at end of file + "configurePhases": "Configurar fases", + "phaseLabel": "Etiqueta de fase", + "enterPhaseName": "Ingrese un nombre para la etiqueta de fase", + "addOption": "Agregar opción", + "phaseOptions": "Opciones de fase:" +} diff --git a/worklenz-frontend/public/locales/pt/project-view/import-task-templates.json b/worklenz-frontend/public/locales/pt/project-view/import-task-templates.json index 82b3cabb..81a64607 100644 --- a/worklenz-frontend/public/locales/pt/project-view/import-task-templates.json +++ b/worklenz-frontend/public/locales/pt/project-view/import-task-templates.json @@ -1,11 +1,11 @@ { - "importTaskTemplate": "Importar modelo de tarefa", - "templateName": "Nome do modelo", - "templateDescription": "Descrição do modelo", - "selectedTasks": "Tarefas selecionadas", - "tasks": "Tarefas", - "templates": "Modelos", - "remove": "Remover", - "cancel": "Cancelar", - "import": "Importar" -} \ No newline at end of file + "importTaskTemplate": "Importar modelo de tarefa", + "templateName": "Nome do modelo", + "templateDescription": "Descrição do modelo", + "selectedTasks": "Tarefas selecionadas", + "tasks": "Tarefas", + "templates": "Modelos", + "remove": "Remover", + "cancel": "Cancelar", + "import": "Importar" +} diff --git a/worklenz-frontend/public/locales/pt/project-view/project-member-drawer.json b/worklenz-frontend/public/locales/pt/project-view/project-member-drawer.json index b4c402e4..0afe3d87 100644 --- a/worklenz-frontend/public/locales/pt/project-view/project-member-drawer.json +++ b/worklenz-frontend/public/locales/pt/project-view/project-member-drawer.json @@ -1,8 +1,7 @@ { - "title": "Membros do Projeto", - "searchLabel": "Adicionar membros inserindo nome ou e-mail", - "searchPlaceholder": "Digite nome ou e-mail", - "inviteAsAMember": "Convidar como membro", - "inviteNewMemberByEmail": "Convidar novo membro por e-mail" - -} \ No newline at end of file + "title": "Membros do Projeto", + "searchLabel": "Adicionar membros inserindo nome ou e-mail", + "searchPlaceholder": "Digite nome ou e-mail", + "inviteAsAMember": "Convidar como membro", + "inviteNewMemberByEmail": "Convidar novo membro por e-mail" +} diff --git a/worklenz-frontend/public/locales/pt/project-view/project-view-header.json b/worklenz-frontend/public/locales/pt/project-view/project-view-header.json index 4e27c8a1..e776c67d 100644 --- a/worklenz-frontend/public/locales/pt/project-view/project-view-header.json +++ b/worklenz-frontend/public/locales/pt/project-view/project-view-header.json @@ -1,17 +1,17 @@ { - "importTasks": "Importar tarefas", - "importTask": "Importar tarefa", - "createTask": "Criar tarefa", - "settings": "Configurações", - "subscribe": "Inscrever-se", - "unsubscribe": "Cancelar inscrição", - "deleteProject": "Excluir projeto", - "startDate": "Data de início", - "endDate": "Data de fim", - "projectSettings": "Configurações do projeto", - "projectSummary": "Resumo do projeto", - "receiveProjectSummary": "Receber um resumo do projeto todas as noites.", - "refreshProject": "Atualizar projeto", - "saveAsTemplate": "Salvar como modelo", - "invite": "Convidar" -} \ No newline at end of file + "importTasks": "Importar tarefas", + "importTask": "Importar tarefa", + "createTask": "Criar tarefa", + "settings": "Configurações", + "subscribe": "Inscrever-se", + "unsubscribe": "Cancelar inscrição", + "deleteProject": "Excluir projeto", + "startDate": "Data de início", + "endDate": "Data de fim", + "projectSettings": "Configurações do projeto", + "projectSummary": "Resumo do projeto", + "receiveProjectSummary": "Receber um resumo do projeto todas as noites.", + "refreshProject": "Atualizar projeto", + "saveAsTemplate": "Salvar como modelo", + "invite": "Convidar" +} diff --git a/worklenz-frontend/public/locales/pt/project-view/save-as-template.json b/worklenz-frontend/public/locales/pt/project-view/save-as-template.json index 70629b2f..c67eb20e 100644 --- a/worklenz-frontend/public/locales/pt/project-view/save-as-template.json +++ b/worklenz-frontend/public/locales/pt/project-view/save-as-template.json @@ -11,7 +11,7 @@ "taskIncludesOptions": { "statuses": "Status", "phases": "Fases", - "labels": "Etiquetas", + "labels": "Etiquetas", "name": "Nome", "priority": "Prioridade", "status": "Status", diff --git a/worklenz-frontend/public/locales/pt/settings/appearance.json b/worklenz-frontend/public/locales/pt/settings/appearance.json index eaffbb32..13e5a1e6 100644 --- a/worklenz-frontend/public/locales/pt/settings/appearance.json +++ b/worklenz-frontend/public/locales/pt/settings/appearance.json @@ -2,4 +2,4 @@ "title": "Aparência", "darkMode": "Modo Escuro", "darkModeDescription": "Alterne entre o modo claro e escuro para personalizar sua experiência de visualização." -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/settings/categories.json b/worklenz-frontend/public/locales/pt/settings/categories.json index 2d4534c1..9972d2a9 100644 --- a/worklenz-frontend/public/locales/pt/settings/categories.json +++ b/worklenz-frontend/public/locales/pt/settings/categories.json @@ -7,4 +7,4 @@ "searchPlaceholder": "Pesquisar por nome", "emptyText": "As categorias podem ser criadas ao atualizar ou criar projetos.", "colorChangeTooltip": "Clique para mudar a cor" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/settings/clients.json b/worklenz-frontend/public/locales/pt/settings/clients.json index 4f990a6e..932a7f5e 100644 --- a/worklenz-frontend/public/locales/pt/settings/clients.json +++ b/worklenz-frontend/public/locales/pt/settings/clients.json @@ -19,4 +19,4 @@ "createClientErrorMessage": "Criar cliente falhou!", "updateClientSuccessMessage": "Atualizar cliente sucesso!", "updateClientErrorMessage": "Atualizar cliente falhou!" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/settings/job-titles.json b/worklenz-frontend/public/locales/pt/settings/job-titles.json index 9f641ba0..379ddc03 100644 --- a/worklenz-frontend/public/locales/pt/settings/job-titles.json +++ b/worklenz-frontend/public/locales/pt/settings/job-titles.json @@ -17,4 +17,4 @@ "createJobTitleErrorMessage": "Falha ao criar título de emprego!", "updateJobTitleSuccessMessage": "Atualizar título de emprego com sucesso!", "updateJobTitleErrorMessage": "Falha ao atualizar título de emprego!" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/settings/labels.json b/worklenz-frontend/public/locales/pt/settings/labels.json index 90c5450f..737dccef 100644 --- a/worklenz-frontend/public/locales/pt/settings/labels.json +++ b/worklenz-frontend/public/locales/pt/settings/labels.json @@ -8,4 +8,4 @@ "emptyText": "Os rótulos podem ser criados ao atualizar ou criar tarefas.", "pinTooltip": "Clique para fixar isso no menu principal", "colorChangeTooltip": "Clique para mudar a cor" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/settings/language.json b/worklenz-frontend/public/locales/pt/settings/language.json index 44a2cacc..f4494ff3 100644 --- a/worklenz-frontend/public/locales/pt/settings/language.json +++ b/worklenz-frontend/public/locales/pt/settings/language.json @@ -4,4 +4,4 @@ "time_zone": "Fuso horário", "time_zone_required": "O fuso horário é obrigatório", "save_changes": "Salvar alterações" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/settings/notifications.json b/worklenz-frontend/public/locales/pt/settings/notifications.json index ca402552..5a61cdf0 100644 --- a/worklenz-frontend/public/locales/pt/settings/notifications.json +++ b/worklenz-frontend/public/locales/pt/settings/notifications.json @@ -7,4 +7,4 @@ "popupDescription": "As notificações pop-up podem ser desativadas pelo seu navegador. Altere as configurações do seu navegador para permiti-las.", "unreadItemsTitle": "Mostrar o número de itens não lidos", "unreadItemsDescription": "Você verá contagens para cada notificação." -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/settings/profile.json b/worklenz-frontend/public/locales/pt/settings/profile.json index fd3c3e2c..61e94e8b 100644 --- a/worklenz-frontend/public/locales/pt/settings/profile.json +++ b/worklenz-frontend/public/locales/pt/settings/profile.json @@ -10,4 +10,4 @@ "profileJoinedText": "Entrou há um mês", "profileLastUpdatedText": "Última atualização há um mês", "avatarTooltip": "Clique para carregar um avatar" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/settings/project-templates.json b/worklenz-frontend/public/locales/pt/settings/project-templates.json index a4a28eef..55546630 100644 --- a/worklenz-frontend/public/locales/pt/settings/project-templates.json +++ b/worklenz-frontend/public/locales/pt/settings/project-templates.json @@ -5,4 +5,4 @@ "confirmText": "Tem a certeza?", "okText": "Sim", "cancelText": "Cancelar" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/settings/sidebar.json b/worklenz-frontend/public/locales/pt/settings/sidebar.json index 67fac9dc..0cb663f1 100644 --- a/worklenz-frontend/public/locales/pt/settings/sidebar.json +++ b/worklenz-frontend/public/locales/pt/settings/sidebar.json @@ -12,4 +12,4 @@ "change-password": "Alterar Senha", "language-and-region": "Idioma e Região", "appearance": "Aparência" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/settings/task-templates.json b/worklenz-frontend/public/locales/pt/settings/task-templates.json index 0fa425a8..fb501000 100644 --- a/worklenz-frontend/public/locales/pt/settings/task-templates.json +++ b/worklenz-frontend/public/locales/pt/settings/task-templates.json @@ -6,4 +6,4 @@ "confirmText": "Tem a certeza?", "okText": "Sim", "cancelText": "Cancelar" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/settings/team-members.json b/worklenz-frontend/public/locales/pt/settings/team-members.json index b9ff5696..9c6d80b6 100644 --- a/worklenz-frontend/public/locales/pt/settings/team-members.json +++ b/worklenz-frontend/public/locales/pt/settings/team-members.json @@ -41,4 +41,4 @@ "addedText": "Adicionado", "updatedText": "Atualizado", "noResultFound": "Digite um endereço de email e pressione enter..." -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/task-drawer/task-drawer-info-tab.json b/worklenz-frontend/public/locales/pt/task-drawer/task-drawer-info-tab.json index fde2215a..cf26b1a3 100644 --- a/worklenz-frontend/public/locales/pt/task-drawer/task-drawer-info-tab.json +++ b/worklenz-frontend/public/locales/pt/task-drawer/task-drawer-info-tab.json @@ -27,4 +27,4 @@ "add-sub-task": "+ Adicionar subtarefa", "refresh-sub-tasks": "Atualizar subtarefas" } -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/task-drawer/task-drawer-recurring-config.json b/worklenz-frontend/public/locales/pt/task-drawer/task-drawer-recurring-config.json index d693f277..5592d897 100644 --- a/worklenz-frontend/public/locales/pt/task-drawer/task-drawer-recurring-config.json +++ b/worklenz-frontend/public/locales/pt/task-drawer/task-drawer-recurring-config.json @@ -31,4 +31,4 @@ "intervalWeeks": "Intervalo (semanas)", "intervalMonths": "Intervalo (meses)", "saveChanges": "Salvar alterações" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/task-drawer/task-drawer.json b/worklenz-frontend/public/locales/pt/task-drawer/task-drawer.json index 6288af92..e86db311 100644 --- a/worklenz-frontend/public/locales/pt/task-drawer/task-drawer.json +++ b/worklenz-frontend/public/locales/pt/task-drawer/task-drawer.json @@ -90,4 +90,4 @@ "cancelMarkAsDone": "Não, manter status atual", "markAsDoneDescription": "Você definiu o progresso como 100%. Deseja atualizar o status da tarefa para \"Concluída\"?" } -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/task-list-filters.json b/worklenz-frontend/public/locales/pt/task-list-filters.json index cf2cb7b0..cfbcf22e 100644 --- a/worklenz-frontend/public/locales/pt/task-list-filters.json +++ b/worklenz-frontend/public/locales/pt/task-list-filters.json @@ -34,7 +34,7 @@ "completeddateText": "Data de Conclusão", "createddateText": "Data de Criação", "lastupdatedText": "Última Atualização", - + "lowText": "Baixa", "mediumText": "Média", "highText": "Alta", diff --git a/worklenz-frontend/public/locales/pt/task-list-table.json b/worklenz-frontend/public/locales/pt/task-list-table.json index 23240945..01972b99 100644 --- a/worklenz-frontend/public/locales/pt/task-list-table.json +++ b/worklenz-frontend/public/locales/pt/task-list-table.json @@ -47,7 +47,7 @@ "searchInputPlaceholder": "Buscar ou criar", "assigneeSelectorInviteButton": "Convide um novo membro por e-mail", "labelInputPlaceholder": "Buscar ou criar", - + "pendingInvitation": "Convite Pendente", "contextMenu": { diff --git a/worklenz-frontend/public/locales/pt/task-management.json b/worklenz-frontend/public/locales/pt/task-management.json index 5f9bc0d4..dc8f86b9 100644 --- a/worklenz-frontend/public/locales/pt/task-management.json +++ b/worklenz-frontend/public/locales/pt/task-management.json @@ -12,4 +12,4 @@ "enterSubtaskName": "Digite o nome da subtarefa...", "add": "Adicionar", "cancel": "Cancelar" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/tasks/task-table-bulk-actions.json b/worklenz-frontend/public/locales/pt/tasks/task-table-bulk-actions.json index 8d03c678..f4a3a10e 100644 --- a/worklenz-frontend/public/locales/pt/tasks/task-table-bulk-actions.json +++ b/worklenz-frontend/public/locales/pt/tasks/task-table-bulk-actions.json @@ -1,41 +1,41 @@ { - "taskSelected": "Tarefa selecionada", - "tasksSelected": "Tarefas selecionadas", - "changeStatus": "Alterar Status/ Prioridade/ Fases", - "changeLabel": "Alterar Etiqueta", - "assignToMe": "Atribuir a mim", - "changeAssignees": "Alterar Assignados", - "archive": "Arquivar", - "unarchive": "Desarquivar", - "delete": "Deletar", - "moreOptions": "Mais opções", - "deselectAll": "Desmarcar todas", - "status": "Status", - "priority": "Prioridade", - "phase": "Fase", - "member": "Membro", - "createTaskTemplate": "Criar Modelo de Tarefa", - "apply": "Aplicar", - "createLabel": "+ Criar etiqueta", - "searchOrCreateLabel": "Pesquisar ou criar etiqueta...", - "hitEnterToCreate": "Pressione Enter para criar", - "labelExists": "A etiqueta já existe", - "pendingInvitation": "Convite Pendente", - "noMatchingLabels": "Nenhuma etiqueta correspondente", - "noLabels": "Sem etiquetas", - "CHANGE_STATUS": "Alterar Status", - "CHANGE_PRIORITY": "Alterar Prioridade", - "CHANGE_PHASE": "Alterar Fase", - "ADD_LABELS": "Adicionar Etiquetas", - "ASSIGN_TO_ME": "Atribuir a Mim", - "ASSIGN_MEMBERS": "Atribuir Membros", - "ARCHIVE": "Arquivar", - "DELETE": "Deletar", - "CANCEL": "Cancelar", - "CLEAR_SELECTION": "Limpar Seleção", - "TASKS_SELECTED": "{{count}} tarefa selecionada", - "TASKS_SELECTED_plural": "{{count}} tarefas selecionadas", - "DELETE_TASKS_CONFIRM": "Deletar {{count}} tarefa?", - "DELETE_TASKS_CONFIRM_plural": "Deletar {{count}} tarefas?", - "DELETE_TASKS_WARNING": "Esta ação não pode ser desfeita." -} \ No newline at end of file + "taskSelected": "Tarefa selecionada", + "tasksSelected": "Tarefas selecionadas", + "changeStatus": "Alterar Status/ Prioridade/ Fases", + "changeLabel": "Alterar Etiqueta", + "assignToMe": "Atribuir a mim", + "changeAssignees": "Alterar Assignados", + "archive": "Arquivar", + "unarchive": "Desarquivar", + "delete": "Deletar", + "moreOptions": "Mais opções", + "deselectAll": "Desmarcar todas", + "status": "Status", + "priority": "Prioridade", + "phase": "Fase", + "member": "Membro", + "createTaskTemplate": "Criar Modelo de Tarefa", + "apply": "Aplicar", + "createLabel": "+ Criar etiqueta", + "searchOrCreateLabel": "Pesquisar ou criar etiqueta...", + "hitEnterToCreate": "Pressione Enter para criar", + "labelExists": "A etiqueta já existe", + "pendingInvitation": "Convite Pendente", + "noMatchingLabels": "Nenhuma etiqueta correspondente", + "noLabels": "Sem etiquetas", + "CHANGE_STATUS": "Alterar Status", + "CHANGE_PRIORITY": "Alterar Prioridade", + "CHANGE_PHASE": "Alterar Fase", + "ADD_LABELS": "Adicionar Etiquetas", + "ASSIGN_TO_ME": "Atribuir a Mim", + "ASSIGN_MEMBERS": "Atribuir Membros", + "ARCHIVE": "Arquivar", + "DELETE": "Deletar", + "CANCEL": "Cancelar", + "CLEAR_SELECTION": "Limpar Seleção", + "TASKS_SELECTED": "{{count}} tarefa selecionada", + "TASKS_SELECTED_plural": "{{count}} tarefas selecionadas", + "DELETE_TASKS_CONFIRM": "Deletar {{count}} tarefa?", + "DELETE_TASKS_CONFIRM_plural": "Deletar {{count}} tarefas?", + "DELETE_TASKS_WARNING": "Esta ação não pode ser desfeita." +} diff --git a/worklenz-frontend/public/locales/pt/unauthorized.json b/worklenz-frontend/public/locales/pt/unauthorized.json index fa542df0..e67e0ffd 100644 --- a/worklenz-frontend/public/locales/pt/unauthorized.json +++ b/worklenz-frontend/public/locales/pt/unauthorized.json @@ -1,5 +1,5 @@ { - "title": "¡Não autorizado!", - "subtitle": "Você não tem permissão para acessar esta página", - "button": "Ir para Início" -} \ No newline at end of file + "title": "¡Não autorizado!", + "subtitle": "Você não tem permissão para acessar esta página", + "button": "Ir para Início" +} diff --git a/worklenz-frontend/public/unregister-sw.js b/worklenz-frontend/public/unregister-sw.js index 02c9bc86..4fbd8774 100644 --- a/worklenz-frontend/public/unregister-sw.js +++ b/worklenz-frontend/public/unregister-sw.js @@ -1,9 +1,9 @@ if ('serviceWorker' in navigator) { // Check if we've already attempted to unregister in this session if (!sessionStorage.getItem('swUnregisterAttempted')) { - navigator.serviceWorker.getRegistrations().then(function(registrations) { + navigator.serviceWorker.getRegistrations().then(function (registrations) { const ngswWorker = registrations.find(reg => reg.active?.scriptURL.includes('ngsw-worker')); - + if (ngswWorker) { // Mark that we've attempted to unregister sessionStorage.setItem('swUnregisterAttempted', 'true'); @@ -14,10 +14,10 @@ if ('serviceWorker' in navigator) { }); } else { // If no ngsw-worker is found, unregister any other service workers - for(let registration of registrations) { + for (let registration of registrations) { registration.unregister(); } } }); } -} \ No newline at end of file +} diff --git a/worklenz-frontend/scripts/copy-tinymce.js b/worklenz-frontend/scripts/copy-tinymce.js index 8f801c46..00a27dd7 100644 --- a/worklenz-frontend/scripts/copy-tinymce.js +++ b/worklenz-frontend/scripts/copy-tinymce.js @@ -16,7 +16,7 @@ copyFolderRecursiveSync(sourceDir, path.join(__dirname, '..', 'public')); function copyFolderRecursiveSync(source, target) { const targetFolder = path.join(target, path.basename(source)); - + // Create target folder if it doesn't exist if (!fs.existsSync(targetFolder)) { fs.mkdirSync(targetFolder); @@ -25,7 +25,7 @@ function copyFolderRecursiveSync(source, target) { // Copy files if (fs.lstatSync(source).isDirectory()) { const files = fs.readdirSync(source); - files.forEach(function(file) { + files.forEach(function (file) { const curSource = path.join(source, file); if (fs.lstatSync(curSource).isDirectory()) { copyFolderRecursiveSync(curSource, targetFolder); @@ -36,4 +36,4 @@ function copyFolderRecursiveSync(source, target) { } } -console.log('TinyMCE files copied successfully!'); \ No newline at end of file +console.log('TinyMCE files copied successfully!'); diff --git a/worklenz-frontend/src/App.tsx b/worklenz-frontend/src/App.tsx index 404cddd1..37a581b6 100644 --- a/worklenz-frontend/src/App.tsx +++ b/worklenz-frontend/src/App.tsx @@ -22,7 +22,7 @@ import { SuspenseFallback } from './components/suspense-fallback/suspense-fallba /** * Main App Component - Performance Optimized - * + * * Performance optimizations applied: * 1. React.memo() - Prevents unnecessary re-renders * 2. useMemo() - Memoizes expensive computations @@ -37,7 +37,7 @@ const App: React.FC = memo(() => { // Memoize mixpanel initialization to prevent re-initialization const mixpanelToken = useMemo(() => import.meta.env.VITE_MIXPANEL_TOKEN as string, []); - + useEffect(() => { initMixpanel(mixpanelToken); }, [mixpanelToken]); @@ -60,12 +60,12 @@ const App: React.FC = memo(() => { // Initialize CSRF token and translations on app startup useEffect(() => { let isMounted = true; - + const initializeApp = async () => { try { // Initialize CSRF token await initializeCsrfToken(); - + // Preload essential translations await ensureTranslationsLoaded(); } catch (error) { @@ -85,11 +85,11 @@ const App: React.FC = memo(() => { return ( }> - diff --git a/worklenz-frontend/src/api/admin-center/admin-center.api.service.ts b/worklenz-frontend/src/api/admin-center/admin-center.api.service.ts index 4d45b222..60269917 100644 --- a/worklenz-frontend/src/api/admin-center/admin-center.api.service.ts +++ b/worklenz-frontend/src/api/admin-center/admin-center.api.service.ts @@ -112,7 +112,7 @@ export const adminCenterApiService = { async updateTeam( team_id: string, - body: {name: string, teamMembers: IOrganizationUser[]} + body: { name: string; teamMembers: IOrganizationUser[] } ): Promise> { const response = await apiClient.put>( `${rootUrl}/organization/team/${team_id}`, @@ -152,7 +152,6 @@ export const adminCenterApiService = { return response.data; }, - // Billing - Configuration async getCountries(): Promise> { const response = await apiClient.get>( @@ -168,7 +167,9 @@ export const adminCenterApiService = { return response.data; }, - async updateBillingConfiguration(body: IBillingConfiguration): Promise> { + async updateBillingConfiguration( + body: IBillingConfiguration + ): Promise> { const response = await apiClient.put>( `${rootUrl}/billing/configuration`, body @@ -178,42 +179,58 @@ export const adminCenterApiService = { // Billing - Current Bill async getCharges(): Promise> { - const response = await apiClient.get>(`${rootUrl}/billing/charges`); + const response = await apiClient.get>( + `${rootUrl}/billing/charges` + ); return response.data; }, async getTransactions(): Promise> { - const response = await apiClient.get>(`${rootUrl}/billing/transactions`); + const response = await apiClient.get>( + `${rootUrl}/billing/transactions` + ); return response.data; }, async getBillingAccountInfo(): Promise> { - const response = await apiClient.get>(`${rootUrl}/billing/info`); + const response = await apiClient.get>( + `${rootUrl}/billing/info` + ); return response.data; }, async getFreePlanSettings(): Promise> { - const response = await apiClient.get>(`${rootUrl}/billing/free-plan`); + const response = await apiClient.get>( + `${rootUrl}/billing/free-plan` + ); return response.data; }, async upgradePlan(plan: string): Promise> { - const response = await apiClient.get>(`${rootUrl}/billing/upgrade-plan${toQueryString({plan})}`); + const response = await apiClient.get>( + `${rootUrl}/billing/upgrade-plan${toQueryString({ plan })}` + ); return response.data; }, async changePlan(plan: string): Promise> { - const response = await apiClient.get>(`${rootUrl}/billing/change-plan${toQueryString({plan})}`); + const response = await apiClient.get>( + `${rootUrl}/billing/change-plan${toQueryString({ plan })}` + ); return response.data; }, async getPlans(): Promise> { - const response = await apiClient.get>(`${rootUrl}/billing/plans`); + const response = await apiClient.get>( + `${rootUrl}/billing/plans` + ); return response.data; }, async getStorageInfo(): Promise> { - const response = await apiClient.get>(`${rootUrl}/billing/storage`); + const response = await apiClient.get>( + `${rootUrl}/billing/storage` + ); return response.data; }, @@ -225,7 +242,7 @@ export const adminCenterApiService = { async resumeSubscription(): Promise> { const response = await apiClient.get>(`${rootUrl}/billing/resume-plan`); return response.data; - }, + }, async cancelSubscription(): Promise> { const response = await apiClient.get>(`${rootUrl}/billing/cancel-plan`); @@ -233,26 +250,34 @@ export const adminCenterApiService = { }, async addMoreSeats(totalSeats: number): Promise> { - const response = await apiClient.post>(`${rootUrl}/billing/purchase-more-seats`, {seatCount: totalSeats}); + const response = await apiClient.post>( + `${rootUrl}/billing/purchase-more-seats`, + { seatCount: totalSeats } + ); return response.data; }, async redeemCode(code: string): Promise> { - const response = await apiClient.post>(`${rootUrl}/billing/redeem`, { - code, - }); + const response = await apiClient.post>( + `${rootUrl}/billing/redeem`, + { + code, + } + ); return response.data; }, async getAccountStorage(): Promise> { - const response = await apiClient.get>(`${rootUrl}/billing/account-storage`); + const response = await apiClient.get>( + `${rootUrl}/billing/account-storage` + ); return response.data; }, async switchToFreePlan(teamId: string): Promise> { - const response = await apiClient.get>(`${rootUrl}/billing/switch-to-free-plan/${teamId}`); + const response = await apiClient.get>( + `${rootUrl}/billing/switch-to-free-plan/${teamId}` + ); return response.data; }, - }; - diff --git a/worklenz-frontend/src/api/admin-center/billing.api.service.ts b/worklenz-frontend/src/api/admin-center/billing.api.service.ts index 3d51cb3b..42c91b64 100644 --- a/worklenz-frontend/src/api/admin-center/billing.api.service.ts +++ b/worklenz-frontend/src/api/admin-center/billing.api.service.ts @@ -6,7 +6,10 @@ import { IUpgradeSubscriptionPlanResponse } from '@/types/admin-center/admin-cen const rootUrl = `${API_BASE_URL}/billing`; export const billingApiService = { - async upgradeToPaidPlan(plan: string, seatCount: number): Promise> { + async upgradeToPaidPlan( + plan: string, + seatCount: number + ): Promise> { const q = toQueryString({ plan, seatCount }); const response = await apiClient.get>( `${rootUrl}/upgrade-to-paid-plan${q}` @@ -14,7 +17,9 @@ export const billingApiService = { return response.data; }, - async purchaseMoreSeats(seatCount: number): Promise> { + async purchaseMoreSeats( + seatCount: number + ): Promise> { const response = await apiClient.post>( `${rootUrl}/purchase-more-seats`, { seatCount } @@ -27,9 +32,5 @@ export const billingApiService = { `${rootUrl}/contact-us${toQueryString({ contactNo })}` ); return response.data; - } - - - - + }, }; diff --git a/worklenz-frontend/src/api/api-client.ts b/worklenz-frontend/src/api/api-client.ts index 353ebdf9..79404c74 100644 --- a/worklenz-frontend/src/api/api-client.ts +++ b/worklenz-frontend/src/api/api-client.ts @@ -16,16 +16,16 @@ export const refreshCsrfToken = async (): Promise => { try { const tokenStart = performance.now(); console.log('[CSRF] Starting CSRF token refresh...'); - + // Make a GET request to the server to get a fresh CSRF token with timeout - const response = await axios.get(`${config.apiUrl}/csrf-token`, { + const response = await axios.get(`${config.apiUrl}/csrf-token`, { withCredentials: true, - timeout: 10000 // 10 second timeout for CSRF token requests + timeout: 10000, // 10 second timeout for CSRF token requests }); - + const tokenEnd = performance.now(); console.log(`[CSRF] CSRF token refresh completed in ${(tokenEnd - tokenStart).toFixed(2)}ms`); - + if (response.data && response.data.token) { csrfToken = response.data.token; console.log('[CSRF] CSRF token successfully refreshed'); @@ -61,22 +61,22 @@ const apiClient = axios.create({ apiClient.interceptors.request.use( async config => { const requestStart = performance.now(); - + // Ensure we have a CSRF token before making requests if (!csrfToken) { const tokenStart = performance.now(); await refreshCsrfToken(); const tokenEnd = performance.now(); } - + if (csrfToken) { config.headers['X-CSRF-Token'] = csrfToken; } else { console.warn('No CSRF token available after refresh attempt'); } - + const requestEnd = performance.now(); - + return config; }, error => Promise.reject(error) @@ -114,14 +114,17 @@ apiClient.interceptors.response.use( const errorResponse = error.response; // Handle CSRF token errors - if (errorResponse?.status === 403 && - (typeof errorResponse.data === 'object' && - errorResponse.data !== null && - 'message' in errorResponse.data && - (errorResponse.data.message === 'invalid csrf token' || errorResponse.data.message === 'Invalid CSRF token') || - (error as any).code === 'EBADCSRFTOKEN')) { + if ( + errorResponse?.status === 403 && + ((typeof errorResponse.data === 'object' && + errorResponse.data !== null && + 'message' in errorResponse.data && + (errorResponse.data.message === 'invalid csrf token' || + errorResponse.data.message === 'Invalid CSRF token')) || + (error as any).code === 'EBADCSRFTOKEN') + ) { alertService.error('Security Error', 'Invalid security token. Refreshing your session...'); - + // Try to refresh the CSRF token and retry the request const newToken = await refreshCsrfToken(); if (newToken && error.config) { diff --git a/worklenz-frontend/src/api/attachments/attachments.api.service.ts b/worklenz-frontend/src/api/attachments/attachments.api.service.ts index e6a3c7c8..5aaf6fc1 100644 --- a/worklenz-frontend/src/api/attachments/attachments.api.service.ts +++ b/worklenz-frontend/src/api/attachments/attachments.api.service.ts @@ -1,25 +1,37 @@ -import { IServerResponse } from "@/types/common.types"; -import { IProjectAttachmentsViewModel } from "@/types/tasks/task-attachment-view-model"; -import apiClient from "../api-client"; -import { API_BASE_URL } from "@/shared/constants"; -import { toQueryString } from "@/utils/toQueryString"; +import { IServerResponse } from '@/types/common.types'; +import { IProjectAttachmentsViewModel } from '@/types/tasks/task-attachment-view-model'; +import apiClient from '../api-client'; +import { API_BASE_URL } from '@/shared/constants'; +import { toQueryString } from '@/utils/toQueryString'; const rootUrl = `${API_BASE_URL}/attachments`; export const attachmentsApiService = { - getTaskAttachments: async (taskId: string): Promise> => { - const response = await apiClient.get>(`${rootUrl}/tasks/${taskId}`); + getTaskAttachments: async ( + taskId: string + ): Promise> => { + const response = await apiClient.get>( + `${rootUrl}/tasks/${taskId}` + ); return response.data; }, - getProjectAttachments: async (projectId: string, index: number, size: number): Promise> => { + getProjectAttachments: async ( + projectId: string, + index: number, + size: number + ): Promise> => { const q = toQueryString({ index, size }); - const response = await apiClient.get>(`${rootUrl}/project/${projectId}${q}`); + const response = await apiClient.get>( + `${rootUrl}/project/${projectId}${q}` + ); return response.data; }, downloadAttachment: async (id: string, filename: string): Promise> => { - const response = await apiClient.get>(`${rootUrl}/download?id=${id}&file=${filename}`); + const response = await apiClient.get>( + `${rootUrl}/download?id=${id}&file=${filename}` + ); return response.data; }, @@ -27,7 +39,4 @@ export const attachmentsApiService = { const response = await apiClient.delete>(`${rootUrl}/tasks/${id}`); return response.data; }, - }; - - diff --git a/worklenz-frontend/src/api/home-page/home-page.api.service.ts b/worklenz-frontend/src/api/home-page/home-page.api.service.ts index b71e03a3..8e0c5881 100644 --- a/worklenz-frontend/src/api/home-page/home-page.api.service.ts +++ b/worklenz-frontend/src/api/home-page/home-page.api.service.ts @@ -20,7 +20,7 @@ const api = createApi({ if (!token) { token = await refreshCsrfToken(); } - + if (token) { headers.set('X-CSRF-Token', token); } diff --git a/worklenz-frontend/src/api/project-members/project-members.api.service.ts b/worklenz-frontend/src/api/project-members/project-members.api.service.ts index 3eeac3da..926d4550 100644 --- a/worklenz-frontend/src/api/project-members/project-members.api.service.ts +++ b/worklenz-frontend/src/api/project-members/project-members.api.service.ts @@ -10,7 +10,7 @@ export const projectMembersApiService = { createProjectMember: async ( body: IProjectMemberViewModel ): Promise> => { - const q = toQueryString({current_project_id: body.project_id}); + const q = toQueryString({ current_project_id: body.project_id }); const response = await apiClient.post>( `${rootUrl}${q}`, diff --git a/worklenz-frontend/src/api/project-templates/project-templates.api.service.ts b/worklenz-frontend/src/api/project-templates/project-templates.api.service.ts index 9dcc8ba8..542d27e6 100644 --- a/worklenz-frontend/src/api/project-templates/project-templates.api.service.ts +++ b/worklenz-frontend/src/api/project-templates/project-templates.api.service.ts @@ -34,7 +34,9 @@ export const projectTemplatesApiService = { return response.data; }, - createCustomTemplate: async (body: { template_id: string }): Promise> => { + createCustomTemplate: async (body: { + template_id: string; + }): Promise> => { const response = await apiClient.post(`${rootUrl}/custom-template`, body); return response.data; }, @@ -44,15 +46,17 @@ export const projectTemplatesApiService = { return response.data; }, - createFromWorklenzTemplate: async (body: { template_id: string }): Promise> => { + createFromWorklenzTemplate: async (body: { + template_id: string; + }): Promise> => { const response = await apiClient.post(`${rootUrl}/import-template`, body); return response.data; - }, - - createFromCustomTemplate: async (body: { template_id: string }): Promise> => { - const response = await apiClient.post(`${rootUrl}/import-custom-template`, body); - return response.data; }, + createFromCustomTemplate: async (body: { + template_id: string; + }): Promise> => { + const response = await apiClient.post(`${rootUrl}/import-custom-template`, body); + return response.data; + }, }; - diff --git a/worklenz-frontend/src/api/projects/projects.api.service.ts b/worklenz-frontend/src/api/projects/projects.api.service.ts index f9132be5..b18aa038 100644 --- a/worklenz-frontend/src/api/projects/projects.api.service.ts +++ b/worklenz-frontend/src/api/projects/projects.api.service.ts @@ -101,7 +101,9 @@ export const projectsApiService = { return response.data; }, - updateProject: async (payload: UpdateProjectPayload): Promise> => { + updateProject: async ( + payload: UpdateProjectPayload + ): Promise> => { const { id, ...data } = payload; const q = toQueryString({ current_project_id: id }); const url = `${API_BASE_URL}/projects/${id}${q}`; @@ -127,7 +129,10 @@ export const projectsApiService = { return response.data; }, - updateDefaultTab: async (body: { project_id: string; default_view: string }): Promise> => { + updateDefaultTab: async (body: { + project_id: string; + default_view: string; + }): Promise> => { const url = `${rootUrl}/update-pinned-view`; const response = await apiClient.put>(`${url}`, body); return response.data; @@ -139,4 +144,3 @@ export const projectsApiService = { return response.data; }, }; - diff --git a/worklenz-frontend/src/api/projects/projects.v1.api.service.ts b/worklenz-frontend/src/api/projects/projects.v1.api.service.ts index 1ad45b8b..f4ec6ea5 100644 --- a/worklenz-frontend/src/api/projects/projects.v1.api.service.ts +++ b/worklenz-frontend/src/api/projects/projects.v1.api.service.ts @@ -20,7 +20,7 @@ export const projectsApi = createApi({ if (!token) { token = await refreshCsrfToken(); } - + if (token) { headers.set('X-CSRF-Token', token); } diff --git a/worklenz-frontend/src/api/reporting/reporting-members.api.service.ts b/worklenz-frontend/src/api/reporting/reporting-members.api.service.ts index d8658465..d4f26f28 100644 --- a/worklenz-frontend/src/api/reporting/reporting-members.api.service.ts +++ b/worklenz-frontend/src/api/reporting/reporting-members.api.service.ts @@ -1,5 +1,10 @@ import { IServerResponse } from '@/types/common.types'; -import { IGetProjectsRequestBody, IRPTMembersViewModel, IRPTOverviewProjectMember, IRPTProjectsViewModel } from '@/types/reporting/reporting.types'; +import { + IGetProjectsRequestBody, + IRPTMembersViewModel, + IRPTOverviewProjectMember, + IRPTProjectsViewModel, +} from '@/types/reporting/reporting.types'; import apiClient from '../api-client'; import { API_BASE_URL } from '@/shared/constants'; import { toQueryString } from '@/utils/toQueryString'; @@ -7,9 +12,7 @@ import { toQueryString } from '@/utils/toQueryString'; const rootUrl = `${API_BASE_URL}/reporting/members`; export const reportingMembersApiService = { - getMembers: async ( - body: any - ): Promise> => { + getMembers: async (body: any): Promise> => { const q = toQueryString(body); const url = `${rootUrl}${q}`; const response = await apiClient.get>(url); diff --git a/worklenz-frontend/src/api/reporting/reporting-projects.api.service.ts b/worklenz-frontend/src/api/reporting/reporting-projects.api.service.ts index 8b5e51ec..2d57e153 100644 --- a/worklenz-frontend/src/api/reporting/reporting-projects.api.service.ts +++ b/worklenz-frontend/src/api/reporting/reporting-projects.api.service.ts @@ -1,5 +1,10 @@ import { IServerResponse } from '@/types/common.types'; -import { IGetProjectsRequestBody, IRPTOverviewProjectInfo, IRPTOverviewProjectMember, IRPTProjectsViewModel } from '@/types/reporting/reporting.types'; +import { + IGetProjectsRequestBody, + IRPTOverviewProjectInfo, + IRPTOverviewProjectMember, + IRPTProjectsViewModel, +} from '@/types/reporting/reporting.types'; import apiClient from '../api-client'; import { API_BASE_URL } from '@/shared/constants'; import { toQueryString } from '@/utils/toQueryString'; @@ -33,8 +38,11 @@ export const reportingProjectsApiService = { return response.data; }, - getTasks: async (projectId: string, groupBy: string): Promise> => { - const q = toQueryString({group: groupBy}) + getTasks: async ( + projectId: string, + groupBy: string + ): Promise> => { + const q = toQueryString({ group: groupBy }); const url = `${API_BASE_URL}/reporting/overview/project/tasks/${projectId}${q}`; const response = await apiClient.get>(url); diff --git a/worklenz-frontend/src/api/reporting/reporting.timesheet.api.service.ts b/worklenz-frontend/src/api/reporting/reporting.timesheet.api.service.ts index 1529d46b..22342527 100644 --- a/worklenz-frontend/src/api/reporting/reporting.timesheet.api.service.ts +++ b/worklenz-frontend/src/api/reporting/reporting.timesheet.api.service.ts @@ -3,12 +3,20 @@ import { toQueryString } from '@/utils/toQueryString'; import apiClient from '../api-client'; import { IServerResponse } from '@/types/common.types'; import { IAllocationViewModel } from '@/types/reporting/reporting-allocation.types'; -import { IProjectLogsBreakdown, IRPTTimeMember, IRPTTimeProject, ITimeLogBreakdownReq } from '@/types/reporting/reporting.types'; +import { + IProjectLogsBreakdown, + IRPTTimeMember, + IRPTTimeProject, + ITimeLogBreakdownReq, +} from '@/types/reporting/reporting.types'; const rootUrl = `${API_BASE_URL}/reporting`; export const reportingTimesheetApiService = { - getTimeSheetData: async (body = {}, archived = false): Promise> => { + getTimeSheetData: async ( + body = {}, + archived = false + ): Promise> => { const q = toQueryString({ archived }); const response = await apiClient.post(`${rootUrl}/allocation/${q}`, body); return response.data; @@ -19,24 +27,35 @@ export const reportingTimesheetApiService = { return response.data; }, - getProjectTimeSheets: async (body = {}, archived = false): Promise> => { + getProjectTimeSheets: async ( + body = {}, + archived = false + ): Promise> => { const q = toQueryString({ archived }); const response = await apiClient.post(`${rootUrl}/time-reports/projects/${q}`, body); return response.data; }, - getMemberTimeSheets: async (body = {}, archived = false): Promise> => { + getMemberTimeSheets: async ( + body = {}, + archived = false + ): Promise> => { const q = toQueryString({ archived }); const response = await apiClient.post(`${rootUrl}/time-reports/members/${q}`, body); return response.data; }, - getProjectTimeLogs: async (body: ITimeLogBreakdownReq): Promise> => { + getProjectTimeLogs: async ( + body: ITimeLogBreakdownReq + ): Promise> => { const response = await apiClient.post(`${rootUrl}/project-timelogs`, body); return response.data; }, - getProjectEstimatedVsActual: async (body = {}, archived = false): Promise> => { + getProjectEstimatedVsActual: async ( + body = {}, + archived = false + ): Promise> => { const q = toQueryString({ archived }); const response = await apiClient.post(`${rootUrl}/time-reports/estimated-vs-actual${q}`, body); return response.data; diff --git a/worklenz-frontend/src/api/schedule/schedule.api.service.ts b/worklenz-frontend/src/api/schedule/schedule.api.service.ts index 1d54ab1a..47f16b56 100644 --- a/worklenz-frontend/src/api/schedule/schedule.api.service.ts +++ b/worklenz-frontend/src/api/schedule/schedule.api.service.ts @@ -2,7 +2,13 @@ import { API_BASE_URL } from '@/shared/constants'; import apiClient from '../api-client'; import { IServerResponse } from '@/types/common.types'; import { ITeamMemberViewModel } from '@/types/teamMembers/teamMembersGetResponse.types'; -import { DateList, Member, Project, ScheduleData, Settings } from '@/types/schedule/schedule-v2.types'; +import { + DateList, + Member, + Project, + ScheduleData, + Settings, +} from '@/types/schedule/schedule-v2.types'; const rootUrl = `${API_BASE_URL}/schedule-gannt-v2`; @@ -45,16 +51,18 @@ export const scheduleAPIService = { }, fetchMemberProjects: async ({ id }: { id: string }): Promise> => { - const response = await apiClient.get>(`${rootUrl}/members/projects/${id}`); + const response = await apiClient.get>( + `${rootUrl}/members/projects/${id}` + ); return response.data; }, submitScheduleData: async ({ - schedule + schedule, }: { - schedule: ScheduleData + schedule: ScheduleData; }): Promise> => { const response = await apiClient.post>(`${rootUrl}/schedule`, schedule); return response.data; - } + }, }; diff --git a/worklenz-frontend/src/api/settings/profile/profile-settings.api.service.ts b/worklenz-frontend/src/api/settings/profile/profile-settings.api.service.ts index 9e269c59..10d61a68 100644 --- a/worklenz-frontend/src/api/settings/profile/profile-settings.api.service.ts +++ b/worklenz-frontend/src/api/settings/profile/profile-settings.api.service.ts @@ -52,8 +52,11 @@ export const profileSettingsApiService = { return response.data; }, - updateTeamName: async (id: string, body: ITeam): Promise> => { - const response = await apiClient.put>(`${rootUrl}/team-name/${id}`, body); + updateTeamName: async (id: string, body: ITeam): Promise> => { + const response = await apiClient.put>( + `${rootUrl}/team-name/${id}`, + body + ); return response.data; }, diff --git a/worklenz-frontend/src/api/task-templates/task-templates.api.service.ts b/worklenz-frontend/src/api/task-templates/task-templates.api.service.ts index 2be6b5ff..98f6904e 100644 --- a/worklenz-frontend/src/api/task-templates/task-templates.api.service.ts +++ b/worklenz-frontend/src/api/task-templates/task-templates.api.service.ts @@ -21,12 +21,18 @@ export const taskTemplatesApiService = { const response = await apiClient.get>(`${url}`); return response.data; }, - createTemplate: async (body: { name: string, tasks: IProjectTask[] }): Promise> => { + createTemplate: async (body: { + name: string; + tasks: IProjectTask[]; + }): Promise> => { const url = `${rootUrl}`; const response = await apiClient.post>(`${url}`, body); return response.data; }, - updateTemplate: async (id: string, body: { name: string, tasks: IProjectTask[] }): Promise> => { + updateTemplate: async ( + id: string, + body: { name: string; tasks: IProjectTask[] } + ): Promise> => { const url = `${rootUrl}/${id}`; const response = await apiClient.put>(`${url}`, body); return response.data; diff --git a/worklenz-frontend/src/api/taskAttributes/phases/phases.api.service.ts b/worklenz-frontend/src/api/taskAttributes/phases/phases.api.service.ts index c12831ad..3c494049 100644 --- a/worklenz-frontend/src/api/taskAttributes/phases/phases.api.service.ts +++ b/worklenz-frontend/src/api/taskAttributes/phases/phases.api.service.ts @@ -43,7 +43,7 @@ export const phasesApiService = { return response.data; }, - updateNameOfPhase: async (phaseId: string, body: ITaskPhase, projectId: string,) => { + updateNameOfPhase: async (phaseId: string, body: ITaskPhase, projectId: string) => { const q = toQueryString({ id: projectId, current_project_id: projectId }); const response = await apiClient.put>( `${rootUrl}/${phaseId}${q}`, diff --git a/worklenz-frontend/src/api/taskAttributes/status/status.api.service.ts b/worklenz-frontend/src/api/taskAttributes/status/status.api.service.ts index 282505e2..363b7484 100644 --- a/worklenz-frontend/src/api/taskAttributes/status/status.api.service.ts +++ b/worklenz-frontend/src/api/taskAttributes/status/status.api.service.ts @@ -69,8 +69,16 @@ export const statusApiService = { return response.data; }, - deleteStatus: async (statusId: string, projectId: string, replacingStatusId: string): Promise> => { - const q = toQueryString({ project: projectId, current_project_id: projectId, replace: replacingStatusId || null }); + deleteStatus: async ( + statusId: string, + projectId: string, + replacingStatusId: string + ): Promise> => { + const q = toQueryString({ + project: projectId, + current_project_id: projectId, + replace: replacingStatusId || null, + }); const response = await apiClient.delete>(`${rootUrl}/${statusId}${q}`); return response.data; }, diff --git a/worklenz-frontend/src/api/tasks/subtasks.api.service.ts b/worklenz-frontend/src/api/tasks/subtasks.api.service.ts index db028a59..f9c59685 100644 --- a/worklenz-frontend/src/api/tasks/subtasks.api.service.ts +++ b/worklenz-frontend/src/api/tasks/subtasks.api.service.ts @@ -1,7 +1,7 @@ -import { API_BASE_URL } from "@/shared/constants"; -import apiClient from "../api-client"; -import { IServerResponse } from "@/types/common.types"; -import { ISubTask } from "@/types/tasks/subTask.types"; +import { API_BASE_URL } from '@/shared/constants'; +import apiClient from '../api-client'; +import { IServerResponse } from '@/types/common.types'; +import { ISubTask } from '@/types/tasks/subTask.types'; const root = `${API_BASE_URL}/sub-tasks`; @@ -10,7 +10,4 @@ export const subTasksApiService = { const response = await apiClient.get(`${root}/${parentTaskId}`); return response.data; }, - - }; - diff --git a/worklenz-frontend/src/api/tasks/task-attachments.api.service.ts b/worklenz-frontend/src/api/tasks/task-attachments.api.service.ts index 774771bf..03167bc9 100644 --- a/worklenz-frontend/src/api/tasks/task-attachments.api.service.ts +++ b/worklenz-frontend/src/api/tasks/task-attachments.api.service.ts @@ -1,5 +1,9 @@ import { IServerResponse } from '@/types/common.types'; -import { IProjectAttachmentsViewModel, ITaskAttachment, ITaskAttachmentViewModel } from '@/types/tasks/task-attachment-view-model'; +import { + IProjectAttachmentsViewModel, + ITaskAttachment, + ITaskAttachmentViewModel, +} from '@/types/tasks/task-attachment-view-model'; import apiClient from '../api-client'; import { API_BASE_URL } from '@/shared/constants'; import { IAvatarAttachment } from '@/types/avatarAttachment.types'; @@ -8,7 +12,6 @@ import { toQueryString } from '@/utils/toQueryString'; const rootUrl = `${API_BASE_URL}/attachments`; const taskAttachmentsApiService = { - createTaskAttachment: async ( body: ITaskAttachment ): Promise> => { @@ -16,18 +19,26 @@ const taskAttachmentsApiService = { return response.data; }, - createAvatarAttachment: async (body: IAvatarAttachment): Promise> => { + createAvatarAttachment: async ( + body: IAvatarAttachment + ): Promise> => { const response = await apiClient.post(`${rootUrl}/avatar`, body); return response.data; }, - getTaskAttachments: async (taskId: string): Promise> => { + getTaskAttachments: async ( + taskId: string + ): Promise> => { const response = await apiClient.get(`${rootUrl}/tasks/${taskId}`); return response.data; }, - getProjectAttachments: async (projectId: string, index: number, size: number ): Promise> => { - const q = toQueryString({ index, size }); + getProjectAttachments: async ( + projectId: string, + index: number, + size: number + ): Promise> => { + const q = toQueryString({ index, size }); const response = await apiClient.get(`${rootUrl}/project/${projectId}${q}`); return response.data; }, diff --git a/worklenz-frontend/src/api/tasks/task-comments.api.service.ts b/worklenz-frontend/src/api/tasks/task-comments.api.service.ts index 711fc50e..478588c7 100644 --- a/worklenz-frontend/src/api/tasks/task-comments.api.service.ts +++ b/worklenz-frontend/src/api/tasks/task-comments.api.service.ts @@ -2,10 +2,16 @@ import apiClient from '@api/api-client'; import { API_BASE_URL } from '@/shared/constants'; import { IServerResponse } from '@/types/common.types'; import { toQueryString } from '@/utils/toQueryString'; -import { ITaskComment, ITaskCommentsCreateRequest, ITaskCommentViewModel } from '@/types/tasks/task-comments.types'; +import { + ITaskComment, + ITaskCommentsCreateRequest, + ITaskCommentViewModel, +} from '@/types/tasks/task-comments.types'; const taskCommentsApiService = { - create: async (data: ITaskCommentsCreateRequest): Promise> => { + create: async ( + data: ITaskCommentsCreateRequest + ): Promise> => { const response = await apiClient.post(`${API_BASE_URL}/task-comments`, data); return response.data; }, @@ -21,12 +27,16 @@ const taskCommentsApiService = { }, deleteAttachment: async (id: string, taskId: string): Promise> => { - const response = await apiClient.delete(`${API_BASE_URL}/task-comments/attachment/${id}/${taskId}`); + const response = await apiClient.delete( + `${API_BASE_URL}/task-comments/attachment/${id}/${taskId}` + ); return response.data; }, download: async (id: string, filename: string): Promise> => { - const response = await apiClient.get(`${API_BASE_URL}/task-comments/download?id=${id}&file=${filename}`); + const response = await apiClient.get( + `${API_BASE_URL}/task-comments/download?id=${id}&file=${filename}` + ); return response.data; }, @@ -35,8 +45,13 @@ const taskCommentsApiService = { return response.data; }, - updateReaction: async (id: string, body: {reaction_type: string, task_id: string}): Promise> => { - const response = await apiClient.put(`${API_BASE_URL}/task-comments/reaction/${id}${toQueryString(body)}`); + updateReaction: async ( + id: string, + body: { reaction_type: string; task_id: string } + ): Promise> => { + const response = await apiClient.put( + `${API_BASE_URL}/task-comments/reaction/${id}${toQueryString(body)}` + ); return response.data; }, }; diff --git a/worklenz-frontend/src/api/tasks/task-dependencies.api.service.ts b/worklenz-frontend/src/api/tasks/task-dependencies.api.service.ts index 89faee1e..59def2cf 100644 --- a/worklenz-frontend/src/api/tasks/task-dependencies.api.service.ts +++ b/worklenz-frontend/src/api/tasks/task-dependencies.api.service.ts @@ -1,7 +1,7 @@ -import { API_BASE_URL } from "@/shared/constants"; -import apiClient from "../api-client"; -import { ITaskDependency } from "@/types/tasks/task-dependency.types"; -import { IServerResponse } from "@/types/common.types"; +import { API_BASE_URL } from '@/shared/constants'; +import apiClient from '../api-client'; +import { ITaskDependency } from '@/types/tasks/task-dependency.types'; +import { IServerResponse } from '@/types/common.types'; const rootUrl = `${API_BASE_URL}/task-dependencies`; @@ -10,7 +10,9 @@ export const taskDependenciesApiService = { const response = await apiClient.get(`${rootUrl}/${taskId}`); return response.data; }, - createTaskDependency: async (body: ITaskDependency): Promise> => { + createTaskDependency: async ( + body: ITaskDependency + ): Promise> => { const response = await apiClient.post(`${rootUrl}`, body); return response.data; }, @@ -18,4 +20,4 @@ export const taskDependenciesApiService = { const response = await apiClient.delete(`${rootUrl}/${dependencyId}`); return response.data; }, -}; \ No newline at end of file +}; diff --git a/worklenz-frontend/src/api/tasks/task-recurring.api.service.ts b/worklenz-frontend/src/api/tasks/task-recurring.api.service.ts index 6e19d7cb..fb19d0c4 100644 --- a/worklenz-frontend/src/api/tasks/task-recurring.api.service.ts +++ b/worklenz-frontend/src/api/tasks/task-recurring.api.service.ts @@ -1,16 +1,21 @@ -import { API_BASE_URL } from "@/shared/constants"; -import { IServerResponse } from "@/types/common.types"; -import { ITaskRecurringSchedule } from "@/types/tasks/task-recurring-schedule"; -import apiClient from "../api-client"; +import { API_BASE_URL } from '@/shared/constants'; +import { IServerResponse } from '@/types/common.types'; +import { ITaskRecurringSchedule } from '@/types/tasks/task-recurring-schedule'; +import apiClient from '../api-client'; const rootUrl = `${API_BASE_URL}/task-recurring`; export const taskRecurringApiService = { - getTaskRecurringData: async (schedule_id: string): Promise> => { - const response = await apiClient.get(`${rootUrl}/${schedule_id}`); - return response.data; - }, - updateTaskRecurringData: async (schedule_id: string, body: any): Promise> => { - return apiClient.put(`${rootUrl}/${schedule_id}`, body); - } -} \ No newline at end of file + getTaskRecurringData: async ( + schedule_id: string + ): Promise> => { + const response = await apiClient.get(`${rootUrl}/${schedule_id}`); + return response.data; + }, + updateTaskRecurringData: async ( + schedule_id: string, + body: any + ): Promise> => { + return apiClient.put(`${rootUrl}/${schedule_id}`, body); + }, +}; diff --git a/worklenz-frontend/src/api/tasks/task-time-logs.api.service.ts b/worklenz-frontend/src/api/tasks/task-time-logs.api.service.ts index 37673590..1a9191ee 100644 --- a/worklenz-frontend/src/api/tasks/task-time-logs.api.service.ts +++ b/worklenz-frontend/src/api/tasks/task-time-logs.api.service.ts @@ -1,7 +1,7 @@ -import { API_BASE_URL } from "@/shared/constants"; -import apiClient from "../api-client"; -import { IServerResponse } from "@/types/common.types"; -import { ITaskLogViewModel } from "@/types/tasks/task-log-view.types"; +import { API_BASE_URL } from '@/shared/constants'; +import apiClient from '../api-client'; +import { IServerResponse } from '@/types/common.types'; +import { ITaskLogViewModel } from '@/types/tasks/task-log-view.types'; const rootUrl = `${API_BASE_URL}/task-time-log`; @@ -16,12 +16,12 @@ export interface IRunningTimer { } export const taskTimeLogsApiService = { - getByTask: async (id: string) : Promise> => { + getByTask: async (id: string): Promise> => { const response = await apiClient.get(`${rootUrl}/task/${id}`); return response.data; }, - delete: async (id: string, taskId: string) : Promise> => { + delete: async (id: string, taskId: string): Promise> => { const response = await apiClient.delete(`${rootUrl}/${id}?task=${taskId}`); return response.data; }, diff --git a/worklenz-frontend/src/api/tasks/tasks-custom-columns.service.ts b/worklenz-frontend/src/api/tasks/tasks-custom-columns.service.ts index 187a27e2..117f1f25 100644 --- a/worklenz-frontend/src/api/tasks/tasks-custom-columns.service.ts +++ b/worklenz-frontend/src/api/tasks/tasks-custom-columns.service.ts @@ -1,23 +1,23 @@ -import { ITaskListColumn } from "@/types/tasks/taskList.types"; -import apiClient from "../api-client"; -import { IServerResponse } from "@/types/common.types"; +import { ITaskListColumn } from '@/types/tasks/taskList.types'; +import apiClient from '../api-client'; +import { IServerResponse } from '@/types/common.types'; export const tasksCustomColumnsService = { getCustomColumns: async (projectId: string): Promise> => { const response = await apiClient.get(`/api/v1/custom-columns/project/${projectId}/columns`); return response.data; }, - + updateTaskCustomColumnValue: async ( - taskId: string, - columnKey: string, + taskId: string, + columnKey: string, value: string | number | boolean, projectId: string ): Promise> => { const response = await apiClient.put(`/api/v1/tasks/${taskId}/custom-column`, { column_key: columnKey, value: value, - project_id: projectId + project_id: projectId, }); return response.data; }, @@ -35,7 +35,7 @@ export const tasksCustomColumnsService = { ): Promise> => { const response = await apiClient.post('/api/v1/custom-columns', { project_id: projectId, - ...columnData + ...columnData, }); return response.data; }, @@ -63,7 +63,10 @@ export const tasksCustomColumnsService = { projectId: string, item: ITaskListColumn ): Promise> => { - const response = await apiClient.put(`/api/v1/custom-columns/project/${projectId}/columns`, item); + const response = await apiClient.put( + `/api/v1/custom-columns/project/${projectId}/columns`, + item + ); return response.data; - } + }, }; diff --git a/worklenz-frontend/src/api/tasks/tasks.api.service.ts b/worklenz-frontend/src/api/tasks/tasks.api.service.ts index 460983d1..c348fdbe 100644 --- a/worklenz-frontend/src/api/tasks/tasks.api.service.ts +++ b/worklenz-frontend/src/api/tasks/tasks.api.service.ts @@ -131,14 +131,19 @@ export const tasksApiService = { return response.data; }, - getTaskDependencyStatus: async (taskId: string, statusId: string): Promise> => { - const q = toQueryString({taskId, statusId}); + getTaskDependencyStatus: async ( + taskId: string, + statusId: string + ): Promise> => { + const q = toQueryString({ taskId, statusId }); const response = await apiClient.get(`${rootUrl}/dependency-status${q}`); return response.data; }, - getTaskListV3: async (config: ITaskListConfigV2): Promise> => { - const q = toQueryString({ ...config, include_empty: "true" }); + getTaskListV3: async ( + config: ITaskListConfigV2 + ): Promise> => { + const q = toQueryString({ ...config, include_empty: 'true' }); const response = await apiClient.get(`${rootUrl}/list/v3/${config.id}${q}`); return response.data; }, @@ -148,20 +153,28 @@ export const tasksApiService = { return response.data; }, - getTaskProgressStatus: async (projectId: string): Promise> => { + getTaskProgressStatus: async ( + projectId: string + ): Promise< + IServerResponse<{ + projectId: string; + totalTasks: number; + completedTasks: number; + avgProgress: number; + lastUpdated: string; + completionPercentage: number; + }> + > => { const response = await apiClient.get(`${rootUrl}/progress-status/${projectId}`); return response.data; }, // API method to reorder tasks - reorderTasks: async (params: { taskIds: string[]; newOrder: number[]; projectId: string }): Promise> => { + reorderTasks: async (params: { + taskIds: string[]; + newOrder: number[]; + projectId: string; + }): Promise> => { const response = await apiClient.post(`${rootUrl}/reorder`, { task_ids: params.taskIds, new_order: params.newOrder, @@ -171,7 +184,12 @@ export const tasksApiService = { }, // API method to update task group (status, priority, phase) - updateTaskGroup: async (params: { taskId: string; groupType: 'status' | 'priority' | 'phase'; groupValue: string; projectId: string }): Promise> => { + updateTaskGroup: async (params: { + taskId: string; + groupType: 'status' | 'priority' | 'phase'; + groupValue: string; + projectId: string; + }): Promise> => { const response = await apiClient.put(`${rootUrl}/${params.taskId}/group`, { group_type: params.groupType, group_value: params.groupValue, diff --git a/worklenz-frontend/src/api/team-members/teamMembers.api.service.ts b/worklenz-frontend/src/api/team-members/teamMembers.api.service.ts index f96de294..45ab9eaa 100644 --- a/worklenz-frontend/src/api/team-members/teamMembers.api.service.ts +++ b/worklenz-frontend/src/api/team-members/teamMembers.api.service.ts @@ -44,7 +44,9 @@ export const teamMembersApiService = { return response.data; }, - getAll: async (projectId: string | null = null): Promise> => { + getAll: async ( + projectId: string | null = null + ): Promise> => { const params = new URLSearchParams(projectId ? { project: projectId } : {}); const response = await apiClient.get>( `${rootUrl}/all${params.toString() ? '?' + params.toString() : ''}` diff --git a/worklenz-frontend/src/api/teams/teams.api.service.ts b/worklenz-frontend/src/api/teams/teams.api.service.ts index 4fb56b00..626e7e0d 100644 --- a/worklenz-frontend/src/api/teams/teams.api.service.ts +++ b/worklenz-frontend/src/api/teams/teams.api.service.ts @@ -14,11 +14,8 @@ const rootUrl = `${API_BASE_URL}/teams`; export const teamsApiService = { getTeams: async (): Promise> => { - const response = await apiClient.get>( - `${rootUrl}` - ); + const response = await apiClient.get>(`${rootUrl}`); return response.data; - }, setActiveTeam: async (teamId: string): Promise> => { @@ -29,23 +26,18 @@ export const teamsApiService = { return response.data; }, - createTeam: async (team: IOrganizationTeam): Promise> => { const response = await apiClient.post>(`${rootUrl}`, team); return response.data; }, - getInvitations: async (): Promise> => { - const response = await apiClient.get>( - `${rootUrl}/invites` - ); + const response = await apiClient.get>(`${rootUrl}/invites`); return response.data; }, acceptInvitation: async (body: IAcceptTeamInvite): Promise> => { const response = await apiClient.put>(`${rootUrl}`, body); return response.data; - } + }, }; - diff --git a/worklenz-frontend/src/app/performance-monitor.ts b/worklenz-frontend/src/app/performance-monitor.ts index b4146d3e..66599e6e 100644 --- a/worklenz-frontend/src/app/performance-monitor.ts +++ b/worklenz-frontend/src/app/performance-monitor.ts @@ -15,7 +15,7 @@ class ReduxPerformanceMonitor { logMetric(metric: PerformanceMetrics) { this.metrics.push(metric); - + // Keep only recent metrics if (this.metrics.length > this.maxMetrics) { this.metrics = this.metrics.slice(-this.maxMetrics); @@ -49,14 +49,14 @@ class ReduxPerformanceMonitor { export const performanceMonitor = new ReduxPerformanceMonitor(); // Redux middleware for performance monitoring -export const performanceMiddleware: Middleware = (store) => (next) => (action: any) => { +export const performanceMiddleware: Middleware = store => next => (action: any) => { const start = performance.now(); - + const result = next(action); - + const end = performance.now(); const duration = end - start; - + // Calculate approximate state size (in development only) let stateSize = 0; if (process.env.NODE_ENV === 'development') { @@ -101,7 +101,7 @@ export function analyzeReduxPerformance() { // Count action frequencies metrics.forEach(m => { - analysis.mostFrequentActions[m.actionType] = + analysis.mostFrequentActions[m.actionType] = (analysis.mostFrequentActions[m.actionType] || 0) + 1; }); @@ -109,14 +109,15 @@ export function analyzeReduxPerformance() { if (analysis.slowActions > analysis.totalActions * 0.1) { analysis.recommendations.push('Consider optimizing selectors with createSelector'); } - - if (analysis.largestStateSize > 1000000) { // 1MB + + if (analysis.largestStateSize > 1000000) { + // 1MB analysis.recommendations.push('State size is large - consider normalizing data'); } - + if (analysis.averageActionTime > 20) { analysis.recommendations.push('Average action time is high - check for expensive reducers'); } return analysis; -} \ No newline at end of file +} diff --git a/worklenz-frontend/src/app/routes/index.tsx b/worklenz-frontend/src/app/routes/index.tsx index d9361804..eb9148b7 100644 --- a/worklenz-frontend/src/app/routes/index.tsx +++ b/worklenz-frontend/src/app/routes/index.tsx @@ -62,10 +62,12 @@ export const AdminGuard = memo(({ children }: GuardProps) => { const guardResult = useMemo(() => { try { // Defensive checks to ensure authService and its methods exist - if (!authService || - typeof authService.isAuthenticated !== 'function' || - typeof authService.isOwnerOrAdmin !== 'function' || - typeof authService.getCurrentSession !== 'function') { + if ( + !authService || + typeof authService.isAuthenticated !== 'function' || + typeof authService.isOwnerOrAdmin !== 'function' || + typeof authService.getCurrentSession !== 'function' + ) { return null; // Don't redirect if auth service is not ready } @@ -75,7 +77,7 @@ export const AdminGuard = memo(({ children }: GuardProps) => { const currentSession = authService.getCurrentSession(); const isFreePlan = currentSession?.subscription_type === ISUBSCRIPTION_TYPE.FREE; - + if (!authService.isOwnerOrAdmin() || isFreePlan) { return { redirect: '/worklenz/unauthorized' }; } @@ -103,9 +105,11 @@ export const LicenseExpiryGuard = memo(({ children }: GuardProps) => { const shouldRedirect = useMemo(() => { try { // Defensive checks to ensure authService and its methods exist - if (!authService || - typeof authService.isAuthenticated !== 'function' || - typeof authService.getCurrentSession !== 'function') { + if ( + !authService || + typeof authService.isAuthenticated !== 'function' || + typeof authService.getCurrentSession !== 'function' + ) { return false; // Don't redirect if auth service is not ready } @@ -120,37 +124,40 @@ export const LicenseExpiryGuard = memo(({ children }: GuardProps) => { const currentSession = authService.getCurrentSession(); // Check if trial is expired more than 7 days or if is_expired flag is set - const isLicenseExpiredMoreThan7Days = () => { + const isLicenseExpiredMoreThan7Days = () => { // Quick bail if no session data is available if (!currentSession) return false; - + // Check is_expired flag first - if (currentSession.is_expired) { + if (currentSession.is_expired) { // If no trial_expire_date exists but is_expired is true, defer to backend check if (!currentSession.trial_expire_date) return true; - + // If there is a trial_expire_date, check if it's more than 7 days past const today = new Date(); const expiryDate = new Date(currentSession.trial_expire_date); const diffTime = today.getTime() - expiryDate.getTime(); const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); - + // Redirect if more than 7 days past expiration return diffDays > 7; } - + // If not marked as expired but has trial_expire_date, do a date check - if (currentSession.subscription_type === ISUBSCRIPTION_TYPE.TRIAL && currentSession.trial_expire_date) { + if ( + currentSession.subscription_type === ISUBSCRIPTION_TYPE.TRIAL && + currentSession.trial_expire_date + ) { const today = new Date(); const expiryDate = new Date(currentSession.trial_expire_date); const diffTime = today.getTime() - expiryDate.getTime(); const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); - + // If expired more than 7 days, redirect return diffDays > 7; } - + // No expiration data found return false; }; @@ -227,30 +234,34 @@ const wrapRoutes = ( // Optimized static license expired component const StaticLicenseExpired = memo(() => { return ( -
-
+
+

Your Worklenz trial has expired!

Please upgrade now to continue using Worklenz.

- @@ -272,11 +283,7 @@ const StaticLicenseExpired = memo(() => { StaticLicenseExpired.displayName = 'StaticLicenseExpired'; // Create route arrays (moved outside of useMemo to avoid hook violations) -const publicRoutes = [ - ...rootRoutes, - ...authRoutes, - notFoundRoute -]; +const publicRoutes = [...rootRoutes, ...authRoutes, notFoundRoute]; const protectedMainRoutes = wrapRoutes(mainRoutes, AuthGuard); const adminRoutes = wrapRoutes(reportingRoutes, AdminGuard); @@ -305,37 +312,35 @@ const withLicenseExpiryCheck = (routes: RouteObject[]): RouteObject[] => { const licenseCheckedMainRoutes = withLicenseExpiryCheck(protectedMainRoutes); // Create optimized router with future flags for better performance -const router = createBrowserRouter([ +const router = createBrowserRouter( + [ + { + element: ( + + + + ), + errorElement: ( + + }> + + + + ), + children: [...licenseCheckedMainRoutes, ...adminRoutes, ...setupRoutes, licenseExpiredRoute], + }, + ...publicRoutes, + ], { - element: ( - - - - ), - errorElement: ( - - }> - - - - ), - children: [ - ...licenseCheckedMainRoutes, - ...adminRoutes, - ...setupRoutes, - licenseExpiredRoute, - ], - }, - ...publicRoutes, -], { - // Enable React Router future features for better performance - future: { - v7_relativeSplatPath: true, - v7_fetcherPersist: true, - v7_normalizeFormMethod: true, - v7_partialHydration: true, - v7_skipActionErrorRevalidation: true + // Enable React Router future features for better performance + future: { + v7_relativeSplatPath: true, + v7_fetcherPersist: true, + v7_normalizeFormMethod: true, + v7_partialHydration: true, + v7_skipActionErrorRevalidation: true, + }, } -}); +); export default router; diff --git a/worklenz-frontend/src/app/routes/main-routes.tsx b/worklenz-frontend/src/app/routes/main-routes.tsx index 225fd9a7..8ec8cb9a 100644 --- a/worklenz-frontend/src/app/routes/main-routes.tsx +++ b/worklenz-frontend/src/app/routes/main-routes.tsx @@ -11,7 +11,9 @@ import { SuspenseFallback } from '@/components/suspense-fallback/suspense-fallba const HomePage = lazy(() => import('@/pages/home/home-page')); const ProjectList = lazy(() => import('@/pages/projects/project-list')); const Schedule = lazy(() => import('@/pages/schedule/schedule')); -const ProjectTemplateEditView = lazy(() => import('@/pages/settings/project-templates/projectTemplateEditView/ProjectTemplateEditView')); +const ProjectTemplateEditView = lazy( + () => import('@/pages/settings/project-templates/projectTemplateEditView/ProjectTemplateEditView') +); const LicenseExpired = lazy(() => import('@/pages/license-expired/license-expired')); const ProjectView = lazy(() => import('@/pages/projects/projectView/project-view')); const Unauthorized = lazy(() => import('@/pages/unauthorized/unauthorized')); @@ -23,9 +25,11 @@ const AdminGuard = ({ children }: { children: React.ReactNode }) => { try { // Defensive checks to ensure authService and its methods exist - if (!authService || - typeof authService.isAuthenticated !== 'function' || - typeof authService.isOwnerOrAdmin !== 'function') { + if ( + !authService || + typeof authService.isAuthenticated !== 'function' || + typeof authService.isOwnerOrAdmin !== 'function' + ) { // If auth service is not ready, render children (don't block) return <>{children}; } @@ -52,21 +56,21 @@ const mainRoutes: RouteObject[] = [ element: , children: [ { index: true, element: }, - { - path: 'home', + { + path: 'home', element: ( }> - ) + ), }, - { - path: 'projects', + { + path: 'projects', element: ( }> - ) + ), }, { path: 'schedule', @@ -76,15 +80,15 @@ const mainRoutes: RouteObject[] = [ - ) + ), }, - { - path: `projects/:projectId`, + { + path: `projects/:projectId`, element: ( }> - ) + ), }, { path: `settings/project-templates/edit/:templateId/:templateName`, @@ -94,13 +98,13 @@ const mainRoutes: RouteObject[] = [ ), }, - { - path: 'unauthorized', + { + path: 'unauthorized', element: ( }> - ) + ), }, ...settingsRoutes, ...adminCenterRoutes, @@ -113,15 +117,15 @@ export const licenseExpiredRoute: RouteObject = { path: '/worklenz', element: , children: [ - { - path: 'license-expired', + { + path: 'license-expired', element: ( }> - ) - } - ] + ), + }, + ], }; export default mainRoutes; diff --git a/worklenz-frontend/src/app/routes/settings-routes.tsx b/worklenz-frontend/src/app/routes/settings-routes.tsx index 39468efb..9999841b 100644 --- a/worklenz-frontend/src/app/routes/settings-routes.tsx +++ b/worklenz-frontend/src/app/routes/settings-routes.tsx @@ -4,7 +4,13 @@ import SettingsLayout from '@/layouts/SettingsLayout'; import { settingsItems } from '@/lib/settings/settings-constants'; import { useAuthService } from '@/hooks/useAuth'; -const SettingsGuard = ({ children, adminRequired }: { children: React.ReactNode; adminRequired: boolean }) => { +const SettingsGuard = ({ + children, + adminRequired, +}: { + children: React.ReactNode; + adminRequired: boolean; +}) => { const isOwnerOrAdmin = useAuthService().isOwnerOrAdmin(); if (adminRequired && !isOwnerOrAdmin) { @@ -20,11 +26,7 @@ const settingsRoutes: RouteObject[] = [ element: , children: settingsItems.map(item => ({ path: item.endpoint, - element: ( - - {item.element} - - ), + element: {item.element}, })), }, ]; diff --git a/worklenz-frontend/src/app/selectors.ts b/worklenz-frontend/src/app/selectors.ts index 29cbd3be..7796fb5d 100644 --- a/worklenz-frontend/src/app/selectors.ts +++ b/worklenz-frontend/src/app/selectors.ts @@ -7,10 +7,7 @@ import { RootState } from './store'; // Auth selectors export const selectAuth = (state: RootState) => state.auth; export const selectUser = (state: RootState) => state.userReducer; -export const selectIsAuthenticated = createSelector( - [selectAuth], - (auth) => !!auth.user -); +export const selectIsAuthenticated = createSelector([selectAuth], auth => !!auth.user); // Project selectors export const selectProjects = (state: RootState) => state.projectsReducer; @@ -69,13 +66,10 @@ export const selectGroupByFilter = (state: RootState) => state.groupByFilterDrop // Memoized computed selectors for common use cases export const selectHasActiveProject = createSelector( [selectCurrentProject], - (project) => !!project && Object.keys(project).length > 0 + project => !!project && Object.keys(project).length > 0 ); -export const selectIsLoading = createSelector( - [selectTasks, selectProjects], - (tasks, projects) => { - // Check if any major feature is loading - return (tasks as any)?.loading || (projects as any)?.loading; - } -); \ No newline at end of file +export const selectIsLoading = createSelector([selectTasks, selectProjects], (tasks, projects) => { + // Check if any major feature is loading + return (tasks as any)?.loading || (projects as any)?.loading; +}); diff --git a/worklenz-frontend/src/app/store.ts b/worklenz-frontend/src/app/store.ts index 573333d7..262f654b 100644 --- a/worklenz-frontend/src/app/store.ts +++ b/worklenz-frontend/src/app/store.ts @@ -122,7 +122,7 @@ export const store = configureStore({ taskListCustomColumnsReducer: taskListCustomColumnsReducer, boardReducer: boardReducer, projectDrawerReducer: projectDrawerReducer, - + projectViewReducer: projectViewReducer, // Project Lookups diff --git a/worklenz-frontend/src/components/AssigneeSelector.tsx b/worklenz-frontend/src/components/AssigneeSelector.tsx index 50cabbab..650b4b6f 100644 --- a/worklenz-frontend/src/components/AssigneeSelector.tsx +++ b/worklenz-frontend/src/components/AssigneeSelector.tsx @@ -22,10 +22,10 @@ interface AssigneeSelectorProps { isDarkMode?: boolean; } -const AssigneeSelector: React.FC = ({ - task, - groupId = null, - isDarkMode = false +const AssigneeSelector: React.FC = ({ + task, + groupId = null, + isDarkMode = false, }) => { const [isOpen, setIsOpen] = useState(false); const [searchQuery, setSearchQuery] = useState(''); @@ -63,8 +63,12 @@ const AssigneeSelector: React.FC = ({ // Close dropdown when clicking outside and handle scroll useEffect(() => { const handleClickOutside = (event: MouseEvent) => { - if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node) && - buttonRef.current && !buttonRef.current.contains(event.target as Node)) { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) && + buttonRef.current && + !buttonRef.current.contains(event.target as Node) + ) { setIsOpen(false); } }; @@ -74,10 +78,12 @@ const AssigneeSelector: React.FC = ({ // Check if the button is still visible in the viewport if (buttonRef.current) { const rect = buttonRef.current.getBoundingClientRect(); - const isVisible = rect.top >= 0 && rect.left >= 0 && - rect.bottom <= window.innerHeight && - rect.right <= window.innerWidth; - + const isVisible = + rect.top >= 0 && + rect.left >= 0 && + rect.bottom <= window.innerHeight && + rect.right <= window.innerWidth; + if (isVisible) { updateDropdownPosition(); } else { @@ -98,7 +104,7 @@ const AssigneeSelector: React.FC = ({ document.addEventListener('mousedown', handleClickOutside); window.addEventListener('scroll', handleScroll, true); window.addEventListener('resize', handleResize); - + return () => { document.removeEventListener('mousedown', handleClickOutside); window.removeEventListener('scroll', handleScroll, true); @@ -113,10 +119,10 @@ const AssigneeSelector: React.FC = ({ const handleDropdownToggle = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - + if (!isOpen) { updateDropdownPosition(); - + // Prepare team members data when opening const assignees = task?.assignees?.map(assignee => assignee.team_member_id); const membersData = (members?.data || []).map(member => ({ @@ -125,7 +131,7 @@ const AssigneeSelector: React.FC = ({ })); const sortedMembers = sortTeamMembers(membersData); setTeamMembers({ data: sortedMembers }); - + setIsOpen(true); // Focus search input after opening setTimeout(() => { @@ -160,11 +166,9 @@ const AssigneeSelector: React.FC = ({ // Update local team members state for dropdown UI setTeamMembers(prev => ({ ...prev, - data: (prev.data || []).map(member => - member.id === memberId - ? { ...member, selected: checked } - : member - ) + data: (prev.data || []).map(member => + member.id === memberId ? { ...member, selected: checked } : member + ), })); const body = { @@ -178,12 +182,9 @@ const AssigneeSelector: React.FC = ({ // Emit socket event - the socket handler will update Redux with proper types socket?.emit(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), JSON.stringify(body)); - socket?.once( - SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), - (data: any) => { - dispatch(updateEnhancedKanbanTaskAssignees(data)); - } - ); + socket?.once(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), (data: any) => { + dispatch(updateEnhancedKanbanTaskAssignees(data)); + }); // Remove from pending changes after a short delay (optimistic) setTimeout(() => { @@ -198,9 +199,10 @@ const AssigneeSelector: React.FC = ({ const checkMemberSelected = (memberId: string) => { if (!memberId) return false; // Use optimistic assignees if available, otherwise fall back to task assignees - const assignees = optimisticAssignees.length > 0 - ? optimisticAssignees - : task?.assignees?.map(assignee => assignee.team_member_id) || []; + const assignees = + optimisticAssignees.length > 0 + ? optimisticAssignees + : task?.assignees?.map(assignee => assignee.team_member_id) || []; return assignees.includes(memberId); }; @@ -217,149 +219,159 @@ const AssigneeSelector: React.FC = ({ className={` w-5 h-5 rounded-full border border-dashed flex items-center justify-center transition-colors duration-200 - ${isOpen - ? isDarkMode - ? 'border-blue-500 bg-blue-900/20 text-blue-400' - : 'border-blue-500 bg-blue-50 text-blue-600' - : isDarkMode - ? 'border-gray-600 hover:border-gray-500 hover:bg-gray-800 text-gray-400' - : 'border-gray-300 hover:border-gray-400 hover:bg-gray-100 text-gray-600' + ${ + isOpen + ? isDarkMode + ? 'border-blue-500 bg-blue-900/20 text-blue-400' + : 'border-blue-500 bg-blue-50 text-blue-600' + : isDarkMode + ? 'border-gray-600 hover:border-gray-500 hover:bg-gray-800 text-gray-400' + : 'border-gray-300 hover:border-gray-400 hover:bg-gray-100 text-gray-600' } `} > - {isOpen && createPortal( -
e.stopPropagation()} - className={` + {isOpen && + createPortal( +
e.stopPropagation()} + className={` fixed z-9999 w-72 rounded-md shadow-lg border - ${isDarkMode - ? 'bg-gray-800 border-gray-600' - : 'bg-white border-gray-200' - } + ${isDarkMode ? 'bg-gray-800 border-gray-600' : 'bg-white border-gray-200'} `} - style={{ - top: dropdownPosition.top, - left: dropdownPosition.left, - }} - > - {/* Header */} -
- setSearchQuery(e.target.value)} - placeholder="Search members..." - className={` + style={{ + top: dropdownPosition.top, + left: dropdownPosition.left, + }} + > + {/* Header */} +
+ setSearchQuery(e.target.value)} + placeholder="Search members..." + className={` w-full px-2 py-1 text-xs rounded border - ${isDarkMode - ? 'bg-gray-700 border-gray-600 text-gray-100 placeholder-gray-400 focus:border-blue-500' - : 'bg-white border-gray-300 text-gray-900 placeholder-gray-500 focus:border-blue-500' + ${ + isDarkMode + ? 'bg-gray-700 border-gray-600 text-gray-100 placeholder-gray-400 focus:border-blue-500' + : 'bg-white border-gray-300 text-gray-900 placeholder-gray-500 focus:border-blue-500' } focus:outline-none focus:ring-1 focus:ring-blue-500 `} - /> -
+ /> +
- {/* Members List */} -
- {filteredMembers && filteredMembers.length > 0 ? ( - filteredMembers.map((member) => ( -
+ {filteredMembers && filteredMembers.length > 0 ? ( + filteredMembers.map(member => ( +
{ - if (!member.pending_invitation) { - const isSelected = checkMemberSelected(member.id || ''); - handleMemberToggle(member.id || '', !isSelected); - } - }} - style={{ - // Add visual feedback for immediate response - transition: 'all 0.15s ease-in-out', - }} - > -
- e.stopPropagation()}> - handleMemberToggle(member.id || '', checked)} - disabled={member.pending_invitation || pendingChanges.has(member.id || '')} - isDarkMode={isDarkMode} - /> - - {pendingChanges.has(member.id || '') && ( -
-
-
- )} -
- - - -
-
- {member.name} -
-
- {member.email} - {member.pending_invitation && ( - (Pending) + onClick={() => { + if (!member.pending_invitation) { + const isSelected = checkMemberSelected(member.id || ''); + handleMemberToggle(member.id || '', !isSelected); + } + }} + style={{ + // Add visual feedback for immediate response + transition: 'all 0.15s ease-in-out', + }} + > +
+ e.stopPropagation()}> + handleMemberToggle(member.id || '', checked)} + disabled={ + member.pending_invitation || pendingChanges.has(member.id || '') + } + isDarkMode={isDarkMode} + /> + + {pendingChanges.has(member.id || '') && ( +
+
+
)}
-
-
- )) - ) : ( -
-
No members found
-
- )} -
- {/* Footer */} -
-
+ )) + ) : ( +
+
No members found
+
+ )} +
+ + {/* Footer */} +
+ -
-
, - document.body - )} + onClick={handleInviteProjectMemberDrawer} + > + + Invite member + +
+
, + document.body + )} ); }; -export default AssigneeSelector; \ No newline at end of file +export default AssigneeSelector; diff --git a/worklenz-frontend/src/components/Avatar.tsx b/worklenz-frontend/src/components/Avatar.tsx index 413a4e3d..59da1650 100644 --- a/worklenz-frontend/src/components/Avatar.tsx +++ b/worklenz-frontend/src/components/Avatar.tsx @@ -11,47 +11,63 @@ interface AvatarProps { style?: React.CSSProperties; } -const Avatar: React.FC = ({ - name = '', - size = 'default', - isDarkMode = false, +const Avatar: React.FC = ({ + name = '', + size = 'default', + isDarkMode = false, className = '', src, backgroundColor, onClick, - style = {} + style = {}, }) => { // Handle both numeric and string sizes const getSize = () => { if (typeof size === 'number') { return { width: size, height: size, fontSize: `${size * 0.4}px` }; } - + const sizeMap = { small: { width: 24, height: 24, fontSize: '10px' }, default: { width: 32, height: 32, fontSize: '14px' }, - large: { width: 48, height: 48, fontSize: '18px' } + large: { width: 48, height: 48, fontSize: '18px' }, }; - + return sizeMap[size]; }; const sizeStyle = getSize(); - + const lightColors = [ - '#f56565', '#4299e1', '#48bb78', '#ed8936', '#9f7aea', - '#ed64a6', '#667eea', '#38b2ac', '#f6ad55', '#4fd1c7' + '#f56565', + '#4299e1', + '#48bb78', + '#ed8936', + '#9f7aea', + '#ed64a6', + '#667eea', + '#38b2ac', + '#f6ad55', + '#4fd1c7', ]; - + const darkColors = [ - '#e53e3e', '#3182ce', '#38a169', '#dd6b20', '#805ad5', - '#d53f8c', '#5a67d8', '#319795', '#d69e2e', '#319795' + '#e53e3e', + '#3182ce', + '#38a169', + '#dd6b20', + '#805ad5', + '#d53f8c', + '#5a67d8', + '#319795', + '#d69e2e', + '#319795', ]; - + const colors = isDarkMode ? darkColors : lightColors; const colorIndex = name.charCodeAt(0) % colors.length; const defaultBgColor = backgroundColor || colors[colorIndex]; - + const handleClick = (e: React.MouseEvent) => { e.stopPropagation(); onClick?.(e); @@ -60,7 +76,7 @@ const Avatar: React.FC = ({ const avatarStyle = { ...sizeStyle, backgroundColor: defaultBgColor, - ...style + ...style, }; if (src) { @@ -74,9 +90,9 @@ const Avatar: React.FC = ({ /> ); } - + return ( -
= ({ ); }; -export default Avatar; \ No newline at end of file +export default Avatar; diff --git a/worklenz-frontend/src/components/AvatarGroup.tsx b/worklenz-frontend/src/components/AvatarGroup.tsx index a0eaf410..04e4b57a 100644 --- a/worklenz-frontend/src/components/AvatarGroup.tsx +++ b/worklenz-frontend/src/components/AvatarGroup.tsx @@ -20,42 +20,49 @@ interface AvatarGroupProps { onClick?: (e: React.MouseEvent) => void; } -const AvatarGroup: React.FC = ({ - members, - maxCount, - size = 28, +const AvatarGroup: React.FC = ({ + members, + maxCount, + size = 28, isDarkMode = false, className = '', - onClick + onClick, }) => { - const stopPropagation = useCallback((e: React.MouseEvent) => { - e.stopPropagation(); - onClick?.(e); - }, [onClick]); + const stopPropagation = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation(); + onClick?.(e); + }, + [onClick] + ); - const renderAvatar = useCallback((member: Member, index: number) => { - const memberName = member.end && member.names ? member.names.join(', ') : member.name || ''; - const displayName = member.end && member.names ? member.name : member.name?.charAt(0).toUpperCase(); - - return ( - - { + const memberName = member.end && member.names ? member.names.join(', ') : member.name || ''; + const displayName = + member.end && member.names ? member.name : member.name?.charAt(0).toUpperCase(); + + return ( + - - ); - }, [stopPropagation, size, isDarkMode]); + > + + + ); + }, + [stopPropagation, size, isDarkMode] + ); const visibleMembers = useMemo(() => { return maxCount ? members.slice(0, maxCount) : members; @@ -73,13 +80,13 @@ const AvatarGroup: React.FC = ({ if (typeof size === 'number') { return { width: size, height: size, fontSize: `${size * 0.4}px` }; } - + const sizeMap = { small: { width: 24, height: 24, fontSize: '10px' }, default: { width: 32, height: 32, fontSize: '14px' }, - large: { width: 48, height: 48, fontSize: '18px' } + large: { width: 48, height: 48, fontSize: '18px' }, }; - + return sizeMap[size]; }; @@ -87,15 +94,10 @@ const AvatarGroup: React.FC = ({
{avatarElements} {remainingCount > 0 && ( - -
+
= ({ ); }; -export default AvatarGroup; \ No newline at end of file +export default AvatarGroup; diff --git a/worklenz-frontend/src/components/Button.tsx b/worklenz-frontend/src/components/Button.tsx index 51d79d32..8d9ce9d1 100644 --- a/worklenz-frontend/src/components/Button.tsx +++ b/worklenz-frontend/src/components/Button.tsx @@ -12,25 +12,25 @@ interface ButtonProps { type?: 'button' | 'submit' | 'reset'; } -const Button: React.FC> = ({ - children, - onClick, - variant = 'default', - size = 'default', - className = '', - icon, - isDarkMode = false, +const Button: React.FC> = ({ + children, + onClick, + variant = 'default', + size = 'default', + className = '', + icon, + isDarkMode = false, disabled = false, type = 'button', - ...props + ...props }) => { const baseClasses = `inline-flex items-center justify-center font-medium transition-colors duration-200 focus:outline-none focus:ring-2 ${isDarkMode ? 'focus:ring-blue-400' : 'focus:ring-blue-500'} focus:ring-offset-2 ${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}`; - + const variantClasses = { - text: isDarkMode + text: isDarkMode ? 'text-gray-400 hover:text-gray-200 hover:bg-gray-700/50' : 'text-gray-600 hover:text-gray-800 hover:bg-gray-100', - default: isDarkMode + default: isDarkMode ? 'bg-gray-800 border border-gray-600 text-gray-200 hover:bg-gray-700' : 'bg-white border border-gray-300 text-gray-700 hover:bg-gray-50', primary: isDarkMode @@ -38,15 +38,15 @@ const Button: React.FC - {icon && {icon}} + {icon && {icon}} {children} ); }; -export default Button; \ No newline at end of file +export default Button; diff --git a/worklenz-frontend/src/components/Checkbox.tsx b/worklenz-frontend/src/components/Checkbox.tsx index 4ed89018..f663c3f7 100644 --- a/worklenz-frontend/src/components/Checkbox.tsx +++ b/worklenz-frontend/src/components/Checkbox.tsx @@ -9,36 +9,48 @@ interface CheckboxProps { indeterminate?: boolean; } -const Checkbox: React.FC = ({ - checked, - onChange, - isDarkMode = false, +const Checkbox: React.FC = ({ + checked, + onChange, + isDarkMode = false, className = '', disabled = false, - indeterminate = false + indeterminate = false, }) => { return ( -