Compare commits

...

609 Commits

Author SHA1 Message Date
chamikaJ
eeec5b2b84 feat(navbar): implement new notification and invitation components
- Added NotificationDrawer and InvitationItem components to enhance the notification system.
- Refactored existing notification handling to improve user experience and maintainability.
- Introduced new styles and structure for notifications using Tailwind CSS for better visual consistency.
- Updated Navbar to include new components and improve overall layout.
- Created a centralized navRoutes file for better route management within the navbar.
2025-07-25 17:01:15 +05:30
chamikaJ
8380b354cc refactor(template-drawer): remove unused parameters from TemplateDrawer component
- Eliminated unused parameters in the TemplateDrawer component to clean up the code and improve readability.
2025-07-25 15:23:02 +05:30
chamikaJ
2aaf0fc19a feat(account-setup): refactor language handling in account setup process
- Updated language keys to use constants from the Language enum for better maintainability.
- Changed the language state selector to use 'lng' instead of 'language' for consistency.
- Adjusted the language change handler to accept ILanguageType for type safety.
2025-07-25 15:22:51 +05:30
Chamika J
65745e368f Merge branch 'release-v2.1.4' into chore/added-sign-up-survey 2025-07-25 13:03:03 +05:30
Chamika J
cabd05e0da Merge pull request #290 from Worklenz/imp/invite--improvement
Imp/invite  improvement
2025-07-25 13:01:29 +05:30
Chamika J
6e71a91d6c Merge pull request #288 from shancds/test/invitation-process
feat(assignee-selector): enhance member invitation functionality and …
2025-07-25 13:01:01 +05:30
chamikaJ
7dc3dedda5 feat(account-setup): enhance localization and UI for account setup process
- Added new language support and improved translations for account setup steps across multiple languages.
- Updated the organization step to streamline user input and enhance suggestions for organization names.
- Refactored task management components to improve user experience when adding and managing tasks.
- Removed outdated CSS for admin center components to simplify styling and improve maintainability.
- Introduced new UI elements and transitions for a more engaging account setup experience.
- Enhanced Redux state management to accommodate new features and localization updates.
2025-07-25 12:50:19 +05:30
shancds
944acf99db feat(project-member-drawer): filter out already invited members from the selection list
- Implemented logic to filter available members by excluding those already part of the current project.
- Updated the member selection dropdown to display only non-invited members, enhancing the user experience during the invitation process.
2025-07-25 12:07:43 +05:30
shancds
a9d0244ca2 fix(update-member-drawer): correct job title assignment in member update request
- Updated the job title assignment in the member update request to use the value from the form field instead of a previously hardcoded variable.
2025-07-25 11:31:36 +05:30
chamikaJ
b688f8e114 feat(account-setup): enhance account setup process with new survey and task management features
- Expanded localization files to include additional text for account setup steps in multiple languages.
- Introduced new components for the survey step, allowing users to provide feedback on their needs and preferences.
- Implemented task management features, enabling users to add and manage tasks during the account setup process.
- Enhanced the organization step with suggestions for organization names based on industry categories.
- Improved UI/UX with new design elements and transitions for a smoother user experience.
- Updated Redux state management to handle new survey and task data effectively.
- Added language switcher functionality to support multilingual users during the setup process.
2025-07-25 10:52:07 +05:30
shancds
e7e9cfce8c Merge branch 'release-v2.1.4' of https://github.com/Worklenz/worklenz into test/invitation-process 2025-07-25 09:48:29 +05:30
shancds
27605b4d68 feat(assignee-selector): enhance member invitation functionality and integrate project manager checks
- Added hooks for project manager status and authentication to manage member invitation visibility.
- Refactored dropdown toggle logic for improved readability and performance.
- Updated UI to conditionally render the invite member button based on user roles (admin or project manager).
- Cleaned up code formatting for better consistency and maintainability.
2025-07-25 09:47:09 +05:30
Chamika J
ff4b0ed315 Merge pull request #287 from Worklenz/imp/invite--improvement
Imp/invite  improvement
2025-07-25 08:52:44 +05:30
chamikaJ
fe7c15ced1 feat(surveys): implement account setup survey functionality
- Added new database migration to create survey-related tables for storing questions and responses.
- Developed SurveyController to handle fetching and submitting survey data.
- Created survey API routes for account setup, including endpoints for retrieving the survey and submitting responses.
- Implemented frontend components for displaying the survey and capturing user responses, integrating with Redux for state management.
- Enhanced localization files to include survey-related text for multiple languages.
- Added validation middleware for survey submissions to ensure data integrity.
2025-07-24 17:12:47 +05:30
chamikaJ
15ff69a031 refactor(styles): remove outdated HubSpot widget dark mode CSS overrides
- Eliminated unnecessary CSS rules for the HubSpot widget's dark mode styling to streamline the stylesheet.
- Cleaned up the customOverrides.css file by removing redundant background and box-shadow properties for improved maintainability.
2025-07-24 16:08:11 +05:30
Chamika J
070c643105 Merge pull request #283 from shancds/test/invitation-process
Test/invitation process
2025-07-24 10:30:54 +05:30
Chamika J
980af8bd4f Merge branch 'imp/invite--improvement' into test/invitation-process 2025-07-24 10:30:46 +05:30
chamikaJ
1931856d31 Merge branch 'main' of https://github.com/Worklenz/worklenz into fix/timelog-timezone-fix 2025-07-24 10:18:52 +05:30
Chamika J
fe3fb5e627 Merge pull request #285 from Worklenz/development
Development
2025-07-24 10:17:22 +05:30
Chamika J
ef47df804f Merge pull request #284 from Worklenz/fix/release-v2.1.3
feat(database): add performance indexes for optimized task queries
2025-07-24 10:16:34 +05:30
chamikaJ
7ea05d2982 feat(database): add performance indexes for optimized task queries
- Introduced multiple new indexes in the SQL schema to enhance query performance for tasks, including composite indexes for filtering and sorting.
- Added indexes for various task-related entities such as assignees, phases, labels, comments, and attachments to improve data retrieval efficiency.
- Implemented covering indexes to optimize task aggregation and search functionalities, ensuring faster access to frequently queried data.
2025-07-24 10:16:09 +05:30
chamikaJ
daa65465dd feat(reporting): add utility methods for date and time calculations in ReportingMembersController
- Introduced methods to calculate percentage, convert time formats, and determine date-related metrics such as days left, overdue status, and if a date is today.
- Updated existing logic in getSingleMemberProjects to utilize the new utility methods for improved clarity and maintainability.
2025-07-24 09:56:23 +05:30
chamikaJ
de26417247 refactor(reporting): enhance timezone handling and clean up migration
- Updated SQL queries in reporting controllers to join with the timezones table for accurate timezone retrieval.
- Refactored ReportingMembersController to extend ReportingControllerBaseWithTimezone for centralized timezone logic.
- Removed obsolete migration file that added a timezone column to the users table, as it is no longer needed.
2025-07-24 09:25:50 +05:30
chamiakJ
69b2fe1a90 feat(reporting): implement timezone support in reporting allocation and related components
- Added timezone handling in the getMemberTimeSheets method to ensure accurate date calculations based on user timezone.
- Created ReportingControllerBaseWithTimezone to centralize timezone-related logic for reporting.
- Introduced a migration to add a timezone column to the users table for better user experience.
- Updated frontend API services and hooks to include user's timezone in requests.
- Enhanced members time reports page to display time logs in the user's local timezone.
2025-07-24 07:50:01 +05:30
Chamika J
5c327a3a21 Merge pull request #281 from Worklenz/development
Development
2025-07-23 16:07:57 +05:30
Chamika J
123a912e64 Merge pull request #280 from Worklenz/fix/release-v2.1.3
Fix/release v2.1.3
2025-07-23 15:09:27 +05:30
chamikaJ
78516d8d6c feat(analytics-hubspot): modularize analytics and HubSpot integration
- Moved Google Analytics and HubSpot integration scripts to separate modules for better organization and maintainability.
- Implemented an AnalyticsManager class to handle Google Analytics initialization and privacy notice display.
- Created a HubSpotManager class to manage HubSpot widget loading and dark mode theming.
- Updated index.html to reference the new modules, improving code clarity and separation of concerns.
2025-07-23 14:36:50 +05:30
chamikaJ
9946c9a00e fix(upgrade-plans): adjust minimum seat value logic and enhance HubSpot widget dark mode styles
- Updated the minimum seat value logic in the upgrade plans component to allow the current seat count.
- Added CSS overrides for the HubSpot widget to ensure proper display in dark mode, removing unwanted backgrounds and shadows.
2025-07-23 13:24:58 +05:30
chamikaJ
4887383dc4 feat(upgrade-plans): add responsive styles and improve layout for upgrade plans component
- Introduced a new CSS file for responsive design in the upgrade plans component, enhancing mobile usability.
- Updated the upgrade plans component to utilize the new styles, ensuring a better layout on various screen sizes.
- Refactored seat count options logic for improved clarity and functionality.
2025-07-23 13:03:00 +05:30
chamikaJ
a6863d8280 refactor: update code to use centralized Ant Design imports
- Replaced direct import of '@ant-design/icons' with centralized import from '@/shared/antd-imports'
2025-07-23 12:07:48 +05:30
chamikaJ
edf81dbe57 refactor: update Ant Design imports to centralized path
- Replaced direct imports from 'antd' with centralized imports from '@/shared/antd-imports' to align with new import rules and improve maintainability.
2025-07-23 11:05:39 +05:30
shancds
300d4763f5 Enhance project member management with search functionality and localization updates
- Implemented search functionality for project members in the backend, allowing users to filter members by name or email.
- Updated frontend components to include a search input for members, improving user experience.
- Added localization strings for the search placeholder in multiple languages (Albanian, German, English, Spanish, Portuguese).
- Refactored SQL queries for better performance and clarity in fetching project members.
2025-07-23 10:49:00 +05:30
chamikaJ
80f5febb51 feat(antd-imports): establish centralized import rules for Ant Design components
- Introduced a new rules file for Ant Design component imports, enforcing the use of centralized imports from `@antd-imports` to enhance tree-shaking, maintainability, and performance.
- Updated various components to replace direct imports from 'antd' and '@ant-design/icons' with the centralized import path.
- Refactored existing components to utilize memoization and callbacks for improved performance and readability.
- Enhanced the billing and configuration components with updated styles and improved user experience.
2025-07-23 10:33:55 +05:30
shancds
aaaac09212 Merge branch 'fix/release-v2.1.3' of https://github.com/Worklenz/worklenz into test/invitation-process 2025-07-23 08:50:54 +05:30
shancds
c4400d178f Refactor invite components to comment out unused UI elements
- Commented out the button for copying team and project links in the invite team members and project member invite drawer components, respectively, to streamline the UI.
- Adjusted conditional rendering for the checkbox related to project invitations to improve code clarity and maintainability.
2025-07-23 08:48:01 +05:30
chamiakJ
a6286eb2b8 feat(migrations): add README for node-pg-migrate and enhance frontend configuration
- Created a README file for database migrations using node-pg-migrate, detailing commands, file format, best practices, and an example migration.
- Added a Vite configuration file for the frontend, including plugin setup, alias resolution, build optimizations, and responsive design adjustments for task list components.
- Updated i18n configuration to preload the 'home' namespace for improved localization.
- Enhanced task list styling with responsive design features for better mobile usability.
2025-07-23 07:46:39 +05:30
Chamika J
ee75aead78 Merge pull request #278 from Worklenz/fix/release-v2.1.3
Fix/release v2.1.3
2025-07-22 17:26:45 +05:30
chamikaJ
e3c002b088 style(task-list): enhance styling for task list components
- Updated the EmptyGroupDropZone component to include top and bottom borders for better visual separation.
- Adjusted the AddTaskRow component by removing the bottom border for a cleaner appearance.
2025-07-22 17:25:37 +05:30
chamikaJ
3beed3dae6 feat(auth): update password requirements and localization for signup
- Updated password label and placeholder to "Password" and "Enter your password" across multiple languages.
- Added password guideline for minimum requirements in English, German, Spanish, Portuguese, Albanian, and Chinese.
- Introduced maximum character requirement for passwords in all supported languages.
- Enhanced password validation messages to improve user experience during signup.
2025-07-22 17:25:25 +05:30
chamikaJ
33aace71c8 refactor(tasks): improve code readability and formatting in tasks-controller-v2
- Refactored import statements for better organization and clarity.
- Enhanced formatting of methods and conditionals for improved readability.
- Updated task filtering methods to maintain consistent formatting and style.
- Added performance logging for deprecated methods to assist in monitoring usage.
- Cleaned up unnecessary comments and improved inline documentation for better understanding.
2025-07-22 17:12:06 +05:30
shancds
da791e2cb7 Enhance project member invitation feature with new localization and UI updates
- Added new localization strings for "invite member" and "also invite to project" across multiple languages (Albanian, German, English, Spanish, Portuguese, Chinese).
- Updated the project member invite drawer to conditionally display UI elements based on the context of the invitation (from assigner or not).
- Introduced a new state management feature to track if the invitation is initiated from the assignee selector, improving user experience.
2025-07-22 16:21:18 +05:30
chamikaJ
354b9422ed feat(tasks): add color_code_dark to task groups and enhance task list display
- Introduced color_code_dark property to task groups for improved theming support.
- Updated task list components to utilize color_code_dark based on the current theme mode.
- Enhanced empty state handling in task list to dynamically create an unmapped group for better user experience.
- Refactored task management slice to support dynamic group creation for unmapped tasks.
2025-07-22 16:08:41 +05:30
shancds
3373dccc58 Update localization and UI for team member invitation feature
- Changed button labels for inviting team members to "Send Invitation" across multiple languages (Albanian, German, English, Spanish, Portuguese, Chinese).
- Updated the invite team members component to use a modal instead of a drawer for a better user experience.
- Added a new button for copying the team link in the modal footer, enhancing functionality.
2025-07-22 15:21:13 +05:30
shancds
06da0d20b9 Enhance project member drawer localization and UI
- Added new localization strings for "members" and "copy project link" in multiple languages (Albanian, German, English, Spanish, Portuguese, Chinese).
- Updated the project member invite drawer to use a modal instead of a drawer for improved user experience.
- Included a button in the modal footer for copying the project link, enhancing functionality for users.
2025-07-22 14:43:04 +05:30
chamikaJ
256f1eb3a9 feat(task-list): enhance empty state display in task list component
- Added EmptyListPlaceholder component to visually represent the empty state in the task list.
- Adjusted styling and layout for the empty group drop zone to improve user experience.
- Removed unused progress properties from the task group data structure for cleaner code.
2025-07-22 12:46:09 +05:30
chamikaJ
5f86ba6b13 feat(auth): implement new authentication pages and refactor routes
- Added new authentication pages: LoginPage, SignupPage, ForgotPasswordPage, AuthenticatingPage, LoggingOutPage, and VerifyResetEmailPage.
- Refactored auth routes to use updated component names for better consistency and clarity.
- Enhanced user experience with improved loading states and error handling across authentication processes.
2025-07-22 12:27:05 +05:30
shancds
5addcee0b2 Merge branch 'release/v2.1.2' of https://github.com/Worklenz/worklenz into test/invitation-process 2025-07-21 09:54:09 +05:30
shancds
3419d7e81d Merge branch 'feature/project-gantt-chart' of https://github.com/Worklenz/worklenz into test/invitation-process 2025-07-21 09:50:23 +05:30
chamikaJ
78d960bf01 feat(gantt): introduce advanced Gantt chart components and demo page
- Added new components for an advanced Gantt chart, including AdvancedGanttChart, GanttGrid, DraggableTaskBar, and TimelineMarkers.
- Implemented a demo page (GanttDemoPage) to showcase the functionality of the new Gantt chart components.
- Enhanced project roadmap features with ProjectRoadmapGantt and related components for better project management visualization.
- Introduced sample data for testing and demonstration purposes, improving the user experience in the Gantt chart interface.
- Updated main routes to include the new Gantt demo page for easy access.
2025-07-20 22:05:42 +05:30
Chamika J
8dc3133814 Merge pull request #275 from shancds/test/kanban-order-v1.2.3
Enhance EnhancedKanbanBoardNativeDnD to support phase reordering
2025-07-17 16:30:39 +05:30
shancds
1709fad733 Add drag-and-drop data transfer support in EnhancedKanbanBoardNativeDnD 2025-07-17 15:51:23 +05:30
shancds
7f71e8952b Enhance EnhancedKanbanBoardNativeDnD with task priority updates and socket integration
- Added functionality to handle task priority changes, emitting updates via socket for real-time synchronization.
- Updated the EnhancedKanbanBoardNativeDnD component to include new logic for managing task priorities within the drag-and-drop interface.
- Cleaned up console log statements in the useTaskSocketHandlers hook for improved performance monitoring.
2025-07-17 12:52:40 +05:30
shancds
22d2023e2a Update phase handling in EnhancedKanbanBoardNativeDnD component
- Modified phase update logic to prevent setting phase_id for 'Unmapped' phases, ensuring only valid phases are processed.
- Cleaned up unnecessary whitespace in the task reordering section for improved code clarity.
2025-07-17 11:54:33 +05:30
shancds
fa08463e65 Enhance localization support in Kanban board
- Added new localized messages for error handling, task management, and filter loading across multiple languages (Albanian, German, English, Spanish, Portuguese, Chinese).
- Updated the EnhancedKanbanBoardNativeDnD component to utilize these localized messages for improved user experience during task operations and error notifications.
2025-07-17 11:10:23 +05:30
shancds
7226932247 Enhance EnhancedKanbanBoardNativeDnD to support phase reordering
- Integrated phase reordering functionality within the EnhancedKanbanBoardNativeDnD component.
- Added logic to fetch phases by project ID and update phase order through API calls.
- Updated drag-and-drop handling to accommodate reordering of phases alongside existing status-based reordering.
2025-07-17 10:56:06 +05:30
Chamika J
6adf40f5a6 Merge pull request #274 from shancds/test/kanban-order-v1.2.2
Test/kanban order v1.2.2
2025-07-16 09:35:12 +05:30
shancds
f03f6e6f5d Implement task order updates and socket emissions in EnhancedKanbanBoardNativeDnD
- Added a utility function to recalculate task orders for all groups based on the specified grouping criteria (status, priority, phase).
- Updated task drag-and-drop logic to handle reordering within the same group and across different groups.
- Enhanced socket emissions to send full task order updates, including task details and indices, improving synchronization with the backend.
2025-07-15 18:17:32 +05:30
Chamika J
a112d39321 Merge pull request #272 from Worklenz/development
Development
2025-07-15 17:20:59 +05:30
Chamika J
4788294bc4 Merge pull request #271 from Worklenz/release/v2.1.2
Release/v2.1.2
2025-07-15 17:20:25 +05:30
shancds
d7416ff793 Merge branch 'test/kanban-right-click-feature-v1.2.1' of https://github.com/shancds/worklenz-open-source into test/kanban-order-v1.2.2 2025-07-15 16:46:43 +05:30
shancds
d89247eb02 Add delete phase confirmation modal with localization support
- Implemented a confirmation modal for deleting phases in the Kanban board.
- Integrated localized messages for delete phase prompts in multiple languages, enhancing user experience.
- Updated KanbanGroup component to utilize the new confirmation modal for phase deletions.
2025-07-15 16:45:57 +05:30
shancds
5318f95037 Merge branch 'release/v2.1.2' of https://github.com/Worklenz/worklenz into test/kanban-order-v1.2.2 2025-07-15 16:39:35 +05:30
shancds
c80b00ec76 Add delete status confirmation modal with localization support
- Implemented a confirmation modal for deleting statuses in the Kanban board.
- Integrated localized messages for delete status prompts in multiple languages, enhancing user experience.
- Removed the previous portal-based confirmation approach in favor of Ant Design's Modal component for better consistency and usability.
2025-07-15 16:34:03 +05:30
shancds
f48476478a Implement task deletion functionality in TaskCard component
- Added context menu for task deletion with confirmation modal.
- Integrated localization for delete task prompts in multiple languages.
- Updated TaskCard to handle task deletion logic, including dispatching actions to update the state and emit socket events for task progress.
2025-07-15 16:19:40 +05:30
chamikaJ
737f7cada2 refactor(App): streamline cache cleanup process on logout
- Removed dynamic import of CacheCleanup in the App component, directly utilizing the imported utility for clearing caches and reloading the application on user logout.
- This change simplifies the logout process and improves code readability.
2025-07-15 16:11:30 +05:30
chamikaJ
833879e0e8 feat(logout): implement cache cleanup and service worker unregistration on logout
- Added a new utility, CacheCleanup, to handle clearing caches and unregistering the service worker during user logout.
- Enhanced the LoggingOutPage to utilize CacheCleanup for clearing local session and caches before redirecting to the login page.
- Introduced ModuleErrorBoundary to manage module loading errors, providing user feedback and options to retry or reload the application.
- Updated App component to include global error handlers for improved error management related to module loading issues.
2025-07-15 16:08:07 +05:30
chamikaJ
cb5610d99b feat(timer): add useTimerInitialization hook for managing running timers
- Introduced a new custom hook, useTimerInitialization, to fetch and initialize running timers from the backend when the project view loads.
- Integrated the hook into the ProjectView component to update Redux state with active timers and their corresponding task details.
- Enhanced error handling and logging for timer initialization to improve debugging and user experience.
2025-07-15 15:57:01 +05:30
chamikaJ
0434bbb73b refactor(account-setup): update ProjectStep to use custom dispatch and refresh user session
- Replaced useDispatch with useAppDispatch for improved state management in ProjectStep component.
- Added logic to refresh user session and update user state after template setup completion.
- Enhanced error handling for session refresh to ensure robustness in user experience.
2025-07-15 15:40:06 +05:30
chamikaJ
6e911d79fc feat(auth): add combined AuthAndSetupGuard for route protection
- Introduced AuthAndSetupGuard to enforce both authentication and setup completion for protected routes.
- Updated main routes to utilize the new guard, ensuring users are redirected appropriately based on their authentication and setup status.
- Adjusted setup route to only require authentication, allowing access without completed setup.
- Refactored account setup component to use custom dispatch hook for improved state management and added session refresh after setup completion.
2025-07-15 15:34:43 +05:30
chamikaJ
0bb748cf89 feat(database-migrations): implement grouping-specific sort orders and constraints
- Added new sort order columns for tasks based on different grouping types (status, priority, phase, member).
- Updated database functions to handle these new sort orders, ensuring proper task updates during sorting operations.
- Implemented a migration to fix existing sort order constraint violations, ensuring data consistency across projects.
- Enhanced functions to avoid sort order conflicts and maintain unique sort orders within each project.
2025-07-15 15:19:16 +05:30
Chamika J
ba5d4975af Merge pull request #270 from shancds/test/row-kanban-board-v1.2.0
Enhance TaskCard accessibility by adding title attribute to task name
2025-07-15 15:09:57 +05:30
shancds
d4620148bd Update TaskProgressCircle to visually indicate task completion
- Changed the stroke color of the progress circle to green when progress reaches 100%.
- Added a green checkmark icon for completed tasks, enhancing visual feedback.
- Retained progress percentage display for tasks that are not yet complete, ensuring clarity in task status.
2025-07-15 14:48:26 +05:30
shancds
8d7d54be78 Enhance TaskCard accessibility by adding title attribute to task name
- Updated TaskCard component to include a title attribute on the task name span for improved accessibility and better user experience when hovering over task names.
2025-07-15 14:28:45 +05:30
Chamika J
c34b94c7db Merge pull request #269 from shancds/test/row-kanban-board-v1.2.0
Test/row kanban board v1.2.0
2025-07-15 14:24:24 +05:30
chamikaJ
55a0028e26 refactor(drag-and-drop): replace console logging with error logging
- Integrated an error logging utility to replace console warnings and errors in the `useDragAndDrop` hook, enhancing error tracking and debugging.
- Removed unnecessary console logs related to drag-and-drop operations, streamlining the code and improving performance.
2025-07-15 14:24:24 +05:30
chamikaJ
17371200ca feat(drag-and-drop): enhance task grouping updates and socket event handling
- Updated the `useDragAndDrop` hook to emit specific grouping field change events (phase, priority, status) when tasks are moved between groups.
- Refactored the task management slice to prevent direct updates to task grouping fields during drag-and-drop operations, ensuring these updates are handled via socket events after backend confirmation.
- Introduced a new socket handler for task sort order changes to update task properties based on backend responses, improving synchronization between frontend and backend task states.
2025-07-15 14:22:27 +05:30
shancds
83044077d3 Enhance task status handling and progress visualization in Kanban board
- Introduced a mechanism to emit a socket event when a task's status changes during drag-and-drop operations.
- Updated TaskCard to consistently display the progress circle regardless of task completion state.
- Improved TaskProgressCircle to handle complete_ratio and progress more robustly, ensuring accurate visual representation.
2025-07-15 14:13:37 +05:30
Chamika J
a03d9ef6a4 Merge pull request #268 from Worklenz/fix/task-sort-order
Fix/task sort order
2025-07-15 13:38:49 +05:30
Chamika J
fca8ace10d Merge pull request #267 from Worklenz/feature/show-multiples-levels-of-subtaks-in-task-list
Feature/show multiples levels of subtaks in task list
2025-07-15 13:31:10 +05:30
chamikaJ
d970cbb626 feat(task-management): enhance task timer synchronization and color handling
- Updated `CustomColordLabel` and `CustomNumberLabel` components to improve color handling by removing the alpha channel logic and implementing a dynamic text color based on background luminance.
- Enhanced task management slice to preserve timer state when fetching tasks, ensuring active timers are maintained across updates.
- Modified socket handlers to synchronize timer state between task slices, improving consistency in task time tracking.
- Refactored `useTaskTimer` hook to streamline local and Redux state synchronization for timer management.
2025-07-15 13:30:59 +05:30
chamikaJ
6d8c475e67 refactor(sort-orders): remove outdated deployment and implementation guides
- Deleted the `DEPLOYMENT_GUIDE_SORT_ORDERS.md` and `SEPARATE_SORT_ORDERS_IMPLEMENTATION.md` files as they are no longer relevant following the recent updates to the sort orders feature.
- Introduced new migration scripts to address duplicate sort orders and ensure data integrity across the updated task sorting system.
- Updated database schema to include new sort order columns and constraints for improved performance and organization.
- Enhanced backend functions and frontend components to support the new sorting logic and maintain user experience during task organization.
2025-07-15 13:18:51 +05:30
shancds
a1c0cef149 Remove PerformanceMonitor and VirtualizedTaskList components along with their associated styles to streamline the enhanced Kanban board. Update TaskCard to include a progress circle for better task visualization. 2025-07-15 12:17:16 +05:30
shancds
8f098143fd Merge branch 'main' of https://github.com/Worklenz/worklenz into test/row-kanban-board-v1.2.0 2025-07-15 09:00:38 +05:30
chamiakJ
407dc416ec feat(sort-orders): implement separate sort orders for task groupings
- Introduced new columns for `status_sort_order`, `priority_sort_order`, `phase_sort_order`, and `member_sort_order` in the tasks table to maintain distinct sort orders for each grouping type.
- Updated database functions to handle grouping-specific sort orders and avoid unique constraint violations.
- Enhanced backend socket handlers to emit changes based on the selected grouping.
- Modified frontend components to support drag-and-drop functionality with the new sort order fields, ensuring task organization is preserved across different views.
- Added comprehensive migration scripts and verification steps to ensure smooth deployment and backward compatibility.
2025-07-15 07:44:15 +05:30
chamikaJ
3d67145af7 refactor(task-list): adjust subtask indentation for improved visual hierarchy
- Reduced spacing for level 1 subtasks and increased spacing for deeper levels in TaskRowWithSubtasks and TitleColumn components.
- Enhanced comments to clarify indentation logic for better maintainability.
2025-07-14 12:37:54 +05:30
chamikaJ
1c981312d4 feat(task-drawer): enhance task navigation and hierarchy display
- Added functionality to navigate back to parent tasks for sub-tasks in the task drawer.
- Introduced a breadcrumb component to visually represent the hierarchy of tasks, improving user navigation.
- Updated task drawer header to display the current task name with truncation and tooltip for better readability.
- Enhanced styling for task name display and breadcrumb for improved user experience.
2025-07-14 12:28:04 +05:30
chamikaJ
02d814b935 refactor(task-list): enhance task row components with depth handling
- Added depth and maxDepth props to TaskRow, TaskRowWithSubtasks, and TitleColumn components to manage nested subtasks more effectively.
- Updated AddSubtaskRow to support depth for proper indentation and visual hierarchy.
- Improved styling for subtasks based on their depth level, ensuring better visual distinction.
- Adjusted task management slice to utilize actual subtask counts from the backend for accurate display.
2025-07-14 12:04:31 +05:30
Chamika J
e87f33dcc8 Merge pull request #265 from Worklenz/development
Development
2025-07-14 11:37:47 +05:30
Chamika J
6286d4315d Merge pull request #262 from Worklenz/fix/home-page-tasks
fix(home-page): exclude archived projects from task queries and updat…
2025-07-14 11:37:22 +05:30
Chamika J
a1234b8af0 Merge pull request #264 from Worklenz/development
Development
2025-07-14 08:57:44 +05:30
Chamika J
bc0a62002b Merge pull request #263 from Worklenz/chore/updated-readme.md
Update README and setup documentation for improved clarity and requir…
2025-07-14 07:54:44 +05:30
chamikaJ
52eca27619 Update README and setup documentation for improved clarity and requirements
- Added license and version badges to the README for better visibility.
- Updated Node.js version requirement from v16 to v20 in both README and setup documentation.
- Enhanced the "Getting Started" section with clearer instructions for Docker setup and manual installation.
- Removed Grunt-related files and dependencies to streamline the backend setup.
- Updated deployment instructions for local and remote environments, including new access URLs and video guides.
2025-07-14 07:53:42 +05:30
chamiakJ
e4c9e22972 refactor(tasks-list): update column widths for improved layout and responsiveness
- Adjusted column widths in the TasksList component to use percentage-based values for better adaptability across different screen sizes.
- Enhanced text styling for task names and project names to improve readability and visual consistency.
2025-07-12 07:27:32 +05:30
chamiakJ
20e7d3c51a fix(home-page): exclude archived projects from task queries and update project color display
- Added a condition to exclude archived projects from the task retrieval queries in the HomePageController.
- Updated the TasksList component to display the project color instead of the phase color for better visual representation.
2025-07-12 06:46:23 +05:30
Chamika J
6d5aa0ccab Merge pull request #261 from Worklenz/development
Development
2025-07-11 21:55:19 +05:30
Chamika J
7618ae7c6a Merge pull request #260 from Worklenz/fix/release-v.2.1.1
Fix/release v.2.1.1
2025-07-11 21:54:39 +05:30
chamikaJ
808731387b feat(localization): update task and project view translations across multiple languages
- Enhanced localization files for task management and project view headers in Albanian, German, Spanish, Portuguese, Chinese, and others.
- Standardized translations for terms related to tasks, project settings, and notifications, including default task names.
- Improved clarity and consistency in language usage to enhance user experience across different locales.
2025-07-11 18:29:15 +05:30
chamikaJ
502726cd83 chore(release-notes): update version to 2.1.1 and enhance feature descriptions
- Changed release version from 2.1.0 to 2.1.1 in the email template.
- Updated feature descriptions to reflect new functionalities, including easier management of statuses and phases, a task progress bar, and a right-click context menu.
- Improved UI elements for clarity and added images to enhance visual representation of new features.
2025-07-11 18:21:03 +05:30
chamikaJ
a26d8d0f90 feat(task-management): enhance task localization and progress visualization
- Added localization entries for task statuses (To Do, In Progress, Done) across multiple languages including Albanian, German, Spanish, Portuguese, and Chinese.
- Updated the GroupProgressBar component to improve visual representation of task progress with distinct color coding for each status.
- Enhanced TaskGroupHeader to calculate and display group progress dynamically based on task completion and status distribution.
- Integrated a new Convert To Subtask Drawer for improved task management functionality.
2025-07-11 18:09:03 +05:30
chamikaJ
747088e7cc refactor(task-management): enhance empty state visuals and improve layout
- Updated the empty state message styling in VirtualizedTaskList and TaskGroup components for better visibility and user experience.
- Adjusted padding and height in various components to create a more consistent and visually appealing layout.
- Removed console log statements from TaskPhaseDropdown to clean up the codebase.
2025-07-11 17:37:22 +05:30
chamikaJ
affbbbffbf feat(task-phases): enhance phase creation with custom naming and localization updates
- Updated the phase creation logic to allow custom names, defaulting to a generated name if none is provided.
- Modified localization files for multiple languages to improve phase-related text consistency and clarity.
- Enhanced the UI components to reflect the new phase naming functionality and ensure proper integration with the task management system.
- Added dark mode styling for confirmation modals to improve user experience across themes.
2025-07-11 17:26:21 +05:30
shancds
d3023618e1 fix(enhanced-kanban): remove unused Skeleton import in EnhancedKanbanBoardNativeDnD 2025-07-11 17:00:48 +05:30
chamikaJ
12b430a349 feat(task-context-menu): implement context menu for task actions
- Added TaskContextMenu component to provide a context menu for task-related actions such as assigning, archiving, deleting, and moving tasks.
- Integrated context menu into TitleColumn component, allowing users to access task actions via right-click.
- Enhanced user experience by providing immediate feedback for actions like assigning tasks and archiving.
- Improved code organization by separating context menu logic into its own component.
2025-07-11 16:41:30 +05:30
Chamika J
2f3e555b5a Merge pull request #259 from shancds/test/row-kanban-board-v1.1.9
fix(enhanced-kanban): integrate socket event handling for real-time u…
2025-07-11 16:23:42 +05:30
shancds
2498effce3 fix(enhanced-kanban): integrate socket event handling for real-time updates
- Added useTaskSocketHandlers hook to manage socket event handlers for real-time task updates, improving code organization and readability.
- Removed inline socket handling logic to streamline the component and enhance maintainability.
2025-07-11 16:20:21 +05:30
Chamika J
2ad3c2dcd4 Merge pull request #258 from shancds/test/row-kanban-board-v1.1.8
fix(enhanced-kanban): refine loading state visuals in EnhancedKanbanB…
2025-07-11 16:10:49 +05:30
shancds
6226ae35ff fix(task-card): add title attribute for better accessibility
- Added a title attribute to the task name div in TaskCard component to improve accessibility and provide additional context on hover.
2025-07-11 16:01:01 +05:30
chamikaJ
26de439fab feat(task-management): add progress statistics and visual representation for task groups
- Implemented progress calculations for tasks grouped by priority and phase, including todo, doing, and done counts.
- Introduced a new GroupProgressBar component to visually represent task progress in the TaskGroupHeader.
- Updated TaskGroupHeader and TaskListV2Table to integrate progress data and display the progress bar conditionally based on grouping.
- Enhanced local storage handling for grouping preferences in the task management feature.
2025-07-11 15:54:43 +05:30
shancds
295d7a92df fix(kanban-group): update empty drop zone behavior and visuals
- Modified the condition for rendering the empty drop zone to exclude the currently hovered group.
- Updated the background gradient colors for the empty drop zone to enhance visual appeal and consistency with the theme.
2025-07-11 15:45:47 +05:30
shancds
e20ab86d6e fix(enhanced-kanban): refine loading state visuals in EnhancedKanbanBoardNativeDnD
- Removed unnecessary Card wrapper around loading skeletons for a cleaner layout.
- Adjusted loading state visuals to enhance user experience during data fetching.
2025-07-11 15:12:22 +05:30
Chamika J
5c938586b8 Merge pull request #257 from shancds/test/row-kanban-board-v1.1.8
Test/row kanban board v1.1.8
2025-07-11 15:10:36 +05:30
shancds
93b67fba07 fix(enhanced-kanban): improve code readability and loading state visuals
- Refactored conditional statements for better clarity and consistency.
- Enhanced loading state visuals by replacing static text with a dynamic skeleton loader, improving user experience during data fetching.
2025-07-11 15:07:33 +05:30
chamikaJ
e4dfae9f1d feat(database): optimize task sorting functions and introduce bulk update capability
- Added new SQL migration to fix window function errors in task sorting functions, replacing CTEs with direct updates for better performance.
- Introduced a bulk update function for task sort orders, allowing multiple updates in a single call to improve efficiency.
- Updated socket command to support bulk updates, enhancing the task sorting experience in the frontend.
- Simplified task update handling in the frontend to utilize the new bulk update feature, improving overall performance and user experience.
2025-07-11 14:46:07 +05:30
shancds
0efcbf448b fix(task-card): improve loading state visual feedback
- Replaced loading text with a skeleton loader for subtasks in TaskCard component, enhancing user experience during data fetching.
2025-07-11 14:34:49 +05:30
Chamika J
f2f12a2dfa Merge pull request #256 from shancds/test/row-kanban-board-v1.1.8
feat(enhanced-kanban): enhance section creation with category selecti…
2025-07-11 14:07:09 +05:30
shancds
ea37b55078 fix(kanban-group): update section name handling in status update
- Removed unused section name generation and replaced it with trimmed name input for better consistency.
- Ensured that the updated name is set correctly after a successful status update, improving data integrity.
2025-07-11 14:06:11 +05:30
chamikaJ
cc0ff20ca1 feat(localization): update translation keys for phase management
- Revised translation keys in Albanian, German, English, Spanish, Portuguese, and Chinese for improved clarity and consistency in phase management.
- Adjusted phrases related to phase naming, options, and actions to enhance user experience across multiple languages.
- Added new keys for creating and canceling phases to align with recent UI updates in the ManagePhaseModal component.
2025-07-11 14:04:02 +05:30
chamikaJ
6b58709848 feat(task-management): enhance status management with drag-and-drop functionality
- Updated ManageStatusModal to support drag-and-drop for reordering statuses and moving them between categories.
- Introduced CategorySection component for better organization of statuses by category.
- Added validation to prevent moving the last status out of a category, ensuring each category retains at least one status.
- Enhanced localization for task management, updating translation keys across multiple languages for improved clarity and consistency.
2025-07-11 13:50:35 +05:30
shancds
f2b1262e3d feat(enhanced-kanban): enhance section creation with category selection and input handling
- Added state management for section creation, including input focus and category selection.
- Implemented dropdown for category selection with visual feedback and improved accessibility.
- Refactored section creation logic to handle user input and category assignment more effectively.
- Enhanced user experience by managing input focus and handling outside clicks to close dropdowns.
2025-07-11 13:45:13 +05:30
Chamika J
7def564950 Merge pull request #255 from shancds/test/row-kanban-board-v1.1.7
feat(assignee-selector): add kanbanMode prop and enhance styling
2025-07-11 11:19:45 +05:30
chamikaJ
278e221c75 feat(task-list): add TaskListSkeleton component for improved loading state
- Introduced TaskListSkeleton to provide a visual loading state for the task list, enhancing user experience during data fetching.
- Updated TaskListV2Table to utilize TaskListSkeleton instead of a generic Skeleton component, allowing for a more tailored loading interface.
- The new skeleton component includes customizable column widths and multiple rows to better represent the task list structure while loading.
2025-07-11 11:18:30 +05:30
shancds
d9a5f76449 feat(assignee-selector): add kanbanMode prop and enhance styling
- Introduced kanbanMode prop to AssigneeSelector for improved functionality in kanban view.
- Updated styling in AssigneeSelector to adjust z-index for better overlay management.
- Enhanced TaskCard to include LazyAssigneeSelectorWrapper, integrating the new prop for task assignment in kanban mode.
2025-07-11 11:17:40 +05:30
Chamika J
b9b707410d Merge pull request #254 from shancds/test/row-kanban-board-v1.1.7
feat(kanban): implement portal for delete confirmation modal
2025-07-11 10:42:39 +05:30
shancds
87675cc73c feat(kanban): implement portal for delete confirmation modal
- Introduced a Portal component to render the delete confirmation modal outside the main DOM hierarchy, improving UI responsiveness.
- Updated the delete confirmation modal to utilize the Portal for better overlay management and user experience.
- Enhanced styling and interaction handling for the modal, ensuring it aligns with the application's theme and accessibility standards.
2025-07-11 10:37:53 +05:30
chamikaJ
0e083868cb chore: moved locale files 2025-07-11 09:37:50 +05:30
chamiakJ
94977f7255 feat(performance): enhance application performance with optimizations and monitoring
- Updated package dependencies for improved localization support and performance.
- Introduced CSS performance optimizations to prevent layout shifts and enhance rendering efficiency.
- Implemented asset preloading and lazy loading strategies for critical components to improve load times.
- Enhanced translation loading with optimized caching and background loading strategies.
- Added performance monitoring utilities to track key metrics and improve user experience.
- Refactored task management components to utilize new performance features and ensure efficient rendering.
- Introduced new utility functions for asset and CSS optimizations to streamline resource management.
2025-07-10 20:39:15 +05:30
chamiakJ
cf686ef8c5 feat(task-management): introduce modals for managing phases and statuses
- Added CreateTaskModal for task creation with integrated status management.
- Implemented ManagePhaseModal and ManageStatusModal for phase and status management, respectively, featuring drag-and-drop functionality.
- Enhanced UI with new CSS styles for dark mode and improved accessibility.
- Updated filter data loading to include phases and statuses for better task management experience.
- Ensured all new components are responsive and align with existing design patterns.
2025-07-10 18:13:41 +05:30
chamiakJ
857b48e225 feat(localization): add new translation keys for task management
- Updated localization JSON files for Albanian, German, English, Spanish, Portuguese, and Chinese to include new keys for managing statuses and phases.
- Enhanced existing translations for clarity and consistency across multiple languages.
- Ensured that new keys align with recent UI changes to improve user experience in task management features.
2025-07-10 16:25:13 +05:30
chamiakJ
f846230d59 feat(localization): update project list translations and add new keys
- Enhanced localization JSON files for multiple languages, including Albanian, German, English, Spanish, Portuguese, and Chinese.
- Updated existing translation keys for consistency and clarity, particularly for task progress, archive confirmations, and filtering instructions.
- Introduced new keys for improved user experience, such as "yes", "no", "list", "group", and "noPermission".
- Ensured all translations align with the latest UI changes for better internationalization support.
2025-07-10 14:16:51 +05:30
chamiakJ
bcfa18b1e8 feat(pwa): implement service worker and PWA enhancements
- Added service worker (sw.js) for offline functionality, caching strategies, and performance improvements.
- Registered service worker in App component to manage updates and offline readiness.
- Introduced ServiceWorkerStatus component to display connection status and provide cache management controls.
- Created manifest.json for PWA configuration, including app name, icons, and display settings.
- Updated index.html with PWA meta tags and links to support mobile web app capabilities.
- Refactored authentication guards to utilize useAuthStatus hook for improved user state management.
- Removed deprecated unregister-sw.js file to streamline service worker management.
2025-07-10 14:07:03 +05:30
chamiakJ
bb8e6ee60f refactor(email): enhance email validation and improve bounced email handling
- Added isValidateEmail utility function to validate email addresses before sending.
- Updated email filtering logic to remove empty, null, undefined, and invalid emails from the recipient list.
- Reversed the iteration order in removeMails function to prevent index issues while splicing bounced emails.
- Ensured that valid emails are present after filtering before proceeding with the email sending process.
2025-07-10 12:28:52 +05:30
chamiakJ
6ebdd78855 feat(task-timer): add timer start and stop handlers for task management
- Implemented handleTimerStart and handleTimerStop functions to manage task timer state via socket events.
- Updated useTaskSocketHandlers to register new socket event listeners for timer actions.
- Enhanced useTaskTimer to retrieve active timer state from both the old and new task management slices.
- Added activeTimer property to Task type for tracking the start timestamp of active timers.
2025-07-10 12:27:15 +05:30
chamiakJ
70cca5d4c0 refactor(task-list): restructure TaskRow and introduce new column components
- Refactored TaskRow to simplify state management and enhance readability by extracting logic into custom hooks.
- Introduced new components for rendering task columns, including DatePickerColumn, TitleColumn, and various column types for better modularity.
- Improved task name editing functionality with better state handling and click outside detection.
- Streamlined date handling and formatting within the task row for improved user experience.
2025-07-10 12:17:22 +05:30
chamiakJ
6448d24e20 refactor(task-list): simplify subtask handling in AddSubtaskRow and TaskRowWithSubtasks
- Updated AddSubtaskRow to remove rowId dependency from onSubtaskAdded and onActivate callbacks, streamlining the subtask addition process.
- Enhanced input handling to maintain focus and visibility after adding a subtask.
- Refactored TaskRowWithSubtasks to consolidate add subtask row management, ensuring a single add subtask row is displayed when not loading.
2025-07-10 12:00:56 +05:30
chamiakJ
5fb2633bc5 refactor(task-drawer): update localization keys for created and updated timestamps
- Modified localization JSON files for multiple languages to use double curly braces for variable interpolation in the createdBy and updatedTime fields.
- Ensured consistency across English, German, Spanish, Portuguese, Albanian, and Chinese translations for better formatting of dynamic content.
2025-07-09 22:51:20 +05:30
chamiakJ
75c55fff21 refactor(search): improve SQL search handling and optimize project list component
- Enhanced search handling in WorklenzControllerBase to properly escape single quotes, preventing SQL syntax errors.
- Refactored search logic in ProjectList to maintain reference stability and improve performance during debounced searches.
- Removed unnecessary console logs and optimized loading state management for better user experience.
2025-07-09 22:38:58 +05:30
chamikaJ
8f5de8f1a1 refactor(task-management): update search handling and improve task filtering
- Modified search handling to utilize the taskManagement slice for consistent state management across components.
- Enhanced placeholder text in search filters for better user guidance.
- Updated task fetching logic to ensure accurate search value retrieval from the correct state slice.
2025-07-09 17:11:15 +05:30
chamikaJ
db9b481e8d refactor(task-list): enhance task addition functionality in TaskListV2Table and AddTaskRow
- Introduced state management for dynamic add task rows in TaskListV2Table, allowing real-time updates when tasks are added.
- Updated handleTaskAdded to manage new task row creation based on group ID.
- Enhanced AddTaskRow to support auto-focus functionality and unique row identification for improved user experience during task addition.
- Refactored input handling in AddTaskRow to maintain focus and streamline task creation process.
2025-07-09 17:02:37 +05:30
Chamika J
cdd22e5f2f Merge pull request #252 from shancds/test/row-kanban-board-v1.1.7
Test/row kanban board v1.1.7
2025-07-09 16:58:46 +05:30
shancds
635b5ce8e1 feat(task-drawer): add functionality to hide task drawer on task deletion
- Imported setShowTaskDrawer action to manage task drawer visibility.
- Updated TaskDrawerHeader to dispatch setShowTaskDrawer(false) after task deletion, improving user experience by closing the drawer automatically.
2025-07-09 16:55:03 +05:30
shancds
1a476a0e3c Merge branch 'test/row-kanban-board-v1.1.6' of https://github.com/shancds/worklenz-open-source into test/row-kanban-board-v1.1.7 2025-07-09 16:41:51 +05:30
shancds
80b1d6c292 Merge branch 'fix/release-v.2.1.1' of https://github.com/Worklenz/worklenz into test/row-kanban-board-v1.1.7 2025-07-09 16:36:59 +05:30
chamikaJ
deb0f3f602 refactor(task-list): enhance task rendering and editing functionality in TaskRow and TaskListV2Table
- Updated TaskListV2Table to pass isFirstInGroup prop to renderTask for improved task grouping logic.
- Enhanced TaskRow to support inline editing of task names with a new input field and associated state management.
- Implemented click outside detection to save task name changes when editing is complete.
- Improved layout and styling for better user experience during task editing and display.
2025-07-09 16:32:28 +05:30
shancds
71f168f8fa feat(kanban-board): add subtasks localization and enhance task drawer functionality
- Added new localization keys for subtasks in the kanban board JSON file to improve user experience.
- Updated the SubTaskTable component to dispatch enhanced kanban subtask updates upon deletion.
- Enhanced the TaskDrawerHeader to handle deletion of subtasks with appropriate state updates in the kanban context.
2025-07-09 16:27:48 +05:30
chamikaJ
6f63041148 refactor(task-list): update status handling and enhance styling in TaskListV2Table
- Modified status assignment in useTaskSocketHandlers to utilize actual status_id from the response for improved accuracy.
- Simplified status logic by directly using data.status in task creation.
- Enhanced styling in TaskListV2Table by adding border styles for better visual separation of elements.
2025-07-09 15:57:08 +05:30
chamikaJ
399a01904a refactor(task-list): enhance styling and structure in TaskListV2 and TaskRow components
- Consolidated import statements for better readability.
- Improved layout and styling consistency by adding border styles to various elements in TaskRow and AddTaskRow components.
- Updated TaskListV2Table to enhance the rendering logic and maintainability.
- Adjusted custom column handling and task estimation display for improved user experience.
2025-07-09 14:58:54 +05:30
chamikaJ
9cc19460bd Merge branch 'fix/release-v.0.1.1' of https://github.com/Worklenz/worklenz into fix/release-v.2.1.1 2025-07-09 14:51:26 +05:30
Chamika J
2920f131f8 Merge pull request #251 from shancds/test/row-kanban-board-v1.1.5
refactor(task-list): streamline TaskListV2 component and improve stru…
2025-07-09 14:30:08 +05:30
shancds
04f622a7f0 refactor(task-list): streamline TaskListV2 component and improve structure
- Removed unused imports and consolidated task list logic for better readability.
- Introduced TaskListV2Section for improved organization and separation of concerns.
- Enhanced task filtering and rendering logic to optimize performance and maintainability.
- Updated styling and layout for a cleaner user interface and better usability.
2025-07-09 14:21:10 +05:30
chamikaJ
fadc115412 feat(task-reporter): add reporter field to task data structure
- Introduced a new 'reporter' field in the task data structure for both backend and frontend task management.
- Updated the tasks-controller to include the reporter information when transforming task data.
- Modified the fetchTasks and fetchTasksV3 functions to handle the reporter field, ensuring it defaults to undefined when not present.
2025-07-09 13:01:51 +05:30
chamikaJ
10c53d954e refactor(task-list): unify date handling and enhance column widths
- Updated TaskRow component to handle both camelCase and snake_case date fields for created and updated timestamps.
- Adjusted column widths for due date, start date, completed date, created date, and last updated fields for better layout consistency.
- Ensured whitespace handling in date display spans for improved UI presentation.
2025-07-09 12:32:17 +05:30
chamikaJ
29a09ec500 refactor(task-drawer): enhance task deletion handling and update imports
- Updated task deletion logic to ensure consistency across task management slices, including clearing selections and updating the board state.
- Refactored imports to streamline task management functionality and improve code clarity.
- Added new Ant Design icon import for enhanced UI options.
2025-07-09 12:22:37 +05:30
chamikaJ
6dba080ade refactor(task-drawer): streamline task name handling and enhance socket management
- Removed local socket listener for task name changes, relying on global socket handlers for real-time updates.
- Simplified task name change handling logic to improve clarity and maintainability.
- Enhanced task status group matching logic in useTaskSocketHandlers for better accuracy with multiple matching strategies.
- Added detailed logging for task movement and status changes to aid in debugging and tracking.
2025-07-09 12:11:11 +05:30
chamikaJ
ab7ca33ac1 feat(localization): improve translation handling and update UI labels
- Updated the 'board' label in project-view translations for consistency.
- Enhanced the getTabLabel function to include fallback labels for better user experience when translations are not available.
- Implemented translation loading checks in ProjectView to ensure labels are updated correctly based on the selected language.
- Refactored tab label updates to handle translation loading errors gracefully.
2025-07-09 11:58:40 +05:30
chamikaJ
bc6a15de8f feat(localization): add 'share' label translations for multiple languages
- Added the 'share' label to project view headers in Albanian, German, Spanish, Portuguese, Chinese, and English to enhance user interaction.
- Updated corresponding button icons and labels in the project view header for improved functionality and consistency.
2025-07-09 11:41:09 +05:30
chamikaJ
a47a9045e6 feat(localization): add new label management translations for multiple languages
- Introduced new translation keys for label management features in Albanian, German, Spanish, Portuguese, Chinese, and English.
- Updated placeholder texts, button labels, and paths to enhance user experience in the task list component.
2025-07-09 11:31:00 +05:30
Tharindu shan
b6e92b4211 Merge branch 'Worklenz:main' into test/row-kanban-board-v1.1.4 2025-07-09 08:58:51 +05:30
Chamika J
6c08f10e9d Merge pull request #249 from Worklenz/release/v2.1.0
Release/v2.1.0
2025-07-09 06:37:37 +05:30
Chamika J
6c620d6878 Merge pull request #247 from Worklenz/release/v2.0.4-bug-fix
Release/v2.0.4 bug fix
2025-07-09 06:22:56 +05:30
chamikaJ
072c1a6a3b refactor(task-list): improve layout and styling for task list and rows
- Adjusted padding and alignment in TaskListV2 for better visual consistency.
- Enhanced styling in TaskRow to ensure uniformity across various task elements.
- Updated flex properties for improved responsiveness and usability in task display.
2025-07-08 17:10:56 +05:30
Chamika J
78e14d6378 Merge pull request #245 from shancds/test/row-kanban-board-v1.1.4
Test/row kanban board v1.1.4
2025-07-08 17:00:19 +05:30
chamikaJ
68e71d09ea refactor(task-list): enhance layout and styling for improved usability
- Adjusted heights and padding in the task list and filters for better space optimization.
- Updated styling in TaskRow for consistent alignment of task elements.
- Refined padding in ImprovedTaskFilters for a cleaner appearance.
- Removed unnecessary margin in ProjectViewHeader to streamline layout.
2025-07-08 16:51:44 +05:30
chamikaJ
6ac2a0c888 refactor(task-list): improve layout and styling for better usability
- Removed unnecessary padding from task filters for a cleaner look.
- Adjusted height calculations to optimize space usage in the task list.
- Added padding to the content area to ensure the horizontal scrollbar is visible.
- Updated subtask count checks for clarity and consistency.
- Modified gap and margin values in project view header for improved alignment.
2025-07-08 16:13:56 +05:30
chamikaJ
66e01119d2 refactor(task-drawer): update tab behavior and enhance link handling in description editor
- Changed tab component property from 'destroyInactiveTabPane' to 'destroyOnHidden' for improved tab management.
- Added CSS styles for links in the description editor to enhance visibility based on theme mode.
- Implemented link click handling to open links in a new tab while preventing default editor behavior, improving user experience.
2025-07-08 16:02:12 +05:30
chamikaJ
8fb33e311d feat(localization): enhance task drawer translations and UI consistency
- Updated translations for task drawer components in Albanian, German, Spanish, Portuguese, Chinese, and English to improve clarity and consistency.
- Added new keys for task progress, activity log, and time log forms to support enhanced user interactions.
- Improved placeholder texts, error messages, and button labels for better user experience across multiple languages.
- Refactored activity log component to utilize translation keys for dynamic content rendering.
2025-07-08 15:51:47 +05:30
chamikaJ
f06851fa37 feat(localization): add and update translations for multiple languages
- Introduced new localization files for Albanian, German, Spanish, Portuguese, and Chinese, enhancing the application's multilingual support.
- Added new keys and updated existing translations in project-view, task-list-table, and settings files to improve user experience across different languages.
- Enhanced error handling and empty state messages in task management components to provide clearer feedback to users.
- Updated tooltip texts and button labels for better clarity and consistency in the user interface.
2025-07-08 15:26:55 +05:30
chamikaJ
e750023fdc Merge branch 'development' of https://github.com/Worklenz/worklenz into release/v2.0.4-bug-fix 2025-07-08 13:12:22 +05:30
shancds
e2e57fbf26 fix(KanbanGroup): enhance input focus behavior to select text on focus
- Updated the focus handling in KanbanGroup to select all text in the input field when focused, improving user experience during task editing.
2025-07-08 12:59:31 +05:30
Chamika J
56d6a53a54 Merge pull request #208 from jiuhao47/main
add support for zh_cn
2025-07-08 12:57:07 +05:30
shancds
ee6055934c refactor(AssigneeSelector): streamline component logic and enhance dropdown behavior
- Removed unused kanbanMode prop and related logic to simplify the AssigneeSelector component.
- Updated dropdown position handling to improve visibility and responsiveness during interactions.
- Optimized member selection logic for better performance and user feedback.
- Enhanced the rendering of team members with improved visual feedback for pending changes.
2025-07-08 12:49:49 +05:30
chamikaJ
03b3f55400 fix(project-list): enhance grouped request parameters handling
- Updated the initialization of grouped request parameters to use a proper groupBy value.
- Improved the effect dependencies to include groupBy, ensuring correct state management.
- Enhanced the logic for fetching grouped projects, ensuring parameters are set correctly and data is retrieved when necessary.
- Added comments for clarity on the conditions for fetching grouped projects.
2025-07-08 12:43:00 +05:30
shancds
2aab2a21b6 feat(EnhancedKanbanBoard): implement drag end handling for improved task interaction
- Added handleDragEnd function to reset hovered task and group states after drag operations in EnhancedKanbanBoard.
- Updated KanbanGroup and TaskCard components to support onDragEnd prop, enhancing drag-and-drop functionality and user experience during task management.
2025-07-08 12:13:23 +05:30
chamikaJ
a44b276269 feat(email-templates): update release note template for Worklenz 2.1.0
- Added a title and meta subject for the release note template.
- Enhanced styling for better readability and user experience, including background color, font adjustments, and button styles.
- Introduced new sections for features, including a new tasks list, kanban board, group view, language support, and bug fixes.
- Improved responsiveness and dark mode support for the email template.
2025-07-08 11:59:17 +05:30
shancds
d150747f83 refactor(ProjectViewEnhancedBoard): remove unused EnhancedKanbanBoard import 2025-07-08 10:57:34 +05:30
shancds
fa9e765e37 refactor(KanbanGroup, TaskCard): enhance drag-and-drop indicators and task rendering
- Added visual drop indicators before and after task cards in the KanbanGroup component to improve user experience during drag-and-drop operations.
- Removed the isDropIndicator prop from TaskCard as the drop indicator logic is now handled within the KanbanGroup, simplifying the TaskCard component.
- Updated drag-and-drop event handling in TaskCard to better manage task positioning during drag operations, enhancing overall functionality.
2025-07-07 17:20:34 +05:30
chamikaJ
b0253135e5 feat(project-drawer): enhance project data fetching and error handling
- Updated project data fetching logic in the project drawer and related components to ensure the drawer opens only after successful data retrieval.
- Added detailed logging for successful and failed fetch attempts to improve debugging and user feedback.
- Introduced error handling to maintain user experience by allowing the drawer to open even if data fetching fails, displaying an error state.
- Refactored project list and project view components to optimize search functionality and improve loading states.
- Removed deprecated components related to task management to streamline the project view.
2025-07-07 17:07:45 +05:30
shancds
8e62594eff fix(TaskCard): update background color for improved UI consistency
- Changed the background color of the TaskCard component from '#f0f0f0' to '#E2EAF4' in light mode to enhance visual consistency and user experience.
2025-07-07 15:17:33 +05:30
chamikaJ
978d9158c0 feat(database): add performance indexes and materialized view for optimization
- Created multiple new indexes in the performance-indexes.sql file to enhance query performance for tasks, team members, and related tables.
- Added a materialized view for team member information in 3_views.sql to pre-calculate expensive joins, improving data retrieval efficiency.
- Introduced a function to refresh the materialized view, ensuring up-to-date information while optimizing performance.
- Implemented an optimized method in tasks-controller-v2.ts to split complex queries into focused segments, significantly improving response times and overall performance.
2025-07-07 15:01:11 +05:30
chamikaJ
134899114d refactor(index.html): update production tracking ID logic
- Simplified the logic for determining the production tracking ID by removing the check for the 'worklenz.com' hostname.
- Ensured that the tracking ID is now solely based on the 'app.worklenz.com' hostname, streamlining the analytics loading process.
2025-07-07 14:50:01 +05:30
chamikaJ
8533a440bc refactor(tasks-controller): enhance getTasksV3 method for performance and clarity
- Improved logging for performance tracking in the getTasksV3 method.
- Streamlined progress refresh logic and removed unnecessary calculations to optimize initial load times.
- Unified query handling by aligning with the getList method for consistency.
- Transformed response structure to maintain compatibility with V3 format while ensuring efficient data processing.
- Added memoized selectors in the frontend for better performance and reduced re-renders.
2025-07-07 14:30:19 +05:30
chamikaJ
9ec422c6e2 Merge branch 'release/v2.0.4-bug-fix' of https://github.com/Worklenz/worklenz into release/v2.0.4-bug-fix 2025-07-07 14:10:36 +05:30
Chamika J
6c03bf71c2 Merge pull request #243 from shancds/test/row-kanban-board-v1.1.3
Test/row kanban board v1.1.3
2025-07-07 14:10:06 +05:30
chamikaJ
3887cc477d refactor(tasks-controller): enhance SQL query structure and improve task filtering
- Updated SQL queries in TasksControllerV2 to use table aliases for clarity and consistency.
- Improved filtering logic for project IDs and assignees to ensure accurate task retrieval.
- Refactored JSON object construction in SQL queries for better readability and maintainability.
- Adjusted parameter handling in query execution to streamline data retrieval processes.
2025-07-07 14:10:04 +05:30
chamikaJ
0b96d59285 refactor(tasks-controller): remove Redis caching logic for task retrieval
- Eliminated caching logic from the TasksControllerV2 to streamline task retrieval and improve performance.
- Updated response handling to directly return task data without caching, enhancing clarity and reducing complexity.
- Removed cache invalidation method to simplify the controller's responsibilities.
2025-07-07 13:22:17 +05:30
chamikaJ
a3f317cbeb refactor(vite.config): simplify chunking strategy and optimize asset naming
- Updated the chunking strategy to keep React and related libraries together, improving compatibility and reducing context issues.
- Simplified asset naming conventions for better caching and consistency, removing unnecessary hash lengths.
- Adjusted optimization settings to ensure critical libraries are included while excluding heavy libraries for lazy loading.
2025-07-07 13:16:26 +05:30
shancds
5a9ceb4a94 feat(EnhancedKanbanBoard): add task dependency check during drag-and-drop
- Implemented a check for task dependencies when moving tasks between groups in the EnhancedKanbanBoard component.
- Integrated alert notifications to inform users if a task cannot be moved due to incomplete dependencies, enhancing user experience and task management integrity.
2025-07-07 13:12:50 +05:30
chamikaJ
bdc3050a5e feat(database): add performance indexes for optimized task queries
- Introduced a new SQL migration file to create various performance indexes on tasks, task_assignees, task_phase, and related tables.
- These indexes aim to enhance query performance for task filtering, status joins, assignees lookup, and other operations, improving overall application efficiency.
2025-07-07 13:10:27 +05:30
chamikaJ
bc085926a6 refactor(vite.config): clean up unnecessary whitespace in configuration file 2025-07-07 12:47:19 +05:30
chamikaJ
aa1fb1c6f5 feat(performance): optimize resource loading and initialization
- Added resource hints in index.html for improved loading performance, including preconnect and dns-prefetch links.
- Implemented preload for critical JSON resources to enhance initial load times.
- Optimized Google Analytics and HubSpot script loading to defer execution and reduce blocking during initial render.
- Refactored app initialization in App.tsx to defer non-critical operations, improving perceived performance.
- Introduced lazy loading for chart components and TinyMCE editor to minimize initial bundle size and enhance user experience.
- Enhanced Vite configuration for optimized chunking strategy and improved caching with shorter hash lengths.
2025-07-07 12:41:23 +05:30
chamikaJ
26b47aac53 refactor(i18n): optimize translation loading and initialization
- Updated ensureTranslationsLoaded function to prevent duplicate requests by caching loaded translations and managing loading promises.
- Simplified translation preloading on app startup to only load essential namespaces for the current language.
- Adjusted useTranslationPreloader hook to avoid multiple requests for translations and ensure efficient loading state management.
2025-07-07 12:31:11 +05:30
shancds
8dcd0295e5 refactor(EnhancedKanbanBoard): improve drag-and-drop handling and task index management
- Updated drag-and-drop event handlers in EnhancedKanbanBoard to support null task indices, enhancing flexibility during task interactions.
- Adjusted KanbanGroup component to reflect changes in task index handling, ensuring consistent behavior when dragging tasks over empty drop zones.
- Enhanced the visual structure of the KanbanGroup to improve user experience during task creation and management.
2025-07-07 12:05:05 +05:30
shancds
3206af160a refactor(TaskCard): enhance subtask visibility animation and styling
- Updated the TaskCard component to improve the visibility and animation of subtasks, adding a smooth transition effect for better user experience.
- Adjusted the rendering logic to conditionally display the subtasks container based on the task's state, enhancing the overall UI interaction.
2025-07-07 10:31:33 +05:30
shancds
b500c801ee refactor(KanbanGroup, TaskCard): simplify card creation logic and enhance drag-and-drop functionality
- Removed conditional checks for user roles when displaying the new task card options in KanbanGroup, streamlining the UI for task creation.
- Updated TaskCard component to improve the drag-and-drop indicator styling and structure, enhancing the user experience during task interactions.
2025-07-07 10:11:56 +05:30
Chamika J
3f1b8762dd Merge pull request #241 from Worklenz/release/v2.0.4-bug-fix
feat(task-management): add all_labels support and improve label handling
2025-07-07 09:41:24 +05:30
chamikaJ
a6f9046b42 feat(task-management): add all_labels support and improve label handling
- Introduced all_labels property in task management to provide a complete list of labels for selection logic.
- Updated TasksControllerV2, TaskRow, and LabelsSelector components to utilize all_labels for enhanced label management.
- Improved checkbox handling in LabelsSelector to prevent event propagation and ensure better user interaction.
- Enhanced useTaskSocketHandlers to manage temporary subtasks effectively, preventing duplication during optimistic updates.
2025-07-07 09:40:56 +05:30
Chamika J
2cf91bddea Merge pull request #240 from Worklenz/fix/task-drag-and-drop-improvement
refactor(TaskRow): improve layout and styling for task indicators and…
2025-07-07 07:22:13 +05:30
chamiakJ
e1e4187ded refactor(TaskRow): improve layout and styling for task indicators and subtasks
- Enhanced the layout of the TaskRow component to ensure better spacing and alignment for task indicators and subtasks.
- Updated CSS classes to prevent compression of elements and maintain consistent visual presentation.
- Improved tooltip functionality for task display names and indicators, ensuring better accessibility and user experience.
2025-07-07 07:20:45 +05:30
Chamika J
e02796c310 Merge pull request #239 from Worklenz/fix/task-drag-and-drop-improvement
refactor(components): enhance component structure and add forwardRef …
2025-07-07 07:16:57 +05:30
chamiakJ
5d9e96033e refactor(components): enhance component structure and add forwardRef support
- Refactored CustomAvatar, CustomColordLabel, CustomNumberLabel, and ProjectStatusIcon components to utilize React.forwardRef for improved ref handling.
- Introduced TooltipWrapper component to avoid findDOMNode warnings in React StrictMode, ensuring better compatibility with Ant Design's Tooltip.
- Updated TaskRow component to enhance layout and tooltip functionality for task display names, improving user experience and accessibility.
2025-07-07 07:16:10 +05:30
Chamika J
cc618960e6 Merge pull request #238 from Worklenz/fix/task-drag-and-drop-improvement
Fix/task drag and drop improvement
2025-07-07 07:05:59 +05:30
chamiakJ
f9926e7a5d feat(task-list): add tooltips for task indicators and enhance localization
- Introduced tooltips for subtasks, comments, attachments, subscribers, dependencies, and recurring tasks across various components to improve user experience.
- Enhanced localization by adding new translation keys for these indicators in multiple languages, ensuring consistent messaging for users.
- Updated components such as TaskRow, KanbanTaskCard, and EnhancedKanbanTaskCard to utilize the new tooltip functionality, improving clarity and accessibility.
2025-07-07 07:05:29 +05:30
chamiakJ
03fc2fb7ee refactor(task-list): update subtask and indicator visibility logic
- Modified subtask visibility conditions across TaskCard, EnhancedKanbanTaskCard, KanbanTaskCard, and TaskRow components to only display when the count is greater than 1.
- Enhanced task indicators for comments and attachments to follow the same visibility logic.
- Added new dependencies for task management types to support subscriber and attachment features.
2025-07-07 06:33:22 +05:30
chamiakJ
b6efa3f37e feat(task-filters): enhance search filter functionality and UI interactions
- Implemented synchronization of local search value with external value prop to maintain expanded state during searches.
- Updated button styles for better visibility in dark mode and improved user experience.
- Refactored search handling to ensure consistent use of taskReducer for search actions across different views.
- Enhanced filter section visibility logic to improve user interaction during data loading.
2025-07-07 05:52:07 +05:30
chamiakJ
85f20eaf1c refactor(task-list): update custom column handling and improve UI interactions
- Renamed CSS class for focused custom column cells to enhance clarity and consistency.
- Implemented optimistic updates in TaskListV2 for immediate UI feedback when updating custom column values.
- Adjusted rendering logic to support sticky headers and improved layout for task list components.
- Enhanced loading state management in SelectionCustomColumnCell for better user experience during updates.
2025-07-07 05:39:09 +05:30
chamiakJ
411147efce fix(task-list): update styling and improve task handling
- Adjusted color styling for the CreateStatusButton based on theme mode.
- Enhanced TaskGroupHeader with improved border styling and spacing for better visual consistency.
- Increased width and padding for the AddCustomColumnButton to improve usability.
- Updated TaskRow to include additional dependencies for task labels and phase updates in socket handling.
- Refactored task management slice to ensure accurate label and phase updates during real-time interactions.
- Removed unnecessary debug logging from CustomColumnModal and SelectionTypeColumn components for cleaner code.
2025-07-07 05:07:05 +05:30
chamiakJ
48c3d58f7e feat(task-list): enhance TaskListV2 with scroll synchronization and custom column handling
- Added scroll synchronization between header and content sections using refs for improved user experience.
- Refactored custom column handling to prioritize UUIDs for API interactions, ensuring accurate updates and data retrieval.
- Introduced a message display for empty task groups, enhancing user feedback when no tasks are available.
- Updated rendering logic for task groups and tasks to improve performance and maintainability.
2025-07-07 04:21:09 +05:30
chamiakJ
746d38017f feat(task-list): refine task list components and improve UI consistency
- Updated SubtaskLoadingSkeleton and TaskRow components for better spacing and visual consistency.
- Simplified TaskGroupHeader by removing unnecessary elements and enhancing the display of group names.
- Adjusted TaskListV2 to improve column rendering and added state management for field visibility synchronization with the database.
- Enhanced AddTaskRow and AddSubtaskRow components for improved user interaction and layout.
- Updated placeholder texts in CustomColumnComponents for better clarity.
2025-07-07 03:39:39 +05:30
chamiakJ
01298928c7 feat(task-filters): enhance localization and UI for task filters
- Added new translation keys for task filter functionalities across multiple languages, improving user experience and accessibility.
- Updated the ImprovedTaskFilters component to utilize localized strings for search placeholders, loading states, and filter messages.
- Enhanced styling for filter buttons and dropdowns to ensure consistency in both light and dark modes.
- Introduced a description column in task management components, improving the display of task details.
2025-07-07 03:04:16 +05:30
Chamika J
13ee16452b Merge pull request #237 from Worklenz/fix/task-drag-and-drop-improvement
Fix/task drag and drop improvement
2025-07-07 02:40:15 +05:30
chamiakJ
9a57413624 feat(task-filters): enhance archived task handling in task management
- Introduced state management for archived tasks in the task management slice, allowing for better control over task visibility.
- Updated ImprovedTaskFilters component to utilize the new archived state, enabling toggling of archived tasks based on the current view (list or kanban).
- Refactored related functions to ensure proper dispatching of archived state changes, improving user experience and task management efficiency.
2025-07-07 02:38:50 +05:30
chamiakJ
8d8250bc17 feat(task-list): enhance bulk action functionality with improved task handling
- Updated TaskListV2 to pass selected task IDs to bulk action handlers, improving functionality and user experience.
- Refactored useBulkActions hook to implement detailed handling for bulk status, priority, phase changes, and other actions, ensuring proper task management.
- Added loading states for individual bulk actions to provide visual feedback during processing.
- Implemented error handling and alerts for task dependency checks before executing bulk actions, enhancing reliability.
- Introduced new methods for bulk assigning members, adding labels, archiving, deleting, and duplicating tasks, streamlining task management processes.
2025-07-07 02:38:42 +05:30
chamiakJ
174c6bcedf feat(custom-columns): enhance task management with custom column support
- Added custom column values to task responses in the API for better task management flexibility.
- Implemented custom column components in the frontend, including dropdowns and date pickers, to improve user interaction.
- Updated TaskListV2 and TaskRow components to handle custom columns, ensuring proper rendering and functionality.
- Introduced a new PeopleDropdown component for selecting team members in custom columns, enhancing usability.
- Enhanced styling for custom column components to support both light and dark modes, improving visual consistency.
2025-07-07 02:04:05 +05:30
chamiakJ
c70f8e7b6d feat(task-statuses): add category update functionality and enhance localization
- Implemented updateCategory method in TaskStatusesController to allow updating task status categories.
- Added corresponding route for category updates in statusesApiRouter.
- Enhanced task management localization by adding new translation keys for category-related actions in multiple languages.
- Updated TaskGroupHeader component to support category changes with a modal for selecting categories.
2025-07-06 16:54:11 +05:30
chamiakJ
6ba1ff57b2 refactor(task-list): streamline task addition and socket handling
- Removed local socket listener from AddTaskRow, delegating task addition to the global socket handler for real-time updates.
- Simplified the handleTaskAdded function in TaskListV2, eliminating the need for refetching tasks upon addition.
- Updated useTaskSocketHandlers to handle task data more efficiently, ensuring proper integration with the Redux store.
2025-07-06 15:42:12 +05:30
chamiakJ
a5291483f7 feat(task-list): enhance task creation and UI components
- Improved the on_quick_task function to handle empty task names and emit null when no task is created, ensuring better user feedback.
- Updated SubtaskLoadingSkeleton and TaskRow components for improved styling and spacing, enhancing visual consistency.
- Introduced AddTaskRow component for streamlined task addition, integrating socket communication for real-time updates.
- Refactored TaskListV2 to optimize rendering and improve performance, including adjustments to column headers and task display.
- Added custom column components for enhanced task management flexibility and user interaction.
2025-07-06 14:51:22 +05:30
chamikaJ
e3a9618dc9 feat(task-list): improve layout and scrolling behavior in TaskListV2 component
- Adjusted the layout to ensure fixed heights for the task list and table container, enhancing usability.
- Implemented horizontal scrolling for the table container and ensured the bulk action bar is positioned absolutely for better accessibility.
- Updated styles for column headers and task list content to improve visual consistency and user experience.
2025-07-04 20:54:11 +05:30
chamikaJ
f30fde553d feat(task-management): enhance task grouping and localization support
- Implemented unmapped task grouping for better organization of tasks without valid phases.
- Updated task distribution logic to handle unmapped tasks and added a corresponding group in the response.
- Enhanced localization by adding translations for "noTasksInGroup" in multiple languages.
- Improved task list components to support custom columns and better task management features.
- Refactored task management slice to include loading states for columns and custom columns.
2025-07-04 20:41:03 +05:30
Chamika J
f9c1537ca0 Merge pull request #236 from shancds/test/row-kanban-board-v1.1.2
Test/row kanban board v1.1.2
2025-07-04 17:31:02 +05:30
shancds
208a6db1a6 feat(AssigneeSelector): add kanbanMode prop for enhanced task assignment
- Introduced kanbanMode prop to AssigneeSelector and LazyAssigneeSelectorWrapper for improved functionality in Kanban view.
- Updated EnhancedKanbanTaskCard and TaskCard components to utilize the new kanbanMode prop.
- Adjusted task sorting logic to handle cases where sort_order may be undefined, ensuring robust behavior during task updates.
2025-07-04 17:13:56 +05:30
shancds
9e1798cc3e fix(TaskCard): improve UI and interaction for subtasks
- Adjusted styling for task and subtask elements to enhance visual consistency.
- Updated subtask rendering to include priority color indicators based on theme mode.
- Added click handling for subtask items to improve user interaction.
- Refined layout and spacing for better usability and readability.
2025-07-04 15:41:52 +05:30
chamikaJ
9e29031703 feat(task-list): update column labels for localization and improve task count display
- Changed column labels to be more descriptive for localization support.
- Integrated translation functionality for column headers using `useTranslation`.
- Updated task count display to reflect actual task counts instead of collapsed counts.
2025-07-04 14:38:26 +05:30
shancds
3626192f31 Merge branch 'release/v2.0.4' of https://github.com/Worklenz/worklenz into test/row-kanban-board-v1.1.2 2025-07-04 14:23:44 +05:30
shancds
d246f8e3ed feat(TaskCard): add subtask expansion functionality and improve UI interactions
- Implemented subtask expansion logic with dispatch actions for toggling visibility and fetching subtasks.
- Enhanced UI to include a button for showing/hiding subtasks, improving user interaction.
- Updated task display to show subtask counts and loading states, ensuring better feedback during data fetching.
2025-07-04 14:23:23 +05:30
Chamika J
3ddf6900c9 Merge pull request #235 from Worklenz/fix/task-drag-and-drop-improvement
feat(task-management): enhance task management UI with subtask functi…
2025-07-04 13:25:42 +05:30
chamikaJ
aab3ffe262 feat(task-management): enhance task management UI with subtask functionality
- Updated task list components to support subtasks, including TaskRowWithSubtasks for rendering tasks with their subtasks.
- Introduced AddSubtaskRow for adding new subtasks directly within the task list.
- Enhanced TaskRow to handle task expansion and display subtask counts.
- Implemented optimistic updates for subtask creation to improve user experience.
- Added loading states for subtasks to provide visual feedback during data fetching.
- Refactored task management slice to manage subtasks and their loading states effectively.
2025-07-04 13:24:41 +05:30
shancds
56f129d784 feat(TaskCard): implement end date selection and refine label styling
- Added useEffect to set the selected date based on task's end date for improved date management.
- Adjusted label styling for better visual consistency, including reduced border radius, padding, and updated color scheme based on theme mode.
2025-07-04 12:31:25 +05:30
shancds
7fe35d646a style(TaskCard): refine date picker UI and adjust styling for better usability
- Reduced dimensions and padding of the date picker for a more compact design.
- Updated button sizes and text styles for improved readability and interaction.
- Adjusted layout spacing to enhance overall visual consistency and user experience.
2025-07-04 12:22:01 +05:30
shancds
31891fae6e feat(task-management): integrate date picker functionality in TaskCard component
- Added a date picker to the TaskCard for selecting and updating task due dates.
- Implemented real-time updates for due date changes using socket communication.
- Enhanced UI with a custom calendar popup and improved date selection options.
- Updated localization files to include new strings related to date management in multiple languages.
2025-07-04 12:16:10 +05:30
Chamika J
33ee3a521c Merge pull request #234 from shancds/test/row-kanban-board-v1.1.1
feat(localization): add new translations for Kanban board sections
2025-07-04 11:14:08 +05:30
shancds
df581b965a feat(localization): add new translations for Kanban board sections
- Introduced translations for "untitledSection" and "unmapped" in multiple languages including Albanian, German, English, Spanish, and Portuguese.
- Updated KanbanGroup component to utilize these new translations for improved internationalization support.
2025-07-04 11:10:16 +05:30
Chamika J
6cd7500073 Merge pull request #232 from shancds/test/row-kanban-board-v1.1.1
feat(enhanced-kanban): enhance KanbanGroup component with editing and…
2025-07-04 11:03:03 +05:30
Chamika J
20b9251eab Merge pull request #233 from Worklenz/fix/task-drag-and-drop-improvement
feat(task-management): improve dropdown behavior and enhance task row…
2025-07-04 11:02:26 +05:30
chamikaJ
6f66367282 feat(task-management): improve dropdown behavior and enhance task row components
- Updated AssigneeSelector and LabelsSelector to close dropdowns only when scrolling outside the dropdown area, enhancing user experience.
- Introduced TaskLabelsCell component in TaskRow for better label rendering and organization.
- Refactored date handling in TaskRow to consolidate formatted date logic and improve clarity.
- Memoized date picker handlers for better performance and cleaner code.
2025-07-04 11:01:21 +05:30
shancds
e566514ac0 feat(enhanced-kanban): enhance KanbanGroup component with editing and deletion features
- Added functionality for renaming Kanban groups with unique name generation.
- Implemented status updates and deletion logic for Kanban groups based on user interactions.
- Introduced loading states and dropdown menus for better user experience when managing groups.
- Enhanced UI elements for task creation and deletion confirmations, improving overall usability.
2025-07-04 10:57:02 +05:30
Chamika J
02db84e7f2 Merge pull request #231 from Worklenz/fix/task-drag-and-drop-improvement
Fix/task drag and drop improvement
2025-07-04 10:34:30 +05:30
chamikaJ
8adeabce61 feat(task-management): integrate TaskPhaseDropdown in TaskRow for enhanced task phase management
- Added TaskPhaseDropdown component to TaskRow for improved phase selection.
- Replaced static phase display with a dropdown for better interactivity and user experience.
2025-07-04 10:33:48 +05:30
chamikaJ
7e6d7d8580 feat(task-management): enhance task date handling and UI components in TaskListV2
- Added startDate and dueDate fields to task data structure for improved date management.
- Updated TaskRow to include date pickers for start and due dates with clear functionality.
- Enhanced LabelsSelector to support dynamic label rendering and improved visual feedback.
- Refactored AssigneeSelector and CustomColordLabel components for better integration with task data.
- Improved dropdown positioning logic in LabelsSelector for better user experience.
- Added translations for new date-related UI elements in multiple languages.
2025-07-04 10:29:51 +05:30
Chamika J
0781f3e13d Merge pull request #230 from Worklenz/fix/task-drag-and-drop-improvement
Fix/task drag and drop improvement
2025-07-04 09:00:38 +05:30
chamiakJ
64f1e5831a feat(task-management): enhance TaskGroupHeader and TaskListV2 for improved task display and reordering
- Updated TaskGroupHeader to display task count alongside group name for better clarity.
- Refactored task reordering logic in TaskListV2 to streamline drag-and-drop functionality across groups.
- Added TypeScript definitions for Heroicons to improve type safety and component usage.
- Adjusted styling in TaskGroupHeader for a more consistent visual presentation.
2025-07-04 07:50:16 +05:30
chamikaJ
551924c384 feat(task-management): integrate real-time updates and enhance task row components
- Added useTaskSocketHandlers to TaskListV2 for real-time task updates.
- Updated TaskRow to accept projectId prop for better context handling.
- Replaced static status and priority displays with TaskStatusDropdown and TaskPriorityDropdown for improved interactivity.
- Enhanced dropdown positioning logic in TaskStatusDropdown and TaskPriorityDropdown to handle scrollable containers effectively.
2025-07-03 20:02:00 +05:30
Chamika J
c889f8e9c8 Merge pull request #229 from shancds/test/row-kanban-board-v1.1.1
Test/row kanban board v1.1.1
2025-07-03 19:51:23 +05:30
Chamika J
86b5ec0afd Merge pull request #228 from Worklenz/fix/task-drag-and-drop-improvement
feat(task-management): enhance task assignment handling and UI feedba…
2025-07-03 19:29:05 +05:30
chamikaJ
6bf98b787e feat(task-management): enhance task assignment handling and UI feedback in AssigneeSelector
- Introduced optimistic updates for assignee selection to improve UI responsiveness.
- Updated AssigneeSelector to initialize optimistic assignees from task data on mount.
- Refactored task assignment logic to ensure unique assignee IDs and improved state management.
- Enhanced TaskGroupHeader and TaskListV2 to support bulk actions and selection state.
- Integrated a new bulk action bar for managing selected tasks efficiently.
2025-07-03 19:28:14 +05:30
shancds
3532b0bbfb feat(enhanced-kanban): enhance task card styling for improved UI
- Added a light border, box shadow, and background color to the EnhancedKanbanTaskCard for a more visually appealing design.
- Refactored the rendering of TaskCard components in KanbanGroup to eliminate unnecessary React.Fragment wrappers, improving code clarity.
2025-07-03 19:24:17 +05:30
shancds
6d4d851f1d feat(enhanced-kanban): replace loading spinner with skeleton loader
- Updated the loading state in the EnhancedKanbanBoardNativeDnD component to use a Skeleton loader instead of a Spin component for improved user experience during data fetching.
2025-07-03 19:13:03 +05:30
shancds
fb9e430ba0 feat(enhanced-kanban): update TaskCard to display task due date
- Added a new due date display within the TaskCard component, enhancing task visibility.
- Removed the previous due date element to streamline the layout and improve UI consistency.
2025-07-03 19:05:40 +05:30
shancds
73c78dd28f feat(enhanced-kanban): enhance TaskCard with task selection and improved UI
- Added task selection functionality by dispatching actions on card click to show the task drawer.
- Introduced labels display for tasks, enhancing visual organization.
- Improved task content layout with priority indicators and due date formatting.
- Integrated AvatarGroup and LazyAssigneeSelector for better assignee management and visibility.
2025-07-03 18:51:48 +05:30
Chamika J
509e654123 Merge pull request #227 from Worklenz/fix/task-drag-and-drop-improvement
Fix/task drag and drop improvement
2025-07-03 18:02:34 +05:30
chamikaJ
6b7f412341 feat(task-management): enhance task list with improved drag-and-drop functionality and visual feedback
- Integrated droppable areas in TaskGroupHeader for better task organization.
- Implemented drag-over visual feedback to indicate valid drop zones.
- Enhanced TaskListV2 to handle cross-group task movement and reordering.
- Optimized TaskRow for better rendering performance and added memoization for task details.
- Improved Redux state management for task reordering and group handling.
2025-07-03 18:02:00 +05:30
chamikaJ
edf051adc7 feat(task-management): enhance task list with drag-and-drop functionality and improved styling
- Integrated drag-and-drop capabilities using @dnd-kit for task reordering within the TaskListV2 component.
- Updated TaskGroupHeader to dynamically adjust colors based on group properties, improving visual clarity.
- Refactored TaskRow to support drag-and-drop interactions and optimized rendering of task details.
- Enhanced Redux state management for collapsed groups, transitioning from Set to array for better compatibility.
- Improved task display logic to handle optional fields and ensure consistent rendering across components.
2025-07-03 17:34:31 +05:30
shancds
aee09aeb0d feat(enhanced-kanban): implement real-time task updates via socket integration
- Added a useEffect hook to handle incoming tasks through a socket connection.
- Implemented logic to differentiate between regular tasks and subtasks, updating the state accordingly.
- Enhanced the EnhancedKanbanBoardNativeDnD component to support real-time task management, improving user interactivity.
2025-07-03 15:39:11 +05:30
chamikaJ
d15c00c29b feat(task-management): enhance task list with new components and improved state management
- Introduced TaskListV2 and TaskGroupHeader components for a more organized task display.
- Implemented virtualized rendering using react-virtuoso for efficient task list handling.
- Updated Redux state management to include new selectors and improved task grouping logic.
- Added task filtering capabilities with TaskListFilters component for better user experience.
- Enhanced task selection handling and integrated drag-and-drop functionality for task rows.
- Updated package dependencies to include new libraries for icons and forms.
2025-07-03 15:31:54 +05:30
shancds
6c4bcbe300 feat(enhanced-kanban): allow dynamic grouping in task reordering
- Integrated dynamic grouping by utilizing the `groupBy` state from the enhancedKanbanReducer.
- Updated the task reordering logic to respect the selected grouping, defaulting to 'status' when no group is specified.
- Enhanced the overall flexibility of the EnhancedKanbanBoardNativeDnD component for improved user experience.
2025-07-03 15:17:19 +05:30
shancds
2ff0555493 feat(enhanced-kanban): implement real-time task and group reordering with socket integration
- Added socket functionality to emit task sort order changes during drag-and-drop operations.
- Enhanced group drop handling to include API calls for updating group order, with error handling and alerts for failed updates.
- Integrated authentication and socket context to manage project and team information effectively.
2025-07-03 14:52:42 +05:30
shancds
e84ab43b36 refactor(enhanced-kanban): clean up TaskCard component structure
- Reformatted the TaskCard component for improved readability by adjusting indentation and spacing.
- Ensured consistent styling and structure for the drop indicator and task card elements.
- Maintained existing functionality while enhancing code clarity and maintainability.
2025-07-03 14:08:09 +05:30
shancds
8134c6af35 feat(enhanced-kanban): add task creation functionality to Kanban groups
- Introduced EnhancedKanbanCreateTaskCard component for adding tasks directly within Kanban groups.
- Implemented conditional rendering of task creation buttons based on user roles (owner/admin or project manager).
- Enhanced the KanbanGroup component to support task creation at both the top and bottom of the task list, improving user experience.
2025-07-03 12:31:51 +05:30
shancds
e05169b7b4 refactor(enhanced-kanban): simplify EnhancedKanbanBoardNativeDnD component
- Removed unused imports and commented-out code to clean up the component.
- Consolidated task and group rendering logic by importing KanbanGroup directly.
- Streamlined the code structure for better readability and maintainability.
2025-07-03 11:33:31 +05:30
shancds
df62f15734 refactor(enhanced-kanban): improve task reordering logic in EnhancedKanbanBoardNativeDnD
- Enhanced drag-and-drop functionality to handle both same-group and cross-group task reordering more efficiently.
- Simplified the task update process by consolidating logic for updating task arrays.
- Ensured proper index adjustments during reordering to maintain task integrity and prevent errors.
2025-07-03 11:22:52 +05:30
chamikaJ
e26f16bbc2 feat(routes): implement lazy loading and suspense fallback for route components
- Refactored account setup, admin center, reporting, and settings routes to utilize React's lazy loading for improved performance.
- Wrapped route components in Suspense with a fallback UI to enhance user experience during loading states.
- Removed unused drag-and-drop CSS file to clean up the codebase.
2025-07-03 10:34:06 +05:30
Chamika J
7623ea2f7f Merge pull request #225 from Worklenz/fix/WB-705-task-list-timer-cell
Fix/wb 705 task list timer cell
2025-07-03 09:46:09 +05:30
chamikaJ
c19c1c2f34 refactor(task-management): optimize task selection handling in TaskListBoard
- Improved task selection logic by utilizing a Set for efficient addition and removal of selected task IDs.
- Streamlined the dispatch of selected tasks and IDs to Redux state, enhancing performance and readability.
- Removed unused context menu logic from TaskRow component, simplifying the codebase and focusing on subtask expansion management.
2025-07-03 09:44:16 +05:30
shancds
6443a03afd Merge branch 'release/v2.0.4' of https://github.com/Worklenz/worklenz into test/row-kanban-board-v1 2025-07-03 08:10:29 +05:30
chamiakJ
bb4229a82d Merge branch 'release/v2.0.4' of https://github.com/Worklenz/worklenz into fix/WB-705-task-list-timer-cell 2025-07-03 07:48:00 +05:30
chamiakJ
e41cead10b feat(task-management): implement context menu for task actions
- Added a context menu to the TaskRow component, allowing users to perform actions such as assigning tasks, archiving, deleting, and moving tasks between statuses, priorities, and phases.
- Introduced TaskContextMenu component to handle context menu logic and interactions.
- Enhanced task row styling for improved hover effects and visibility in both light and dark modes.
- Updated task management slice to include new actions for handling task assignments and conversions.
2025-07-03 07:42:43 +05:30
chamiakJ
ecd4d29a38 expand sub tasks 2025-07-03 01:31:05 +05:30
shancds
7dfaacd28e refactor(enhanced-kanban): update drag-and-drop functionality in EnhancedKanbanBoardNativeDnD
- Added `idx` prop to TaskCard for better task index management during drag-and-drop.
- Adjusted drop indicator styling for improved visibility.
- Commented out unused drag-and-drop handlers in KanbanGroup for clarity.
2025-07-02 18:11:31 +05:30
shancds
775a91889f refactor(enhanced-kanban): relocate EnhancedKanbanBoardNativeDnD component
- Moved EnhancedKanbanBoardNativeDnD to a new directory for better organization.
- Updated import paths in ProjectViewEnhancedBoard to reflect the new location.
2025-07-02 16:36:11 +05:30
Chamika J
3159ba14b9 Merge pull request #224 from Worklenz/fix/WB-705-task-list-timer-cell
feat(task-management): add configuration buttons and permission check…
2025-07-02 16:12:12 +05:30
chamikaJ
3bef18901a feat(task-management): add configuration buttons and permission checks to task filters
- Introduced ConfigPhaseButton and CreateStatusButton components for enhanced task filtering options.
- Implemented permission checks to conditionally render configuration buttons based on user roles (owner/admin or project manager).
- Removed the TaskManagementDemo component as it was no longer needed, streamlining the codebase.
2025-07-02 16:11:48 +05:30
Chamika J
a2395f121b Merge pull request #223 from Worklenz/fix/WB-705-task-list-timer-cell
Fix/wb 705 task list timer cell
2025-07-02 16:01:29 +05:30
chamikaJ
a1e8a4c464 feat(task-management): enhance bulk action bar and localization updates
- Added new features to the OptimizedBulkActionBar, including dropdowns for labels and assignees, improving task management capabilities.
- Integrated task template creation functionality for owners/admins, allowing users to create templates from selected tasks.
- Updated localization files for multiple languages, adding new strings for label searching and template name requirements to enhance user experience.
- Refactored LabelsDropdown component to support label filtering and improved UI feedback for label creation.
2025-07-02 16:01:04 +05:30
shancds
11e5a6d379 feat(enhanced-kanban): enhance Kanban board with improved task filtering and loading states
- Integrated ImprovedTaskFilters component for better task management.
- Added loading and error handling states to the Kanban board for improved user experience.
- Updated drag-and-drop functionality to dispatch actions for reordering tasks and groups directly from Redux state.
2025-07-02 15:42:53 +05:30
chamikaJ
365369cc31 feat(i18n): enhance translation loading and preloading mechanism
- Introduced a utility function `ensureTranslationsLoaded` to preload essential translation namespaces, improving app initialization.
- Updated `App` component to initialize translations alongside CSRF token on startup.
- Created custom hooks `useTranslationPreloader`, `useBulkActionTranslations`, and `useTaskManagementTranslations` to manage translation readiness and prevent Suspense issues.
- Refactored components to utilize new translation hooks, ensuring translations are ready before rendering.
- Enhanced `OptimizedBulkActionBar` and `TaskListBoard` components to improve user experience during language switching.
2025-07-02 15:37:24 +05:30
chamikaJ
0452dbd179 feat(task-management): implement task reordering and group updates via API
- Added API methods for reordering tasks and updating task groups (status, priority, phase).
- Enhanced task management slice with async thunks for handling task reordering and group movements.
- Updated task list board to support real-time updates during drag-and-drop operations, emitting socket events for task sort order changes.
- Refactored task-related components to utilize shared Ant Design imports for consistency and maintainability.
- Removed unused Ant Design imports and optimized drag-and-drop CSS for improved performance.
2025-07-02 15:17:21 +05:30
shancds
d70fb133b7 feat(enhanced-kanban): integrate native drag-and-drop functionality
- Replaced the existing EnhancedKanbanBoard component with EnhancedKanbanBoardNativeDnD to support native drag-and-drop interactions.
- Commented out the previous EnhancedKanbanBoard usage for potential future reference.
2025-07-02 14:33:51 +05:30
chamikaJ
2064c0833c feat(task-management): enhance task details and subtask handling
- Added subtask-related properties to the Task interface for better management of subtasks.
- Implemented functionality to show and add subtasks directly within the task list, improving user interaction.
- Updated task rendering logic to accommodate new subtask features, enhancing overall task management experience.
- Removed unused components and optimized imports across various task management files for cleaner code.
2025-07-02 12:38:24 +05:30
Chamika J
d0947112eb Merge pull request #222 from shancds/fix/project-drawer-issues
refactor(enhanced-kanban): update state management for editable section
2025-07-02 12:34:14 +05:30
shancds
c9d9134049 refactor(enhanced-kanban): update state management for editable section
- Changed the source of `editableSectionId` from `boardReducer` to `enhancedKanbanReducer` for improved state organization.
- Added `setEditableSection` and `deleteSection` actions to manage the editable section state within the enhanced kanban slice.
2025-07-02 12:28:34 +05:30
Chamika J
91b8f4ca2b Merge pull request #221 from shancds/fix/project-drawer-issues
fix(enhanced-kanban): update subtask placeholder text for clarity
2025-07-02 12:23:30 +05:30
shancds
d56eaa9f02 fix(enhanced-kanban): prevent unnecessary state updates on blur event 2025-07-02 12:05:54 +05:30
shancds
71e1d58ec6 refactor(enhanced-kanban): simplify task creation event handling
- Updated the event handler in EnhancedKanbanCreateTaskCard to clarify that the form reset is separate from the global task addition to Redux, improving code readability and maintainability.
2025-07-02 11:41:43 +05:30
shancds
382283d0ce fix(enhanced-kanban): update subtask placeholder text for clarity 2025-07-02 11:20:56 +05:30
chamikaJ
c29ba6ea69 refactor(task-row): clean up imports and remove unused components
- Removed unused imports and components from task-row.tsx
2025-07-02 09:34:02 +05:30
chamikaJ
cf5f5c1449 feat(task-row): integrate task timer functionality into task management
- Added TaskTimer component to manage task time tracking.
- Updated TaskTimeTracking to utilize the new timer functionality, enhancing user interaction with task timing.
- Refactored props to pass task ID for timer management, improving state handling.
2025-07-02 09:27:36 +05:30
Chamika J
d5796b2cb5 Merge pull request #219 from Worklenz/fix/WB-708-task-progress
feat(task-row): add progress indicator with CheckCircle icon
2025-07-02 09:21:38 +05:30
chamikaJ
dd8bfe9fce feat(task-row): add progress indicator with CheckCircle icon
- Introduced a visual indicator for task progress, displaying a CheckCircle icon when progress reaches 100%.
- Updated the rendering logic to show a progress bar for incomplete tasks, enhancing user feedback on task status.
2025-07-02 09:21:27 +05:30
Chamika J
f80ec9797e Merge pull request #216 from Dev-Tanaay/bug/Documentation
Solved the documentation error with issue number
2025-07-02 09:09:27 +05:30
Chamika J
eb158678d4 Merge pull request #217 from shancds/fix/enhanced-board-assignees
Fix/enhanced board assignees
2025-07-02 09:08:55 +05:30
Chamika J
865502a796 Merge pull request #218 from Worklenz/fix/bulk-action-bar
feat(task-management): enhance bulk action bar with new features and …
2025-07-02 09:08:36 +05:30
chamikaJ
7a7856bc36 Merge branch 'release/v2.0.4' of https://github.com/Worklenz/worklenz into fix/bulk-action-bar 2025-07-02 09:07:09 +05:30
chamikaJ
756c9b892f feat(suspense-fallback): replace Spin with Skeleton for improved loading experience
- Updated the SuspenseFallback component to use Ant Design's Skeleton instead of Spin for a more visually appealing loading state.
- Enhanced the layout with padding and responsive width settings for better presentation.
- Adjusted InlineSuspenseFallback to implement similar Skeleton loading structure, improving consistency across components.
2025-07-02 08:59:45 +05:30
chamiakJ
ccde08b700 feat(task-management): enhance bulk action bar with new features and translations
- Added new bulk action options for changing status, priority, and phase, as well as adding labels and assigning tasks.
- Integrated translations for English, German, Spanish, and Portuguese in the task table bulk actions.
- Updated the `OptimizedBulkActionBar` to utilize Redux state for status, priority, and phase selections.
- Improved user experience with dynamic dropdown menus for bulk actions and confirmation prompts.
- Refactored task selection handling to ensure proper state management during bulk actions.
2025-07-02 07:59:34 +05:30
shancds
87f73ee4c2 feat(assignee-selector): enhance real-time updates and UI interactions
- Added socket event handling for quick assignee updates, dispatching changes to the enhanced Kanban state.
- Updated AssigneeSelector component to prevent event propagation on dropdown interactions.
- Integrated AvatarGroup and LazyAssigneeSelectorWrapper in EnhancedKanbanTaskCard for improved assignee display and selection.
2025-07-01 18:21:52 +05:30
Dev-Tanaay
fbbd820512 Updation in Documentation 2025-07-01 17:29:02 +05:30
shancds
0a92d38ccf refactor(task-sort-order): optimize access check and clean up code
- Improved the access check logic by incorporating team member validation in the SQL query, enhancing security and accuracy.
- Removed unnecessary whitespace for cleaner code formatting.
- Updated socket event emission for consistency in response structure.
2025-07-01 16:57:07 +05:30
Chamika J
e4e6d3c74d Merge pull request #213 from Worklenz/fix/task-list-realtime-update
chore(dependencies): update package-lock.json and package.json for de…
2025-07-01 15:22:59 +05:30
chamikaJ
f352d823a8 Merge branch 'main' of https://github.com/Worklenz/worklenz into development 2025-07-01 15:22:41 +05:30
chamikaJ
98a96b4fcc chore(dependencies): update package-lock.json and package.json for dependency management
- Removed `pg-native` from dependencies in both package files.
- Updated several AWS SDK and Smithy packages to their latest versions for improved functionality and security.
- Added new dependencies for emoji-regex and string-width to enhance task management features.
- Adjusted various package versions to ensure compatibility and performance optimizations across the application.
2025-07-01 15:19:21 +05:30
shancds
63483e01c2 Merge branch 'release/v2.0.4' of https://github.com/Worklenz/worklenz into fix/enhanced-board-sub-task-section 2025-07-01 12:42:05 +05:30
Chamika J
b247186a0a Merge pull request #212 from Worklenz/fix/task-list-realtime-update
Fix/task list realtime update
2025-07-01 12:31:36 +05:30
shancds
4304ebf7b1 refactor(task-socket-handlers): remove unnecessary console logs for cleaner code
- Eliminated various console log statements from the task socket handlers to enhance code clarity and performance.
- Maintained functionality while reducing noise in the console output during task status, priority, and phase changes.
2025-07-01 12:24:32 +05:30
Chamika J
4d229c79d5 Merge pull request #211 from shancds/fix/enhanced-board-sub-task-section
Fix/enhanced board sub task section
2025-07-01 12:11:18 +05:30
Chamika J
6e995e7fc2 Merge pull request #209 from shancds/fix/task-manual-progress-update
Fix/task manual progress update
2025-07-01 12:10:59 +05:30
Chamika J
eec100dfe8 Merge pull request #207 from shancds/fix/kanban-board-enhanced-feat
feat(enhanced-kanban): enhance drag-and-drop overlays with theme-awar…
2025-07-01 12:10:01 +05:30
shancds
10d64c88e3 feat(enhanced-kanban): implement real-time updates and task expansion handling
- Integrated socket event handlers for real-time updates in the enhanced Kanban board, improving task management responsiveness.
- Added functionality to toggle task expansion for subtasks, enhancing user interaction with task details.
- Updated state management to handle subtasks more effectively, including loading states and counts.
- Refactored subtask fetching logic to utilize a dedicated API endpoint, streamlining data retrieval.
2025-07-01 12:04:30 +05:30
shancds
165a87ce69 feat(enhanced-kanban): add start and end date updates for tasks in task drawer
- Implemented actions to update task start and end dates within the enhanced Kanban feature, ensuring real-time synchronization when the task drawer is used.
- Enhanced task drawer components to dispatch updates to the enhanced Kanban state based on the current tab, specifically when on the board tab.
- Improved state management for task date properties, allowing for better handling of task updates across different views.
2025-07-01 11:02:04 +05:30
shancds
e5ff036d81 feat(task-status): enhance task status change handling to reset manual progress
- Added logic to reset manual_progress to FALSE when a task transitions from "done" to "todo" or "doing", allowing for accurate progress recalculation based on subtasks.
- Improved logging for task status changes to provide better insights into task management actions.
- Ensured parent task progress updates are triggered appropriately for subtasks during status changes.
2025-07-01 10:18:56 +05:30
chamiakJ
326f283d4e feat(task-management): introduce optimized bulk action bar component
- Added a new `OptimizedBulkActionBar` component for enhanced task management.
- Implemented performance optimizations, including memoization and smooth animations.
- Integrated bulk action handlers for status, priority, phase changes, and more.
- Updated `TaskListBoard` to utilize the new bulk action bar, improving user experience for task selection and actions.
- Included responsive design adjustments and accessibility features.
2025-07-01 10:11:39 +05:30
shancds
c048085c8a feat(enhanced-kanban): enhance task progress and name updates in task drawer
- Integrated updates for task progress and name within the enhanced Kanban feature, allowing for real-time synchronization based on the selected tab (tasks-list or board).
- Added new actions to handle task progress updates specifically for the enhanced Kanban view.
- Updated task drawer components to dispatch the appropriate actions based on the current tab, improving user experience and state management.
2025-07-01 09:58:49 +05:30
shancds
8fcd4d0d53 feat(enhanced-kanban): integrate task assignee, label, and priority updates
- Added actions to update task assignees, labels, and priority within the enhanced Kanban feature, enhancing task management capabilities.
- Updated task drawer components to utilize new actions for real-time updates based on user interactions.
- Improved state management for better handling of task properties across different views.
2025-07-01 09:42:47 +05:30
chamiakJ
30bdaf1ed5 chore(dependencies): update Tailwind CSS and related configurations
- Removed @tailwindcss/postcss from devDependencies and updated tailwindcss to version 3.4.15 for improved styling.
- Added autoprefixer as a new dependency to enhance CSS compatibility.
- Updated postcss.config.js to reflect the new Tailwind CSS plugin structure.
- Created a new tailwind.config.js file to define Tailwind's configuration.
- Adjusted CSS imports in index.css to utilize Tailwind's utility classes.
- Refined focus styles across various components for better accessibility and visual consistency.
2025-07-01 09:23:10 +05:30
jiuhao47
39e09bedd3 add support for zh_cn 2025-06-30 20:52:49 +08:00
shancds
487fb76776 feat(enhanced-kanban): enhance drag-and-drop overlays with theme-aware styling
- Added theme mode support to the drag-and-drop overlays for tasks and groups, improving visual consistency in dark and light modes.
- Updated the display of active tasks and groups during drag operations to enhance user experience and clarity.
2025-06-30 17:09:17 +05:30
chamikaJ
41e563297a Merge branch 'release/v2.0.4' of https://github.com/Worklenz/worklenz into fix/task-list-realtime-update 2025-06-30 16:30:51 +05:30
Chamika J
9743adaed5 Merge pull request #206 from shancds/fix/kanban-board-enhanced-feat
refactor(enhanced-kanban): remove unnecessary div wrapper in Virtuali…
2025-06-30 15:25:30 +05:30
shancds
b179a0274f refactor(enhanced-kanban): remove unnecessary div wrapper in VirtualizedTaskList component
- Eliminated the redundant div element surrounding the EnhancedKanbanTaskCard to streamline the component structure and improve rendering efficiency.
2025-06-30 15:22:34 +05:30
chamikaJ
61574c847f chore(dependencies): update Tailwind CSS and related packages for improved styling
- Added @tailwindcss/postcss as a new dependency.
- Updated tailwindcss to version 4.1.11 for enhanced features and performance.
- Upgraded prettier-plugin-tailwindcss to version 0.6.13 for better formatting support.
- Adjusted postcss.config.js to use the new Tailwind CSS plugin structure.
- Removed the obsolete tailwind.config.js file as it is no longer needed.
2025-06-30 15:11:30 +05:30
Chamika J
2eee15be03 Merge pull request #205 from shancds/fix/kanban-board-enhanced-feat
feat(enhanced-kanban): add task assignees and labels fetching with im…
2025-06-30 14:58:24 +05:30
Chamika J
0ae615cc77 Merge branch 'release/v2.0.4' into fix/kanban-board-enhanced-feat 2025-06-30 14:58:15 +05:30
shancds
7f46b10a42 feat(enhanced-kanban): add updateEnhancedKanbanTaskStatus action for task status management
- Introduced a new action to update task status within the enhanced Kanban feature, allowing for dynamic task movement between groups based on status changes.
- Updated the task drawer status dropdown to utilize the new action for improved task management experience.
2025-06-30 14:53:00 +05:30
shancds
dee385c6db refactor(enhanced-kanban): remove console log from handleSubTaskExpand function
- Removed debugging console log from the handleSubTaskExpand function to clean up the code and improve performance.
2025-06-30 13:42:35 +05:30
Chamika J
207e038315 Merge pull request #204 from Worklenz/fix/task-list-realtime-update
Fix/task list realtime update
2025-06-30 12:49:17 +05:30
shancds
dc3433a036 feat(enhanced-kanban): add task assignees and labels fetching with improved filter management
- Implemented async thunks to fetch task assignees and labels for enhanced Kanban board.
- Updated state management to store original and current data for task assignees and labels.
- Enhanced filter selection actions to update both original and current data seamlessly.
- Integrated filter data loader for improved user experience in task management.
2025-06-30 12:25:22 +05:30
chamikaJ
14c5c148b9 refactor(task-management): optimize task management components with performance enhancements
- Updated import statements for consistency and clarity.
- Refined task sorting and update logic to improve responsiveness.
- Enhanced error logging for better debugging during task sort order changes.
- Increased overscan count in virtualized task lists for smoother scrolling experience.
- Introduced lazy loading for heavy components to reduce initial load times.
- Improved CSS styles for better responsiveness and user interaction across task management components.
2025-06-30 11:02:41 +05:30
chamiakJ
7fdea2a285 feat(performance): implement extensive performance optimizations across task management components
- Introduced batching and optimized query handling in SQL functions for improved performance during large updates.
- Enhanced task sorting functions with batching to reduce load times and improve responsiveness.
- Implemented performance monitoring utilities to track render times, memory usage, and long tasks, providing insights for further optimizations.
- Added performance analysis component to visualize metrics and identify bottlenecks in task management.
- Optimized drag-and-drop functionality with CSS enhancements to ensure smooth interactions and reduce layout thrashing.
- Refined task row rendering logic to minimize DOM updates and improve loading behavior for large lists.
- Introduced aggressive virtualization and memoization strategies to enhance rendering performance in task lists.
2025-06-30 07:48:32 +05:30
chamikaJ
e3324f0707 feat(task-management): enhance priority and status dropdowns with fallback rendering
- Added helper functions to display names and colors for raw priority and status values, improving user experience.
- Implemented fallback rendering for dropdowns to handle cases where the priority or status is not found in the list.
- Updated task row to display formatted priority and status values, ensuring consistency across the UI.
- Enhanced error handling in task list rendering to provide meaningful feedback when data is unavailable.
2025-06-27 17:39:47 +05:30
Chamika J
0336715103 Merge pull request #200 from Worklenz/fix/task-list-realtime-update
feat(assignee-selector): implement optimistic updates for assignee ma…
2025-06-27 15:58:54 +05:30
chamikaJ
c37ffd6991 feat(assignee-selector): implement optimistic updates for assignee management
- Added optimistic UI updates for assignee selection, improving user experience with immediate feedback.
- Introduced state management for pending changes to visually indicate ongoing updates.
- Enhanced member toggle functionality to reflect changes instantly in the UI while maintaining socket communication for backend updates.
- Improved checkbox behavior to prevent interaction during pending state, ensuring clarity in user actions.
2025-06-27 15:58:19 +05:30
Chamika J
5a07bcce77 Merge pull request #199 from Worklenz/fix/task-list-realtime-update
feat(task-management): improve hover state management and performance…
2025-06-27 15:28:26 +05:30
chamikaJ
ceb962a92a feat(task-management): improve hover state management and performance in task rows
- Enhanced CSS styles to ensure proper hover state resets and visibility of task action buttons.
- Implemented optimizations for hover effects to prevent flickering and improve user interaction.
- Adjusted containment properties to enhance rendering performance during hover states.
- Refined transition effects for smoother visibility changes of task-related elements.
2025-06-27 15:27:33 +05:30
Chamika J
4af204daec Merge pull request #198 from Worklenz/fix/task-list-realtime-update
feat(task-management): enhance real-time updates and performance opti…
2025-06-27 15:18:13 +05:30
chamikaJ
30edda1762 feat(task-management): enhance real-time updates and performance optimizations
- Implemented CSS styles to prevent flickering during socket updates, ensuring stable content visibility.
- Modified `TaskRow` component to improve loading behavior and prevent blank content during real-time updates.
- Enhanced socket handlers to update task management state immediately upon receiving real-time data, reducing the need for unnecessary refetches.
- Introduced logic to track loading state, ensuring consistent rendering and improved user experience during task updates.
2025-06-27 15:17:29 +05:30
Chamika J
5bd06a12dd Merge pull request #197 from Worklenz/fix/task-list-realtime-update
feat(task-management): enhance task row functionality and URL synchro…
2025-06-27 13:57:41 +05:30
chamikaJ
8b63c1cf9e feat(task-management): enhance task row functionality and URL synchronization
- Integrated Redux for managing task drawer state, allowing for task selection and data fetching when opening the task drawer.
- Improved URL synchronization logic to handle task ID updates more effectively, ensuring proper state management during drawer interactions.
- Updated task indicators to use type-safe access for subtasks, comments, and attachments counts, enhancing code reliability and readability.
- Refactored URL clearing logic to prevent unnecessary updates when closing the task drawer, improving user experience.
2025-06-27 13:56:46 +05:30
Chamika J
1e6b1b7d96 Merge pull request #196 from Worklenz/fix/task-list-realtime-update
feat(localization): update project view header translations and enhan…
2025-06-27 13:24:43 +05:30
chamikaJ
e74668c389 feat(localization): update project view header translations and enhance UI functionality
- Added new translations for "Import task", "Refresh project", "Save as template", and "Invite" in Albanian, German, English, Spanish, and Portuguese.
- Refactored `ProjectViewHeader` component to optimize rendering with memoization and improved state management.
- Enhanced task creation and subscription handling with better performance and error management.
- Improved dropdown and button actions for a more intuitive user experience.
2025-06-27 13:24:01 +05:30
Chamika J
cf52140bca Merge pull request #195 from Worklenz/fix/task-list-realtime-update
Fix/task list realtime update
2025-06-27 13:13:04 +05:30
chamikaJ
7e44d53bb3 feat(performance): implement various performance optimizations across components
- Added a new `usePerformanceOptimization` hook for tracking render performance, debouncing, throttling, and optimized selectors to reduce unnecessary re-renders.
- Enhanced `ProjectGroupList` and `ProjectList` components with preloading of project view and task management components on hover for smoother navigation.
- Updated `TaskListBoard` to import `ImprovedTaskFilters` synchronously, avoiding suspense issues.
- Introduced a `resetTaskDrawer` action in the task drawer slice for better state management.
- Improved layout and positioning in `SuspenseFallback` for better user experience during loading states.
- Documented performance optimizations in `PERFORMANCE_OPTIMIZATIONS.md` outlining key improvements and metrics.
2025-06-27 13:12:47 +05:30
chamikaJ
fdb485614f feat(task-management): enhance task management UI with subtask functionality
- Added localization support for task management messages in multiple languages, including Albanian, German, English, Spanish, and Portuguese.
- Implemented subtask addition feature in the `TaskRow` component, allowing users to create and manage subtasks directly within the task interface.
- Introduced hover effects and improved styling for task rows to enhance user experience and interaction.
- Updated task indicators to display counts for subtasks, comments, and attachments, providing better visibility of task details.
2025-06-27 10:59:21 +05:30
Chamika J
6b35ffe930 Merge pull request #194 from Worklenz/fix/task-list-realtime-update
Fix/task list realtime update
2025-06-27 08:50:02 +05:30
chamiakJ
9a254105fb feat(task-management): add task phase and priority dropdown components
- Introduced `TaskPhaseDropdown` and `TaskPriorityDropdown` components for managing task phases and priorities within the task management interface.
- Integrated these components into the `TaskRow` to enhance user interaction and streamline task updates.
- Updated socket handlers to handle phase and priority changes, ensuring real-time updates and improved task organization.
- Enhanced dropdown functionality with animations and improved accessibility features.
2025-06-27 07:28:47 +05:30
chamiakJ
e73196a249 feat(task-management): implement task movement between groups
- Added `moveTaskBetweenGroups` action to facilitate moving tasks across different groups while maintaining state integrity.
- Enhanced task management slice to support task updates during group transitions, including logging for better debugging.
- Updated socket handlers to utilize the new action for moving tasks based on status, priority, and phase changes, improving task organization and user experience.
2025-06-27 07:06:14 +05:30
chamiakJ
84f77940fd feat(task-management): add functionality to assign tasks to specific groups
- Introduced `addTaskToGroup` action to allow tasks to be added to designated groups based on group IDs.
- Enhanced task management slice to support group assignment for better organization and compatibility with V3 API.
- Updated socket handlers to dispatch `addTaskToGroup` with appropriate group IDs extracted from backend responses.
2025-06-27 07:06:02 +05:30
chamiakJ
3d1cb29a67 feat(tasks): optimize task retrieval and performance metrics logging
- Updated `getList` and `getTasksOnly` methods to skip expensive progress calculations by default, enhancing performance.
- Introduced logging for performance metrics, including method execution times and warnings for deprecated methods.
- Added new `getTaskProgressStatus` endpoint to provide basic progress stats without heavy calculations.
- Implemented performance optimizations in the frontend, including lazy loading and improved rendering for task rows.
- Enhanced task management slice with reset actions for better state management.
- Added localization support for task management messages in multiple languages.
2025-06-26 12:26:50 +05:30
Chamika J
345b8500cd Merge pull request #188 from shancds/fix/get-pull-6-26
Fix/get pull 6 26
2025-06-26 12:26:28 +05:30
shancds
3672d02d6f Merge branch 'fix/enhanced-task-board' into fix/get-pull-6-26 2025-06-26 12:18:29 +05:30
shancds
efbfe77deb feat(enhanced-kanban): integrate status categories fetching and improve task card behavior
- Added fetching of status categories in EnhancedKanbanBoard to ensure data availability.
- Enhanced task creation flow in EnhancedKanbanCreateTaskCard by resetting input state and managing focus more effectively.
- Improved cleanup in useEffect hooks to prevent memory leaks and ensure proper resource management.
- Streamlined imports and updated logic in DeleteStatusDrawer for better task group fetching and error handling.
2025-06-26 12:02:21 +05:30
Chamika J
09cf5d8990 Merge pull request #186 from shancds/refact/board-task-card-performance
feat(task-filters): enhance ImprovedTaskFilters for Kanban integration
2025-06-26 09:50:44 +05:30
Chamika J
1e15630708 Merge branch 'release/v2.0.4' into refact/board-task-card-performance 2025-06-26 09:50:30 +05:30
chamiakJ
8c02ad9291 feat(task-filters): enhance performance and debounce functionality in task filters
- Introduced performance constants to limit filter options and improve UI responsiveness.
- Implemented an enhanced debounced function with cancellation support for filter and search changes, reducing unnecessary API calls.
- Optimized filter data retrieval and state updates using memoization to prevent redundant calculations.
- Improved the clear all filters functionality to batch state updates and prevent multiple re-renders, enhancing user experience.
- Updated the handling of search input to immediately clear and dispatch actions, ensuring efficient task fetching.
2025-06-26 00:07:19 +05:30
chamiakJ
4c34a01729 refactor(task-filters): update priority handling in task filters
- Replaced the use of selectedPriorities from taskManagement slice with priorities from taskReducer for consistency across the application.
- Updated dispatch calls in ImprovedTaskFilters to utilize the new setPriorities action, enhancing clarity and maintainability.
- Removed unnecessary imports and streamlined the selector logic for improved performance and readability.
2025-06-25 23:51:59 +05:30
chamiakJ
19cd0e577c feat(task-filters): implement comprehensive filter clearing functionality
- Added logic to clear label, assignee, and priority filters in ImprovedTaskFilters, enhancing user experience by allowing users to reset all filters at once.
- Updated the dependency array in the useEffect hook to include currentTaskLabels and currentTaskAssignees, ensuring proper updates on filter changes.
- Modified task management slice to change the delimiter for selected labels, assignees, and priorities from commas to spaces for improved readability.
2025-06-25 23:46:38 +05:30
chamiakJ
e096bc66ab feat(task-filters): implement search functionality for task filtering
- Added search state management to ImprovedTaskFilters, allowing users to filter tasks based on search input.
- Integrated search actions for both list and board views, ensuring appropriate task fetching based on the current view.
- Updated task management slice to include a search field, enhancing the overall task filtering capabilities.
2025-06-25 23:40:41 +05:30
chamiakJ
f22caea1e5 refactor(task-filters, project-view-header): streamline imports and optimize task fetching
- Removed unused imports and optimized the import structure in ImprovedTaskFilters for better readability and performance.
- Updated ProjectViewHeader to include a call to fetchTasksV3, ensuring enhanced task data is refreshed appropriately.
- Adjusted task creation request type to Partial for improved type safety.
2025-06-25 23:33:32 +05:30
shancds
208d1ad5d4 feat(task-filters): enhance ImprovedTaskFilters for Kanban integration
- Updated useFilterData to support both board and list views, utilizing enhanced Kanban state for filtering.
- Integrated new selectors for Kanban-specific filters including priorities, labels, and assignees.
- Refactored handleSelectionChange and handleSearchChange to accommodate Kanban logic, ensuring proper dispatch of actions based on the selected view.
- Improved overall filter functionality and user experience in task management.
2025-06-25 17:10:38 +05:30
chamikaJ
44527f68cf feat(assignee-selector, suspense-fallback, project-view): optimize component loading and enhance user experience
- Integrated synchronous imports for TaskListFilters and filter dropdowns to improve performance and reduce loading times.
- Refactored AssigneeSelector to include a new invite member drawer functionality, enhancing user interaction.
- Simplified SuspenseFallback components for better loading experiences, ensuring they do not block the main UI.
- Updated project view constants to utilize InlineSuspenseFallback for lazy-loaded components, improving rendering efficiency.
2025-06-25 17:08:40 +05:30
Chamika J
3c7cacc46f Merge pull request #185 from shancds/refact/board-task-card-performance
Refact/board task card performance
2025-06-25 16:25:32 +05:30
chamikaJ
bbd602a297 feat(assignee-selector, labels-selector): improve dropdown visibility handling
- Enhanced dropdown behavior by checking button visibility in the viewport before updating position.
- Added logic to hide the dropdown if the button is not visible, improving user experience during scrolling.
2025-06-25 15:43:24 +05:30
chamikaJ
df2a40b861 feat(assignee-selector): enhance dropdown functionality and position handling
- Added button reference and dropdown position state to improve dropdown positioning.
- Implemented useCallback for updating dropdown position on scroll and resize events.
- Enhanced click outside handling to close the dropdown correctly.
- Utilized createPortal for rendering the dropdown, ensuring it overlays correctly in the DOM.
- Improved styling and behavior of the dropdown button based on its open state.
2025-06-25 15:40:20 +05:30
shancds
e29e5ed0a4 feat(enhanced-kanban): integrate ImprovedTaskFilters and fetchBoardSubTasks for enhanced task management
- Replaced the existing TaskListFilters with ImprovedTaskFilters in EnhancedKanbanBoard for better filtering capabilities.
- Updated EnhancedKanbanTaskCard to handle subtask expansion and fetching using the new fetchBoardSubTasks action.
- Added sectionId prop to EnhancedKanbanTaskCard and EnhancedKanbanGroup for improved task organization.
- Refactored project-view-header to utilize fetchEnhancedKanbanGroups for loading task groups.
2025-06-25 15:24:44 +05:30
chamikaJ
734b5f807b feat(task-management): enhance task components with performance optimizations and new status field
- Rearranged the order of task fields in the dropdown for better usability.
- Introduced a new CSS file for task row optimizations, improving rendering performance and responsiveness.
- Added utility functions for date formatting and performance monitoring to enhance task management efficiency.
- Updated TaskRow and TaskStatusDropdown components to improve rendering and user experience, including better handling of status display and dark mode support.
- Integrated new status field into various task management components, ensuring consistent visibility and functionality across the application.
2025-06-25 15:22:38 +05:30
chamikaJ
85cce6e707 refactor(vite.config.ts): enhance build configuration and chunking strategy
- Updated Vite configuration to ensure a single React instance and improve Ant Design integration by refining the deduplication process.
- Enhanced build settings, including target specification and sourcemap handling, to optimize performance.
- Streamlined manual chunking strategy for better React context management and improved file naming conventions for assets.
- Adjusted CSS optimization settings and introduced experimental features for improved build performance.
2025-06-25 13:24:41 +05:30
chamikaJ
a4da6cdf3a refactor(vite.config.ts): simplify chunking strategy and enhance Ant Design integration
- Streamlined manual chunking strategy in Vite config to improve React context sharing and Ant Design component loading.
- Increased chunk size warning limit to accommodate larger Ant Design chunks.
- Updated Ant Design imports in `antd-imports.ts` for better tree-shaking and consistent React context availability across components.
2025-06-25 13:16:58 +05:30
chamikaJ
f837ca6b23 feat(configuration): enhance Vite config for React and Ant Design integration
- Updated Vite configuration to ensure a single React instance and prevent context issues by adding 'react/jsx-runtime' to deduplication.
- Improved manual chunking strategy to keep React core together and optimize Ant Design component loading.
- Adjusted build settings for better source map handling and chunk size warnings.
- Centralized Ant Design imports in `antd-imports.ts` to ensure React is available for all components.
- Removed exclusions for Ant Design to enhance compatibility and performance.
2025-06-25 13:10:19 +05:30
chamikaJ
7b326e8ff0 feat(project-view): optimize component loading and enhance configuration
- Introduced lazy loading for project view components and chart components to reduce initial bundle size.
- Centralized Ant Design imports in a new `antd-imports.ts` file for better tree-shaking and maintainability.
- Updated project view header and task list components to utilize centralized imports, improving consistency and performance.
- Enhanced project view constants to streamline component rendering and improve user experience.
2025-06-25 13:05:38 +05:30
chamikaJ
680e84d19b feat(task-management): centralize Ant Design imports and enhance task components
- Introduced a new `antd-imports.ts` file to centralize Ant Design component imports, optimizing tree-shaking and improving maintainability.
- Updated various task management components (e.g., TaskRow, TaskGroup, VirtualizedTaskList) to utilize centralized imports, ensuring consistent styling and configuration.
- Enhanced task filtering and display features by adding new fields (e.g., start date, due date, estimation) to task components for improved usability.
- Refactored date handling in task components to utilize memoization for performance optimization.
- Improved overall styling and responsiveness of task management components, particularly in dark mode.
2025-06-25 12:31:39 +05:30
chamikaJ
cf5919a3a0 feat(task-management): enhance Checkbox component and task selection functionality
- Added `indeterminate` state to Checkbox component for better visual representation of partial selections.
- Updated TaskGroup and VirtualizedTaskList components to utilize the new Checkbox features, allowing for group selection with indeterminate states.
- Implemented custom debounce function for saving task fields to localStorage, improving performance during user interactions.
- Enhanced task row styling for better visibility and user experience, particularly in dark mode.
2025-06-25 10:48:01 +05:30
shancds
9ce6cd63d1 refactor(enhanced-kanban): remove inline style from VirtualizedTaskList for cleaner markup 2025-06-25 09:53:41 +05:30
shancds
6f5e5f5c30 style(enhanced-kanban): comment out unused styles in EnhancedKanbanGroup for cleaner code 2025-06-25 09:08:06 +05:30
chamiakJ
a25fcf209a feat(task-management): implement customizable task list fields and configuration modal
- Added a new slice for managing task list fields, allowing users to toggle visibility and order of fields in the task list.
- Introduced a ColumnConfigurationModal for users to configure which fields appear in the dropdown and their order.
- Updated ShowFieldsFilterDropdown to integrate the new configuration modal and manage field visibility.
- Enhanced task management components to utilize the new field visibility settings, improving the overall user experience and customization options.
2025-06-25 07:57:53 +05:30
Chamika J
5d0777f67c Merge pull request #183 from Worklenz/fix/added-i18n-alb-and-de
Update LANGUAGE_TYPE enum to include 'alb' and 'de' for Albanian and …
2025-06-24 22:39:29 +05:30
chamiakJ
f1d504f985 Update LANGUAGE_TYPE enum to include 'alb' and 'de' for Albanian and German languages 2025-06-24 22:28:33 +05:30
chamiakJ
9a070ef5d3 chore: update package-lock.json and refactor index.html for improved readability
- Updated dependencies in package-lock.json, downgrading several @esbuild packages to version 0.17.19 and adjusting node engine requirements to >=12.
- Refactored index.html to enhance readability by consolidating script loading logic and improving formatting.
- Removed unnecessary comments and whitespace for cleaner code structure.
2025-06-24 22:21:13 +05:30
chamiakJ
3e5bc71535 Merge branch 'main' of https://github.com/Worklenz/worklenz into release/v2.0.4 2025-06-24 21:49:12 +05:30
Chamika J
6a4d77d904 Merge pull request #182 from Worklenz/feature/german-i18n
Feature/german i18n
2025-06-24 21:47:45 +05:30
Chamika J
c35d53266a Merge pull request #179 from OminduHirushka/language/de
German Language
2025-06-24 21:47:06 +05:30
Chamika J
ea79270bff Merge pull request #180 from shancds/refact/board-task-card-performance
Refact/board task card performance
2025-06-24 21:46:15 +05:30
chamiakJ
975e5c4faf Merge branch 'imp/task-list-performance-fixes' of https://github.com/Worklenz/worklenz into release/v2.0.4 2025-06-24 21:45:44 +05:30
chamiakJ
f405777463 feat(task-management): enhance task filtering and UI components for improved usability
- Updated AssigneeSelector and LabelsSelector components to include text color adjustments for better visibility in dark mode.
- Introduced ImprovedTaskFilters component for a more efficient task filtering experience, integrating Redux state management for selected priorities and labels.
- Refactored task management slice to support new filtering capabilities, including selected priorities and improved task fetching logic.
- Enhanced TaskGroup and TaskRow components to accommodate new filtering features and improve overall layout consistency.
2025-06-24 21:40:01 +05:30
shancds
217ec39503 feat(enhanced-kanban): enhance EnhancedKanbanTaskCard with improved task interaction and display
- Refactored EnhancedKanbanTaskCard to include new features such as task labels rendering, progress display, and a custom due date picker.
- Implemented click handling to open a task drawer for detailed task management.
- Enhanced UI with Ant Design components for better visual feedback and user interaction.
2025-06-24 17:08:18 +05:30
shancds
e89f81152e style(enhanced-kanban): remove background color from drag-over state in EnhancedKanbanGroup 2025-06-24 16:09:07 +05:30
Chamika J
a34b9a8fb0 Merge pull request #178 from shancds/imp/board-task-list-performance-fix
Imp/board task list performance fix
2025-06-24 14:51:32 +05:30
Omindu Hirushka
dc096f5e12 german language switch 2025-06-24 14:17:23 +05:30
Omindu Hirushka
a681aadcfa german language 2025-06-24 14:17:08 +05:30
shancds
29618660aa style(enhanced-kanban): enhance styling for EnhancedKanbanCreateTaskCard
- Added minimum height, z-index, box shadow, and margin adjustments to the EnhancedKanbanCreateTaskCard component for improved visual appearance.
- Updated class names to include hover effects, enhancing user interaction feedback.
2025-06-24 12:50:51 +05:30
shancds
d3c4fdef9d fix(enhanced-kanban): correct background color assignment in EnhancedKanbanCreateTaskCard
- Updated the background color logic in the EnhancedKanbanCreateTaskCard component to ensure proper color assignment based on theme mode.
- Refactored import statement for ITaskCreateRequest to use the correct path, improving code clarity and consistency.
2025-06-24 12:37:02 +05:30
shancds
4f7cbf3527 feat(enhanced-kanban): enhance EnhancedKanbanBoard with new task creation options and layout adjustments
- Updated the EnhancedKanbanBoard component to include a new section for creating tasks at both the top and bottom of each group.
- Adjusted the CSS for the kanban groups container to improve layout responsiveness.
- Refactored EnhancedKanbanGroup to manage task creation visibility and interactions more effectively, enhancing user experience during task management.
2025-06-24 12:24:54 +05:30
shancds
ad76563543 feat(enhanced-kanban): implement API call for column order updates during group reordering
- Added functionality to prevent reordering of groups when grouped by phases.
- Implemented API call to update the status order after reordering groups, with error handling to revert changes if the API call fails.
- Enhanced user feedback with alert notifications for failed updates, improving overall user experience during drag-and-drop operations.
2025-06-24 09:55:17 +05:30
Chamika J
4e973f3d51 Merge pull request #177 from shancds/imp/board-task-list-performance-fix
Imp/board task list performance fix
2025-06-24 08:19:33 +05:30
shancds
17bcf8c41f fix(enhanced-kanban): correct sort order handling for task drop positions
- Updated the logic for determining the sort order when tasks are dropped in the EnhancedKanbanBoard component.
- Added handling for dropping tasks at the end of a group and ensured proper assignment of sort orders, improving task organization during drag-and-drop operations.
2025-06-23 17:05:35 +05:30
shancds
a8bf4671fa feat(enhanced-kanban): add socket integration for task and column reordering
- Integrated socket functionality to emit events for task and column sort order changes during drag-and-drop operations in the EnhancedKanbanBoard component.
- Enhanced the handleDragEnd function to prepare and send the updated column order to the backend or via socket, improving real-time synchronization.
- Updated error handling for socket emissions to ensure robustness during reordering processes.
2025-06-23 16:57:23 +05:30
chamikaJ
95d0985f3d feat(task-management): enhance task row and virtualized list components for improved layout and performance
- Added support for customizable columns in `TaskRow` component, allowing for fixed and scrollable columns.
- Implemented synchronized scrolling between header and body in `VirtualizedTaskList` for better user experience.
- Refactored column header rendering to dynamically generate based on column definitions, improving maintainability.
- Enhanced styles for task group headers and column headers to ensure consistent appearance and responsiveness.
2025-06-23 16:49:57 +05:30
chamikaJ
2dd756bbb8 feat(tasks): implement V3 API for task management and enhance UI components
- Introduced `getTasksV3` and `refreshTaskProgress` methods in `TasksControllerV2` to optimize task retrieval and progress refreshing.
- Updated API routes to include new endpoints for V3 task management.
- Enhanced frontend components to utilize the new V3 API, improving performance by reducing frontend processing.
- Added `VirtualizedTaskList` and `VirtualizedTaskGroup` components for efficient rendering of task lists.
- Updated task management slice to support new V3 data structure and improved state management.
- Refactored styles for better dark mode support and overall UI consistency.
2025-06-23 16:34:57 +05:30
shancds
3be97b1da2 feat(enhanced-kanban): enhance EnhancedKanbanGroup with editable section names and status management
- Implemented functionality to edit section names directly within the EnhancedKanbanGroup component, allowing for a more dynamic user experience.
- Added unique name generation for sections to prevent duplicates.
- Integrated status update and deletion capabilities, enabling users to manage task statuses effectively.
- Enhanced UI with new Ant Design components for better interaction and visual feedback during editing and deletion processes.
2025-06-23 16:02:50 +05:30
shancds
b436db183f feat(enhanced-kanban): implement synchronous reordering for tasks and groups
- Added synchronous state updates for task and group reordering in the EnhancedKanbanBoard component, improving UI responsiveness during drag-and-drop operations.
- Introduced new actions `reorderTasks` and `reorderGroups` in the enhanced-kanban slice for better state management.
- Updated EnhancedKanbanGroup and EnhancedKanbanTaskCard components to utilize the new layout change animations, enhancing the user experience during reordering.
2025-06-23 14:08:32 +05:30
shancds
6508dc6c64 fix(enhanced-kanban): remove background color from drop preview in drag-over state
- Eliminated the background color for the drop preview when a kanban group is dragged over, enhancing visual clarity during drag-and-drop operations.
2025-06-23 12:03:46 +05:30
shancds
b3d39b65b0 feat(enhanced-kanban): implement group reordering and improve drag-and-drop functionality
- Added support for reordering kanban groups via drag-and-drop, enhancing user experience.
- Updated EnhancedKanbanBoard and EnhancedKanbanGroup components to handle group dragging and state management.
- Introduced visual feedback for dragging groups and tasks, improving usability.
- Refined CSS styles for better layout and responsiveness during drag operations.
2025-06-23 11:37:40 +05:30
shancds
67c26a973e refactor(enhanced-kanban): improve code readability and integrate TaskListFilters component
- Refactored EnhancedKanbanBoard and EnhancedKanbanGroup components for better code organization and readability.
- Integrated TaskListFilters component to enhance task filtering capabilities within the kanban board.
- Cleaned up unnecessary whitespace and improved formatting for consistency across the codebase.
2025-06-23 10:13:47 +05:30
chamiakJ
687fff9c74 feat(task-management): optimize task components for performance and usability
- Refactored TaskGroup and TaskRow components to improve rendering efficiency by utilizing memoization and callbacks.
- Moved color mappings for group statuses and priorities outside of components to prevent unnecessary re-creations.
- Enhanced drag-and-drop functionality with optimistic updates and throttling for smoother user experience.
- Updated task management slice to support new properties and batch updates for better performance.
- Simplified selectors and improved error handling in the task management slice.
2025-06-23 07:29:50 +05:30
Chamika J
f15f3f5110 Merge pull request #176 from Worklenz/feature/albanian-i18n
Feature/albanian i18n
2025-06-23 07:24:12 +05:30
Chamika J
07ae71fd23 Merge pull request #168 from OminduHirushka/upstream/language/alb
albanian language
2025-06-23 07:23:42 +05:30
Chamika J
9c7fad790f Merge pull request #151 from kithmina1999/feat/docker-db-backup
Feat/Add auto backup and manual backup functionality  to docker-compose.yaml
2025-06-23 06:57:24 +05:30
Chamika J
26270b2842 Merge pull request #166 from kithmina1999/chore/docker-image-size-reduction
Chore/docker image size reduction
2025-06-23 06:56:14 +05:30
chamiakJ
05729285af feat(components): introduce new UI components and enhance Vite configuration
- Added AssigneeSelector, Avatar, AvatarGroup, Button, Checkbox, CustomColordLabel, CustomNumberLabel, LabelsSelector, Progress, Tag, and Tooltip components for improved UI functionality.
- Updated Vite configuration to change the development server port to 5173 and removed unnecessary interop settings for module compatibility.
- Enhanced task management components to utilize new task structure and improve performance.
2025-06-22 14:16:39 +05:30
Chamika J
d713ed5900 Merge pull request #175 from Worklenz/imp/task-list-performance-fixes
feat(config): refine Vite configuration for improved chunking and mod…
2025-06-21 19:25:28 +05:30
chamiakJ
cfbb4534d8 feat(config): refine Vite configuration for improved chunking and module interop
- Added deduplication for React and React DOM to ensure a single instance.
- Simplified the chunking strategy to group React-related libraries together, enhancing dependency management.
- Introduced interop settings for better module compatibility.
2025-06-21 19:24:41 +05:30
Chamika J
67cff68581 Merge pull request #174 from Worklenz/imp/task-list-performance-fixes
fix(index.html): change script type for env-config.js to improve comp…
2025-06-21 19:21:02 +05:30
chamiakJ
b63df394cc fix(index.html): change script type for env-config.js to improve compatibility
feat(env-config): add env-config.js for development environment setup

- Updated index.html to use a standard script tag for env-config.js.
- Introduced env-config.js as a development placeholder for environment variables, allowing fallback to build-time env vars.
2025-06-21 19:20:36 +05:30
Chamika J
2a96e61a97 Merge pull request #173 from Worklenz/imp/task-list-performance-fixes
feat(lazy-loading): implement lazy loading and suspense for improved …
2025-06-21 19:17:54 +05:30
Chamika J
be26d241c0 Merge branch 'release/v2.0.4' into imp/task-list-performance-fixes 2025-06-21 19:16:00 +05:30
chamiakJ
2670eb2925 feat(lazy-loading): implement lazy loading and suspense for improved performance
- Added lazy loading for NotFoundPage and TaskListFilters components to enhance initial load times.
- Wrapped lazy-loaded components in Suspense boundaries to provide loading states and improve user experience.
- Updated Vite configuration to optimize chunking strategy and preserve module signatures for better dependency management.
2025-06-21 19:15:13 +05:30
Chamika J
75c8e678bf Merge pull request #172 from Worklenz/imp/task-list-performance-fixes
refactor(config): switch configuration files to CommonJS module syntax
2025-06-21 19:10:05 +05:30
chamiakJ
ddb3e2bc17 refactor(config): switch configuration files to CommonJS module syntax
- Updated jest.config.js, postcss.config.js, and tailwind.config.js to use CommonJS module.exports syntax for compatibility with Node.js environments.
2025-06-21 19:09:30 +05:30
Chamika J
613d7aba71 Merge pull request #171 from Worklenz/imp/task-list-performance-fixes
refactor(config): migrate configuration files to ES module syntax
2025-06-21 19:06:49 +05:30
chamiakJ
7a7eeefe3b refactor(config): migrate configuration files to ES module syntax
- Updated jest.config.js, postcss.config.js, and tailwind.config.js to use ES module export syntax.
- Removed unused tsconfigPaths import from vite.config.ts to streamline the configuration.
2025-06-21 19:05:48 +05:30
Chamika J
1c306c571b Merge pull request #170 from Worklenz/imp/task-list-performance-fixes
feat(config): enhance Vite configuration for improved build performan…
2025-06-21 19:03:21 +05:30
chamiakJ
fb56a12297 feat(config): enhance Vite configuration for improved build performance and development experience
- Updated Vite configuration to include production checks for sourcemaps and minification.
- Added development server settings for automatic opening and HMR overlay customization.
- Optimized chunking strategy for better caching and organization of dependencies.
- Enhanced asset file naming strategies for better categorization of images and fonts.
- Introduced experimental features for performance improvements and CSS optimization.
- Adjusted task-row component styling for improved font size consistency.
2025-06-21 19:02:35 +05:30
Chamika J
26171fd846 Merge pull request #169 from Worklenz/imp/task-list-performance-fixes
Imp/task list performance fixes
2025-06-21 18:53:55 +05:30
chamiakJ
5a475a84b5 feat(performance): add Redux performance monitoring and memoized selectors
- Introduced a Redux performance monitoring system to log action metrics, including duration and state size.
- Implemented middleware for tracking performance of Redux actions and logging slow actions in development.
- Added utility functions for analyzing performance metrics and generating recommendations for optimization.
- Created memoized selectors to enhance performance and prevent unnecessary re-renders across various application states.
2025-06-21 18:40:57 +05:30
chamiakJ
b617d15c62 feat(performance): enhance routing and component loading efficiency
- Implemented lazy loading for all route components to improve initial load times and reduce bundle size.
- Added Suspense boundaries around lazy-loaded components for better loading states and user experience.
- Memoized guard components to prevent unnecessary re-renders and optimize performance.
- Introduced defensive programming practices in guard components to handle potential errors gracefully.
- Updated routing structure to utilize React Router's future features for enhanced performance.
2025-06-21 18:32:41 +05:30
shancds
f7ba4f202b feat(enhanced-kanban): integrate react-window-infinite-loader and update project view
- Added react-window-infinite-loader to improve performance in rendering large lists.
- Integrated enhancedKanbanReducer into the Redux store for state management.
- Updated ProjectViewEnhancedBoard to utilize EnhancedKanbanBoard for better project visualization.
2025-06-21 18:24:09 +05:30
chamiakJ
bb57280c8c feat(performance): implement comprehensive performance improvements for Worklenz frontend
- Introduced a new document summarizing performance optimizations across the application.
- Applied React.memo(), useMemo(), and useCallback() to key components to minimize unnecessary re-renders and optimize rendering performance.
- Implemented a route preloading system to enhance navigation speed and user experience.
- Added performance monitoring utilities for development to track component render times and function execution.
- Enhanced lazy loading and suspense boundaries for better loading states.
- Conducted production optimizations, including TypeScript error fixes and memory management improvements.
- Memoized style and configuration objects to reduce garbage collection pressure and improve memory usage.
2025-06-21 18:16:13 +05:30
shancds
bbca644b40 feat(project-view-constants): add enhanced board view to project tabs
- Introduced ProjectViewEnhancedBoard to the project view constants.
- Added a new tab item for the enhanced board view, improving project management options.
- Updated tab items structure to include the new board variant for better user navigation.
2025-06-20 17:13:37 +05:30
chamikaJ
5221061241 feat(task-management): implement new task management features with BulkActionBar and task grouping
- Introduced BulkActionBar component for bulk actions on selected tasks, including status, priority, and assignee changes.
- Added TaskGroup and TaskRow components to enhance task organization and display.
- Implemented grouping functionality with GroupingSelector for improved task categorization.
- Enhanced drag-and-drop capabilities for task reordering within groups.
- Updated styling and responsiveness across task management components for better user experience.
2025-06-20 10:56:48 +05:30
kithmina1999
0d0596b767 ci(docker): update postgres healthcheck and retry settings
- Modify healthcheck command to use DB_NAME and DB_USER environment variables
- Reduce retries from 10 to 5 to fail faster if database is unavailable
2025-06-20 08:48:51 +05:30
Chamika J
dfb360733e Merge pull request #165 from Worklenz/imp/task-list-performance-fixes
Imp/task list performance fixes
2025-06-20 08:40:14 +05:30
chamiakJ
c1e6689beb refactor(task-management): improve layout and styling for TaskGroup and TaskRow components
- Updated TaskGroup and TaskRow to enhance column widths and visibility.
- Adjusted styling for task headers and buttons for better user experience.
- Improved overflow handling and responsiveness in task management components.
- Streamlined CSS for consistency across task-related components.
2025-06-20 07:33:14 +05:30
chamiakJ
4e1c6fb333 refactor(task-management): enhance TaskGroup and TaskRow components with column visibility and improved layout
- Integrated Redux for column visibility management in TaskGroup and TaskRow components.
- Simplified the rendering of task details based on column visibility settings.
- Updated styling for better consistency and responsiveness across task rows and groups.
- Removed unused imports and components to streamline the codebase.
2025-06-20 06:44:47 +05:30
kithmina1999
eca7af2d6f chore: remove obsolete git file with process information
The file contained outdated process information that was no longer relevant to the project. This cleanup helps maintain a cleaner codebase.
2025-06-19 22:32:17 +05:30
kithmina1999
3ace14fcdb build(docker): optimize docker setup and compose configuration
- Split Dockerfile into multi-stage build to reduce final image size
- Update docker-compose.yml with restart policies and health checks
- Improve .dockerignore files for both frontend and backend
- Fix MinIO bucket creation script to use 'alias set' instead of deprecated command
- Enhance PostgreSQL healthcheck configuration
2025-06-19 22:26:20 +05:30
Chamika J
69cd40dc95 Merge pull request #163 from shancds/fix/customer-feedback
refactor(project-view-updates): optimize comment handling and renderi…
2025-06-19 13:47:55 +05:30
shancds
ece614941e feat(task-comments): enhance comment processing with linkification and sanitization
- Added a utility to linkify URLs in comments, allowing users to click and open links.
- Refactored comment content processing to handle both mentions and links, improving user interaction.
- Updated the sanitizeHtml function to allow <span> tags for mentions, enhancing content display.
2025-06-19 13:23:40 +05:30
shancds
b47b3253f6 feat(project-view-updates): add linkification and context menu for comments
- Implemented linkification for URLs in comments, allowing users to click and open links in a new tab.
- Introduced a context menu for each comment with an option to delete, enhancing user interaction.
- Refactored comment rendering to include link handling and improved code organization.
2025-06-19 12:39:32 +05:30
shancds
889335c579 refactor(info-tab-footer): remove console log for member selection 2025-06-19 11:27:18 +05:30
shancds
7b657120e9 refactor(project-view-updates): optimize comment handling and rendering logic
- Introduced useMemo and useCallback hooks to enhance performance and prevent unnecessary re-renders.
- Refactored comment rendering logic into a separate function for better readability and maintainability.
- Updated mentionsOptions to utilize useMemo for efficient computation based on members.
- Improved comment change handling and member selection logic for a smoother user experience.
- Cleaned up code by removing redundant comments and optimizing dependencies in useEffect hooks.
2025-06-19 10:40:40 +05:30
Chamika J
a0cf5099f8 Merge pull request #162 from Worklenz/imp/kanban-performance
Imp/kanban performance
2025-06-19 10:35:44 +05:30
chamiakJ
82aa207e0d refactor(task-management): enhance TaskGroup and TaskRow components for improved functionality and styling
- Updated TaskGroup to include new props for task selection and toggling subtasks.
- Refactored TaskRow to improve layout and styling, including fixed and scrollable columns.
- Replaced drag handle icon and adjusted task metadata display for better clarity.
- Enhanced overall styling for better responsiveness and dark mode support.
2025-06-19 09:21:55 +05:30
Chamika J
301b58f0ba Merge pull request #161 from shancds/release/v2.0.3-kanban-handle-drag-over
Release/v2.0.3 kanban handle drag over
2025-06-19 08:31:43 +05:30
shancds
4c4a860c76 feat(board): enhance task and subtask management in board components
- Updated boardSlice to allow updating task assignees and names for both main tasks and subtasks.
- Improved BoardSubTaskCard to include context menu options for assigning tasks, deleting subtasks, and handling errors.
- Refactored BoardViewTaskCard to integrate dropdown menus for better task interaction and organization.
- Enhanced user experience by adding loading states and error handling for task actions.
2025-06-18 17:11:39 +05:30
chamikaJ
d0310ded28 refactor(project-view-enhanced-tasks): update project ID handling and improve error messaging
- Replaced use of `useParams` with `useAppSelector` to retrieve project information from the Redux store.
- Updated error message from "Project ID not found" to "Project not found" for better clarity.
- Adjusted the way the project ID is passed to the `TaskListBoard` component.
2025-06-18 17:10:14 +05:30
chamikaJ
c01ef4579a feat(project-view): add Enhanced Tasks tab and component
- Introduced a new tab for Enhanced Tasks in the project view.
- Created ProjectViewEnhancedTasks component to display task management features.
- Updated project-view-constants to include the new tab and adjusted indices for existing tabs.
- Enhanced task management styles for improved dark mode support.
2025-06-18 17:07:26 +05:30
Omindu Hirushka
99bec6c7f9 albanian language switch 2025-06-18 17:04:58 +05:30
chamikaJ
c1a303e78c feat(store): integrate task management reducers into the store
- Added taskManagementReducer, groupingReducer, and selectionReducer to the Redux store.
- Organized imports and store configuration for better clarity and maintainability.
2025-06-18 17:02:23 +05:30
shancds
193288013e refactor(priority-section): remove task name display from PrioritySection
- Eliminated the task name display from the PrioritySection component for a cleaner layout.
- Updated BoardViewTaskCard to include task name alongside the PrioritySection for improved organization.
2025-06-18 15:31:00 +05:30
shancds
39e8add103 feat(filters): enhance labels and members filter dropdowns
- Added useEffect to fetch labels when the component mounts or projectId changes in LabelsFilterDropdown.
- Improved members filter logic to only sync board members when the board task assignees are empty.
- Cleaned up redundant checks and optimized dependencies in the members filter dropdown.
2025-06-18 12:56:24 +05:30
shancds
0f82c9738b feat(project-view-board): implement task priority change handling
- Added a new function to handle task priority changes via socket events.
- Integrated priority change logic into the drag-and-drop functionality for improved task management.
- Cleaned up unused imports and improved code organization for better readability.
2025-06-18 12:18:58 +05:30
shancds
a4237a6f17 refactor(project-view-board): update collision detection strategy for drag-and-drop
- Replaced `closestCorners` with `collisionDetectionStrategy` to enhance drag-and-drop functionality.
- Aims to improve performance and accuracy during task movement on the project board.
2025-06-18 09:45:37 +05:30
Chamika J
20039a07ff Merge pull request #160 from Worklenz/imp/kanban-performance
Imp/kanban performance
2025-06-18 08:37:17 +05:30
Chamika J
dfc38a6829 Merge pull request #159 from shancds/release/v2.0.3-kanban-handle-drag-over
Release/v2.0.3 kanban handle drag over
2025-06-18 08:35:17 +05:30
shancds
0e0d1a5f11 refactor(project-view-board): clean up code and improve task handling logic
- Removed unnecessary conditional checks and whitespace for better readability.
- Streamlined task movement logic to enhance performance during drag-and-drop operations.
- Improved socket event emission for task sort order changes, ensuring more reliable updates.
- Cleaned up comments and organized code structure for clarity.
2025-06-17 16:46:36 +05:30
Omindu Hirushka
ef299f1f4a albanian language 2025-06-17 16:03:08 +05:30
shancds
4dbaab060a Merge branch 'release/v2.0.3' of https://github.com/Worklenz/worklenz into release/v2.0.3-kanban-handle-drag-over 2025-06-17 09:54:07 +05:30
shancds
b8811ab5b6 refactor(board-section): optimize component rendering and enhance task card functionality
- Imported React to ensure proper usage of hooks.
- Wrapped `BoardSectionCardContainer` in `React.memo` for performance optimization.
- Integrated `useAuthService` to manage user session within `BoardViewTaskCard`.
- Replaced priority icon rendering with a dedicated `PrioritySection` component for cleaner code and improved readability.
- Cleaned up unused code and improved overall structure of task card rendering.
2025-06-16 17:13:36 +05:30
Chamika J
5248c26b76 Merge pull request #157 from Worklenz/feature/project-list-grouping
Feature/project list grouping
2025-06-16 10:05:54 +05:30
chamikaJ
eed0fb6eca chore(deps): update brace-expansion to version 2.0.2 in package-lock.json 2025-06-16 09:51:03 +05:30
Chamika J
2a9447b506 Merge pull request #156 from Worklenz/imp/kanban-performance
Imp/kanban performance
2025-06-16 08:58:49 +05:30
Chamika J
fb94028410 Merge pull request #155 from shancds/release/v2.0.3-kanban-handle-drag-over
Release/v2.0.3 kanban handle drag over
2025-06-16 08:46:14 +05:30
chamikaJ
25639afe1a refactor(reporting): optimize project reports components with memoization
- Refactored ProjectsReports, ProjectsReportsFilters, and ProjectsReportsTable components to utilize React.memo, useCallback, and useMemo for improved performance and reduced unnecessary re-renders.
- Memoized various handlers and configurations to enhance rendering efficiency and maintain responsiveness.
- Updated component exports to use memoization, ensuring optimal performance during re-renders.
2025-06-13 16:37:03 +05:30
chamikaJ
4426b5f3ef feat(reporting): enhance overview reports with memoization and dark mode support
- Refactored components in the reporting section to utilize React.memo, useCallback, and useMemo for improved performance and reduced unnecessary re-renders.
- Updated the OverviewStatCard to support dark mode styling and added enhanced hover effects.
- Improved the Avatars component by memoizing rendering logic and preventing event propagation.
- Enhanced OverviewReportsTable with memoized columns and row props for better performance.
- Applied consistent styling adjustments across various components to ensure a cohesive user experience.
2025-06-13 16:04:32 +05:30
chamikaJ
3cae2771de feat(projects): implement grouped project retrieval and UI enhancements
- Added a new endpoint for retrieving projects grouped by category, client, or status.
- Enhanced the ProjectsController with a method to handle grouped project queries.
- Updated the projects API router to include the new grouped endpoint.
- Improved the frontend to support displaying grouped projects with pagination and filtering options.
- Updated localization files for English, Spanish, and Portuguese to include new grouping options.
- Refactored project list components to accommodate the new grouped view and improved UI elements.
2025-06-13 15:46:03 +05:30
chamikaJ
81f55adb41 feat(projects): enhance project selection and grouping functionality
- Added grouping options for projects by category, team, and status in the project list.
- Implemented search functionality with a clear search option.
- Improved UI with expandable/collapsible project groups and selection summary.
- Updated localization files for English, Spanish, and Portuguese to include new grouping and UI strings.
- Enhanced project type definitions to support additional grouping properties.
2025-06-13 13:16:25 +05:30
chamikaJ
bd4c88833d Merge branch 'release/v2.0.3' of https://github.com/Worklenz/worklenz into feature/project-list-grouping 2025-06-13 13:02:57 +05:30
Chamika J
2374d7a357 Merge pull request #152 from OminduHirushka/upstream/feature/project-groupby
Projects - List / Group View
2025-06-13 13:02:30 +05:30
Chamika J
91730026fd Merge branch 'feature/project-list-grouping' into upstream/feature/project-groupby 2025-06-13 13:02:17 +05:30
shancds
9d10b23ba7 Merge branch 'release/v2.0.3' of https://github.com/Worklenz/worklenz into release/v2.0.3-kanban-handle-drag-over 2025-06-13 08:40:27 +05:30
chamiakJ
d0c231ee43 fix(account-storage): update billingInfo property name for progress percentage
- Changed the property name from `usedPercentage` to `used_percent` in the billing account info interface and updated the corresponding usage in the AccountStorage component to ensure consistency.
2025-06-12 14:28:05 +05:30
chamiakJ
58ce8e40c7 fix(migrations): correct progress_mode type casting in SQL migration
- Updated the SQL migration to ensure proper type casting for progress_mode when updating progress values based on project_id.
2025-06-12 14:11:04 +05:30
Chamika J
66b0709e6e Merge pull request #154 from Worklenz/chore/added-google-analytics
Chore/added google analytics
2025-06-12 10:10:00 +05:30
chamiakJ
a2ed33214d Merge branch 'main' of https://github.com/Worklenz/worklenz into chore/added-google-analytics 2025-06-12 09:49:32 +05:30
Chamika J
a3dccd690d Merge pull request #141 from gdevenyi/patch-1
Fix use of deprecated mc command for bucket creation
2025-06-12 09:46:45 +05:30
chamiakJ
69313fba34 Enhance privacy notice functionality in index.html
- Updated the privacy notice button to use an ID for easier event handling.
- Added an event listener to the button to manage the display of the notice and store user consent in localStorage.
2025-06-12 09:38:24 +05:30
chamiakJ
1889c58598 Refactor privacy notice implementation in index.html
- Introduced a dedicated function to display the privacy notice for users of the open source version.
- Updated the styling and content of the privacy notice for improved visibility and clarity.
- Added a DOMContentLoaded event listener to conditionally show the notice based on the environment and previous interactions.
2025-06-12 09:34:53 +05:30
chamiakJ
e9f0162439 Enhance documentation and integrate Google Analytics
- Added an Analytics section to the README.md, detailing what is tracked, privacy measures, and opt-out instructions.
- Implemented Google Analytics in index.html, including a privacy notice for open source users and environment-specific tracking IDs.
- Updated worklenz-frontend README.md to include a License section.
2025-06-12 09:26:00 +05:30
shancds
2aa4fe9673 feat(project-view-board): enhance drag-and-drop functionality and optimize task handling
- Added debounced task movement to prevent rapid updates during drag-and-drop operations.
- Implemented a custom collision detection strategy for improved task placement logic.
- Introduced new refs and state management for better handling of drag events and task cloning.
- Refactored drag event handlers to streamline task movement between groups and sections.
- Enhanced loading state management and cleanup for better user experience during task interactions.
2025-06-11 14:50:48 +05:30
chamikaJ
ccb50e3c62 feat(navbar): add HelpButton to the navbar and comment out TimerButton
- Temporarily commented out TimerButton for future adjustments.
2025-06-11 12:59:17 +05:30
chamikaJ
5ce9e66fea feat(timer-button): enhance error handling and improve timer updates
- Added error state management and logging for API calls and timer updates.
- Refactored timer update logic to handle invalid data and improve robustness.
- Updated dropdown rendering to display error messages and handle empty states more gracefully.
- Improved socket event handling with error logging for better debugging.
2025-06-11 12:55:53 +05:30
chamikaJ
6492a4672b Merge branch 'feature/recurring-tasks' of https://github.com/Worklenz/worklenz into release/v2.0.3 2025-06-11 12:41:04 +05:30
Chamika J
46acb26c42 Merge pull request #153 from shancds/fix/project-list-issues
feat(locales): add 'enterProjectKey' translation to project drawer fo…
2025-06-11 12:36:49 +05:30
shancds
c9aab73a2a Merge branch 'release/v2.0.3' of https://github.com/Worklenz/worklenz into fix/release/v2.0.3-update 2025-06-11 12:01:45 +05:30
shancds
13a202cca4 feat(locales): add 'enterProjectKey' translation to project drawer for en, es, and pt 2025-06-09 14:40:46 +05:30
chamikaJ
bdb9c9ca28 feat(project-subscribers): implement project subscriber management and loading state
- Added `getProjectSubscribers` method in `TasksControllerV2` to retrieve project subscribers with user details.
- Updated socket command to handle project subscription changes, ensuring no duplicate entries on conflict.
- Enhanced `ProjectViewHeader` to manage subscription loading state, providing user feedback during subscription updates.
- Implemented error handling and timeout for subscription requests to improve user experience.
2025-06-09 13:13:45 +05:30
chamikaJ
5ed5a86bad refactor(tasks-controller): enhance progress calculation logic for tasks without subtasks
- Updated progress calculation to consider project settings for time-based progress.
- Implemented a cap on progress to prevent exceeding 100%.
- Defaulted progress to 0% when time-based calculation is not enabled, improving accuracy in task status representation.
2025-06-09 13:12:24 +05:30
chamikaJ
520888988e feat(task-list): implement drag-and-drop functionality for task reordering
- Integrated drag-and-drop capabilities in the task list using `@dnd-kit` for improved user experience.
- Created a `DraggableRow` component to handle individual task dragging and dropping.
- Updated task list rendering to support dynamic reordering and socket integration for backend persistence.
- Enhanced task selection and hover effects for better visual feedback during drag operations.
- Refactored task list components to streamline rendering and improve performance.
2025-06-09 09:58:28 +05:30
chamiakJ
de28f87c62 refactor(task-drag-and-drop): remove unused drag-and-drop hook and simplify task group handling
- Deleted `useTaskDragAndDrop` hook to streamline drag-and-drop functionality.
- Updated `TaskGroupWrapperOptimized` to remove drag-and-drop context and simplify rendering.
- Refactored `TaskListTable` to integrate drag-and-drop directly, enhancing performance and maintainability.
- Adjusted task rendering logic to ensure proper handling of task states during drag operations.
2025-06-09 07:19:15 +05:30
chamikaJ
81a6c44090 refactor(info-tab-footer): enhance member selection and comment handling
- Updated member selection to use member names as values for better display.
- Improved handling of selected members to ensure correct identification using member IDs.
- Reset selected members and comment value upon comment submission.
- Enhanced comment input with filtering options for mentions, improving user experience.
2025-06-06 17:03:31 +05:30
kithmina1999
f142046dcc fix(docker): correct bash syntax in postgres backup command
The command was using incorrect quote escaping which could cause issues with variable expansion and file deletion. Fixed by using single quotes for the outer string and proper escaping for date command substitution.
2025-06-06 16:32:11 +05:30
kithmina1999
c5e480af52 fix(db): correct variable syntax in pg_dump command
The previous version used $$ for variable expansion which doesn't work in this context. Changed to single $ for proper shell variable expansion in the database backup command.
2025-06-06 16:30:46 +05:30
kithmina1999
f89e3e8554 refactor(database): improve database initialization and backup handling
- Rename init script from init.sh to 00_init.sh for better ordering
- Rewrite database initialization script to support backup restoration
- Add proper migration handling with schema_migrations table
- Fix indentation and quoting issues in docker-compose.yml
- Remove old 00-init-db.sh script as it's replaced by new implementation
2025-06-06 16:26:11 +05:30
kithmina1999
1442c57e18 refactor(docker): improve postgres container initialization script
The command for the postgres service was restructured to:
1. Use more readable multi-line formatting
2. Replace the find -exec with a more efficient for loop
3. Maintain the same functionality while improving maintainability
2025-06-06 16:06:42 +05:30
kithmina1999
0987fb14b2 refactor(docker): improve command formatting and fix shell script issues
- Fix shell script syntax in db service command by using proper quoting and loop structure
- Clean up indentation and formatting in both db and db-backup services
- Ensure consistent command structure while maintaining the same functionality
2025-06-06 15:34:18 +05:30
kithmina1999
dc22d1e6cb fix(db): improve database initialization script and docker setup
- Rename init.sh to 00_init.sh for better ordering
- Format docker-compose command for better readability
- Add comprehensive database initialization script with backup restoration
- Implement proper migration handling with schema_migrations table
2025-06-06 15:12:06 +05:30
Omindu Hirushka
e9e9bffd9a group by client / category 2025-06-06 13:23:23 +05:30
kithmina1999
11694de4e6 feat(db): add database backup and initialization system
- Add backup.sh script for manual PostgreSQL database backups
- Update .gitignore to exclude pg_backups directory
- Modify docker-compose.yml to include backup service and volume mounts
- Add init.sh script for automated database initialization with backup restoration
2025-06-06 12:38:46 +05:30
Chamika J
8f181c687b Merge pull request #150 from Worklenz/feature/recurring-tasks
Feature/recurring tasks
2025-06-06 12:09:03 +05:30
Chamika J
926c058d1e Merge pull request #149 from shancds/fix/home-task-list-status-update
Fix/home task list status update
2025-06-06 12:06:26 +05:30
shancds
1583221232 fix: enable automatic refetching for task queries in dropdown and date picker components 2025-06-06 11:45:22 +05:30
Omindu Hirushka
585a65be31 current updates 2025-06-06 09:47:42 +05:30
Tharindu Kosgahakumbura
2de9b7f6b7 Merge branch 'Worklenz:main' into fix/kanban-board-optimization 2025-06-06 09:10:32 +05:30
Chamika J
323b17185c Merge pull request #148 from kithmina1999/fix/postgres-password-auth
fix: change DB_PASSWORD to static value
2025-06-05 13:18:41 +05:30
chamiakJ
6002ab7c50 refactor(task-list): optimize selector usage and update component properties
- Changed `name` property in `ITaskAssignee` interface from optional to required for better type safety.
- Updated `Dropdown` component's prop from `dropdownRender` to `popupRender` for consistency.
- Renamed `destroyPopupOnHide` to `destroyOnHidden` in relevant components for clarity.
- Split combined selectors in `ProjectViewTaskList` to prevent unnecessary re-renders, enhancing performance.
2025-06-05 11:24:07 +05:30
chamiakJ
bd77733935 feat(timers): add running timers feature to the navbar
- Introduced a new `TimerButton` component to display and manage running timers.
- Implemented API service method `getRunningTimers` to fetch active timers.
- Updated the navbar to replace the HelpButton with the TimerButton for better functionality.
- Enhanced timer display with real-time updates and socket event handling for timer start/stop actions.
2025-06-05 11:11:16 +05:30
kithmina1999
09f44a5685 fix: change DB_PASSWORD to static value for development
Using a static password simplifies development environment setup. The previous random password generation caused issues during local testing and debugging.
2025-06-05 10:40:06 +05:30
Omindu Hirushka
0e67434515 list/group view toggle button 2025-06-02 15:38:50 +05:30
chamikaJ
cfa0af24ae refactor(session-middleware): improve cookie handling and security settings
- Updated session middleware to use secure cookies in production environments.
- Adjusted sameSite attribute to "lax" for standard handling of same-origin requests.
- Removed unnecessary comments and streamlined cookie settings for clarity.
2025-06-02 13:29:05 +05:30
chamikaJ
69f5009579 refactor(auth): remove debug logging and enhance session middleware
- Eliminated extensive debug logging from the login strategy and verification endpoint to streamline the authentication process.
- Updated session middleware to improve cookie handling, enabling proxy support and adjusting session creation behavior.
- Ensured secure cookie settings for cross-origin requests in production environments.
2025-06-02 13:20:40 +05:30
chamikaJ
24fa837a39 feat(auth): enhance login and verification processes with detailed debug logging
- Added comprehensive debug logging to the login strategy and verification endpoint to track authentication flow and errors.
- Improved title determination logic for login and signup success/failure messages based on authentication status.
- Implemented middleware for logging request details on the login route to aid in debugging.
2025-06-02 13:07:50 +05:30
chamikaJ
5e4d78c6f5 refactor(task-details-form): enhance progress input handling and improve assignee rendering
- Added `InlineMember` type import for better type management.
- Enhanced the `Avatars` component to handle multiple sources for assignee names, improving flexibility in data handling.
2025-06-02 09:19:58 +05:30
chamikaJ
837692e808 feat(task-list): implement optimized task group handling and filter data loading
- Introduced `useFilterDataLoader` hook to manage asynchronous loading of filter data without blocking the main UI.
- Created `TaskGroupWrapperOptimized` for improved rendering of task groups with drag-and-drop functionality.
- Refactored `ProjectViewTaskList` to utilize the new optimized components and enhance loading state management.
- Added `TaskGroup` component for better organization and interaction with task groups.
- Updated `TaskListFilters` to leverage the new filter data loading mechanism, ensuring a smoother user experience.
2025-05-30 11:40:27 +05:30
chamikaJ
6ffdbc64d0 refactor(navbar): comment out license expiry alert for future implementation
- Commented out the conditional rendering of the license expiry alert in the Navbar component for future adjustments.
2025-05-30 11:10:11 +05:30
chamikaJ
65af5f659e refactor(build): remove Gruntfile and transition to npm scripts for build process
- Deleted Gruntfile.js to streamline the build process.
- Updated package.json to include new npm scripts for build, clean, and watch tasks.
- Added dependencies for concurrent execution and CSRF token management.
- Integrated csrf-sync for improved CSRF protection in the application.
- Refactored app and API client to utilize the new CSRF token management approach.
2025-05-30 10:56:19 +05:30
chamikaJ
f38a7b4d56 Merge branch 'feature/recurring-tasks' of https://github.com/Worklenz/worklenz into fix/performance-improvements 2025-05-30 10:55:28 +05:30
Chamika J
f4ab7841fb Merge pull request #105 from MRNafisiA/main
increase the memory limit to prevent crashing during build time.
2025-05-30 10:15:47 +05:30
Chamika J
3de4f69a62 Merge pull request #142 from gdevenyi/patch-2
Generate random passwords in update-docker-env.sh
2025-05-30 09:42:46 +05:30
Gabriel A. Devenyi
102be2c24a Generate random passwords in update-docker-env.sh 2025-05-29 15:56:56 -04:00
Omindu Hirushka
378dc22bb0 setting up 2025-05-29 11:10:22 +05:30
Chamika J
3a39b25e64 Merge pull request #144 from kithmina1999/docs/add-video-guides
Update README.md to include video guides for local and remote deployment
2025-05-28 13:01:16 +05:30
kithmina1999
32248f8424 Update README.md to include video guides for local and remote deployment
- Added a section for a video guide on local Docker deployment.
- Included a video guide for deploying Worklenz to a remote server.
2025-05-28 09:32:32 +05:30
Gabriel A. Devenyi
a1f8776743 Fix use of deprecated mc command for bucket creation 2025-05-27 22:32:36 -04:00
chamiakJ
80797e043c Merge branch 'development' of https://github.com/Worklenz/worklenz into feature/recurring-tasks 2025-05-27 17:14:43 +05:30
chamiakJ
312c6b5be8 feat(settings): add project templates settings to the configuration
- Restored the project templates settings in the settings constants file, making it accessible for admin users.
2025-05-27 17:13:04 +05:30
chamikaJ
c18889a127 refactor(task-drawer): remove unused imports and add edit mode for task name
Removed unused `useEffect` import in `task-drawer-status-dropdown.tsx` and unused `connected` variable. Added edit mode for task name in `task-drawer-header.tsx` to improve user interaction by allowing inline editing of the task name.
2025-05-22 09:39:53 +05:30
chamiakJ
c1e923c703 feat(ownership-transfer): implement transfer_team_ownership function for team ownership management
- Added a new PostgreSQL function to handle the transfer of team ownership between users
2025-05-21 21:42:16 +05:30
chamiakJ
f716971654 feat(hubspot-integration): dynamically load HubSpot script in production environment
- Added a script to conditionally load the HubSpot tracking script in the index.html file when the hostname matches 'app.worklenz.com'.
- Removed the HubSpot component from MainLayout to streamline the integration process.
2025-05-21 18:50:18 +05:30
chamiakJ
d7ca1d8bd2 style: remove HubSpot chat widget styles from global CSS
- Deleted specific styles that prevented global styles from affecting the HubSpot chat widget, streamlining the CSS file.
2025-05-21 18:39:28 +05:30
chamiakJ
8704b6a8c8 style: adjust font-family formatting and add styles for HubSpot chat widget
- Reformatted the font-family declaration for improved readability.
- Added specific styles to prevent global styles from affecting the HubSpot chat widget, ensuring consistent appearance.
2025-05-21 15:45:50 +05:30
chamiakJ
4687478704 fix: update empty list image source to use S3 URL for consistency across components 2025-05-21 15:09:42 +05:30
chamiakJ
2bdae400ac feat(hubspot-integration): add HubSpot script component for production environment
- Introduced a new HubSpot component that dynamically loads the HubSpot tracking script when in production.
- Updated MainLayout to replace TawkTo with HubSpot for improved customer engagement tracking.
2025-05-21 11:42:42 +05:30
chamiakJ
0cb0efe43e feat(cron-jobs): conditionally enable recurring tasks based on environment variable
- Updated the cron job initialization to start recurring tasks only if the ENABLE_RECURRING_JOBS environment variable is set to "true". This allows for more flexible job management based on deployment configurations.
2025-05-21 11:28:10 +05:30
Chamika J
7e431d645a Merge pull request #134 from Worklenz/chamikaJ-patch-1
Update README.md
2025-05-21 08:28:32 +05:30
Chamika J
cef4bffd69 Update README.md
updated logo URL
2025-05-21 08:28:09 +05:30
chamiakJ
84c7428fed feat(recurring-tasks): enhance recurring task functionality and documentation
- Expanded schedule options for recurring tasks, including new intervals for every X days, weeks, and months.
- Added future task creation logic to ensure tasks are created within defined limits based on their schedule type.
- Updated user guide to reflect new scheduling options and future task creation details.
- Improved backend logic for recurring task creation, including batch processing and future limit calculations.
- Added environment configuration for enabling recurring jobs.
- Enhanced frontend localization for recurring task configuration labels.
2025-05-21 08:07:59 +05:30
chamikaJ
a568ee808f Merge branch 'feature/member-time-progress-and-utilization' of https://github.com/Worklenz/worklenz into feature/recurring-tasks 2025-05-19 13:39:13 +05:30
chamikaJ
fc30c1854e feat(reporting): add support for 'all time' date range in reporting allocation
- Implemented logic to fetch the earliest start date from selected projects when the 'all time' duration is specified.
- Updated the start date to default to January 1, 2000 if no valid date is found, ensuring robust date handling in reports.
2025-05-19 12:35:32 +05:30
chamikaJ
c19e06d902 refactor: enhance task completion ratio calculation and reporting
- Updated the `get_task_complete_ratio` function to improve handling of manual, weighted, and time-based progress calculations.
- Added logic to ensure accurate task completion ratios, including checks for subtasks and project settings.
- Enhanced error logging in the `refreshProjectTaskProgressValues` method for better debugging.
- Introduced new fields in the reporting allocation controller to calculate and display total working hours and utilization metrics for team members.
- Updated the frontend time sheet component to display utilization and over/under utilized hours in tooltips for better user insights.
2025-05-19 12:11:32 +05:30
chamikaJ
82155cab8d docs: add user guide and cron job documentation for recurring tasks
Add detailed documentation for recurring tasks, including a user guide explaining how to set up and manage recurring tasks, and a technical guide for the recurring tasks cron job. The user guide covers the purpose, setup process, and schedule options, while the technical guide explains the cron job's logic, database interactions, and configuration options. Additionally, include a migration script to fix ENUM type and casting issues for progress_mode_type.
2025-05-19 10:57:43 +05:30
chamiakJ
69b910f2a4 refactor(sql-functions): enhance SQL functions with COALESCE for better null handling
- Updated various SQL queries to use COALESCE, ensuring that null values are replaced with defaults for improved data integrity.
- Modified the handling of schedule_id for recurring tasks to return a JSON object or 'null' as appropriate.
- Improved the return structure of task-related JSON objects to prevent null values in the response.
2025-05-19 06:28:03 +05:30
chamiakJ
f9858fbd4b refactor(task-list): enhance performance with useMemo and useCallback
- Introduced useMemo to optimize loading state and empty state calculations.
- Added useMemo for socket event handler functions to prevent unnecessary re-renders.
- Refactored data fetching logic to improve initial data load handling.
- Improved drag-and-drop functionality with memoized handlers for better performance.
2025-05-18 20:58:20 +05:30
chamiakJ
f3a7fd8be5 refactor(project-view): optimize component with useMemo and useCallback for performance improvements
- Introduced useMemo and useCallback to memoize tab menu items and callback functions, enhancing performance.
- Added resetProjectData function to clean up project state on component unmount.
- Refactored the component to use React.memo for preventing unnecessary re-renders.
2025-05-18 20:15:40 +05:30
chamikaJ
49bdd00dac fix(todo-list): update empty list image source to use relative path 2025-05-16 14:45:47 +05:30
chamikaJ
2e985bd051 feat(recurring-tasks): implement recurring task scheduling and API integration 2025-05-16 14:32:45 +05:30
chamikaJ
8e74f1ddb5 Merge branch 'chore/add-google-analytics' of https://github.com/Worklenz/worklenz into feature/recurring-tasks 2025-05-16 12:43:06 +05:30
chamikaJ
2a3f87cac1 fix(index.html): update Google Analytics integration to load only in production 2025-05-16 12:42:33 +05:30
Chamika J
217a6941a1 Merge pull request #132 from Worklenz/chore/add-google-analytics
fix(index.html): update Google Analytics integration to load only in …
2025-05-16 12:42:10 +05:30
Chamika J
753e3be83f Merge pull request #131 from chamikaJ/feature/recurring-tasks
Feature/recurring tasks
2025-05-16 12:36:23 +05:30
chamikaJ
ebd0f66768 feat(task-drawer): integrate socket handling for recurring task updates
Enhance the TaskDrawerRecurringConfig component to include socket communication for handling recurring task changes. This update introduces the use of Redux for managing state updates related to recurring schedules, ensuring real-time synchronization of task configurations. Additionally, the code has been refactored for improved readability and maintainability.
2025-05-16 12:37:02 +05:30
Chamika J
a07584b3af Merge pull request #130 from Worklenz/chore/add-google-analytics
chore: added google analytics
2025-05-16 12:10:10 +05:30
chamikaJ
0d08634c78 chore: added google analytics 2025-05-16 12:09:26 +05:30
chamiakJ
d333104f43 feat(i18n): add recurring task translation keys
Add new localization entries for the recurring task feature in English, Spanish, and Portuguese. This update includes the addition of the "recurring" key to the task drawer JSON files, enhancing support for recurring task configurations across multiple languages.
2025-05-16 07:21:57 +05:30
Chamika J
a724247aec Merge pull request #129 from chamikaJ/chore/add-analytics
fix(index.html): update Microsoft Clarity integration for tracking
2025-05-15 19:16:38 +05:30
chamiakJ
2e36a477ce fix(index.html): update Microsoft Clarity integration for tracking
- Modified the Microsoft Clarity script in index.html to use a hardcoded tracking ID instead of relying on the VITE_CLARITY_ID environment variable. This change ensures consistent tracking on the app.worklenz.com domain.
2025-05-15 19:13:05 +05:30
Chamika J
6892de487f Merge pull request #128 from chamikaJ/chore/add-analytics
Chore/add analytics
2025-05-15 13:30:05 +05:30
chamiakJ
7b04821ef1 feat(routes): add default redirect to home in main routes
- Introduced a default index route that redirects to the home page within the main routes configuration, improving user navigation experience.
2025-05-15 13:28:16 +05:30
chamiakJ
f8a216fb6e feat(index.html): integrate Microsoft Clarity for enhanced user analytics
- Added a script to index.html for Microsoft Clarity, enabling tracking on the app.worklenz.com domain when the VITE_CLARITY_ID is set. This integration aims to improve user behavior analysis and application insights.
2025-05-15 13:23:28 +05:30
Chamika J
86b5d94ff8 Merge pull request #127 from chamikaJ/fix/ses-and-s3-credential-update
refactor(env): update AWS configuration for S3 integration
2025-05-15 08:44:04 +05:30
chamiakJ
fb3a505c22 refactor(env): update AWS configuration for S3 integration
- Modified the .env.template to separate AWS SES and S3 configurations, adding new environment variables for S3 region and access keys.
- Updated constants.ts to reflect changes in environment variable names for S3, ensuring consistency in accessing S3 credentials.
2025-05-15 08:43:04 +05:30
Chamika J
72d372b685 Merge pull request #126 from chamikaJ/chore/improved-dark-mode-toggle
feat(settings): add appearance settings with dark mode toggle and tra…
2025-05-15 07:57:05 +05:30
chamiakJ
536c1c37b1 feat(settings): add appearance settings with dark mode toggle and translations
- Introduced new appearance settings page with a dark mode toggle feature.
- Added localization support for English, Spanish, and Portuguese in appearance settings.
- Removed the ThemeSelector component and updated PreferenceSelector accordingly.
2025-05-15 07:56:15 +05:30
Chamika J
40caea7d79 Merge pull request #125 from chamikaJ/chore/update-logos
Chore/update logos
2025-05-15 07:43:08 +05:30
chamiakJ
33c15ac138 feat(assets): add empty box placeholder image and update component reference
- Introduced a new empty box placeholder image to enhance the visual representation of empty states in the application.
- Updated the EmptyListPlaceholder component to reference the new image path, ensuring proper display in relevant contexts.
2025-05-15 07:42:21 +05:30
chamiakJ
05ab135ed2 feat(assets): add light and dark mode logos for improved branding
- Introduced new logo images for both light and dark modes, enhancing the visual consistency of the application.
- Updated references in AuthPageHeader, NavbarLogo, AccountSetup, and ProjectViewInsights components to use the new logo images.
2025-05-15 07:22:56 +05:30
Chamika J
19deef9298 Merge pull request #124 from chamikaJ/imp/task-list-loading-improvement
fix(tasks-controller): update SQL queries to use template literals fo…
2025-05-14 19:21:13 +05:30
chamiakJ
c4837e7e5c fix(tasks-controller): update SQL queries to use template literals for projectId
- Modified SQL queries in TasksControllerV2 to use template literals for the projectId variable, enhancing readability and consistency in the code.
- Removed the parameterized query approach for projectId in the relevant sections of the code.
2025-05-14 19:17:39 +05:30
Chamika J
b73ef12eac Merge pull request #123 from chamikaJ/imp/task-list-loading-improvement
fix(index.html): change env-config.js script type for compatibility
2025-05-14 18:43:35 +05:30
chamiakJ
c52b223c59 fix(index.html): change env-config.js script type for compatibility
- Updated the script tag for env-config.js in index.html to remove the type="module" attribute, ensuring better compatibility with existing code.
2025-05-14 18:43:07 +05:30
Chamika J
ffc9101030 Merge pull request #122 from chamikaJ/imp/task-list-loading-improvement
Imp/task list loading improvement
2025-05-14 18:41:44 +05:30
Chamika J
b5c5225867 Merge branch 'Worklenz:main' into imp/task-list-loading-improvement 2025-05-14 18:41:19 +05:30
chamiakJ
407b3c5ba7 fix(service-worker): enhance unregister logic and update index.html
- Updated the index.html to load the env-config.js script as a module for better compatibility.
- Improved the unregister logic in both the unregister-sw.js and login-page.tsx to specifically target the ngsw-worker, ensuring it is unregistered correctly and the page reloads afterward. This prevents multiple unregister attempts and enhances user experience.
2025-05-14 18:41:06 +05:30
Chamika J
528db06cd8 Merge pull request #121 from chamikaJ/imp/task-list-loading-improvement
fix(service-worker): prevent multiple unregister attempts in session
2025-05-14 18:35:10 +05:30
chamiakJ
0e1314d183 fix(service-worker): prevent multiple unregister attempts in session
- Updated the unregister script to check if an attempt to unregister service workers has already been made in the current session, preventing unnecessary reloads and improving user experience.
- If service workers are registered, the script will perform a hard reload; otherwise, it will unregister any pending registrations.
2025-05-14 18:34:08 +05:30
chamikaJ
7ac35bfdbc fix(service-worker): improve unregister logic for service workers
- Updated the unregister script to first check for registered service workers and perform a hard reload if any are found.
- If no service workers are registered, the script will now properly unregister any pending registrations, enhancing the service worker lifecycle management.
2025-05-14 17:07:38 +05:30
Chamika J
cc6d647f5a Merge pull request #120 from chamikaJ/imp/task-list-loading-improvement
fix(service-worker): improve unregister logic for service workers
2025-05-14 17:07:09 +05:30
Chamika J
fba1adda35 Merge pull request #119 from chamikaJ/imp/task-list-loading-improvement
Imp/task list loading improvement
2025-05-14 16:31:19 +05:30
chamikaJ
fe2518d53c feat(service-worker): add unregister script and update index.html
- Introduced a new script to unregister service workers, enhancing control over service worker lifecycle.
- Updated index.html to include the unregister script, ensuring it is loaded for proper service worker management.
2025-05-14 16:31:17 +05:30
chamikaJ
62548e5c37 feat(task-drawer): add recurring task configuration
Add support for configuring recurring tasks in the task drawer. This includes adding a new `schedule_id` field to the task type, creating a new `TaskDrawerRecurringConfig` component, and updating localization files for English, Spanish, and Portuguese. The configuration allows setting repeat intervals, days of the week, and monthly recurrence options.
2025-05-14 15:41:09 +05:30
chamikaJ
faa5d26601 feat(i18n): add new translations for billing plan details
Add new translation keys for billing plan details in English, Spanish, and Portuguese to support localized text for credit plan, custom plan, seat purchase, and contact sales. Also, update the `current-plan-details.tsx` component to use these translations for better internationalization support.
2025-05-14 12:39:15 +05:30
chamikaJ
ba90fa1274 feat(task): add progress mode handling and update related functions
Introduce a new `progress_mode` field to tasks and projects to support different progress calculation methods (manual, weighted, time, default). Update database migrations, task progress calculation functions, and related handlers to accommodate these changes. This ensures consistent progress tracking across different project management needs.

The changes include:
- Adding `progress_mode` to the `tasks` table.
- Updating progress calculation functions to respect the selected mode.
- Adding triggers to reset progress values when the project's progress mode changes.
- Enhancing documentation to explain the default progress method.
2025-05-09 15:59:25 +05:30
Chamika J
1676fc1314 Merge pull request #114 from chamikaJ/fix/reporting-member-pagination
Fix/reporting member pagination
2025-05-09 09:50:51 +05:30
chamiakJ
aaaaec6f36 Update project insights controller and frontend components for overlogged tasks
- Modified SQL query in the ProjectInsightsController to filter out tasks with zero total minutes and mismatched logged time.
- Updated OverLoggedTasksTable to render overlogged time as a string for better readability.
- Added a new property `overlogged_time_string` to the IInsightTasks interface to support the updated rendering logic.
2025-05-08 20:56:51 +05:30
chamiakJ
e0b2fa2d6f Implement pagination for members reports and update UI components
- Added a new `setPagination` action to manage pagination state in the members reports slice.
- Updated the members reports page to display the total number of members in the header.
- Enhanced the members reports table to handle pagination changes, ensuring data is fetched correctly based on the current page and page size.
2025-05-08 13:59:31 +05:30
chamikaJ
4a2393881b Enhance project view board loading state and data fetching
- Introduced a local loading state to improve user experience by displaying a skeleton loader while data is being fetched.
- Refactored data loading logic to utilize async/await and Promise.all for concurrent dispatching of task-related data, ensuring efficient data retrieval.
- Updated rendering logic to conditionally display the skeleton loader based on the new loading state, enhancing UI responsiveness.
2025-05-07 16:32:13 +05:30
chamikaJ
583fec04d7 Update package dependencies and add tinyglobby
- Upgraded dependencies: antd to version 5.24.9, axios to version 1.9.0, dompurify to version 3.2.5, and vite to version 6.3.5.
- Updated various rc-* packages to their latest versions for improved functionality and performance.
- Added tinyglobby as a new dependency for enhanced file globbing capabilities in the project.
2025-05-07 15:57:19 +05:30
Chamika J
e7ff9b645b Merge pull request #113 from chamikaJ/imp/task-list-loading-improvement
Update package dependencies and add tinyglobby
2025-05-07 15:56:20 +05:30
chamikaJ
2b82ff699e Enhance task list loading state management
- Introduced a local loading state to improve user experience by displaying a skeleton loader while data is being fetched.
- Refactored data loading logic to utilize Promise.all for concurrent dispatching of task-related data, ensuring efficient data retrieval.
- Updated the rendering logic to conditionally display the skeleton loader based on the new loading state, enhancing UI responsiveness.
2025-05-07 15:48:02 +05:30
Chamika J
d1136a549a Merge pull request #112 from chamikaJ/imp/task-list-loading-improvement
Enhance task list loading state management
2025-05-07 15:47:37 +05:30
chamikaJ
ec4d3e738a Enhance team member role management and localization updates
- Added a new SQL field to indicate pending invitations for team members, improving role management logic.
- Updated the settings drawer component to display tooltips for roles that cannot be changed, enhancing user experience.
- Introduced new localization entries for pending invitations and role change restrictions in English, Spanish, and Portuguese, ensuring consistency across languages.
2025-05-07 13:50:34 +05:30
Chamika J
c8380e1c30 Merge pull request #111 from chamikaJ/fix/admin-center-teams-role-change
Enhance team member role management and localization updates
2025-05-07 13:49:46 +05:30
chamikaJ
cabc97afc0 Enhance team update logic and error handling
- Refactored the team update function in the Admin Center controller to improve error handling and response messages.
- Implemented concurrent updates for team member roles using Promise.all, enhancing performance and user experience.
- Updated the frontend API service to accept a structured body for team updates, ensuring consistency in data handling.
- Enhanced the settings drawer component to manage team member roles more effectively, improving the overall user interface.
2025-05-07 12:02:25 +05:30
Chamika J
349f0ecfec Merge pull request #110 from chamikaJ/fix/custom-progress-methods
Enhance team update logic and error handling
2025-05-07 12:02:15 +05:30
Chamika J
890ad5e969 Merge pull request #109 from chamikaJ/fix/custom-progress-methods
Enhance task progress calculation and update logic
2025-05-06 15:30:32 +05:30
chamiakJ
0fc79d9ae5 Enhance task progress calculation and update logic
- Updated SQL migration to fix multilevel subtask progress calculation, ensuring accurate parent task updates based on subtasks.
- Refactored backend functions to recursively recalculate task progress values, improving data integrity across task hierarchies.
- Enhanced frontend components to refresh task progress values when tasks are updated, ensuring real-time synchronization.
- Integrated logging for task progress updates to improve traceability and debugging.
2025-05-06 15:05:25 +05:30
Chamika J
d60ac2246d Merge pull request #108 from chamikaJ/fix/custom-progress-methods
Add task progress confirmation prompts and localization updates
2025-05-06 10:15:43 +05:30
chamiakJ
5d04718394 Add task progress confirmation prompts and localization updates
- Introduced new localization entries for task progress confirmation prompts in English, Spanish, and Portuguese, enhancing user experience.
- Updated frontend components to utilize localized strings for task completion modals, ensuring consistency across languages.
- Implemented logic to restrict task progress input to a maximum of 100%, improving data integrity and user feedback during task updates.
2025-05-06 10:14:42 +05:30
chamikaJ
4bece298c1 Enhance task progress tracking and UI updates
- Updated SQL migration to insert default task statuses ('To Do', 'Doing', 'Done') upon project creation, improving task management.
- Enhanced socket commands to emit progress updates for subtasks, ensuring real-time synchronization of task progress.
- Refactored frontend components to handle progress calculations and updates more effectively, including improved logging for debugging.
- Removed deprecated members reports components to streamline the codebase and improve maintainability.
2025-05-05 16:59:43 +05:30
Chamika J
469901ab88 Merge pull request #107 from chamikaJ/fix/custom-progress-methods
Enhance task progress tracking and UI updates
2025-05-05 16:58:51 +05:30
Chamika J
13c7015b1c Merge pull request #106 from chamikaJ/fix/custom-progress-methods
Fix/custom progress methods
2025-05-05 11:49:06 +05:30
chamikaJ
21ab2f8a82 Enhance task status change handling and progress updates
- Updated SQL queries to retrieve color codes for task statuses from the correct table, ensuring accurate data representation.
- Added logic to automatically set task progress to 100% when a task is marked as done, improving task completion handling.
- Enhanced frontend components to manage task status changes and reflect updates in real-time, including handling parent task progress.
- Integrated logging for task status changes and progress updates to improve traceability and debugging.
2025-05-05 11:47:17 +05:30
MRNafisiA
75391641fd increase the memory limit to prevent crashing during build time. 2025-05-02 15:53:48 +03:30
chamikaJ
a368b979d5 Implement task completion prompt and enhance progress handling
- Added logic to prompt users to mark tasks as done when progress reaches 100%, integrating with the socket events for real-time updates.
- Updated backend functions to check task statuses and determine if a prompt is necessary based on the task's current state.
- Enhanced frontend components to display a modal for confirming task completion, improving user experience and clarity in task management.
- Refactored socket event handling to include new events for retrieving "done" statuses, ensuring accurate task status updates across the application.
2025-05-02 17:05:16 +05:30
chamikaJ
a5b881c609 Enhance task progress calculation and UI handling
- Updated task progress calculation logic to incorporate weights and time-based estimations for subtasks.
- Improved SQL migrations to support new progress calculation methods and ensure accurate parent task updates.
- Enhanced frontend components to conditionally display progress inputs based on task type and project settings.
- Implemented socket events for real-time updates on subtask counts and progress changes, ensuring consistent UI behavior.
- Added logging for progress updates and task state changes to improve debugging and user experience.
2025-05-02 13:21:32 +05:30
Chamika J
9dbab2c5d3 Merge pull request #103 from chamikaJ/fix/custom-progress-methods
Fix/custom progress methods
2025-05-02 13:21:11 +05:30
chamiakJ
8f913b0f4e Add task progress tracking methods documentation and enhance progress update logic
- Introduced a new markdown file detailing task progress tracking methods: manual, weighted, and time-based.
- Updated backend logic to include complete ratio calculations for tasks.
- Improved socket command for task progress updates, enabling recursive updates for ancestor tasks.
- Enhanced frontend components to reflect progress changes based on the selected tracking method, including updates to task display and progress input handling.
- Added support for manual progress flag in task model to facilitate accurate progress representation.
2025-05-02 07:37:40 +05:30
chamiakJ
31ac184107 Refactor project log insertion in SQL migration
- Removed team_member_id from project_logs insert statements.
2025-05-02 07:04:49 +05:30
Chamika J
23558b8efc Merge pull request #100 from chamikaJ/fix/custom-progress-methods
Fix/custom progress methods
2025-04-30 15:48:16 +05:30
Chamika J
4bb3b42c76 Merge branch 'Worklenz:main' into fix/custom-progress-methods 2025-04-30 15:47:40 +05:30
chamiakJ
0c5eff7121 Refactor task progress update logic in socket command
- Improved error logging for manual progress updates on parent tasks.
- Cleaned up console log statements and replaced them with a logging function for consistency.
- Fixed SQL query to remove unnecessary team_id selection, streamlining the data retrieval process.
2025-04-30 15:47:28 +05:30
Chamika J
136530adf1 Merge pull request #99 from chamikaJ/fix/custom-progress-methods
Fix/custom progress methods
2025-04-30 15:28:50 +05:30
chamiakJ
6128c64c31 Add task progress tracking methods and enhance UI components
- Introduced a comprehensive guide for users on task progress tracking methods, including manual, weighted, and time-based progress.
- Implemented backend support for progress calculations, including SQL functions and migrations to accommodate new progress features.
- Enhanced frontend components to support progress input and display, including updates to task and project drawers.
- Added localization for new progress-related terms and validation messages.
- Integrated real-time updates for task progress and weight changes through socket events.
2025-04-30 15:24:07 +05:30
chamikaJ
a2bfdb682b Add weight column to tasks table for enhanced progress tracking
- Updated the SQL migration to include a new 'weight' column in the tasks table, allowing for more detailed progress calculations.
2025-04-29 17:06:58 +05:30
chamikaJ
f7582173ed Implement manual and weighted progress features for tasks
- Added SQL migrations to support manual progress and weighted progress calculations in tasks.
- Updated the `get_task_complete_ratio` function to consider manual progress and subtask weights.
- Enhanced the project model to include flags for manual, weighted, and time-based progress.
- Integrated new progress settings in the project drawer and task drawer components.
- Implemented socket events for real-time updates on task progress and weight changes.
- Updated frontend localization files to include new progress-related terms and tooltips.
2025-04-29 17:04:36 +05:30
1537 changed files with 100565 additions and 17119 deletions

View File

@@ -0,0 +1,15 @@
{
"permissions": {
"allow": [
"Bash(find:*)",
"Bash(npm run build:*)",
"Bash(npm run type-check:*)",
"Bash(npm run:*)",
"Bash(move:*)",
"Bash(mv:*)",
"Bash(grep:*)",
"Bash(rm:*)"
],
"deny": []
}
}

View File

@@ -0,0 +1,237 @@
---
alwaysApply: true
---
# Ant Design Import Rules for Worklenz
## 🚨 CRITICAL: Always Use Centralized Imports
**NEVER import Ant Design components directly from 'antd' or '@ant-design/icons'**
### ✅ Correct Import Pattern
```typescript
import { Button, Input, Select, EditOutlined, PlusOutlined } from '@antd-imports';
// or
import { Button, Input, Select, EditOutlined, PlusOutlined } from '@/shared/antd-imports';
```
### ❌ Forbidden Import Patterns
```typescript
// NEVER do this:
import { Button, Input, Select } from 'antd';
import { EditOutlined, PlusOutlined } from '@ant-design/icons';
```
## Why This Rule Exists
### Benefits of Centralized Imports:
- **Better Tree-Shaking**: Optimized bundle size through centralized management
- **Consistent React Context**: Proper context sharing across components
- **Type Safety**: Centralized TypeScript definitions
- **Maintainability**: Single source of truth for all Ant Design imports
- **Performance**: Reduced bundle size and improved loading times
## What's Available in `@antd-imports`
### Core Components
- **Layout**: Layout, Row, Col, Flex, Divider, Space
- **Navigation**: Menu, Tabs, Breadcrumb, Pagination
- **Data Entry**: Input, Select, DatePicker, TimePicker, Form, Checkbox, InputNumber
- **Data Display**: Table, List, Card, Tag, Avatar, Badge, Progress, Statistic
- **Feedback**: Modal, Drawer, Alert, Message, Notification, Spin, Skeleton, Result
- **Other**: Button, Typography, Tooltip, Popconfirm, Dropdown, ConfigProvider
### Icons
Common icons including: EditOutlined, DeleteOutlined, PlusOutlined, MoreOutlined, CheckOutlined, CloseOutlined, CalendarOutlined, UserOutlined, TeamOutlined, and many more.
### Utilities
- **appMessage**: Centralized message utility
- **appNotification**: Centralized notification utility
- **antdConfig**: Default Ant Design configuration
- **taskManagementAntdConfig**: Task-specific configuration
## Implementation Guidelines
### When Creating New Components:
1. **Always** import from `@/shared/antd-imports`
2. Use `appMessage` and `appNotification` for user feedback
3. Apply `antdConfig` for consistent styling
4. Use `taskManagementAntdConfig` for task-related components
### When Refactoring Existing Code:
1. Replace direct 'antd' imports with `@/shared/antd-imports`
2. Replace direct '@ant-design/icons' imports with `@/shared/antd-imports`
3. Update any custom message/notification calls to use the utilities
### File Location
The centralized import file is located at: `worklenz-frontend/src/shared/antd-imports.ts`
## Examples
### Component Creation
```typescript
import React from 'react';
import { Button, Input, Modal, EditOutlined, appMessage } from '@antd-imports';
const MyComponent = () => {
const handleClick = () => {
appMessage.success('Operation completed!');
};
return (
<Button icon={<EditOutlined />} onClick={handleClick}>
Edit Item
</Button>
);
};
```
### Form Implementation
```typescript
import { Form, Input, Select, Button, DatePicker } from '@antd-imports';
const MyForm = () => {
return (
<Form layout="vertical">
<Form.Item label="Name" name="name">
<Input />
</Form.Item>
<Form.Item label="Type" name="type">
<Select options={options} />
</Form.Item>
<Form.Item label="Date" name="date">
<DatePicker />
</Form.Item>
</Form>
);
};
```
## Enforcement
This rule is **MANDATORY** and applies to:
- All new component development
- All code refactoring
- All bug fixes
- All feature implementations
**Violations will result in code review rejection.**
### File Path:
The centralized file is located at: `worklenz-frontend/src/shared/antd-imports.ts`
# Ant Design Import Rules for Worklenz
## 🚨 CRITICAL: Always Use Centralized Imports
**NEVER import Ant Design components directly from 'antd' or '@ant-design/icons'**
### ✅ Correct Import Pattern
```typescript
import { Button, Input, Select, EditOutlined, PlusOutlined } from '@antd-imports';
// or
import { Button, Input, Select, EditOutlined, PlusOutlined } from '@/shared/antd-imports';
```
### ❌ Forbidden Import Patterns
```typescript
// NEVER do this:
import { Button, Input, Select } from 'antd';
import { EditOutlined, PlusOutlined } from '@ant-design/icons';
```
## Why This Rule Exists
### Benefits of Centralized Imports:
- **Better Tree-Shaking**: Optimized bundle size through centralized management
- **Consistent React Context**: Proper context sharing across components
- **Type Safety**: Centralized TypeScript definitions
- **Maintainability**: Single source of truth for all Ant Design imports
- **Performance**: Reduced bundle size and improved loading times
## What's Available in `@antd-imports`
### Core Components
- **Layout**: Layout, Row, Col, Flex, Divider, Space
- **Navigation**: Menu, Tabs, Breadcrumb, Pagination
- **Data Entry**: Input, Select, DatePicker, TimePicker, Form, Checkbox, InputNumber
- **Data Display**: Table, List, Card, Tag, Avatar, Badge, Progress, Statistic
- **Feedback**: Modal, Drawer, Alert, Message, Notification, Spin, Skeleton, Result
- **Other**: Button, Typography, Tooltip, Popconfirm, Dropdown, ConfigProvider
### Icons
Common icons including: EditOutlined, DeleteOutlined, PlusOutlined, MoreOutlined, CheckOutlined, CloseOutlined, CalendarOutlined, UserOutlined, TeamOutlined, and many more.
### Utilities
- **appMessage**: Centralized message utility
- **appNotification**: Centralized notification utility
- **antdConfig**: Default Ant Design configuration
- **taskManagementAntdConfig**: Task-specific configuration
## Implementation Guidelines
### When Creating New Components:
1. **Always** import from `@antd-imports` or `@/shared/antd-imports`
2. Use `appMessage` and `appNotification` for user feedback
3. Apply `antdConfig` for consistent styling
4. Use `taskManagementAntdConfig` for task-related components
### When Refactoring Existing Code:
1. Replace direct 'antd' imports with `@antd-imports`
2. Replace direct '@ant-design/icons' imports with `@antd-imports`
3. Update any custom message/notification calls to use the utilities
### File Location
The centralized import file is located at: `worklenz-frontend/src/shared/antd-imports.ts`
## Examples
### Component Creation
```typescript
import React from 'react';
import { Button, Input, Modal, EditOutlined, appMessage } from '@antd-imports';
const MyComponent = () => {
const handleClick = () => {
appMessage.success('Operation completed!');
};
return (
<Button icon={<EditOutlined />} onClick={handleClick}>
Edit Item
</Button>
);
};
```
### Form Implementation
```typescript
import { Form, Input, Select, Button, DatePicker } from '@antd-imports';
const MyForm = () => {
return (
<Form layout="vertical">
<Form.Item label="Name" name="name">
<Input />
</Form.Item>
<Form.Item label="Type" name="type">
<Select options={options} />
</Form.Item>
<Form.Item label="Date" name="date">
<DatePicker />
</Form.Item>
</Form>
);
};
```
## Enforcement
This rule is **MANDATORY** and applies to:
- All new component development
- All code refactoring
- All bug fixes
- All feature implementations
**Violations will result in code review rejection.**
### File Path:
The centralized file is located at: `worklenz-frontend/src/shared/antd-imports.ts`

422
README.md
View File

@@ -1,11 +1,29 @@
<h1 align="center">
<a href="https://worklenz.com" target="_blank" rel="noopener noreferrer">
<img src="https://app.worklenz.com/assets/icons/icon-144x144.png" alt="Worklenz Logo" width="75">
<img src="https://s3.us-west-2.amazonaws.com/worklenz.com/assets/icon-144x144.png" alt="Worklenz Logo" width="75">
</a>
<br>
Worklenz
</h1>
<p align="center">
<a href="https://github.com/Worklenz/worklenz/blob/main/LICENSE">
<img src="https://img.shields.io/badge/license-AGPL--3.0-blue.svg" alt="License">
</a>
<a href="https://github.com/Worklenz/worklenz/releases">
<img src="https://img.shields.io/github/v/release/Worklenz/worklenz" alt="Release">
</a>
<a href="https://github.com/Worklenz/worklenz/stargazers">
<img src="https://img.shields.io/github/stars/Worklenz/worklenz" alt="Stars">
</a>
<a href="https://github.com/Worklenz/worklenz/network/members">
<img src="https://img.shields.io/github/forks/Worklenz/worklenz" alt="Forks">
</a>
<a href="https://github.com/Worklenz/worklenz/issues">
<img src="https://img.shields.io/github/issues/Worklenz/worklenz" alt="Issues">
</a>
</p>
<p align="center">
<a href="https://worklenz.com/task-management/">Task Management</a> |
<a href="https://worklenz.com/time-tracking/">Time Tracking</a> |
@@ -27,6 +45,24 @@
Worklenz is a project management tool designed to help organizations improve their efficiency. It provides a
comprehensive solution for managing projects, tasks, and collaboration within teams.
## Table of Contents
- [Features](#features)
- [Tech Stack](#tech-stack)
- [Getting Started](#getting-started)
- [Quick Start (Docker)](#-quick-start-docker---recommended)
- [Manual Installation](#-manual-installation-for-development)
- [Deployment](#deployment)
- [Local Development](#local-development-with-docker)
- [Remote Server Deployment](#remote-server-deployment)
- [Configuration](#configuration)
- [MinIO Integration](#minio-integration)
- [Security](#security)
- [Analytics](#analytics)
- [Screenshots](#screenshots)
- [Contributing](#contributing)
- [License](#license)
## Features
- **Project Planning**: Create and organize projects, assign tasks to team members.
@@ -50,42 +86,80 @@ This repository contains the frontend and backend code for Worklenz.
## Getting Started
These instructions will help you set up and run the Worklenz project on your local machine for development and testing purposes.
Choose your preferred setup method below. Docker is recommended for quick setup and testing.
### Prerequisites
### 🚀 Quick Start (Docker - Recommended)
- Node.js (version 18 or higher)
- PostgreSQL database
- An S3-compatible storage service (like MinIO) or Azure Blob Storage
The fastest way to get Worklenz running locally with all dependencies included.
### Option 1: Manual Installation
**Prerequisites:**
- Docker and Docker Compose installed on your system
- Git
1. Clone the repository
**Steps:**
1. Clone the repository:
```bash
git clone https://github.com/Worklenz/worklenz.git
cd worklenz
```
2. Set up environment variables
- Copy the example environment files
```bash
cp .env.example .env
cp worklenz-backend/.env.example worklenz-backend/.env
```
- Update the environment variables with your configuration
3. Install dependencies
2. Start the Docker containers:
```bash
# Install backend dependencies
docker-compose up -d
```
3. Access the application:
- **Frontend**: http://localhost:5000
- **Backend API**: http://localhost:3000
- **MinIO Console**: http://localhost:9001 (login: minioadmin/minioadmin)
4. To stop the services:
```bash
docker-compose down
```
**Alternative startup methods:**
- **Windows**: Run `start.bat`
- **Linux/macOS**: Run `./start.sh`
**Video Guide**: For a visual walkthrough of the local Docker deployment process, check out our [step-by-step video guide](https://www.youtube.com/watch?v=AfwAKxJbqLg).
### 🛠️ Manual Installation (For Development)
For developers who want to run the services individually or customize the setup.
**Prerequisites:**
- Node.js (version 18 or higher)
- PostgreSQL (version 15 or higher)
- An S3-compatible storage service (like MinIO) or Azure Blob Storage
**Steps:**
1. Clone the repository:
```bash
git clone https://github.com/Worklenz/worklenz.git
cd worklenz
```
2. Set up environment variables:
```bash
cp worklenz-backend/.env.template worklenz-backend/.env
# Update the environment variables with your configuration
```
3. Install dependencies:
```bash
# Backend dependencies
cd worklenz-backend
npm install
# Install frontend dependencies
# Frontend dependencies
cd ../worklenz-frontend
npm install
```
4. Set up the database
4. Set up the database:
```bash
# Create a PostgreSQL database named worklenz_db
cd worklenz-backend
@@ -101,49 +175,47 @@ psql -U your_username -d worklenz_db -f database/sql/2_dml.sql
psql -U your_username -d worklenz_db -f database/sql/5_database_user.sql
```
5. Start the development servers
5. Start the development servers:
```bash
# In one terminal, start the backend
# Terminal 1: Start the backend
cd worklenz-backend
npm run dev
# In another terminal, start the frontend
# Terminal 2: Start the frontend
cd worklenz-frontend
npm run dev
```
6. Access the application at http://localhost:5000
### Option 2: Docker Setup
## Deployment
The project includes a fully configured Docker setup with:
- Frontend React application
- Backend server
- PostgreSQL database
- MinIO for S3-compatible storage
For local development, follow the [Quick Start (Docker)](#-quick-start-docker---recommended) section above.
1. Clone the repository:
```bash
git clone https://github.com/Worklenz/worklenz.git
cd worklenz
```
### Remote Server Deployment
2. Start the Docker containers (choose one option):
When deploying to a remote server:
**Using Docker Compose directly**
```bash
docker-compose up -d
```
1. Set up the environment files with your server's hostname:
```bash
# For HTTP/WS
./update-docker-env.sh your-server-hostname
# For HTTPS/WSS
./update-docker-env.sh your-server-hostname true
```
3. The application will be available at:
- Frontend: http://localhost:5000
- Backend API: http://localhost:3000
- MinIO Console: http://localhost:9001 (login with minioadmin/minioadmin)
2. Pull and run the latest Docker images:
```bash
docker-compose pull
docker-compose up -d
```
4. To stop the services:
```bash
docker-compose down
```
3. Access the application through your server's hostname:
- Frontend: http://your-server-hostname:5000
- Backend API: http://your-server-hostname:3000
4. **Video Guide**: For a complete walkthrough of deploying Worklenz to a remote server, check out our [deployment video guide](https://www.youtube.com/watch?v=CAZGu2iOXQs&t=10s).
## Configuration
@@ -158,16 +230,46 @@ Worklenz requires several environment variables to be configured for proper oper
Please refer to the `.env.example` files for a full list of required variables.
### MinIO Integration
The Docker setup uses environment variables to configure the services:
- **Frontend:**
- `VITE_API_URL`: URL of the backend API (default: http://backend:3000 for container networking)
- `VITE_SOCKET_URL`: WebSocket URL for real-time communication (default: ws://backend:3000)
- **Backend:**
- Database connection parameters
- Storage configuration
- Other backend settings
For custom configuration, edit the `.env` file or the `update-docker-env.sh` script.
## MinIO Integration
The project uses MinIO as an S3-compatible object storage service, which provides an open-source alternative to AWS S3 for development and production.
### Working with MinIO
MinIO provides an S3-compatible API, so any code that works with S3 will work with MinIO by simply changing the endpoint URL. The backend has been configured to use MinIO by default, with no additional configuration required.
- **MinIO Console**: http://localhost:9001
- Username: minioadmin
- Password: minioadmin
- **Default Bucket**: worklenz-bucket (created automatically when the containers start)
### Backend Storage Configuration
The backend is pre-configured to use MinIO with the following settings:
```javascript
// S3 credentials with MinIO defaults
export const REGION = process.env.AWS_REGION || "us-east-1";
export const BUCKET = process.env.AWS_BUCKET || "worklenz-bucket";
export const S3_URL = process.env.S3_URL || "http://minio:9000/worklenz-bucket";
export const S3_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID || "minioadmin";
export const S3_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY || "minioadmin";
```
### Security Considerations
For production deployments:
@@ -178,19 +280,32 @@ For production deployments:
4. Enable HTTPS for all public endpoints
5. Review and update dependencies regularly
## Contributing
We welcome contributions from the community! If you'd like to contribute, please follow our [contributing guidelines](CONTRIBUTING.md).
## Security
If you believe you have found a security vulnerability in Worklenz, we encourage you to responsibly disclose this and not open a public issue. We will investigate all legitimate reports.
Email [info@worklenz.com](mailto:info@worklenz.com) to disclose any security vulnerabilities.
## License
## Analytics
This project is licensed under the [MIT License](LICENSE).
Worklenz uses Google Analytics to understand how the application is being used. This helps us improve the application and make better decisions about future development.
### What We Track
- Anonymous usage statistics
- Page views and navigation patterns
- Feature usage
- Browser and device information
### Privacy
- Analytics is opt-in only
- No personal information is collected
- Users can opt-out at any time
- Data is stored according to Google's privacy policy
### How to Opt-Out
If you've previously opted in and want to opt-out:
1. Clear your browser's local storage for the Worklenz domain
2. Or click the "Decline" button in the analytics notice if it appears
## Screenshots
@@ -240,206 +355,13 @@ This project is licensed under the [MIT License](LICENSE).
</a>
</p>
### Contributing
## Contributing
We welcome contributions from the community! If you'd like to contribute, please follow
our [contributing guidelines](CONTRIBUTING.md).
We welcome contributions from the community! If you'd like to contribute, please follow our [contributing guidelines](CONTRIBUTING.md).
### License
## License
Worklenz is open source and released under the [GNU Affero General Public License Version 3 (AGPLv3)](LICENSE).
By contributing to Worklenz, you agree that your contributions will be licensed under its AGPL.
# Worklenz React
This repository contains the React version of Worklenz with a Docker setup for easy development and deployment.
## Getting Started with Docker
The project includes a fully configured Docker setup with:
- Frontend React application
- Backend server
- PostgreSQL database
- MinIO for S3-compatible storage
### Prerequisites
- Docker and Docker Compose installed on your system
- Git
### Quick Start
1. Clone the repository:
```bash
git clone https://github.com/Worklenz/worklenz.git
cd worklenz
```
2. Start the Docker containers (choose one option):
**Option 1: Using the provided scripts (easiest)**
- On Windows:
```
start.bat
```
- On Linux/macOS:
```bash
./start.sh
```
**Option 2: Using Docker Compose directly**
```bash
docker-compose up -d
```
3. The application will be available at:
- Frontend: http://localhost:5000
- Backend API: http://localhost:3000
- MinIO Console: http://localhost:9001 (login with minioadmin/minioadmin)
4. To stop the services (choose one option):
**Option 1: Using the provided scripts**
- On Windows:
```
stop.bat
```
- On Linux/macOS:
```bash
./stop.sh
```
**Option 2: Using Docker Compose directly**
```bash
docker-compose down
```
## MinIO Integration
The project uses MinIO as an S3-compatible object storage service, which provides an open-source alternative to AWS S3 for development and production.
### Working with MinIO
MinIO provides an S3-compatible API, so any code that works with S3 will work with MinIO by simply changing the endpoint URL. The backend has been configured to use MinIO by default, with no additional configuration required.
- **MinIO Console**: http://localhost:9001
- Username: minioadmin
- Password: minioadmin
- **Default Bucket**: worklenz-bucket (created automatically when the containers start)
### Backend Storage Configuration
The backend is pre-configured to use MinIO with the following settings:
```javascript
// S3 credentials with MinIO defaults
export const REGION = process.env.AWS_REGION || "us-east-1";
export const BUCKET = process.env.AWS_BUCKET || "worklenz-bucket";
export const S3_URL = process.env.S3_URL || "http://minio:9000/worklenz-bucket";
export const S3_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID || "minioadmin";
export const S3_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY || "minioadmin";
```
The S3 client is initialized with special MinIO configuration:
```javascript
const s3Client = new S3Client({
region: REGION,
credentials: {
accessKeyId: S3_ACCESS_KEY_ID || "",
secretAccessKey: S3_SECRET_ACCESS_KEY || "",
},
endpoint: getEndpointFromUrl(), // Extracts endpoint from S3_URL
forcePathStyle: true, // Required for MinIO
});
```
### Environment Configuration
The project uses the following environment file structure:
- **Frontend**:
- `worklenz-frontend/.env.development` - Development environment variables
- `worklenz-frontend/.env.production` - Production build variables
- **Backend**:
- `worklenz-backend/.env` - Backend environment variables
### Setting Up Environment Files
The Docker environment script will create or overwrite all environment files:
```bash
# For HTTP/WS
./update-docker-env.sh your-hostname
# For HTTPS/WSS
./update-docker-env.sh your-hostname true
```
This script generates properly configured environment files for both development and production environments.
## Docker Deployment
### Local Development with Docker
1. Set up the environment files:
```bash
# For HTTP/WS
./update-docker-env.sh
# For HTTPS/WSS
./update-docker-env.sh localhost true
```
2. Run the application using Docker Compose:
```bash
docker-compose up -d
```
3. Access the application:
- Frontend: http://localhost:5000
- Backend API: http://localhost:3000 (or https://localhost:3000 with SSL)
### Remote Server Deployment
When deploying to a remote server:
1. Set up the environment files with your server's hostname:
```bash
# For HTTP/WS
./update-docker-env.sh your-server-hostname
# For HTTPS/WSS
./update-docker-env.sh your-server-hostname true
```
This ensures that the frontend correctly connects to the backend API.
2. Pull and run the latest Docker images:
```bash
docker-compose pull
docker-compose up -d
```
3. Access the application through your server's hostname:
- Frontend: http://your-server-hostname:5000
- Backend API: http://your-server-hostname:3000
### Environment Configuration
The Docker setup uses environment variables to configure the services:
- Frontend:
- `VITE_API_URL`: URL of the backend API (default: http://backend:3000 for container networking)
- `VITE_SOCKET_URL`: WebSocket URL for real-time communication (default: ws://backend:3000)
- Backend:
- Database connection parameters
- Storage configuration
- Other backend settings
For custom configuration, edit the `.env` file or the `update-docker-env.sh` script.

View File

@@ -4,7 +4,7 @@ Getting started with development is a breeze! Follow these steps and you'll be c
## Requirements
- Node.js version v16 or newer - [Node.js](https://nodejs.org/en/download/)
- Node.js version v20 or newer - [Node.js](https://nodejs.org/en/download/)
- PostgreSQL version v15 or newer - [PostgreSQL](https://www.postgresql.org/download/)
- S3-compatible storage (like MinIO) for file storage
@@ -38,7 +38,7 @@ Getting started with development is a breeze! Follow these steps and you'll be c
npm start
```
4. Navigate to [http://localhost:5173](http://localhost:5173)
4. Navigate to [http://localhost:5173](http://localhost:5173) (development server)
### Backend installation
@@ -126,7 +126,7 @@ For an easier setup, you can use Docker and Docker Compose:
```
3. Access the application:
- Frontend: http://localhost:5000
- Frontend: http://localhost:5000 (Docker production build)
- Backend API: http://localhost:3000
- MinIO Console: http://localhost:9001 (login with minioadmin/minioadmin)

16
backup.sh Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/bash
set -eu
# Adjust these as needed:
CONTAINER=worklenz_db
DB_NAME=worklenz_db
DB_USER=postgres
BACKUP_DIR=./pg_backups
mkdir -p "$BACKUP_DIR"
timestamp=$(date +%Y-%m-%d_%H-%M-%S)
outfile="${BACKUP_DIR}/${DB_NAME}_${timestamp}.sql"
echo "Creating backup $outfile ..."
docker exec -t "$CONTAINER" pg_dump -U "$DB_USER" -d "$DB_NAME" > "$outfile"
echo "Backup saved to $outfile"

View File

@@ -7,8 +7,8 @@ services:
ports:
- "5000:5000"
depends_on:
backend:
condition: service_started
- backend
restart: unless-stopped
env_file:
- ./worklenz-frontend/.env.production
networks:
@@ -26,6 +26,7 @@ services:
condition: service_healthy
minio:
condition: service_started
restart: unless-stopped
env_file:
- ./worklenz-backend/.env
networks:
@@ -37,6 +38,7 @@ services:
ports:
- "9000:9000"
- "9001:9001"
restart: unless-stopped
environment:
MINIO_ROOT_USER: ${S3_ACCESS_KEY_ID:-minioadmin}
MINIO_ROOT_PASSWORD: ${S3_SECRET_ACCESS_KEY:-minioadmin}
@@ -52,13 +54,14 @@ services:
container_name: worklenz_createbuckets
depends_on:
- minio
restart: on-failure
entrypoint: >
/bin/sh -c '
echo "Waiting for MinIO to start...";
sleep 15;
for i in 1 2 3 4 5; do
echo "Attempt $i to connect to MinIO...";
if /usr/bin/mc config host add myminio http://minio:9000 minioadmin minioadmin; then
if /usr/bin/mc alias set myminio http://minio:9000 minioadmin minioadmin; then
echo "Successfully connected to MinIO!";
/usr/bin/mc mb --ignore-existing myminio/worklenz-bucket;
/usr/bin/mc policy set public myminio/worklenz-bucket;
@@ -80,32 +83,79 @@ services:
POSTGRES_DB: ${DB_NAME:-worklenz_db}
POSTGRES_PASSWORD: ${DB_PASSWORD:-password}
healthcheck:
test: [ "CMD-SHELL", "pg_isready -d ${DB_NAME:-worklenz_db} -U ${DB_USER:-postgres}" ]
test:
[
"CMD-SHELL",
"pg_isready -d ${DB_NAME:-worklenz_db} -U ${DB_USER:-postgres}",
]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- worklenz
volumes:
- worklenz_postgres_data:/var/lib/postgresql/data
- type: bind
source: ./worklenz-backend/database
target: /docker-entrypoint-initdb.d
source: ./worklenz-backend/database/sql
target: /docker-entrypoint-initdb.d/sql
consistency: cached
- type: bind
source: ./worklenz-backend/database/migrations
target: /docker-entrypoint-initdb.d/migrations
consistency: cached
- type: bind
source: ./worklenz-backend/database/00_init.sh
target: /docker-entrypoint-initdb.d/00_init.sh
consistency: cached
- type: bind
source: ./pg_backups
target: /docker-entrypoint-initdb.d/pg_backups
command: >
bash -c ' if command -v apt-get >/dev/null 2>&1; then
apt-get update && apt-get install -y dos2unix
elif command -v apk >/dev/null 2>&1; then
apk add --no-cache dos2unix
fi && find /docker-entrypoint-initdb.d -type f -name "*.sh" -exec sh -c '\''
dos2unix "{}" 2>/dev/null || true
chmod +x "{}"
'\'' \; && exec docker-entrypoint.sh postgres '
bash -c '
if command -v apt-get >/dev/null 2>&1; then
apt-get update && apt-get install -y dos2unix
elif command -v apk >/dev/null 2>&1; then
apk add --no-cache dos2unix
fi
find /docker-entrypoint-initdb.d -type f -name "*.sh" -exec sh -c '"'"'
for f; do
dos2unix "$f" 2>/dev/null || true
chmod +x "$f"
done
'"'"' sh {} +
exec docker-entrypoint.sh postgres
'
db-backup:
image: postgres:15
container_name: worklenz_db_backup
environment:
POSTGRES_USER: ${DB_USER:-postgres}
POSTGRES_DB: ${DB_NAME:-worklenz_db}
POSTGRES_PASSWORD: ${DB_PASSWORD:-password}
depends_on:
db:
condition: service_healthy
volumes:
- ./pg_backups:/pg_backups #host dir for backups files
#setup bassh loop to backup data evey 24h
command: >
bash -c 'while true; do
sleep 86400;
PGPASSWORD=$$POSTGRES_PASSWORD pg_dump -h worklenz_db -U $$POSTGRES_USER -d $$POSTGRES_DB \
> /pg_backups/worklenz_db_$$(date +%Y-%m-%d_%H-%M-%S).sql;
find /pg_backups -type f -name "*.sql" -mtime +30 -delete;
done'
restart: unless-stopped
networks:
- worklenz
volumes:
worklenz_postgres_data:
worklenz_minio_data:
pgdata:
networks:
worklenz:

View File

@@ -0,0 +1,429 @@
# Enhanced Task Management: Technical Guide
## Overview
The Enhanced Task Management system is a comprehensive React-based interface built on top of WorkLenz's existing task infrastructure. It provides a modern, grouped view with drag-and-drop functionality, bulk operations, and responsive design.
## Architecture
### Component Structure
```
src/components/task-management/
├── TaskListBoard.tsx # Main container with DnD context
├── TaskGroup.tsx # Individual group with collapse/expand
├── TaskRow.tsx # Task display with rich metadata
├── GroupingSelector.tsx # Grouping method switcher
└── BulkActionBar.tsx # Bulk operations toolbar
```
### Integration Points
The system integrates with existing WorkLenz infrastructure:
- **Redux Store:** Uses `tasks.slice.ts` for state management
- **Types:** Leverages existing TypeScript interfaces
- **API Services:** Works with existing task API endpoints
- **WebSocket:** Supports real-time updates via existing socket system
## Core Components
### TaskListBoard.tsx
Main orchestrator component that provides:
- **DnD Context:** @dnd-kit drag-and-drop functionality
- **State Management:** Redux integration for task data
- **Event Handling:** Drag events and bulk operations
- **Layout Structure:** Header controls and group container
#### Key Props
```typescript
interface TaskListBoardProps {
projectId: string; // Required: Project identifier
className?: string; // Optional: Additional CSS classes
}
```
#### Redux Selectors Used
```typescript
const {
taskGroups, // ITaskListGroup[] - Grouped task data
loadingGroups, // boolean - Loading state
error, // string | null - Error state
groupBy, // IGroupBy - Current grouping method
search, // string | null - Search filter
archived, // boolean - Show archived tasks
} = useSelector((state: RootState) => state.taskReducer);
```
### TaskGroup.tsx
Renders individual task groups with:
- **Collapsible Headers:** Expand/collapse functionality
- **Progress Indicators:** Visual completion progress
- **Drop Zones:** Accept dropped tasks from other groups
- **Group Statistics:** Task counts and completion rates
#### Key Props
```typescript
interface TaskGroupProps {
group: ITaskListGroup; // Group data with tasks
projectId: string; // Project context
currentGrouping: IGroupBy; // Current grouping mode
selectedTaskIds: string[]; // Selected task IDs
onAddTask?: (groupId: string) => void;
onToggleCollapse?: (groupId: string) => void;
}
```
### TaskRow.tsx
Individual task display featuring:
- **Rich Metadata:** Progress, assignees, labels, due dates
- **Drag Handles:** Sortable within and between groups
- **Selection:** Multi-select with checkboxes
- **Subtask Support:** Expandable hierarchy display
#### Key Props
```typescript
interface TaskRowProps {
task: IProjectTask; // Task data
projectId: string; // Project context
groupId: string; // Parent group ID
currentGrouping: IGroupBy; // Current grouping mode
isSelected: boolean; // Selection state
isDragOverlay?: boolean; // Drag overlay rendering
index?: number; // Position in group
onSelect?: (taskId: string, selected: boolean) => void;
onToggleSubtasks?: (taskId: string) => void;
}
```
## State Management
### Redux Integration
The system uses existing WorkLenz Redux patterns:
```typescript
// Primary slice used
import {
fetchTaskGroups, // Async thunk for loading data
reorderTasks, // Update task order/group
setGroup, // Change grouping method
updateTaskStatus, // Update individual task status
updateTaskPriority, // Update individual task priority
// ... other existing actions
} from '@/features/tasks/tasks.slice';
```
### Data Flow
1. **Component Mount:** `TaskListBoard` dispatches `fetchTaskGroups(projectId)`
2. **Group Changes:** `setGroup(newGroupBy)` triggers data reorganization
3. **Drag Operations:** `reorderTasks()` updates task positions and properties
4. **Real-time Updates:** WebSocket events update Redux state automatically
## Drag and Drop Implementation
### DnD Kit Integration
Uses @dnd-kit for modern, accessible drag-and-drop:
```typescript
// Sensors for different input methods
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: { distance: 8 }
}),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates
})
);
```
### Drag Event Handling
```typescript
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event;
// Determine source and target
const sourceGroup = findTaskGroup(active.id);
const targetGroup = findTargetGroup(over?.id);
// Update task arrays and dispatch changes
dispatch(reorderTasks({
activeGroupId: sourceGroup.id,
overGroupId: targetGroup.id,
fromIndex: sourceIndex,
toIndex: targetIndex,
task: movedTask,
updatedSourceTasks,
updatedTargetTasks,
}));
};
```
### Smart Property Updates
When tasks are moved between groups, properties update automatically:
- **Status Grouping:** Moving to "Done" group sets task status to "done"
- **Priority Grouping:** Moving to "High" group sets task priority to "high"
- **Phase Grouping:** Moving to "Testing" group sets task phase to "testing"
## Bulk Operations
### Selection State Management
```typescript
// Local state for task selection
const [selectedTaskIds, setSelectedTaskIds] = useState<string[]>([]);
// Selection handlers
const handleTaskSelect = (taskId: string, selected: boolean) => {
if (selected) {
setSelectedTaskIds(prev => [...prev, taskId]);
} else {
setSelectedTaskIds(prev => prev.filter(id => id !== taskId));
}
};
```
### Context-Aware Actions
Bulk actions adapt to current grouping:
```typescript
// Only show status changes when not grouped by status
{currentGrouping !== 'status' && (
<Dropdown overlay={statusMenu}>
<Button>Change Status</Button>
</Dropdown>
)}
```
## Performance Optimizations
### Memoized Selectors
```typescript
// Expensive group calculations are memoized
const taskGroups = useMemo(() => {
return createGroupsFromTasks(tasks, currentGrouping);
}, [tasks, currentGrouping]);
```
### Virtual Scrolling Ready
For large datasets, the system is prepared for react-window integration:
```typescript
// Large group detection
const shouldVirtualize = group.tasks.length > 100;
return shouldVirtualize ? (
<VirtualizedTaskList tasks={group.tasks} />
) : (
<StandardTaskList tasks={group.tasks} />
);
```
### Optimistic Updates
UI updates immediately while API calls process in background:
```typescript
// Immediate UI update
dispatch(updateTaskStatusOptimistically(taskId, newStatus));
// API call with rollback on error
try {
await updateTaskStatus(taskId, newStatus);
} catch (error) {
dispatch(rollbackTaskStatusUpdate(taskId));
}
```
## Responsive Design
### Breakpoint Strategy
```css
/* Mobile-first responsive design */
.task-row {
padding: 12px;
}
@media (min-width: 768px) {
.task-row {
padding: 16px;
}
}
@media (min-width: 1024px) {
.task-row {
padding: 20px;
}
}
```
### Progressive Enhancement
- **Mobile:** Essential information only
- **Tablet:** Additional metadata visible
- **Desktop:** Full feature set with optimal layout
## Accessibility
### ARIA Implementation
```typescript
// Proper ARIA labels for screen readers
<div
role="button"
aria-label={`Move task ${task.name}`}
tabIndex={0}
{...dragHandleProps}
>
<DragOutlined />
</div>
```
### Keyboard Navigation
- **Tab:** Navigate between elements
- **Space:** Select/deselect tasks
- **Enter:** Activate buttons
- **Arrows:** Navigate sortable lists with keyboard sensor
### Focus Management
```typescript
// Maintain focus during dynamic updates
useEffect(() => {
if (shouldFocusTask) {
taskRef.current?.focus();
}
}, [taskGroups]);
```
## WebSocket Integration
### Real-time Updates
The system subscribes to existing WorkLenz WebSocket events:
```typescript
// Socket event handlers (existing WorkLenz patterns)
socket.on('TASK_STATUS_CHANGED', (data) => {
dispatch(updateTaskStatus(data));
});
socket.on('TASK_PROGRESS_UPDATED', (data) => {
dispatch(updateTaskProgress(data));
});
```
### Live Collaboration
- Multiple users can work simultaneously
- Changes appear in real-time
- Conflict resolution through server-side validation
## API Integration
### Existing Endpoints Used
```typescript
// Uses existing WorkLenz API services
import { tasksApiService } from '@/api/tasks/tasks.api.service';
// Task data fetching
tasksApiService.getTaskList(config);
// Task updates
tasksApiService.updateTask(taskId, changes);
// Bulk operations
tasksApiService.bulkUpdateTasks(taskIds, changes);
```
### Error Handling
```typescript
try {
await dispatch(fetchTaskGroups(projectId));
} catch (error) {
// Display user-friendly error message
message.error('Failed to load tasks. Please try again.');
logger.error('Task loading error:', error);
}
```
## Testing Strategy
### Component Testing
```typescript
// Example test structure
describe('TaskListBoard', () => {
it('should render task groups correctly', () => {
const mockTasks = generateMockTasks(10);
render(<TaskListBoard projectId="test-project" />);
expect(screen.getByText('Tasks (10)')).toBeInTheDocument();
});
it('should handle drag and drop operations', async () => {
// Test drag and drop functionality
});
});
```
### Integration Testing
- Redux state management
- API service integration
- WebSocket event handling
- Drag and drop operations
## Development Guidelines
### Code Organization
- Follow existing WorkLenz patterns
- Use TypeScript strictly
- Implement proper error boundaries
- Maintain accessibility standards
### Performance Considerations
- Memoize expensive calculations
- Implement virtual scrolling for large datasets
- Debounce user input operations
- Optimize re-render cycles
### Styling Standards
- Use existing Ant Design components
- Follow WorkLenz design system
- Implement responsive breakpoints
- Maintain dark mode compatibility
## Future Enhancements
### Planned Features
- Custom column integration
- Advanced filtering capabilities
- Kanban board view
- Enhanced time tracking
- Task templates
### Extension Points
The system is designed for easy extension:
```typescript
// Plugin architecture ready
interface TaskViewPlugin {
name: string;
component: React.ComponentType;
supportedGroupings: IGroupBy[];
}
const plugins: TaskViewPlugin[] = [
{ name: 'kanban', component: KanbanView, supportedGroupings: ['status'] },
{ name: 'timeline', component: TimelineView, supportedGroupings: ['phase'] },
];
```
## Deployment Considerations
### Bundle Size
- Tree-shake unused dependencies
- Code-split large components
- Optimize asset loading
### Browser Compatibility
- Modern browsers (ES2020+)
- Graceful degradation for older browsers
- Progressive enhancement approach
### Performance Monitoring
- Track component render times
- Monitor API response times
- Measure user interaction latency

View File

@@ -0,0 +1,275 @@
# Enhanced Task Management: User Guide
## What Is Enhanced Task Management?
The Enhanced Task Management system provides a modern, grouped view of your tasks with advanced features like drag-and-drop, bulk operations, and dynamic grouping. This system builds on WorkLenz's existing task infrastructure while offering improved productivity and organization tools.
## Why Use Enhanced Task Management?
- **Better Organization:** Group tasks by Status, Priority, or Phase for clearer project overview
- **Increased Productivity:** Bulk operations let you update multiple tasks at once
- **Intuitive Interface:** Drag-and-drop functionality makes task management feel natural
- **Rich Task Display:** See progress, assignees, labels, and due dates at a glance
- **Responsive Design:** Works seamlessly on desktop, tablet, and mobile devices
## Getting Started
### Accessing Enhanced Task Management
1. Navigate to your project workspace
2. Look for the enhanced task view option in your project interface
3. The system will display your tasks grouped by the current grouping method (default: Status)
### Understanding the Interface
The enhanced task management interface consists of several key areas:
- **Header Controls:** Task count, grouping selector, and action buttons
- **Task Groups:** Collapsible sections containing related tasks
- **Individual Tasks:** Rich task cards with metadata and actions
- **Bulk Action Bar:** Appears when multiple tasks are selected (blue bar)
## Task Grouping
### Available Grouping Options
You can organize your tasks using three different grouping methods:
#### 1. Status Grouping (Default)
Groups tasks by their current status:
- **To Do:** Tasks not yet started
- **Doing:** Tasks currently in progress
- **Done:** Completed tasks
#### 2. Priority Grouping
Groups tasks by their priority level:
- **Critical:** Highest priority, urgent tasks
- **High:** Important tasks requiring attention
- **Medium:** Standard priority tasks
- **Low:** Tasks that can be addressed later
#### 3. Phase Grouping
Groups tasks by project phases:
- **Planning:** Tasks in the planning stage
- **Development:** Implementation and development tasks
- **Testing:** Quality assurance and testing tasks
- **Deployment:** Release and deployment tasks
### Switching Between Groupings
1. Locate the "Group by" dropdown in the header controls
2. Select your preferred grouping method (Status, Priority, or Phase)
3. Tasks will automatically reorganize into the new groups
4. Your grouping preference is saved for future sessions
### Group Features
Each task group includes:
- **Color-coded headers** with visual indicators
- **Task count badges** showing the number of tasks in each group
- **Progress indicators** showing completion percentage
- **Collapse/expand functionality** to hide or show group contents
- **Add task buttons** to quickly create tasks in specific groups
## Drag and Drop
### Moving Tasks Within Groups
1. Hover over a task to reveal the drag handle (⋮⋮ icon)
2. Click and hold the drag handle
3. Drag the task to your desired position within the same group
4. Release to drop the task in its new position
### Moving Tasks Between Groups
1. Click and hold the drag handle on any task
2. Drag the task over a different group
3. The target group will highlight to show it can accept the task
4. Release to drop the task into the new group
5. The task's properties (status, priority, or phase) will automatically update
### Drag and Drop Benefits
- **Instant Updates:** Task properties change automatically when moved between groups
- **Visual Feedback:** Clear indicators show where tasks can be dropped
- **Keyboard Accessible:** Alternative keyboard controls for accessibility
- **Mobile Friendly:** Touch-friendly drag operations on mobile devices
## Multi-Select and Bulk Operations
### Selecting Tasks
You can select multiple tasks using several methods:
#### Individual Selection
- Click the checkbox next to any task to select it
- Click again to deselect
#### Range Selection
- Select the first task in your desired range
- Hold Shift and click the last task in the range
- All tasks between the first and last will be selected
#### Multiple Selection
- Hold Ctrl (or Cmd on Mac) while clicking tasks
- This allows you to select non-consecutive tasks
### Bulk Actions
When you have tasks selected, a blue bulk action bar appears with these options:
#### Change Status (when not grouped by Status)
- Update the status of all selected tasks at once
- Choose from available status options in your project
#### Set Priority (when not grouped by Priority)
- Assign the same priority level to all selected tasks
- Options include Critical, High, Medium, and Low
#### More Actions
Additional bulk operations include:
- **Assign to Member:** Add team members to multiple tasks
- **Add Labels:** Apply labels to selected tasks
- **Archive Tasks:** Move multiple tasks to archive
#### Delete Tasks
- Permanently remove multiple tasks at once
- Confirmation dialog prevents accidental deletions
### Bulk Action Tips
- The bulk action bar only shows relevant options based on your current grouping
- You can clear your selection at any time using the "Clear" button
- Bulk operations provide immediate feedback and can be undone if needed
## Task Display Features
### Rich Task Information
Each task displays comprehensive information:
#### Basic Information
- **Task Key:** Unique identifier (e.g., PROJ-123)
- **Task Name:** Clear, descriptive title
- **Description:** Additional details when available
#### Visual Indicators
- **Progress Bar:** Shows completion percentage (0-100%)
- **Priority Indicator:** Color-coded dot showing task importance
- **Status Color:** Left border color indicates current status
#### Team and Collaboration
- **Assignee Avatars:** Profile pictures of assigned team members (up to 3 visible)
- **Labels:** Color-coded tags for categorization
- **Comment Count:** Number of comments and discussions
- **Attachment Count:** Number of files attached to the task
#### Timing Information
- **Due Dates:** When tasks are scheduled to complete
- Red text: Overdue tasks
- Orange text: Due today or within 3 days
- Gray text: Future due dates
- **Time Tracking:** Estimated vs. logged time when available
### Subtask Support
Tasks with subtasks include additional features:
#### Expanding Subtasks
- Click the "+X" button next to task names to expand subtasks
- Subtasks appear indented below the parent task
- Click "X" to collapse subtasks
#### Subtask Progress
- Parent task progress reflects completion of all subtasks
- Individual subtask progress is visible when expanded
- Subtask counts show total number of child tasks
## Advanced Features
### Real-time Updates
- Changes made by team members appear instantly
- Live collaboration with multiple users
- WebSocket connections ensure data synchronization
### Search and Filtering
- Use existing project search and filter capabilities
- Enhanced task management respects current filter settings
- Search results maintain grouping organization
### Responsive Design
The interface adapts to different screen sizes:
#### Desktop (Large Screens)
- Full feature set with all metadata visible
- Optimal drag-and-drop experience
- Multi-column layouts where appropriate
#### Tablet (Medium Screens)
- Condensed but functional interface
- Touch-friendly interactions
- Simplified metadata display
#### Mobile (Small Screens)
- Stacked layout for easy navigation
- Large touch targets for selections
- Essential information prioritized
## Best Practices
### Organizing Your Tasks
1. **Choose the Right Grouping:** Select the grouping method that best fits your workflow
2. **Use Labels Consistently:** Apply meaningful labels for better categorization
3. **Keep Groups Balanced:** Avoid having too many tasks in a single group
4. **Regular Maintenance:** Review and update task organization periodically
### Collaboration Tips
1. **Clear Task Names:** Use descriptive titles that everyone understands
2. **Proper Assignment:** Assign tasks to appropriate team members
3. **Progress Updates:** Keep progress percentages current for accurate project tracking
4. **Use Comments:** Communicate about tasks using the comment system
### Productivity Techniques
1. **Batch Similar Operations:** Use bulk actions for efficiency
2. **Prioritize Effectively:** Use priority grouping during planning phases
3. **Track Progress:** Monitor completion rates using group progress indicators
4. **Plan Ahead:** Use due dates and time estimates for better scheduling
## Keyboard Shortcuts
### Navigation
- **Tab:** Move focus between elements
- **Enter:** Activate focused button or link
- **Esc:** Close open dialogs or clear selections
### Selection
- **Space:** Select/deselect focused task
- **Shift + Click:** Range selection
- **Ctrl + Click:** Multi-selection (Cmd + Click on Mac)
### Actions
- **Delete:** Remove selected tasks (with confirmation)
- **Ctrl + A:** Select all visible tasks (Cmd + A on Mac)
## Troubleshooting
### Common Issues
#### Tasks Not Moving Between Groups
- Ensure you have edit permissions for the tasks
- Check that you're dragging from the drag handle (⋮⋮ icon)
- Verify the target group allows the task type
#### Bulk Actions Not Working
- Confirm tasks are actually selected (checkboxes checked)
- Ensure you have appropriate permissions
- Check that the action is available for your current grouping
#### Missing Task Information
- Some metadata may be hidden on smaller screens
- Try expanding to full screen or using desktop view
- Check that task has the required information (assignees, labels, etc.)
### Performance Tips
- For projects with hundreds of tasks, consider using filters to reduce visible tasks
- Collapse groups you're not actively working with
- Clear selections when not performing bulk operations
## Getting Help
- Contact your workspace administrator for permission-related issues
- Check the main WorkLenz documentation for general task management help
- Report bugs or feature requests through your organization's support channels
## What's New
This enhanced task management system builds on WorkLenz's solid foundation while adding:
- Modern drag-and-drop interfaces
- Flexible grouping options
- Powerful bulk operation capabilities
- Rich visual task displays
- Mobile-responsive design
- Improved accessibility features

View File

@@ -0,0 +1,60 @@
# Recurring Tasks: User Guide
## What Are Recurring Tasks?
Recurring tasks are tasks that repeat automatically on a schedule you choose. This helps you save time and ensures important work is never forgotten. For example, you can set up a recurring task for weekly team meetings, monthly reports, or daily check-ins.
## Why Use Recurring Tasks?
- **Save time:** No need to create the same task over and over.
- **Stay organized:** Tasks appear automatically when needed.
- **Never miss a deadline:** Tasks are created on time, every time.
## How to Set Up a Recurring Task
1. Go to the tasks section in your workspace.
2. Choose to create a new task and look for the option to make it recurring.
3. Fill in the task details (name, description, assignees, etc.).
4. Select your preferred schedule (see options below).
5. Save the task. It will now be created automatically based on your chosen schedule.
## Schedule Options
You can choose how often your task repeats. Here are the available options:
- **Daily:** The task is created every day.
- **Weekly:** The task is created once a week. You can pick one or more days (e.g., every Monday and Thursday).
- **Monthly:** The task is created once a month. You have two options:
- **On a specific date:** Choose a date from 1 to 28 (limited to 28 to ensure consistency across all months)
- **On a specific day:** Choose a week (first, second, third, fourth, or last) and a day of the week
- **Every X Days:** The task is created every specified number of days (e.g., every 3 days)
- **Every X Weeks:** The task is created every specified number of weeks (e.g., every 2 weeks)
- **Every X Months:** The task is created every specified number of months (e.g., every 3 months)
### Examples
- "Send team update" every Friday (weekly)
- "Submit expense report" on the 15th of each month (monthly, specific date)
- "Monthly team meeting" on the first Monday of each month (monthly, specific day)
- "Check backups" every day (daily)
- "Review project status" every Monday and Thursday (weekly, multiple days)
- "Quarterly report" every 3 months (every X months)
## Future Task Creation
The system automatically creates tasks up to a certain point in the future to ensure timely scheduling:
- **Daily Tasks:** Created up to 7 days in advance
- **Weekly Tasks:** Created up to 2 weeks in advance
- **Monthly Tasks:** Created up to 2 months in advance
- **Every X Days/Weeks/Months:** Created up to 2 intervals in advance
This ensures that:
- You always have upcoming tasks visible in your schedule
- Tasks are created at appropriate intervals
- The system maintains a reasonable number of future tasks
## Tips
- You can edit or stop a recurring task at any time.
- Assign team members and labels to recurring tasks for better organization.
- Check your task list regularly to see newly created recurring tasks.
- For monthly tasks, dates are limited to 1-28 to ensure the task occurs on the same date every month.
- Tasks are created automatically within the future limit window - you don't need to manually create them.
- If you need to see tasks further in the future, they will be created automatically as the current tasks are completed.
## Need Help?
If you have questions or need help setting up recurring tasks, contact your workspace admin or support team.

104
docs/recurring-tasks.md Normal file
View File

@@ -0,0 +1,104 @@
# Recurring Tasks Cron Job Documentation
## Overview
The recurring tasks cron job automates the creation of tasks based on predefined templates and schedules. It ensures that tasks are generated at the correct intervals without manual intervention, supporting efficient project management and timely task assignment.
## Purpose
- Automatically create tasks according to recurring schedules defined in the database.
- Prevent duplicate task creation for the same schedule and date.
- Assign team members and labels to newly created tasks as specified in the template.
## Scheduling Logic
- The cron job is scheduled using the [cron](https://www.npmjs.com/package/cron) package.
- The schedule is defined by a cron expression (e.g., `*/2 * * * *` for every 2 minutes, or `0 11 */1 * 1-5` for 11:00 UTC on weekdays).
- On each tick, the job:
1. Fetches all recurring task templates and their schedules.
2. Determines the next occurrence for each template using `calculateNextEndDate`.
3. Checks if a task for the next occurrence already exists.
4. Creates a new task if it does not exist and the next occurrence is within the allowed future window.
## Future Limit Logic
The system implements different future limits based on the schedule type to maintain an appropriate number of future tasks:
```typescript
const FUTURE_LIMITS = {
daily: moment.duration(7, 'days'),
weekly: moment.duration(2, 'weeks'),
monthly: moment.duration(2, 'months'),
every_x_days: (interval: number) => moment.duration(interval * 2, 'days'),
every_x_weeks: (interval: number) => moment.duration(interval * 2, 'weeks'),
every_x_months: (interval: number) => moment.duration(interval * 2, 'months')
};
```
### Implementation Details
- **Base Calculation:**
```typescript
const futureLimit = moment(template.last_checked_at || template.created_at)
.add(getFutureLimit(schedule.schedule_type, schedule.interval), 'days');
```
- **Task Creation Rules:**
1. Only create tasks if the next occurrence is before the future limit
2. Skip creation if a task already exists for that date
3. Update `last_checked_at` after processing
- **Benefits:**
- Prevents excessive task creation
- Maintains system performance
- Ensures timely task visibility
- Allows for schedule modifications
## Date Handling
- **Monthly Tasks:**
- Dates are limited to 1-28 to ensure consistency across all months
- This prevents issues with months having different numbers of days
- No special handling needed for February or months with 30/31 days
- **Weekly Tasks:**
- Supports multiple days of the week (0-6, where 0 is Sunday)
- Tasks are created for each selected day
- **Interval-based Tasks:**
- Every X days/weeks/months from the last task's end date
- Minimum interval is 1 day/week/month
- No maximum limit, but tasks are only created up to the future limit
## Database Interactions
- **Templates and Schedules:**
- Templates are stored in `task_recurring_templates`.
- Schedules are stored in `task_recurring_schedules`.
- The job joins these tables to get all necessary data for task creation.
- **Task Creation:**
- Uses a stored procedure `create_quick_task` to insert new tasks.
- Assigns team members and labels by calling appropriate functions/controllers.
- **State Tracking:**
- Updates `last_checked_at` and `last_created_task_end_date` in the schedule after processing.
- Maintains future limits based on schedule type.
## Task Creation Process
1. **Fetch Templates:** Retrieve all templates and their associated schedules.
2. **Determine Next Occurrence:** Use the last task's end date or the schedule's creation date to calculate the next due date.
3. **Check for Existing Task:** Ensure no duplicate task is created for the same schedule and date.
4. **Create Task:**
- Insert the new task using the template's data.
- Assign team members and labels as specified.
5. **Update Schedule:** Record the last checked and created dates for accurate future runs.
## Configuration & Extension Points
- **Cron Expression:** Modify the `TIME` constant in the code to change the schedule.
- **Task Template Structure:** Extend the template or schedule interfaces to support additional fields.
- **Task Creation Logic:** Customize the task creation process or add new assignment/labeling logic as needed.
- **Future Window:** Adjust the future limits by modifying the `FUTURE_LIMITS` configuration.
## Error Handling
- Errors are logged using the `log_error` utility.
- The job continues processing other templates even if one fails.
- Failed task creations are not retried automatically.
## References
- Source: `src/cron_jobs/recurring-tasks.ts`
- Utilities: `src/shared/utils.ts`
- Database: `src/config/db.ts`
- Controllers: `src/controllers/tasks-controller.ts`
---
For further customization or troubleshooting, refer to the source code and update the documentation as needed.

View File

@@ -0,0 +1,223 @@
# WorkLenz Task Progress Guide for Users
## Introduction
WorkLenz offers three different ways to track and calculate task progress, each designed for different project management needs. This guide explains how each method works and when to use them.
## Default Progress Method
WorkLenz uses a simple completion-based approach as the default progress calculation method. This method is applied when no special progress methods are enabled.
### Example
If you have a parent task with four subtasks and two of the subtasks are marked complete:
- Parent task: Not done
- 2 subtasks: Done
- 2 subtasks: Not done
The parent task will show as 40% complete (2 completed out of 5 total tasks).
## Available Progress Tracking Methods
WorkLenz provides these progress tracking methods:
1. **Manual Progress** - Directly input progress percentages for tasks
2. **Weighted Progress** - Assign importance levels (weights) to tasks
3. **Time-based Progress** - Calculate progress based on estimated time
Only one method can be enabled at a time for a project. If none are enabled, progress will be calculated based on task completion status.
## How to Select a Progress Method
1. Open the project drawer by clicking on the project settings icon or creating a new project
2. In the project settings, find the "Progress Calculation Method" section
3. Select your preferred method
4. Save your changes
## Manual Progress Method
### How It Works
- You directly enter progress percentages (0-100%) for tasks without subtasks
- Parent task progress is calculated as the average of all subtask progress values
- Progress is updated in real-time as you adjust values
### When to Use Manual Progress
- For creative or subjective work where completion can't be measured objectively
- When task progress doesn't follow a linear path
- For projects where team members need flexibility in reporting progress
### Example
If you have a parent task with three subtasks:
- Subtask A: 30% complete
- Subtask B: 60% complete
- Subtask C: 90% complete
The parent task will show as 60% complete (average of 30%, 60%, and 90%).
## Weighted Progress Method
### How It Works
- You assign "weight" values to tasks to indicate their importance
- More important tasks have higher weights and influence the overall progress more
- You still enter manual progress percentages for tasks without subtasks
- Parent task progress is calculated using a weighted average
### When to Use Weighted Progress
- When some tasks are more important or time-consuming than others
- For projects where all tasks aren't equal
- When you want key deliverables to have more impact on overall progress
### Example
If you have a parent task with three subtasks:
- Subtask A: 50% complete, Weight 60% (important task)
- Subtask B: 75% complete, Weight 20% (less important task)
- Subtask C: 25% complete, Weight 100% (critical task)
The parent task will be approximately 39% complete, with Subtask C having the greatest impact due to its higher weight.
### Important Notes About Weights
- Default weight is 100% if not specified
- Weights range from 0% to 100%
- Setting a weight to 0% removes that task from progress calculations
- Only explicitly set weights for tasks that should have different importance
- Weights are only relevant for subtasks, not for independent tasks
### Detailed Weighted Progress Calculation Example
To understand how weighted progress works with different weight values, consider this example:
For a parent task with two subtasks:
- Subtask A: 80% complete, Weight 50%
- Subtask B: 40% complete, Weight 100%
The calculation works as follows:
1. Each subtask's contribution is: (weight × progress) ÷ (sum of all weights)
2. For Subtask A: (50 × 80%) ÷ (50 + 100) = 26.7%
3. For Subtask B: (100 × 40%) ÷ (50 + 100) = 26.7%
4. Total parent progress: 26.7% + 26.7% = 53.3%
The parent task would be approximately 53% complete.
This shows how the subtask with twice the weight (Subtask B) has twice the influence on the overall progress calculation, even though it has a lower completion percentage.
## Time-based Progress Method
### How It Works
- Use the task's time estimate as its "weight" in the progress calculation
- You still enter manual progress percentages for tasks without subtasks
- Tasks with longer time estimates have more influence on overall progress
- Parent task progress is calculated based on time-weighted averages
### When to Use Time-based Progress
- For projects with well-defined time estimates
- When task importance correlates with its duration
- For billing or time-tracking focused projects
- When you already maintain accurate time estimates
### Example
If you have a parent task with three subtasks:
- Subtask A: 40% complete, Estimated Time 2.5 hours
- Subtask B: 80% complete, Estimated Time 1 hour
- Subtask C: 10% complete, Estimated Time 4 hours
The parent task will be approximately 29% complete, with the lengthy Subtask C pulling down the overall progress despite Subtask B being mostly complete.
### Important Notes About Time Estimates
- Tasks without time estimates don't influence progress calculations
- Time is converted to minutes internally (a 2-hour task = 120 minutes)
- Setting a time estimate to 0 removes that task from progress calculations
- Time estimates serve dual purposes: scheduling/resource planning and progress weighting
### Detailed Time-based Progress Calculation Example
To understand how time-based progress works with different time estimates, consider this example:
For a parent task with three subtasks:
- Subtask A: 40% complete, Estimated Time 2.5 hours
- Subtask B: 80% complete, Estimated Time 1 hour
- Subtask C: 10% complete, Estimated Time 4 hours
The calculation works as follows:
1. Convert hours to minutes: A = 150 min, B = 60 min, C = 240 min
2. Total estimated time: 150 + 60 + 240 = 450 minutes
3. Each subtask's contribution is: (time estimate × progress) ÷ (total time)
4. For Subtask A: (150 × 40%) ÷ 450 = 13.3%
5. For Subtask B: (60 × 80%) ÷ 450 = 10.7%
6. For Subtask C: (240 × 10%) ÷ 450 = 5.3%
7. Total parent progress: 13.3% + 10.7% + 5.3% = 29.3%
The parent task would be approximately 29% complete.
This demonstrates how tasks with longer time estimates (like Subtask C) have more influence on the overall progress calculation. Even though Subtask B is 80% complete, its shorter time estimate means it contributes less to the overall progress than the partially-completed but longer Subtask A.
### How It Works
- Tasks are either 0% (not done) or 100% (done)
- Parent task progress = (completed tasks / total tasks) × 100%
- Both the parent task and all subtasks count in this calculation
### When to Use Default Progress
- For simple projects with clear task completion criteria
- When binary task status (done/not done) is sufficient
- For teams new to project management who want simplicity
### Example
If you have a parent task with four subtasks and two of the subtasks are marked complete:
- Parent task: Not done
- 2 subtasks: Done
- 2 subtasks: Not done
The parent task will show as 40% complete (2 completed out of 5 total tasks).
## Best Practices
1. **Choose the Right Method for Your Project**
- Consider your team's workflow and reporting needs
- Match the method to your project's complexity
2. **Be Consistent**
- Stick with one method throughout the project
- Changing methods mid-project can cause confusion
3. **For Manual Progress**
- Update progress regularly
- Establish guidelines for progress reporting
4. **For Weighted Progress**
- Assign weights based on objective criteria
- Don't overuse extreme weights
5. **For Time-based Progress**
- Keep time estimates accurate and up to date
- Consider using time tracking to validate estimates
## Frequently Asked Questions
**Q: Can I change the progress method mid-project?**
A: Yes, but it may cause progress values to change significantly. It's best to select a method at the project start.
**Q: What happens to task progress when I mark a task complete?**
A: When a task is marked complete, its progress automatically becomes 100%, regardless of the progress method.
**Q: How do I enter progress for a task?**
A: Open the task drawer, go to the Info tab, and use the progress slider for tasks without subtasks.
**Q: Can different projects use different progress methods?**
A: Yes, each project can have its own progress method.
**Q: What if I don't see progress fields in my task drawer?**
A: Progress input is only visible for tasks without subtasks. Parent tasks' progress is automatically calculated.

View File

@@ -0,0 +1,550 @@
# Task Progress Tracking Methods in WorkLenz
## Overview
WorkLenz supports three different methods for tracking task progress, each suitable for different project management approaches:
1. **Manual Progress** - Direct input of progress percentages
2. **Weighted Progress** - Tasks have weights that affect overall progress calculation
3. **Time-based Progress** - Progress calculated based on estimated time vs. time spent
These modes can be selected when creating or editing a project in the project drawer. Only one progress method can be enabled at a time. If none of these methods are enabled, progress will be calculated based on task completion status as described in the "Default Progress Tracking" section below.
## 1. Manual Progress Mode
This mode allows direct input of progress percentages for individual tasks without subtasks.
**Implementation:**
- Enabled by setting `use_manual_progress` to true in the project settings
- Progress is updated through the `on-update-task-progress.ts` socket event handler
- The UI shows a manual progress input slider in the task drawer for tasks without subtasks
- Updates the database with `progress_value` and sets `manual_progress` flag to true
**Calculation Logic:**
- For tasks without subtasks: Uses the manually set progress value
- For parent tasks: Calculates the average of all subtask progress values
- Subtask progress comes from either manual values or completion status (0% or 100%)
**Code Example:**
```typescript
// Manual progress update via socket.io
socket?.emit(SocketEvents.UPDATE_TASK_PROGRESS.toString(), JSON.stringify({
task_id: task.id,
progress_value: value,
parent_task_id: task.parent_task_id
}));
```
## 2. Weighted Progress Mode
This mode allows assigning different weights to subtasks to reflect their relative importance in the overall task or project progress.
**Implementation:**
- Enabled by setting `use_weighted_progress` to true in the project settings
- Weights are updated through the `on-update-task-weight.ts` socket event handler
- The UI shows a weight input for subtasks in the task drawer
- Manual progress input is still required for tasks without subtasks
- Default weight is 100 if not specified
- Weight values range from 0 to 100%
**Calculation Logic:**
- For tasks without subtasks: Uses the manually entered progress value
- Progress is calculated using a weighted average: `SUM(progress_value * weight) / SUM(weight)`
- This gives more influence to tasks with higher weights
- A parent task's progress is the weighted average of its subtasks' progress values
**Code Example:**
```typescript
// Weight update via socket.io
socket?.emit(SocketEvents.UPDATE_TASK_WEIGHT.toString(), JSON.stringify({
task_id: task.id,
weight: value,
parent_task_id: task.parent_task_id
}));
```
## 3. Time-based Progress Mode
This mode calculates progress based on estimated time vs. actual time spent.
**Implementation:**
- Enabled by setting `use_time_progress` to true in the project settings
- Uses task time estimates (hours and minutes) for calculation
- Manual progress input is still required for tasks without subtasks
- No separate socket handler needed as it's calculated automatically
**Calculation Logic:**
- For tasks without subtasks: Uses the manually entered progress value
- Progress is calculated using time as the weight: `SUM(progress_value * estimated_minutes) / SUM(estimated_minutes)`
- For tasks with time tracking, estimated vs. actual time can be factored in
- Parent task progress is weighted by the estimated time of each subtask
**SQL Example:**
```sql
WITH subtask_progress AS (
SELECT
CASE
WHEN manual_progress IS TRUE AND progress_value IS NOT NULL THEN
progress_value
ELSE
CASE
WHEN EXISTS(
SELECT 1
FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = t.id
AND is_done IS TRUE
) THEN 100
ELSE 0
END
END AS progress_value,
COALESCE(total_hours * 60 + total_minutes, 0) AS estimated_minutes
FROM tasks t
WHERE t.parent_task_id = _task_id
AND t.archived IS FALSE
)
SELECT COALESCE(
SUM(progress_value * estimated_minutes) / NULLIF(SUM(estimated_minutes), 0),
0
)
FROM subtask_progress
INTO _ratio;
```
## Default Progress Tracking (when no special mode is selected)
If no specific progress mode is enabled, the system falls back to a traditional completion-based calculation:
**Implementation:**
- Default mode when all three special modes are disabled
- Based on task completion status only
**Calculation Logic:**
- For tasks without subtasks: 0% if not done, 100% if done
- For parent tasks: `(completed_tasks / total_tasks) * 100`
- Counts both the parent and all subtasks in the calculation
**SQL Example:**
```sql
-- Traditional calculation based on completion status
SELECT (CASE
WHEN EXISTS(SELECT 1
FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = _task_id
AND is_done IS TRUE) THEN 1
ELSE 0 END)
INTO _parent_task_done;
SELECT COUNT(*)
FROM tasks_with_status_view
WHERE parent_task_id = _task_id
AND is_done IS TRUE
INTO _sub_tasks_done;
_total_completed = _parent_task_done + _sub_tasks_done;
_total_tasks = _sub_tasks_count + 1; -- +1 for the parent task
IF _total_tasks = 0 THEN
_ratio = 0;
ELSE
_ratio = (_total_completed / _total_tasks) * 100;
END IF;
```
## Technical Implementation Details
The progress calculation logic is implemented in PostgreSQL functions, primarily in the `get_task_complete_ratio` function. Progress updates flow through the system as follows:
1. **User Action**: User updates task progress or weight in the UI
2. **Socket Event**: Client emits socket event (UPDATE_TASK_PROGRESS or UPDATE_TASK_WEIGHT)
3. **Server Handler**: Server processes the event in the respective handler function
4. **Database Update**: Progress/weight value is updated in the database
5. **Recalculation**: If needed, parent task progress is recalculated
6. **Broadcast**: Changes are broadcast to all clients in the project room
7. **UI Update**: Client UI updates to reflect the new progress values
This architecture allows for real-time updates and consistent progress calculation across all clients.
## Manual Progress Input Implementation
Regardless of which progress tracking method is selected for a project, tasks without subtasks (leaf tasks) require manual progress input. This section details how manual progress input is implemented and used across all progress tracking methods.
### UI Component
The manual progress input component is implemented in `worklenz-frontend/src/components/task-drawer/shared/info-tab/details/task-drawer-progress/task-drawer-progress.tsx` and includes:
1. **Progress Slider**: A slider UI control that allows users to set progress values from 0% to 100%
2. **Progress Input Field**: A numeric input field that accepts direct entry of progress percentage
3. **Progress Display**: Visual representation of the current progress value
The component is conditionally rendered in the task drawer for tasks that don't have subtasks.
**Usage Across Progress Methods:**
- In **Manual Progress Mode**: Only the progress slider/input is shown
- In **Weighted Progress Mode**: Both the progress slider/input and weight input are shown
- In **Time-based Progress Mode**: The progress slider/input is shown alongside time estimate fields
### Progress Update Flow
When a user updates a task's progress manually, the following process occurs:
1. **User Input**: User adjusts the progress slider or enters a value in the input field
2. **UI Event Handler**: The UI component captures the change event and validates the input
3. **Socket Event Emission**: The component emits a `UPDATE_TASK_PROGRESS` socket event with:
```typescript
{
task_id: task.id,
progress_value: value, // The new progress value (0-100)
parent_task_id: task.parent_task_id // For recalculation
}
```
4. **Server Processing**: The socket event handler on the server:
- Updates the task's `progress_value` in the database
- Sets the `manual_progress` flag to true
- Triggers recalculation of parent task progress
### Progress Calculation Across Methods
The calculation of progress differs based on the active progress method:
1. **For Leaf Tasks (no subtasks)** in all methods:
- Progress is always the manually entered value (`progress_value`)
- If the task is marked as completed, progress is automatically set to 100%
2. **For Parent Tasks**:
- **Manual Progress Mode**: Simple average of all subtask progress values
- **Weighted Progress Mode**: Weighted average where each subtask's progress is multiplied by its weight
- **Time-based Progress Mode**: Weighted average where each subtask's progress is multiplied by its estimated time
- **Default Mode**: Percentage of completed tasks (including parent) vs. total tasks
### Detailed Calculation for Weighted Progress Method
In Weighted Progress mode, both the manual progress input and weight assignment are critical components:
1. **Manual Progress Input**:
- For leaf tasks (without subtasks), users must manually input progress percentages (0-100%)
- If a leaf task is marked as complete, its progress is automatically set to 100%
- If a leaf task's progress is not manually set, it defaults to 0% (or 100% if completed)
2. **Weight Assignment**:
- Each task can be assigned a weight value between 0-100% (default 100% if not specified)
- Higher weight values give tasks more influence in parent task progress calculations
- A weight of 0% means the task doesn't contribute to the parent's progress calculation
3. **Parent Task Calculation**:
The weighted progress formula is:
```
ParentProgress = ∑(SubtaskProgress * SubtaskWeight) / ∑(SubtaskWeight)
```
**Example Calculation**:
Consider a parent task with three subtasks:
- Subtask A: Progress 50%, Weight 60%
- Subtask B: Progress 75%, Weight 20%
- Subtask C: Progress 25%, Weight 100%
Calculation:
```
ParentProgress = ((50 * 60) + (75 * 20) + (25 * 100)) / (60 + 20 + 100)
ParentProgress = (3000 + 1500 + 2500) / 180
ParentProgress = 7000 / 180
ParentProgress = 38.89%
```
Notice that Subtask C, despite having the lowest progress, has a significant impact on the parent task progress due to its higher weight.
4. **Zero Weight Handling**:
Tasks with zero weight are excluded from the calculation:
- Subtask A: Progress 40%, Weight 50%
- Subtask B: Progress 80%, Weight 0%
Calculation:
```
ParentProgress = ((40 * 50) + (80 * 0)) / (50 + 0)
ParentProgress = 2000 / 50
ParentProgress = 40%
```
In this case, only Subtask A influences the parent task progress because Subtask B has a weight of 0%.
5. **Default Weight Behavior**:
When weights aren't explicitly assigned to some tasks:
- Subtask A: Progress 30%, Weight 60% (explicitly set)
- Subtask B: Progress 70%, Weight not set (defaults to 100%)
- Subtask C: Progress 90%, Weight not set (defaults to 100%)
Calculation:
```
ParentProgress = ((30 * 60) + (70 * 100) + (90 * 100)) / (60 + 100 + 100)
ParentProgress = (1800 + 7000 + 9000) / 260
ParentProgress = 17800 / 260
ParentProgress = 68.46%
```
Note that Subtasks B and C have more influence than Subtask A because they have higher default weights.
6. **All Zero Weights Edge Case**:
If all subtasks have zero weight, the progress is calculated as 0%:
```
ParentProgress = SUM(progress_value * 0) / SUM(0) = 0 / 0 = undefined
```
The SQL implementation handles this with `NULLIF` and `COALESCE` to return 0% in this case.
4. **Actual SQL Implementation**:
The database function implements the weighted calculation as follows:
```sql
WITH subtask_progress AS (
SELECT
CASE
-- If subtask has manual progress, use that value
WHEN manual_progress IS TRUE AND progress_value IS NOT NULL THEN
progress_value
-- Otherwise use completion status (0 or 100)
ELSE
CASE
WHEN EXISTS(
SELECT 1
FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = t.id
AND is_done IS TRUE
) THEN 100
ELSE 0
END
END AS progress_value,
COALESCE(weight, 100) AS weight
FROM tasks t
WHERE t.parent_task_id = _task_id
AND t.archived IS FALSE
)
SELECT COALESCE(
SUM(progress_value * weight) / NULLIF(SUM(weight), 0),
0
)
FROM subtask_progress
INTO _ratio;
```
This SQL implementation:
- Gets all non-archived subtasks of the parent task
- For each subtask, determines its progress value:
- If manual progress is set, uses that value
- Otherwise, uses 100% if the task is done or 0% if not done
- Uses COALESCE to default weight to 100 if not specified
- Calculates the weighted average, handling the case where sum of weights might be zero
- Returns 0 if there are no subtasks with weights
### Detailed Calculation for Time-based Progress Method
In Time-based Progress mode, the task's estimated time serves as its weight in progress calculations:
1. **Manual Progress Input**:
- As with weighted progress, leaf tasks require manual progress input
- Progress is entered as a percentage (0-100%)
- Completed tasks are automatically set to 100% progress
2. **Time Estimation**:
- Each task has an estimated time in hours and minutes
- These values are stored in `total_hours` and `total_minutes` fields
- Time estimates effectively function as weights in progress calculations
- Tasks with longer estimated durations have more influence on parent task progress
- Tasks with zero or no time estimate don't contribute to the parent's progress calculation
3. **Parent Task Calculation**:
The time-based progress formula is:
```
ParentProgress = ∑(SubtaskProgress * SubtaskEstimatedMinutes) / ∑(SubtaskEstimatedMinutes)
```
where `SubtaskEstimatedMinutes = (SubtaskHours * 60) + SubtaskMinutes`
**Example Calculation**:
Consider a parent task with three subtasks:
- Subtask A: Progress 40%, Estimated Time 2h 30m (150 minutes)
- Subtask B: Progress 80%, Estimated Time 1h (60 minutes)
- Subtask C: Progress 10%, Estimated Time 4h (240 minutes)
Calculation:
```
ParentProgress = ((40 * 150) + (80 * 60) + (10 * 240)) / (150 + 60 + 240)
ParentProgress = (6000 + 4800 + 2400) / 450
ParentProgress = 13200 / 450
ParentProgress = 29.33%
```
Note how Subtask C, with its large time estimate, significantly pulls down the overall progress despite Subtask B being mostly complete.
4. **Zero Time Estimate Handling**:
Tasks with zero time estimate are excluded from the calculation:
- Subtask A: Progress 40%, Estimated Time 3h (180 minutes)
- Subtask B: Progress 80%, Estimated Time 0h (0 minutes)
Calculation:
```
ParentProgress = ((40 * 180) + (80 * 0)) / (180 + 0)
ParentProgress = 7200 / 180
ParentProgress = 40%
```
In this case, only Subtask A influences the parent task progress because Subtask B has no time estimate.
5. **All Zero Time Estimates Edge Case**:
If all subtasks have zero time estimates, the progress is calculated as 0%:
```
ParentProgress = SUM(progress_value * 0) / SUM(0) = 0 / 0 = undefined
```
The SQL implementation handles this with `NULLIF` and `COALESCE` to return 0% in this case.
6. **Actual SQL Implementation**:
The SQL function for this calculation first converts hours to minutes for consistent measurement:
```sql
WITH subtask_progress AS (
SELECT
CASE
-- If subtask has manual progress, use that value
WHEN manual_progress IS TRUE AND progress_value IS NOT NULL THEN
progress_value
-- Otherwise use completion status (0 or 100)
ELSE
CASE
WHEN EXISTS(
SELECT 1
FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = t.id
AND is_done IS TRUE
) THEN 100
ELSE 0
END
END AS progress_value,
COALESCE(total_hours * 60 + total_minutes, 0) AS estimated_minutes
FROM tasks t
WHERE t.parent_task_id = _task_id
AND t.archived IS FALSE
)
SELECT COALESCE(
SUM(progress_value * estimated_minutes) / NULLIF(SUM(estimated_minutes), 0),
0
)
FROM subtask_progress
INTO _ratio;
```
This implementation:
- Gets all non-archived subtasks of the parent task
- Determines each subtask's progress value (manual or completion-based)
- Calculates total minutes by converting hours to minutes and adding them together
- Uses COALESCE to treat NULL time estimates as 0 minutes
- Uses NULLIF to handle cases where all time estimates are zero
- Returns 0% progress if there are no subtasks with time estimates
### Common Implementation Considerations
For both weighted and time-based progress calculation:
1. **Null Handling**:
- Tasks with NULL progress values are treated as 0% progress (unless completed)
- Tasks with NULL weights default to 100 in weighted mode
- Tasks with NULL time estimates are treated as 0 minutes in time-based mode
2. **Progress Propagation**:
- When a leaf task's progress changes, all ancestor tasks are recalculated
- Progress updates are propagated through socket events to all connected clients
- The recalculation happens server-side to ensure consistency
3. **Edge Cases**:
- If all subtasks have zero weight/time, the system falls back to a simple average
- If a parent task has no subtasks, its own manual progress value is used
- If a task is archived, it's excluded from parent task calculations
### Database Implementation
The manual progress value is stored in the `tasks` table with these relevant fields:
```sql
tasks (
-- other fields
progress_value FLOAT, -- The manually entered progress value (0-100)
manual_progress BOOLEAN, -- Flag indicating if progress was manually set
weight INTEGER DEFAULT 100, -- For weighted progress calculation
total_hours INTEGER, -- For time-based progress calculation
total_minutes INTEGER -- For time-based progress calculation
)
```
### Integration with Parent Task Calculation
When a subtask's progress is updated manually, the parent task's progress is automatically recalculated based on the active progress method:
```typescript
// Pseudocode for parent task recalculation
function recalculateParentTaskProgress(taskId, parentTaskId) {
if (!parentTaskId) return;
// Get project settings to determine active progress method
const project = getProjectByTaskId(taskId);
if (project.use_manual_progress) {
// Calculate average of all subtask progress values
updateParentProgress(parentTaskId, calculateAverageProgress(parentTaskId));
}
else if (project.use_weighted_progress) {
// Calculate weighted average using subtask weights
updateParentProgress(parentTaskId, calculateWeightedProgress(parentTaskId));
}
else if (project.use_time_progress) {
// Calculate weighted average using time estimates
updateParentProgress(parentTaskId, calculateTimeBasedProgress(parentTaskId));
}
else {
// Default: Calculate based on task completion
updateParentProgress(parentTaskId, calculateCompletionBasedProgress(parentTaskId));
}
// If this parent has a parent, continue recalculation up the tree
const grandparentId = getParentTaskId(parentTaskId);
if (grandparentId) {
recalculateParentTaskProgress(parentTaskId, grandparentId);
}
}
```
This recursive approach ensures that changes to any task's progress are properly propagated up the task hierarchy.
## Associated Files and Components
### Backend Files
1. **Socket Event Handlers**:
- `worklenz-backend/src/socket.io/commands/on-update-task-progress.ts` - Handles manual progress updates
- `worklenz-backend/src/socket.io/commands/on-update-task-weight.ts` - Handles task weight updates
2. **Database Functions**:
- `worklenz-backend/database/migrations/20250423000000-subtask-manual-progress.sql` - Contains the `get_task_complete_ratio` function that calculates progress based on the selected method
- Functions that support project creation/updates with progress mode settings:
- `create_project`
- `update_project`
3. **Controllers**:
- `worklenz-backend/src/controllers/project-workload/workload-gannt-base.ts` - Contains the `calculateTaskCompleteRatio` method
- `worklenz-backend/src/controllers/projects-controller.ts` - Handles project-level progress calculations
### Frontend Files
1. **Project Configuration**:
- `worklenz-frontend/src/components/projects/project-drawer/project-drawer.tsx` - Contains UI for selecting progress method when creating/editing projects
2. **Progress Visualization Components**:
- `worklenz-frontend/src/components/project-list/project-list-table/project-list-progress/progress-list-progress.tsx` - Displays project progress
- `worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListTable/taskListTableCells/TaskProgress.tsx` - Displays task progress
- `worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-progress-cell/task-list-progress-cell.tsx` - Alternative task progress cell
3. **Progress Input Components**:
- `worklenz-frontend/src/components/task-drawer/shared/info-tab/details/task-drawer-progress/task-drawer-progress.tsx` - Component for inputting task progress/weight
## Choosing the Right Progress Method
Each progress method is suitable for different types of projects:
- **Manual Progress**: Best for creative work where progress is subjective
- **Weighted Progress**: Ideal for projects where some tasks are more significant than others
- **Time-based Progress**: Perfect for projects where time estimates are reliable and important
Project managers can choose the appropriate method when creating or editing a project in the project drawer, based on their team's workflow and project requirements.

6
package-lock.json generated Normal file
View File

@@ -0,0 +1,6 @@
{
"name": "worklenz",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

244
task-progress-methods.md Normal file
View File

@@ -0,0 +1,244 @@
# Task Progress Tracking Methods in WorkLenz
## Overview
WorkLenz supports three different methods for tracking task progress, each suitable for different project management approaches:
1. **Manual Progress** - Direct input of progress percentages
2. **Weighted Progress** - Tasks have weights that affect overall progress calculation
3. **Time-based Progress** - Progress calculated based on estimated time vs. time spent
These modes can be selected when creating or editing a project in the project drawer.
## 1. Manual Progress Mode
This mode allows direct input of progress percentages for individual tasks without subtasks.
**Implementation:**
- Enabled by setting `use_manual_progress` to true in the project settings
- Progress is updated through the `on-update-task-progress.ts` socket event handler
- The UI shows a manual progress input slider in the task drawer for tasks without subtasks
- Updates the database with `progress_value` and sets `manual_progress` flag to true
**Calculation Logic:**
- For tasks without subtasks: Uses the manually set progress value
- For parent tasks: Calculates the average of all subtask progress values
- Subtask progress comes from either manual values or completion status (0% or 100%)
**Code Example:**
```typescript
// Manual progress update via socket.io
socket?.emit(SocketEvents.UPDATE_TASK_PROGRESS.toString(), JSON.stringify({
task_id: task.id,
progress_value: value,
parent_task_id: task.parent_task_id
}));
```
### Showing Progress in Subtask Rows
When manual progress is enabled in a project, progress is shown in the following ways:
1. **In Task List Views**:
- Subtasks display their individual progress values in the progress column
- Parent tasks display the calculated average progress of all subtasks
2. **Implementation Details**:
- The progress values are stored in the `progress_value` column in the database
- For subtasks with manual progress set, the value is shown directly
- For subtasks without manual progress, the completion status determines the value (0% or 100%)
- The task view model includes both `progress` and `complete_ratio` properties
**Relevant Components:**
```typescript
// From task-list-progress-cell.tsx
const TaskListProgressCell = ({ task }: TaskListProgressCellProps) => {
return task.is_sub_task ? null : (
<Tooltip title={`${task.completed_count || 0} / ${task.total_tasks_count || 0}`}>
<Progress
percent={task.complete_ratio || 0}
type="circle"
size={24}
style={{ cursor: 'default' }}
/>
</Tooltip>
);
};
```
**Task Progress Calculation in Backend:**
```typescript
// From tasks-controller-base.ts
// For tasks without subtasks, respect manual progress if set
if (task.manual_progress === true && task.progress_value !== null) {
// For manually set progress, use that value directly
task.progress = parseInt(task.progress_value);
task.complete_ratio = parseInt(task.progress_value);
}
```
## 2. Weighted Progress Mode
This mode allows assigning different weights to subtasks to reflect their relative importance in the overall task or project progress.
**Implementation:**
- Enabled by setting `use_weighted_progress` to true in the project settings
- Weights are updated through the `on-update-task-weight.ts` socket event handler
- The UI shows a weight input for subtasks in the task drawer
- Default weight is 100 if not specified
**Calculation Logic:**
- Progress is calculated using a weighted average: `SUM(progress_value * weight) / SUM(weight)`
- This gives more influence to tasks with higher weights
- A parent task's progress is the weighted average of its subtasks' progress
**Code Example:**
```typescript
// Weight update via socket.io
socket?.emit(SocketEvents.UPDATE_TASK_WEIGHT.toString(), JSON.stringify({
task_id: task.id,
weight: value,
parent_task_id: task.parent_task_id
}));
```
## 3. Time-based Progress Mode
This mode calculates progress based on estimated time vs. actual time spent.
**Implementation:**
- Enabled by setting `use_time_progress` to true in the project settings
- Uses task time estimates (hours and minutes) for calculation
- No separate socket handler needed as it's calculated automatically
**Calculation Logic:**
- Progress is calculated using time as the weight: `SUM(progress_value * estimated_minutes) / SUM(estimated_minutes)`
- For tasks with time tracking, estimated vs. actual time can be factored in
- Parent task progress is weighted by the estimated time of each subtask
**SQL Example:**
```sql
WITH subtask_progress AS (
SELECT
CASE
WHEN manual_progress IS TRUE AND progress_value IS NOT NULL THEN
progress_value
ELSE
CASE
WHEN EXISTS(
SELECT 1
FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = t.id
AND is_done IS TRUE
) THEN 100
ELSE 0
END
END AS progress_value,
COALESCE(total_hours * 60 + total_minutes, 0) AS estimated_minutes
FROM tasks t
WHERE t.parent_task_id = _task_id
AND t.archived IS FALSE
)
SELECT COALESCE(
SUM(progress_value * estimated_minutes) / NULLIF(SUM(estimated_minutes), 0),
0
)
FROM subtask_progress
INTO _ratio;
```
## Default Progress Tracking (when no special mode is selected)
If no specific progress mode is enabled, the system falls back to a traditional completion-based calculation:
**Implementation:**
- Default mode when all three special modes are disabled
- Based on task completion status only
**Calculation Logic:**
- For tasks without subtasks: 0% if not done, 100% if done
- For parent tasks: `(completed_tasks / total_tasks) * 100`
- Counts both the parent and all subtasks in the calculation
**SQL Example:**
```sql
-- Traditional calculation based on completion status
SELECT (CASE
WHEN EXISTS(SELECT 1
FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = _task_id
AND is_done IS TRUE) THEN 1
ELSE 0 END)
INTO _parent_task_done;
SELECT COUNT(*)
FROM tasks_with_status_view
WHERE parent_task_id = _task_id
AND is_done IS TRUE
INTO _sub_tasks_done;
_total_completed = _parent_task_done + _sub_tasks_done;
_total_tasks = _sub_tasks_count + 1; -- +1 for the parent task
IF _total_tasks = 0 THEN
_ratio = 0;
ELSE
_ratio = (_total_completed / _total_tasks) * 100;
END IF;
```
## Technical Implementation Details
The progress calculation logic is implemented in PostgreSQL functions, primarily in the `get_task_complete_ratio` function. Progress updates flow through the system as follows:
1. **User Action**: User updates task progress or weight in the UI
2. **Socket Event**: Client emits socket event (UPDATE_TASK_PROGRESS or UPDATE_TASK_WEIGHT)
3. **Server Handler**: Server processes the event in the respective handler function
4. **Database Update**: Progress/weight value is updated in the database
5. **Recalculation**: If needed, parent task progress is recalculated
6. **Broadcast**: Changes are broadcast to all clients in the project room
7. **UI Update**: Client UI updates to reflect the new progress values
This architecture allows for real-time updates and consistent progress calculation across all clients.
## Associated Files and Components
### Backend Files
1. **Socket Event Handlers**:
- `worklenz-backend/src/socket.io/commands/on-update-task-progress.ts` - Handles manual progress updates
- `worklenz-backend/src/socket.io/commands/on-update-task-weight.ts` - Handles task weight updates
2. **Database Functions**:
- `worklenz-backend/database/migrations/20250423000000-subtask-manual-progress.sql` - Contains the `get_task_complete_ratio` function that calculates progress based on the selected method
- Functions that support project creation/updates with progress mode settings:
- `create_project`
- `update_project`
3. **Controllers**:
- `worklenz-backend/src/controllers/project-workload/workload-gannt-base.ts` - Contains the `calculateTaskCompleteRatio` method
- `worklenz-backend/src/controllers/projects-controller.ts` - Handles project-level progress calculations
- `worklenz-backend/src/controllers/tasks-controller-base.ts` - Handles task progress calculation and updates task view models
### Frontend Files
1. **Project Configuration**:
- `worklenz-frontend/src/components/projects/project-drawer/project-drawer.tsx` - Contains UI for selecting progress method when creating/editing projects
2. **Progress Visualization Components**:
- `worklenz-frontend/src/components/project-list/project-list-table/project-list-progress/progress-list-progress.tsx` - Displays project progress
- `worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListTable/taskListTableCells/TaskProgress.tsx` - Displays task progress
- `worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-progress-cell/task-list-progress-cell.tsx` - Alternative task progress cell
- `worklenz-frontend/src/components/task-list-common/task-row/task-row-progress/task-row-progress.tsx` - Displays progress in task rows
3. **Progress Input Components**:
- `worklenz-frontend/src/components/task-drawer/shared/info-tab/details/task-drawer-progress/task-drawer-progress.tsx` - Component for inputting task progress/weight
## Choosing the Right Progress Method
Each progress method is suitable for different types of projects:
- **Manual Progress**: Best for creative work where progress is subjective
- **Weighted Progress**: Ideal for projects where some tasks are more significant than others
- **Time-based Progress**: Perfect for projects where time estimates are reliable and important
Project managers can choose the appropriate method when creating or editing a project in the project drawer, based on their team's workflow and project requirements.

41
test_sort_fix.sql Normal file
View File

@@ -0,0 +1,41 @@
-- Test script to verify the sort order constraint fix
-- Test the helper function
SELECT get_sort_column_name('status'); -- Should return 'status_sort_order'
SELECT get_sort_column_name('priority'); -- Should return 'priority_sort_order'
SELECT get_sort_column_name('phase'); -- Should return 'phase_sort_order'
SELECT get_sort_column_name('members'); -- Should return 'member_sort_order'
SELECT get_sort_column_name('unknown'); -- Should return 'status_sort_order' (default)
-- Test bulk update function (example - would need real project_id and task_ids)
/*
SELECT update_task_sort_orders_bulk(
'[
{"task_id": "example-uuid", "sort_order": 1, "status_id": "status-uuid"},
{"task_id": "example-uuid-2", "sort_order": 2, "status_id": "status-uuid"}
]'::json,
'status'
);
*/
-- Verify that sort_order constraint still exists and works
SELECT
tc.constraint_name,
tc.table_name,
kcu.column_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
WHERE tc.constraint_name = 'tasks_sort_order_unique';
-- Check that new sort order columns don't have unique constraints (which is correct)
SELECT
tc.constraint_name,
tc.table_name,
kcu.column_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
WHERE kcu.table_name = 'tasks'
AND kcu.column_name IN ('status_sort_order', 'priority_sort_order', 'phase_sort_order', 'member_sort_order')
AND tc.constraint_type = 'UNIQUE';

30
test_sort_orders.sql Normal file
View File

@@ -0,0 +1,30 @@
-- Test script to validate the separate sort order implementation
-- Check if new columns exist
SELECT column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_name = 'tasks'
AND column_name IN ('status_sort_order', 'priority_sort_order', 'phase_sort_order', 'member_sort_order')
ORDER BY column_name;
-- Check if helper function exists
SELECT routine_name, routine_type
FROM information_schema.routines
WHERE routine_name IN ('get_sort_column_name', 'update_task_sort_orders_bulk', 'handle_task_list_sort_order_change');
-- Sample test data to verify different sort orders work
-- (This would be run after the migrations)
/*
-- Test: Tasks should have different orders for different groupings
SELECT
id,
name,
sort_order,
status_sort_order,
priority_sort_order,
phase_sort_order,
member_sort_order
FROM tasks
WHERE project_id = '<test-project-id>'
ORDER BY status_sort_order;
*/

View File

@@ -73,8 +73,8 @@ cat > worklenz-backend/.env << EOL
NODE_ENV=production
PORT=3000
SESSION_NAME=worklenz.sid
SESSION_SECRET=change_me_in_production
COOKIE_SECRET=change_me_in_production
SESSION_SECRET=$(openssl rand -base64 48)
COOKIE_SECRET=$(openssl rand -base64 48)
# CORS
SOCKET_IO_CORS=${FRONTEND_URL}
@@ -123,7 +123,7 @@ SLACK_WEBHOOK=
COMMIT_BUILD_IMMEDIATELY=true
# JWT Secret
JWT_SECRET=change_me_in_production
JWT_SECRET=$(openssl rand -base64 48)
EOL
echo "Environment configuration updated for ${HOSTNAME} with" $([ "$USE_SSL" = "true" ] && echo "HTTPS/WSS" || echo "HTTP/WS")
@@ -138,4 +138,4 @@ echo "Frontend URL: ${FRONTEND_URL}"
echo "API URL: ${HTTP_PREFIX}${HOSTNAME}:3000"
echo "Socket URL: ${WS_PREFIX}${HOSTNAME}:3000"
echo "MinIO Dashboard URL: ${MINIO_DASHBOARD_URL}"
echo "CORS is configured to allow requests from: ${FRONTEND_URL}"
echo "CORS is configured to allow requests from: ${FRONTEND_URL}"

View File

@@ -1,5 +1,9 @@
node_modules
npm-debug.log
build
.scannerwork
coverage
.dockerignore
.git
*.md
tests

View File

@@ -47,12 +47,17 @@ FRONTEND_URL=http://localhost:5000
# STORAGE
STORAGE_PROVIDER=s3 # values s3 or azure
# AWS
# AWS - SES
AWS_REGION="your_aws_region"
AWS_ACCESS_KEY_ID="your_aws_access_key_id"
AWS_SECRET_ACCESS_KEY="your_aws_secret_access_key"
AWS_BUCKET="your_s3_bucket"
# S3
S3_REGION="S3_REGION"
S3_BUCKET="your_s3_bucket"
S3_URL="your_s3_url"
S3_ACCESS_KEY_ID="S3_ACCESS_KEY_ID"
S3_SECRET_ACCESS_KEY="S3_SECRET_ACCESS_KEY"
# Azure Storage
AZURE_STORAGE_ACCOUNT_NAME="your_storage_account_name"
@@ -73,4 +78,8 @@ GOOGLE_CAPTCHA_SECRET_KEY=your_captcha_secret_key
GOOGLE_CAPTCHA_PASS_SCORE=0.8
# Email Cronjobs
ENABLE_EMAIL_CRONJOBS=true
ENABLE_EMAIL_CRONJOBS=true
# RECURRING_JOBS
ENABLE_RECURRING_JOBS=true
RECURRING_JOBS_INTERVAL="0 11 */1 * 1-5"

View File

@@ -20,9 +20,6 @@ coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components

View File

@@ -1,26 +1,39 @@
# Use the official Node.js 20 image as a base
FROM node:20
# --- Stage 1: Build ---
FROM node:20-slim AS builder
ARG RELEASE_VERSION
RUN apt-get update && apt-get install -y \
python3 \
make \
g++ \
curl \
postgresql-server-dev-all \
&& rm -rf /var/lib/apt/lists/*
# Create and set the working directory
WORKDIR /usr/src/app
# Install global dependencies
RUN npm install -g ts-node typescript grunt grunt-cli
# Copy package.json and package-lock.json (if available)
COPY package*.json ./
# Install app dependencies
RUN npm ci
# Copy the rest of the application code
COPY . .
# Run the build script to compile TypeScript to JavaScript
RUN npm run build
# Expose the port the app runs on
EXPOSE 3000
RUN echo "$RELEASE_VERSION" > release
# --- Stage 2: Production Image ---
FROM node:20-slim
RUN apt-get update && apt-get install -y libpq5 && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /usr/src/app/package*.json ./
COPY --from=builder /usr/src/app/build ./build
COPY --from=builder /usr/src/app/node_modules ./node_modules
COPY --from=builder /usr/src/app/release ./release
COPY --from=builder /usr/src/app/worklenz-email-templates ./worklenz-email-templates
EXPOSE 3000
CMD ["node", "build/bin/www"]
# Start the application
CMD ["npm", "start"]

View File

@@ -1,131 +0,0 @@
module.exports = function (grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON("package.json"),
clean: {
dist: "build"
},
compress: require("./grunt/grunt-compress"),
copy: {
main: {
files: [
{expand: true, cwd: "src", src: ["public/**"], dest: "build"},
{expand: true, cwd: "src", src: ["views/**"], dest: "build"},
{expand: true, cwd: "landing-page-assets", src: ["**"], dest: "build/public/assets"},
{expand: true, cwd: "src", src: ["shared/sample-data.json"], dest: "build", filter: "isFile"},
{expand: true, cwd: "src", src: ["shared/templates/**"], dest: "build", filter: "isFile"},
{expand: true, cwd: "src", src: ["shared/postgresql-error-codes.json"], dest: "build", filter: "isFile"},
]
},
packages: {
files: [
{expand: true, cwd: "", src: [".env"], dest: "build", filter: "isFile"},
{expand: true, cwd: "", src: [".gitignore"], dest: "build", filter: "isFile"},
{expand: true, cwd: "", src: ["release"], dest: "build", filter: "isFile"},
{expand: true, cwd: "", src: ["jest.config.js"], dest: "build", filter: "isFile"},
{expand: true, cwd: "", src: ["package.json"], dest: "build", filter: "isFile"},
{expand: true, cwd: "", src: ["package-lock.json"], dest: "build", filter: "isFile"},
{expand: true, cwd: "", src: ["common_modules/**"], dest: "build"}
]
}
},
sync: {
main: {
files: [
{cwd: "src", src: ["views/**", "public/**"], dest: "build/"}, // makes all src relative to cwd
],
verbose: true,
failOnError: true,
compareUsing: "md5"
}
},
uglify: {
all: {
files: [{
expand: true,
cwd: "build",
src: "**/*.js",
dest: "build"
}]
},
controllers: {
files: [{
expand: true,
cwd: "build",
src: "controllers/*.js",
dest: "build"
}]
},
routes: {
files: [{
expand: true,
cwd: "build",
src: "routes/**/*.js",
dest: "build"
}]
},
assets: {
files: [{
expand: true,
cwd: "build",
src: "public/assets/**/*.js",
dest: "build"
}]
}
},
shell: {
tsc: {
command: "tsc --build tsconfig.prod.json"
},
esbuild: {
// command: "esbuild `find src -type f -name '*.ts'` --platform=node --minify=false --target=esnext --format=cjs --tsconfig=tsconfig.prod.json --outdir=build"
command: "node esbuild && node cli/esbuild-patch"
},
tsc_dev: {
command: "tsc --build tsconfig.json"
},
swagger: {
command: "node ./cli/swagger"
},
inline_queries: {
command: "node ./cli/inline-queries"
}
},
watch: {
scripts: {
files: ["src/**/*.ts"],
tasks: ["shell:tsc_dev"],
options: {
debounceDelay: 250,
spawn: false,
}
},
other: {
files: ["src/**/*.pug", "landing-page-assets/**"],
tasks: ["sync"]
}
}
});
grunt.registerTask("clean", ["clean"]);
grunt.registerTask("copy", ["copy:main"]);
grunt.registerTask("swagger", ["shell:swagger"]);
grunt.registerTask("build:tsc", ["shell:tsc"]);
grunt.registerTask("build", ["clean", "shell:tsc", "copy:main", "compress"]);
grunt.registerTask("build:es", ["clean", "shell:esbuild", "copy:main", "uglify:assets", "compress"]);
grunt.registerTask("build:strict", ["clean", "shell:tsc", "copy:packages", "uglify:all", "copy:main", "compress"]);
grunt.registerTask("dev", ["clean", "copy:main", "shell:tsc_dev", "shell:inline_queries", "watch"]);
// Load the plugin that provides the "uglify" task.
grunt.loadNpmTasks("grunt-contrib-watch");
grunt.loadNpmTasks("grunt-contrib-clean");
grunt.loadNpmTasks("grunt-contrib-copy");
grunt.loadNpmTasks("grunt-contrib-uglify");
grunt.loadNpmTasks("grunt-contrib-compress");
grunt.loadNpmTasks("grunt-shell");
grunt.loadNpmTasks("grunt-sync");
// Default task(s).
grunt.registerTask("default", []);
};

View File

@@ -1,55 +0,0 @@
#!/bin/bash
set -e
# This script controls the order of SQL file execution during database initialization
echo "Starting database initialization..."
# Check if we have SQL files in expected locations
if [ -f "/docker-entrypoint-initdb.d/sql/0_extensions.sql" ]; then
SQL_DIR="/docker-entrypoint-initdb.d/sql"
echo "Using SQL files from sql/ subdirectory"
elif [ -f "/docker-entrypoint-initdb.d/0_extensions.sql" ]; then
# First time setup - move files to subdirectory
echo "Moving SQL files to sql/ subdirectory..."
mkdir -p /docker-entrypoint-initdb.d/sql
# Move all SQL files (except this script) to the subdirectory
for f in /docker-entrypoint-initdb.d/*.sql; do
if [ -f "$f" ]; then
cp "$f" /docker-entrypoint-initdb.d/sql/
echo "Copied $f to sql/ subdirectory"
fi
done
SQL_DIR="/docker-entrypoint-initdb.d/sql"
else
echo "SQL files not found in expected locations!"
exit 1
fi
# Execute SQL files in the correct order
echo "Executing 0_extensions.sql..."
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -f "$SQL_DIR/0_extensions.sql"
echo "Executing 1_tables.sql..."
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -f "$SQL_DIR/1_tables.sql"
echo "Executing indexes.sql..."
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -f "$SQL_DIR/indexes.sql"
echo "Executing 4_functions.sql..."
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -f "$SQL_DIR/4_functions.sql"
echo "Executing triggers.sql..."
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -f "$SQL_DIR/triggers.sql"
echo "Executing 3_views.sql..."
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -f "$SQL_DIR/3_views.sql"
echo "Executing 2_dml.sql..."
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -f "$SQL_DIR/2_dml.sql"
echo "Executing 5_database_user.sql..."
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -f "$SQL_DIR/5_database_user.sql"
echo "Database initialization completed successfully"

View File

@@ -0,0 +1,88 @@
#!/bin/bash
set -e
echo "Starting database initialization..."
SQL_DIR="/docker-entrypoint-initdb.d/sql"
MIGRATIONS_DIR="/docker-entrypoint-initdb.d/migrations"
BACKUP_DIR="/docker-entrypoint-initdb.d/pg_backups"
# --------------------------------------------
# 🗄️ STEP 1: Attempt to restore latest backup
# --------------------------------------------
if [ -d "$BACKUP_DIR" ]; then
LATEST_BACKUP=$(ls -t "$BACKUP_DIR"/*.sql 2>/dev/null | head -n 1)
else
LATEST_BACKUP=""
fi
if [ -f "$LATEST_BACKUP" ]; then
echo "🗄️ Found latest backup: $LATEST_BACKUP"
echo "⏳ Restoring from backup..."
psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" < "$LATEST_BACKUP"
echo "✅ Backup restoration complete. Skipping schema and migrations."
exit 0
else
echo " No valid backup found. Proceeding with base schema and migrations."
fi
# --------------------------------------------
# 🏗️ STEP 2: Continue with base schema setup
# --------------------------------------------
# Create migrations table if it doesn't exist
psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "
CREATE TABLE IF NOT EXISTS schema_migrations (
version TEXT PRIMARY KEY,
applied_at TIMESTAMP DEFAULT now()
);
"
# List of base schema files to execute in order
BASE_SQL_FILES=(
"0_extensions.sql"
"1_tables.sql"
"indexes.sql"
"4_functions.sql"
"triggers.sql"
"3_views.sql"
"2_dml.sql"
"5_database_user.sql"
)
echo "Running base schema SQL files in order..."
for file in "${BASE_SQL_FILES[@]}"; do
full_path="$SQL_DIR/$file"
if [ -f "$full_path" ]; then
echo "Executing $file..."
psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB" -f "$full_path"
else
echo "WARNING: $file not found, skipping."
fi
done
echo "✅ Base schema SQL execution complete."
# --------------------------------------------
# 🚀 STEP 3: Apply SQL migrations
# --------------------------------------------
if [ -d "$MIGRATIONS_DIR" ] && compgen -G "$MIGRATIONS_DIR/*.sql" > /dev/null; then
echo "Applying migrations..."
for f in "$MIGRATIONS_DIR"/*.sql; do
version=$(basename "$f")
if ! psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -tAc "SELECT 1 FROM schema_migrations WHERE version = '$version'" | grep -q 1; then
echo "Applying migration: $version"
psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -f "$f"
psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "INSERT INTO schema_migrations (version) VALUES ('$version');"
else
echo "Skipping already applied migration: $version"
fi
done
else
echo "No migration files found or directory is empty, skipping migrations."
fi
echo "🎉 Database initialization completed successfully."

View File

@@ -0,0 +1,135 @@
-- Performance indexes for optimized tasks queries
-- Migration: 20250115000000-performance-indexes.sql
-- Composite index for main task filtering
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_project_archived_parent
ON tasks(project_id, archived, parent_task_id)
WHERE archived = FALSE;
-- Index for status joins
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_status_project
ON tasks(status_id, project_id)
WHERE archived = FALSE;
-- Index for assignees lookup
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_assignees_task_member
ON tasks_assignees(task_id, team_member_id);
-- Index for phase lookup
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_phase_task_phase
ON task_phase(task_id, phase_id);
-- Index for subtask counting
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_parent_archived
ON tasks(parent_task_id, archived)
WHERE parent_task_id IS NOT NULL AND archived = FALSE;
-- Index for labels
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_labels_task_label
ON task_labels(task_id, label_id);
-- Index for comments count
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_comments_task
ON task_comments(task_id);
-- Index for attachments count
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_attachments_task
ON task_attachments(task_id);
-- Index for work log aggregation
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_work_log_task
ON task_work_log(task_id);
-- Index for subscribers check
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_subscribers_task
ON task_subscribers(task_id);
-- Index for dependencies check
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_dependencies_task
ON task_dependencies(task_id);
-- Index for timers lookup
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_timers_task_user
ON task_timers(task_id, user_id);
-- Index for custom columns
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_cc_column_values_task
ON cc_column_values(task_id);
-- Index for team member info view optimization
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_team_members_team_user
ON team_members(team_id, user_id)
WHERE active = TRUE;
-- Index for notification settings
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_notification_settings_user_team
ON notification_settings(user_id, team_id);
-- Index for task status categories
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_statuses_category
ON task_statuses(category_id, project_id);
-- Index for project phases
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_project_phases_project_sort
ON project_phases(project_id, sort_index);
-- Index for task priorities
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_priorities_value
ON task_priorities(value);
-- Index for team labels
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_team_labels_team
ON team_labels(team_id);
-- NEW INDEXES FOR PERFORMANCE OPTIMIZATION --
-- Composite index for task main query optimization (covers most WHERE conditions)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_performance_main
ON tasks(project_id, archived, parent_task_id, status_id, priority_id)
WHERE archived = FALSE;
-- Index for sorting by sort_order with project filter
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_project_sort_order
ON tasks(project_id, sort_order)
WHERE archived = FALSE;
-- Index for email_invitations to optimize team_member_info_view
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_email_invitations_team_member
ON email_invitations(team_member_id);
-- Covering index for task status with category information
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_statuses_covering
ON task_statuses(id, category_id, project_id);
-- Index for task aggregation queries (parent task progress calculation)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_parent_status_archived
ON tasks(parent_task_id, status_id, archived)
WHERE archived = FALSE;
-- Index for project team member filtering
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_team_members_project_lookup
ON team_members(team_id, active, user_id)
WHERE active = TRUE;
-- Covering index for tasks with frequently accessed columns
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_covering_main
ON tasks(id, project_id, archived, parent_task_id, status_id, priority_id, sort_order, name)
WHERE archived = FALSE;
-- Index for task search functionality
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_name_search
ON tasks USING gin(to_tsvector('english', name))
WHERE archived = FALSE;
-- Index for date-based filtering (if used)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_dates
ON tasks(project_id, start_date, end_date)
WHERE archived = FALSE;
-- Index for task timers with user filtering
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_timers_user_task
ON task_timers(user_id, task_id);
-- Index for sys_task_status_categories lookups
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_sys_task_status_categories_covering
ON sys_task_status_categories(id, color_code, color_code_dark, is_done, is_doing, is_todo);

View File

@@ -0,0 +1,143 @@
-- Fix window function error in task sort optimized functions
-- Error: window functions are not allowed in UPDATE
-- Replace the optimized sort functions to avoid CTE usage in UPDATE statements
CREATE OR REPLACE FUNCTION handle_task_list_sort_between_groups_optimized(_from_index integer, _to_index integer, _task_id uuid, _project_id uuid, _batch_size integer DEFAULT 100) RETURNS void
LANGUAGE plpgsql
AS
$$
DECLARE
_offset INT := 0;
_affected_rows INT;
BEGIN
-- PERFORMANCE OPTIMIZATION: Use direct updates without CTE in UPDATE
IF (_to_index = -1)
THEN
_to_index = COALESCE((SELECT MAX(sort_order) + 1 FROM tasks WHERE project_id = _project_id), 0);
END IF;
-- PERFORMANCE OPTIMIZATION: Batch updates for large datasets
IF _to_index > _from_index
THEN
LOOP
UPDATE tasks
SET sort_order = sort_order - 1
WHERE project_id = _project_id
AND sort_order > _from_index
AND sort_order < _to_index
AND sort_order > _offset
AND sort_order <= _offset + _batch_size;
GET DIAGNOSTICS _affected_rows = ROW_COUNT;
EXIT WHEN _affected_rows = 0;
_offset := _offset + _batch_size;
END LOOP;
UPDATE tasks SET sort_order = _to_index - 1 WHERE id = _task_id AND project_id = _project_id;
END IF;
IF _to_index < _from_index
THEN
_offset := 0;
LOOP
UPDATE tasks
SET sort_order = sort_order + 1
WHERE project_id = _project_id
AND sort_order > _to_index
AND sort_order < _from_index
AND sort_order > _offset
AND sort_order <= _offset + _batch_size;
GET DIAGNOSTICS _affected_rows = ROW_COUNT;
EXIT WHEN _affected_rows = 0;
_offset := _offset + _batch_size;
END LOOP;
UPDATE tasks SET sort_order = _to_index + 1 WHERE id = _task_id AND project_id = _project_id;
END IF;
END
$$;
-- Replace the second optimized sort function
CREATE OR REPLACE FUNCTION handle_task_list_sort_inside_group_optimized(_from_index integer, _to_index integer, _task_id uuid, _project_id uuid, _batch_size integer DEFAULT 100) RETURNS void
LANGUAGE plpgsql
AS
$$
DECLARE
_offset INT := 0;
_affected_rows INT;
BEGIN
-- PERFORMANCE OPTIMIZATION: Batch updates for large datasets without CTE in UPDATE
IF _to_index > _from_index
THEN
LOOP
UPDATE tasks
SET sort_order = sort_order - 1
WHERE project_id = _project_id
AND sort_order > _from_index
AND sort_order <= _to_index
AND sort_order > _offset
AND sort_order <= _offset + _batch_size;
GET DIAGNOSTICS _affected_rows = ROW_COUNT;
EXIT WHEN _affected_rows = 0;
_offset := _offset + _batch_size;
END LOOP;
END IF;
IF _to_index < _from_index
THEN
_offset := 0;
LOOP
UPDATE tasks
SET sort_order = sort_order + 1
WHERE project_id = _project_id
AND sort_order >= _to_index
AND sort_order < _from_index
AND sort_order > _offset
AND sort_order <= _offset + _batch_size;
GET DIAGNOSTICS _affected_rows = ROW_COUNT;
EXIT WHEN _affected_rows = 0;
_offset := _offset + _batch_size;
END LOOP;
END IF;
UPDATE tasks SET sort_order = _to_index WHERE id = _task_id AND project_id = _project_id;
END
$$;
-- Add simple bulk update function as alternative
CREATE OR REPLACE FUNCTION update_task_sort_orders_bulk(_updates json) RETURNS void
LANGUAGE plpgsql
AS
$$
DECLARE
_update_record RECORD;
BEGIN
-- Simple approach: update each task's sort_order from the provided array
FOR _update_record IN
SELECT
(item->>'task_id')::uuid as task_id,
(item->>'sort_order')::int as sort_order,
(item->>'status_id')::uuid as status_id,
(item->>'priority_id')::uuid as priority_id,
(item->>'phase_id')::uuid as phase_id
FROM json_array_elements(_updates) as item
LOOP
UPDATE tasks
SET
sort_order = _update_record.sort_order,
status_id = COALESCE(_update_record.status_id, status_id),
priority_id = COALESCE(_update_record.priority_id, priority_id)
WHERE id = _update_record.task_id;
-- Handle phase updates separately since it's in a different table
IF _update_record.phase_id IS NOT NULL THEN
INSERT INTO task_phase (task_id, phase_id)
VALUES (_update_record.task_id, _update_record.phase_id)
ON CONFLICT (task_id) DO UPDATE SET phase_id = _update_record.phase_id;
END IF;
END LOOP;
END
$$;

View File

@@ -0,0 +1,78 @@
-- Migration: Add manual task progress
-- Date: 2025-04-22
-- Version: 1.0.0
BEGIN;
-- Add manual progress fields to tasks table
ALTER TABLE tasks
ADD COLUMN IF NOT EXISTS manual_progress BOOLEAN DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS progress_value INTEGER DEFAULT NULL,
ADD COLUMN IF NOT EXISTS weight INTEGER DEFAULT NULL;
-- Update function to consider manual progress
CREATE OR REPLACE FUNCTION get_task_complete_ratio(_task_id uuid) RETURNS json
LANGUAGE plpgsql
AS
$$
DECLARE
_parent_task_done FLOAT = 0;
_sub_tasks_done FLOAT = 0;
_sub_tasks_count FLOAT = 0;
_total_completed FLOAT = 0;
_total_tasks FLOAT = 0;
_ratio FLOAT = 0;
_is_manual BOOLEAN = FALSE;
_manual_value INTEGER = NULL;
BEGIN
-- Check if manual progress is set
SELECT manual_progress, progress_value
FROM tasks
WHERE id = _task_id
INTO _is_manual, _manual_value;
-- If manual progress is enabled and has a value, use it directly
IF _is_manual IS TRUE AND _manual_value IS NOT NULL THEN
RETURN JSON_BUILD_OBJECT(
'ratio', _manual_value,
'total_completed', 0,
'total_tasks', 0,
'is_manual', TRUE
);
END IF;
-- Otherwise calculate automatically as before
SELECT (CASE
WHEN EXISTS(SELECT 1
FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = _task_id
AND is_done IS TRUE) THEN 1
ELSE 0 END)
INTO _parent_task_done;
SELECT COUNT(*) FROM tasks WHERE parent_task_id = _task_id AND archived IS FALSE INTO _sub_tasks_count;
SELECT COUNT(*)
FROM tasks_with_status_view
WHERE parent_task_id = _task_id
AND is_done IS TRUE
INTO _sub_tasks_done;
_total_completed = _parent_task_done + _sub_tasks_done;
_total_tasks = _sub_tasks_count; -- +1 for the parent task
IF _total_tasks > 0 THEN
_ratio = (_total_completed / _total_tasks) * 100;
ELSE
_ratio = _parent_task_done * 100;
END IF;
RETURN JSON_BUILD_OBJECT(
'ratio', _ratio,
'total_completed', _total_completed,
'total_tasks', _total_tasks,
'is_manual', FALSE
);
END
$$;
COMMIT;

View File

@@ -0,0 +1,687 @@
-- Migration: Enhance manual task progress with subtask support
-- Date: 2025-04-23
-- Version: 1.0.0
BEGIN;
-- Update function to consider subtask manual progress when calculating parent task progress
CREATE OR REPLACE FUNCTION get_task_complete_ratio(_task_id uuid) RETURNS json
LANGUAGE plpgsql
AS
$$
DECLARE
_parent_task_done FLOAT = 0;
_sub_tasks_done FLOAT = 0;
_sub_tasks_count FLOAT = 0;
_total_completed FLOAT = 0;
_total_tasks FLOAT = 0;
_ratio FLOAT = 0;
_is_manual BOOLEAN = FALSE;
_manual_value INTEGER = NULL;
_project_id UUID;
_use_manual_progress BOOLEAN = FALSE;
_use_weighted_progress BOOLEAN = FALSE;
_use_time_progress BOOLEAN = FALSE;
BEGIN
-- Check if manual progress is set for this task
SELECT manual_progress, progress_value, project_id
FROM tasks
WHERE id = _task_id
INTO _is_manual, _manual_value, _project_id;
-- Check if the project uses manual progress
IF _project_id IS NOT NULL THEN
SELECT COALESCE(use_manual_progress, FALSE),
COALESCE(use_weighted_progress, FALSE),
COALESCE(use_time_progress, FALSE)
FROM projects
WHERE id = _project_id
INTO _use_manual_progress, _use_weighted_progress, _use_time_progress;
END IF;
-- Get all subtasks
SELECT COUNT(*)
FROM tasks
WHERE parent_task_id = _task_id AND archived IS FALSE
INTO _sub_tasks_count;
-- If manual progress is enabled and has a value AND there are no subtasks, use it directly
IF _is_manual IS TRUE AND _manual_value IS NOT NULL AND _sub_tasks_count = 0 THEN
RETURN JSON_BUILD_OBJECT(
'ratio', _manual_value,
'total_completed', 0,
'total_tasks', 0,
'is_manual', TRUE
);
END IF;
-- If there are no subtasks, just use the parent task's status
IF _sub_tasks_count = 0 THEN
SELECT (CASE
WHEN EXISTS(SELECT 1
FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = _task_id
AND is_done IS TRUE) THEN 1
ELSE 0 END)
INTO _parent_task_done;
_ratio = _parent_task_done * 100;
ELSE
-- If project uses manual progress, calculate based on subtask manual progress values
IF _use_manual_progress IS TRUE THEN
WITH subtask_progress AS (
SELECT
CASE
-- If subtask has manual progress, use that value
WHEN manual_progress IS TRUE AND progress_value IS NOT NULL THEN
progress_value
-- Otherwise use completion status (0 or 100)
ELSE
CASE
WHEN EXISTS(
SELECT 1
FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = t.id
AND is_done IS TRUE
) THEN 100
ELSE 0
END
END AS progress_value
FROM tasks t
WHERE t.parent_task_id = _task_id
AND t.archived IS FALSE
)
SELECT COALESCE(AVG(progress_value), 0)
FROM subtask_progress
INTO _ratio;
-- If project uses weighted progress, calculate based on subtask weights
ELSIF _use_weighted_progress IS TRUE THEN
WITH subtask_progress AS (
SELECT
CASE
-- If subtask has manual progress, use that value
WHEN manual_progress IS TRUE AND progress_value IS NOT NULL THEN
progress_value
-- Otherwise use completion status (0 or 100)
ELSE
CASE
WHEN EXISTS(
SELECT 1
FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = t.id
AND is_done IS TRUE
) THEN 100
ELSE 0
END
END AS progress_value,
COALESCE(weight, 100) AS weight
FROM tasks t
WHERE t.parent_task_id = _task_id
AND t.archived IS FALSE
)
SELECT COALESCE(
SUM(progress_value * weight) / NULLIF(SUM(weight), 0),
0
)
FROM subtask_progress
INTO _ratio;
-- If project uses time-based progress, calculate based on estimated time
ELSIF _use_time_progress IS TRUE THEN
WITH subtask_progress AS (
SELECT
CASE
-- If subtask has manual progress, use that value
WHEN manual_progress IS TRUE AND progress_value IS NOT NULL THEN
progress_value
-- Otherwise use completion status (0 or 100)
ELSE
CASE
WHEN EXISTS(
SELECT 1
FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = t.id
AND is_done IS TRUE
) THEN 100
ELSE 0
END
END AS progress_value,
COALESCE(total_minutes, 0) AS estimated_minutes
FROM tasks t
WHERE t.parent_task_id = _task_id
AND t.archived IS FALSE
)
SELECT COALESCE(
SUM(progress_value * estimated_minutes) / NULLIF(SUM(estimated_minutes), 0),
0
)
FROM subtask_progress
INTO _ratio;
ELSE
-- Traditional calculation based on completion status
SELECT (CASE
WHEN EXISTS(SELECT 1
FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = _task_id
AND is_done IS TRUE) THEN 1
ELSE 0 END)
INTO _parent_task_done;
SELECT COUNT(*)
FROM tasks_with_status_view
WHERE parent_task_id = _task_id
AND is_done IS TRUE
INTO _sub_tasks_done;
_total_completed = _parent_task_done + _sub_tasks_done;
_total_tasks = _sub_tasks_count + 1; -- +1 for the parent task
IF _total_tasks = 0 THEN
_ratio = 0;
ELSE
_ratio = (_total_completed / _total_tasks) * 100;
END IF;
END IF;
END IF;
-- Ensure ratio is between 0 and 100
IF _ratio < 0 THEN
_ratio = 0;
ELSIF _ratio > 100 THEN
_ratio = 100;
END IF;
RETURN JSON_BUILD_OBJECT(
'ratio', _ratio,
'total_completed', _total_completed,
'total_tasks', _total_tasks,
'is_manual', _is_manual
);
END
$$;
CREATE OR REPLACE FUNCTION update_project(_body json) RETURNS json
LANGUAGE plpgsql
AS
$$
DECLARE
_user_id UUID;
_team_id UUID;
_client_id UUID;
_project_id UUID;
_project_manager_team_member_id UUID;
_client_name TEXT;
_project_name TEXT;
BEGIN
-- need a test, can be throw errors
_client_name = TRIM((_body ->> 'client_name')::TEXT);
_project_name = TRIM((_body ->> 'name')::TEXT);
-- add inside the controller
_user_id = (_body ->> 'user_id')::UUID;
_team_id = (_body ->> 'team_id')::UUID;
_project_manager_team_member_id = (_body ->> 'team_member_id')::UUID;
-- cache exists client if exists
SELECT id FROM clients WHERE LOWER(name) = LOWER(_client_name) AND team_id = _team_id INTO _client_id;
-- insert client if not exists
IF is_null_or_empty(_client_id) IS TRUE AND is_null_or_empty(_client_name) IS FALSE
THEN
INSERT INTO clients (name, team_id) VALUES (_client_name, _team_id) RETURNING id INTO _client_id;
END IF;
-- check whether the project name is already in
IF EXISTS(
SELECT name FROM projects WHERE LOWER(name) = LOWER(_project_name)
AND team_id = _team_id AND id != (_body ->> 'id')::UUID
)
THEN
RAISE 'PROJECT_EXISTS_ERROR:%', _project_name;
END IF;
-- update the project
UPDATE projects
SET name = _project_name,
notes = (_body ->> 'notes')::TEXT,
color_code = (_body ->> 'color_code')::TEXT,
status_id = (_body ->> 'status_id')::UUID,
health_id = (_body ->> 'health_id')::UUID,
key = (_body ->> 'key')::TEXT,
start_date = (_body ->> 'start_date')::TIMESTAMPTZ,
end_date = (_body ->> 'end_date')::TIMESTAMPTZ,
client_id = _client_id,
folder_id = (_body ->> 'folder_id')::UUID,
category_id = (_body ->> 'category_id')::UUID,
updated_at = CURRENT_TIMESTAMP,
estimated_working_days = (_body ->> 'working_days')::INTEGER,
estimated_man_days = (_body ->> 'man_days')::INTEGER,
hours_per_day = (_body ->> 'hours_per_day')::INTEGER,
use_manual_progress = COALESCE((_body ->> 'use_manual_progress')::BOOLEAN, FALSE),
use_weighted_progress = COALESCE((_body ->> 'use_weighted_progress')::BOOLEAN, FALSE),
use_time_progress = COALESCE((_body ->> 'use_time_progress')::BOOLEAN, FALSE)
WHERE id = (_body ->> 'id')::UUID
AND team_id = _team_id
RETURNING id INTO _project_id;
UPDATE project_members SET project_access_level_id = (SELECT id FROM project_access_levels WHERE key = 'MEMBER') WHERE project_id = _project_id;
IF NOT (_project_manager_team_member_id IS NULL)
THEN
PERFORM update_project_manager(_project_manager_team_member_id, _project_id::UUID);
END IF;
RETURN JSON_BUILD_OBJECT(
'id', _project_id,
'name', (_body ->> 'name')::TEXT,
'project_manager_id', _project_manager_team_member_id::UUID
);
END;
$$;
-- 3. Also modify the create_project function to handle the new fields during project creation
CREATE OR REPLACE FUNCTION create_project(_body json) RETURNS json
LANGUAGE plpgsql
AS
$$
DECLARE
_project_id UUID;
_user_id UUID;
_team_id UUID;
_team_member_id UUID;
_client_id UUID;
_client_name TEXT;
_project_name TEXT;
_project_created_log TEXT;
_project_member_added_log TEXT;
_project_created_log_id UUID;
_project_manager_team_member_id UUID;
_project_key TEXT;
BEGIN
_client_name = TRIM((_body ->> 'client_name')::TEXT);
_project_name = TRIM((_body ->> 'name')::TEXT);
_project_key = TRIM((_body ->> 'key')::TEXT);
_project_created_log = (_body ->> 'project_created_log')::TEXT;
_project_member_added_log = (_body ->> 'project_member_added_log')::TEXT;
_user_id = (_body ->> 'user_id')::UUID;
_team_id = (_body ->> 'team_id')::UUID;
_project_manager_team_member_id = (_body ->> 'project_manager_id')::UUID;
SELECT id FROM team_members WHERE user_id = _user_id AND team_id = _team_id INTO _team_member_id;
-- cache exists client if exists
SELECT id FROM clients WHERE LOWER(name) = LOWER(_client_name) AND team_id = _team_id INTO _client_id;
-- insert client if not exists
IF is_null_or_empty(_client_id) IS TRUE AND is_null_or_empty(_client_name) IS FALSE
THEN
INSERT INTO clients (name, team_id) VALUES (_client_name, _team_id) RETURNING id INTO _client_id;
END IF;
-- check whether the project name is already in
IF EXISTS(SELECT name FROM projects WHERE LOWER(name) = LOWER(_project_name) AND team_id = _team_id)
THEN
RAISE 'PROJECT_EXISTS_ERROR:%', _project_name;
END IF;
-- create the project
INSERT
INTO projects (name, key, color_code, start_date, end_date, team_id, notes, owner_id, status_id, health_id, folder_id,
category_id, estimated_working_days, estimated_man_days, hours_per_day,
use_manual_progress, use_weighted_progress, use_time_progress, client_id)
VALUES (_project_name,
UPPER(_project_key),
(_body ->> 'color_code')::TEXT,
(_body ->> 'start_date')::TIMESTAMPTZ,
(_body ->> 'end_date')::TIMESTAMPTZ,
_team_id,
(_body ->> 'notes')::TEXT,
_user_id,
(_body ->> 'status_id')::UUID,
(_body ->> 'health_id')::UUID,
(_body ->> 'folder_id')::UUID,
(_body ->> 'category_id')::UUID,
(_body ->> 'working_days')::INTEGER,
(_body ->> 'man_days')::INTEGER,
(_body ->> 'hours_per_day')::INTEGER,
COALESCE((_body ->> 'use_manual_progress')::BOOLEAN, FALSE),
COALESCE((_body ->> 'use_weighted_progress')::BOOLEAN, FALSE),
COALESCE((_body ->> 'use_time_progress')::BOOLEAN, FALSE),
_client_id)
RETURNING id INTO _project_id;
-- register the project log
INSERT INTO project_logs (project_id, team_id, description)
VALUES (_project_id, _team_id, _project_created_log)
RETURNING id INTO _project_created_log_id;
-- insert the project creator as a project member
INSERT INTO project_members (team_member_id, project_access_level_id, project_id, role_id)
VALUES (_team_member_id, (SELECT id FROM project_access_levels WHERE key = 'ADMIN'),
_project_id,
(SELECT id FROM roles WHERE team_id = _team_id AND default_role IS TRUE));
-- insert statuses
INSERT INTO task_statuses (name, project_id, team_id, category_id, sort_order)
VALUES ('To Do', _project_id, _team_id, (SELECT id FROM sys_task_status_categories WHERE is_todo IS TRUE), 0);
INSERT INTO task_statuses (name, project_id, team_id, category_id, sort_order)
VALUES ('Doing', _project_id, _team_id, (SELECT id FROM sys_task_status_categories WHERE is_doing IS TRUE), 1);
INSERT INTO task_statuses (name, project_id, team_id, category_id, sort_order)
VALUES ('Done', _project_id, _team_id, (SELECT id FROM sys_task_status_categories WHERE is_done IS TRUE), 2);
-- insert default project columns
PERFORM insert_task_list_columns(_project_id);
-- add project manager role if exists
IF NOT is_null_or_empty(_project_manager_team_member_id) THEN
PERFORM update_project_manager(_project_manager_team_member_id, _project_id);
END IF;
RETURN JSON_BUILD_OBJECT(
'id', _project_id,
'name', _project_name,
'project_created_log_id', _project_created_log_id
);
END;
$$;
-- 4. Update the getById function to include the new fields in the response
CREATE OR REPLACE FUNCTION getProjectById(_project_id UUID, _team_id UUID) RETURNS JSON
LANGUAGE plpgsql
AS
$$
DECLARE
_result JSON;
BEGIN
SELECT ROW_TO_JSON(rec) INTO _result
FROM (SELECT p.id,
p.name,
p.key,
p.color_code,
p.start_date,
p.end_date,
c.name AS client_name,
c.id AS client_id,
p.notes,
p.created_at,
p.updated_at,
ts.name AS status,
ts.color_code AS status_color,
ts.icon AS status_icon,
ts.id AS status_id,
h.name AS health,
h.color_code AS health_color,
h.icon AS health_icon,
h.id AS health_id,
pc.name AS category_name,
pc.color_code AS category_color,
pc.id AS category_id,
p.phase_label,
p.estimated_man_days AS man_days,
p.estimated_working_days AS working_days,
p.hours_per_day,
p.use_manual_progress,
p.use_weighted_progress,
-- Additional fields
COALESCE((SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(t)))
FROM (SELECT pm.id,
pm.project_id,
tm.id AS team_member_id,
tm.user_id,
u.name,
u.email,
u.avatar_url,
u.phone_number,
pal.name AS access_level,
pal.key AS access_level_key,
pal.id AS access_level_id,
EXISTS(SELECT 1
FROM project_members
INNER JOIN project_access_levels ON
project_members.project_access_level_id = project_access_levels.id
WHERE project_id = p.id
AND project_access_levels.key = 'PROJECT_MANAGER'
AND team_member_id = tm.id) AS is_project_manager
FROM project_members pm
INNER JOIN team_members tm ON pm.team_member_id = tm.id
INNER JOIN users u ON tm.user_id = u.id
INNER JOIN project_access_levels pal ON pm.project_access_level_id = pal.id
WHERE pm.project_id = p.id) t), '[]'::JSON) AS members,
(SELECT COUNT(DISTINCT (id))
FROM tasks
WHERE archived IS FALSE
AND project_id = p.id) AS task_count,
(SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(t)))
FROM (SELECT project_members.id,
project_members.project_id,
team_members.id AS team_member_id,
team_members.user_id,
users.name,
users.email,
users.avatar_url,
project_access_levels.name AS access_level,
project_access_levels.key AS access_level_key,
project_access_levels.id AS access_level_id
FROM project_members
INNER JOIN team_members ON project_members.team_member_id = team_members.id
INNER JOIN users ON team_members.user_id = users.id
INNER JOIN project_access_levels
ON project_members.project_access_level_id = project_access_levels.id
WHERE project_id = p.id
AND project_access_levels.key = 'PROJECT_MANAGER'
LIMIT 1) t) AS project_manager,
(SELECT EXISTS(SELECT 1
FROM project_subscribers
WHERE project_id = p.id
AND user_id = (SELECT user_id
FROM project_members
WHERE team_member_id = (SELECT id
FROM team_members
WHERE user_id IN
(SELECT user_id FROM is_member_of_project_cte))
AND project_id = p.id))) AS subscribed,
(SELECT name
FROM users
WHERE id =
(SELECT owner_id FROM projects WHERE id = p.id)) AS project_owner,
(SELECT default_view
FROM project_members
WHERE project_id = p.id
AND team_member_id IN (SELECT id FROM is_member_of_project_cte)) AS team_member_default_view,
(SELECT EXISTS(SELECT user_id
FROM archived_projects
WHERE user_id IN (SELECT user_id FROM is_member_of_project_cte)
AND project_id = p.id)) AS archived,
(SELECT EXISTS(SELECT user_id
FROM favorite_projects
WHERE user_id IN (SELECT user_id FROM is_member_of_project_cte)
AND project_id = p.id)) AS favorite
FROM projects p
LEFT JOIN sys_project_statuses ts ON p.status_id = ts.id
LEFT JOIN sys_project_healths h ON p.health_id = h.id
LEFT JOIN project_categories pc ON p.category_id = pc.id
LEFT JOIN clients c ON p.client_id = c.id,
LATERAL (SELECT id, user_id
FROM team_members
WHERE id = (SELECT team_member_id
FROM project_members
WHERE project_id = p.id
AND team_member_id IN (SELECT id
FROM team_members
WHERE team_id = _team_id)
LIMIT 1)) is_member_of_project_cte
WHERE p.id = _project_id
AND p.team_id = _team_id) rec;
RETURN _result;
END
$$;
CREATE OR REPLACE FUNCTION public.get_task_form_view_model(_user_id UUID, _team_id UUID, _task_id UUID, _project_id UUID) RETURNS JSON
LANGUAGE plpgsql
AS
$$
DECLARE
_task JSON;
_priorities JSON;
_projects JSON;
_statuses JSON;
_team_members JSON;
_assignees JSON;
_phases JSON;
BEGIN
-- Select task info
SELECT COALESCE(ROW_TO_JSON(rec), '{}'::JSON)
INTO _task
FROM (WITH RECURSIVE task_hierarchy AS (
-- Base case: Start with the given task
SELECT id,
parent_task_id,
0 AS level
FROM tasks
WHERE id = _task_id
UNION ALL
-- Recursive case: Traverse up to parent tasks
SELECT t.id,
t.parent_task_id,
th.level + 1 AS level
FROM tasks t
INNER JOIN task_hierarchy th ON t.id = th.parent_task_id
WHERE th.parent_task_id IS NOT NULL)
SELECT id,
name,
description,
start_date,
end_date,
done,
total_minutes,
priority_id,
project_id,
created_at,
updated_at,
status_id,
parent_task_id,
sort_order,
(SELECT phase_id FROM task_phase WHERE task_id = tasks.id) AS phase_id,
CONCAT((SELECT key FROM projects WHERE id = tasks.project_id), '-', task_no) AS task_key,
(SELECT start_time
FROM task_timers
WHERE task_id = tasks.id
AND user_id = _user_id) AS timer_start_time,
parent_task_id IS NOT NULL AS is_sub_task,
(SELECT COUNT('*')
FROM tasks
WHERE parent_task_id = tasks.id
AND archived IS FALSE) AS sub_tasks_count,
(SELECT COUNT(*)
FROM tasks_with_status_view tt
WHERE (tt.parent_task_id = tasks.id OR tt.task_id = tasks.id)
AND tt.is_done IS TRUE)
AS completed_count,
(SELECT COUNT(*) FROM task_attachments WHERE task_id = tasks.id) AS attachments_count,
(SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON)
FROM (SELECT task_labels.label_id AS id,
(SELECT name FROM team_labels WHERE id = task_labels.label_id),
(SELECT color_code FROM team_labels WHERE id = task_labels.label_id)
FROM task_labels
WHERE task_id = tasks.id
ORDER BY name) r) AS labels,
(SELECT color_code
FROM sys_task_status_categories
WHERE id = (SELECT category_id FROM task_statuses WHERE id = tasks.status_id)) AS status_color,
(SELECT COUNT(*) FROM tasks WHERE parent_task_id = _task_id) AS sub_tasks_count,
(SELECT name FROM users WHERE id = tasks.reporter_id) AS reporter,
(SELECT get_task_assignees(tasks.id)) AS assignees,
(SELECT id FROM team_members WHERE user_id = _user_id AND team_id = _team_id) AS team_member_id,
billable,
schedule_id,
progress_value,
weight,
(SELECT MAX(level) FROM task_hierarchy) AS task_level
FROM tasks
WHERE id = _task_id) rec;
SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON)
INTO _priorities
FROM (SELECT id, name FROM task_priorities ORDER BY value) rec;
SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON)
INTO _phases
FROM (SELECT id, name FROM project_phases WHERE project_id = _project_id ORDER BY name) rec;
SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON)
INTO _projects
FROM (SELECT id, name
FROM projects
WHERE team_id = _team_id
AND (CASE
WHEN (is_owner(_user_id, _team_id) OR is_admin(_user_id, _team_id) IS TRUE) THEN TRUE
ELSE is_member_of_project(projects.id, _user_id, _team_id) END)
ORDER BY name) rec;
SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON)
INTO _statuses
FROM (SELECT id, name FROM task_statuses WHERE project_id = _project_id) rec;
SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON)
INTO _team_members
FROM (SELECT team_members.id,
(SELECT name FROM team_member_info_view WHERE team_member_info_view.team_member_id = team_members.id),
(SELECT email FROM team_member_info_view WHERE team_member_info_view.team_member_id = team_members.id),
(SELECT avatar_url
FROM team_member_info_view
WHERE team_member_info_view.team_member_id = team_members.id)
FROM team_members
LEFT JOIN users u ON team_members.user_id = u.id
WHERE team_id = _team_id
AND team_members.active IS TRUE) rec;
SELECT get_task_assignees(_task_id) INTO _assignees;
RETURN JSON_BUILD_OBJECT(
'task', _task,
'priorities', _priorities,
'projects', _projects,
'statuses', _statuses,
'team_members', _team_members,
'assignees', _assignees,
'phases', _phases
);
END;
$$;
-- Add use_manual_progress, use_weighted_progress, and use_time_progress to projects table if they don't exist
ALTER TABLE projects
ADD COLUMN IF NOT EXISTS use_manual_progress BOOLEAN DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS use_weighted_progress BOOLEAN DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS use_time_progress BOOLEAN DEFAULT FALSE;
-- Add a trigger to reset manual progress when a task gets a new subtask
CREATE OR REPLACE FUNCTION reset_parent_task_manual_progress() RETURNS TRIGGER AS
$$
BEGIN
-- When a task gets a new subtask (parent_task_id is set), reset the parent's manual_progress flag
IF NEW.parent_task_id IS NOT NULL THEN
UPDATE tasks
SET manual_progress = false
WHERE id = NEW.parent_task_id
AND manual_progress = true;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Create the trigger on the tasks table
DROP TRIGGER IF EXISTS reset_parent_manual_progress_trigger ON tasks;
CREATE TRIGGER reset_parent_manual_progress_trigger
AFTER INSERT OR UPDATE OF parent_task_id ON tasks
FOR EACH ROW
EXECUTE FUNCTION reset_parent_task_manual_progress();
COMMIT;

View File

@@ -0,0 +1,157 @@
-- Migration: Add progress and weight activity types support
-- Date: 2025-04-24
-- Version: 1.0.0
BEGIN;
-- Update the get_activity_logs_by_task function to handle progress and weight attribute types
CREATE OR REPLACE FUNCTION get_activity_logs_by_task(_task_id uuid) RETURNS json
LANGUAGE plpgsql
AS
$$
DECLARE
_result JSON;
BEGIN
SELECT ROW_TO_JSON(rec)
INTO _result
FROM (SELECT (SELECT tasks.created_at FROM tasks WHERE tasks.id = _task_id),
(SELECT name
FROM users
WHERE id = (SELECT reporter_id FROM tasks WHERE id = _task_id)),
(SELECT avatar_url
FROM users
WHERE id = (SELECT reporter_id FROM tasks WHERE id = _task_id)),
(SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec2))), '[]'::JSON)
FROM (SELECT task_id,
created_at,
attribute_type,
log_type,
-- Case for previous value
(CASE
WHEN (attribute_type = 'status')
THEN (SELECT name FROM task_statuses WHERE id = old_value::UUID)
WHEN (attribute_type = 'priority')
THEN (SELECT name FROM task_priorities WHERE id = old_value::UUID)
WHEN (attribute_type = 'phase' AND old_value <> 'Unmapped')
THEN (SELECT name FROM project_phases WHERE id = old_value::UUID)
WHEN (attribute_type = 'progress' OR attribute_type = 'weight')
THEN old_value
ELSE (old_value) END) AS previous,
-- Case for current value
(CASE
WHEN (attribute_type = 'assignee')
THEN (SELECT name FROM users WHERE id = new_value::UUID)
WHEN (attribute_type = 'label')
THEN (SELECT name FROM team_labels WHERE id = new_value::UUID)
WHEN (attribute_type = 'status')
THEN (SELECT name FROM task_statuses WHERE id = new_value::UUID)
WHEN (attribute_type = 'priority')
THEN (SELECT name FROM task_priorities WHERE id = new_value::UUID)
WHEN (attribute_type = 'phase' AND new_value <> 'Unmapped')
THEN (SELECT name FROM project_phases WHERE id = new_value::UUID)
WHEN (attribute_type = 'progress' OR attribute_type = 'weight')
THEN new_value
ELSE (new_value) END) AS current,
-- Case for assigned user
(CASE
WHEN (attribute_type = 'assignee')
THEN (SELECT ROW_TO_JSON(rec)
FROM (SELECT (CASE
WHEN (new_value IS NOT NULL)
THEN (SELECT name FROM users WHERE users.id = new_value::UUID)
ELSE (next_string) END) AS name,
(SELECT avatar_url FROM users WHERE users.id = new_value::UUID)) rec)
ELSE (NULL) END) AS assigned_user,
-- Case for label data
(CASE
WHEN (attribute_type = 'label')
THEN (SELECT ROW_TO_JSON(rec)
FROM (SELECT (SELECT name FROM team_labels WHERE id = new_value::UUID),
(SELECT color_code FROM team_labels WHERE id = new_value::UUID)) rec)
ELSE (NULL) END) AS label_data,
-- Case for previous status
(CASE
WHEN (attribute_type = 'status')
THEN (SELECT ROW_TO_JSON(rec)
FROM (SELECT (SELECT name FROM task_statuses WHERE id = old_value::UUID),
(SELECT color_code
FROM sys_task_status_categories
WHERE id = (SELECT category_id FROM task_statuses WHERE id = old_value::UUID)),
(SELECT color_code_dark
FROM sys_task_status_categories
WHERE id = (SELECT category_id FROM task_statuses WHERE id = old_value::UUID))) rec)
ELSE (NULL) END) AS previous_status,
-- Case for next status
(CASE
WHEN (attribute_type = 'status')
THEN (SELECT ROW_TO_JSON(rec)
FROM (SELECT (SELECT name FROM task_statuses WHERE id = new_value::UUID),
(SELECT color_code
FROM sys_task_status_categories
WHERE id = (SELECT category_id FROM task_statuses WHERE id = new_value::UUID)),
(SELECT color_code_dark
FROM sys_task_status_categories
WHERE id = (SELECT category_id FROM task_statuses WHERE id = new_value::UUID))) rec)
ELSE (NULL) END) AS next_status,
-- Case for previous priority
(CASE
WHEN (attribute_type = 'priority')
THEN (SELECT ROW_TO_JSON(rec)
FROM (SELECT (SELECT name FROM task_priorities WHERE id = old_value::UUID),
(SELECT color_code FROM task_priorities WHERE id = old_value::UUID)) rec)
ELSE (NULL) END) AS previous_priority,
-- Case for next priority
(CASE
WHEN (attribute_type = 'priority')
THEN (SELECT ROW_TO_JSON(rec)
FROM (SELECT (SELECT name FROM task_priorities WHERE id = new_value::UUID),
(SELECT color_code FROM task_priorities WHERE id = new_value::UUID)) rec)
ELSE (NULL) END) AS next_priority,
-- Case for previous phase
(CASE
WHEN (attribute_type = 'phase' AND old_value <> 'Unmapped')
THEN (SELECT ROW_TO_JSON(rec)
FROM (SELECT (SELECT name FROM project_phases WHERE id = old_value::UUID),
(SELECT color_code FROM project_phases WHERE id = old_value::UUID)) rec)
ELSE (NULL) END) AS previous_phase,
-- Case for next phase
(CASE
WHEN (attribute_type = 'phase' AND new_value <> 'Unmapped')
THEN (SELECT ROW_TO_JSON(rec)
FROM (SELECT (SELECT name FROM project_phases WHERE id = new_value::UUID),
(SELECT color_code FROM project_phases WHERE id = new_value::UUID)) rec)
ELSE (NULL) END) AS next_phase,
-- Case for done by
(SELECT ROW_TO_JSON(rec)
FROM (SELECT (SELECT name FROM users WHERE users.id = tal.user_id),
(SELECT avatar_url FROM users WHERE users.id = tal.user_id)) rec) AS done_by,
-- Add log text for progress and weight
(CASE
WHEN (attribute_type = 'progress')
THEN 'updated the progress of'
WHEN (attribute_type = 'weight')
THEN 'updated the weight of'
ELSE ''
END) AS log_text
FROM task_activity_logs tal
WHERE task_id = _task_id
ORDER BY created_at DESC) rec2) AS logs) rec;
RETURN _result;
END;
$$;
COMMIT;

View File

@@ -0,0 +1,243 @@
-- Migration: Update time-based progress mode to work for all tasks
-- Date: 2025-04-25
-- Version: 1.0.0
BEGIN;
-- Update function to use time-based progress for all tasks
CREATE OR REPLACE FUNCTION get_task_complete_ratio(_task_id uuid) RETURNS json
LANGUAGE plpgsql
AS
$$
DECLARE
_parent_task_done FLOAT = 0;
_sub_tasks_done FLOAT = 0;
_sub_tasks_count FLOAT = 0;
_total_completed FLOAT = 0;
_total_tasks FLOAT = 0;
_ratio FLOAT = 0;
_is_manual BOOLEAN = FALSE;
_manual_value INTEGER = NULL;
_project_id UUID;
_use_manual_progress BOOLEAN = FALSE;
_use_weighted_progress BOOLEAN = FALSE;
_use_time_progress BOOLEAN = FALSE;
_task_complete BOOLEAN = FALSE;
BEGIN
-- Check if manual progress is set for this task
SELECT manual_progress, progress_value, project_id,
EXISTS(
SELECT 1
FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = tasks.id
AND is_done IS TRUE
) AS is_complete
FROM tasks
WHERE id = _task_id
INTO _is_manual, _manual_value, _project_id, _task_complete;
-- Check if the project uses manual progress
IF _project_id IS NOT NULL THEN
SELECT COALESCE(use_manual_progress, FALSE),
COALESCE(use_weighted_progress, FALSE),
COALESCE(use_time_progress, FALSE)
FROM projects
WHERE id = _project_id
INTO _use_manual_progress, _use_weighted_progress, _use_time_progress;
END IF;
-- Get all subtasks
SELECT COUNT(*)
FROM tasks
WHERE parent_task_id = _task_id AND archived IS FALSE
INTO _sub_tasks_count;
-- If task is complete, always return 100%
IF _task_complete IS TRUE THEN
RETURN JSON_BUILD_OBJECT(
'ratio', 100,
'total_completed', 1,
'total_tasks', 1,
'is_manual', FALSE
);
END IF;
-- Use manual progress value in two cases:
-- 1. When task has manual_progress = TRUE and progress_value is set
-- 2. When project has use_manual_progress = TRUE and progress_value is set
IF (_is_manual IS TRUE AND _manual_value IS NOT NULL) OR
(_use_manual_progress IS TRUE AND _manual_value IS NOT NULL) THEN
RETURN JSON_BUILD_OBJECT(
'ratio', _manual_value,
'total_completed', 0,
'total_tasks', 0,
'is_manual', TRUE
);
END IF;
-- If there are no subtasks, just use the parent task's status (unless in time-based mode)
IF _sub_tasks_count = 0 THEN
-- Use time-based estimation for tasks without subtasks if enabled
IF _use_time_progress IS TRUE THEN
-- For time-based tasks without subtasks, we still need some progress calculation
-- If the task is completed, return 100%
-- Otherwise, use the progress value if set manually, or 0
SELECT
CASE
WHEN _task_complete IS TRUE THEN 100
ELSE COALESCE(_manual_value, 0)
END
INTO _ratio;
ELSE
-- Traditional calculation for non-time-based tasks
SELECT (CASE WHEN _task_complete IS TRUE THEN 1 ELSE 0 END)
INTO _parent_task_done;
_ratio = _parent_task_done * 100;
END IF;
ELSE
-- If project uses manual progress, calculate based on subtask manual progress values
IF _use_manual_progress IS TRUE THEN
WITH subtask_progress AS (
SELECT
t.id,
t.manual_progress,
t.progress_value,
EXISTS(
SELECT 1
FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = t.id
AND is_done IS TRUE
) AS is_complete
FROM tasks t
WHERE t.parent_task_id = _task_id
AND t.archived IS FALSE
),
subtask_with_values AS (
SELECT
CASE
-- For completed tasks, always use 100%
WHEN is_complete IS TRUE THEN 100
-- For tasks with progress value set, use it regardless of manual_progress flag
WHEN progress_value IS NOT NULL THEN progress_value
-- Default to 0 for incomplete tasks with no progress value
ELSE 0
END AS progress_value
FROM subtask_progress
)
SELECT COALESCE(AVG(progress_value), 0)
FROM subtask_with_values
INTO _ratio;
-- If project uses weighted progress, calculate based on subtask weights
ELSIF _use_weighted_progress IS TRUE THEN
WITH subtask_progress AS (
SELECT
t.id,
t.manual_progress,
t.progress_value,
EXISTS(
SELECT 1
FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = t.id
AND is_done IS TRUE
) AS is_complete,
COALESCE(t.weight, 100) AS weight
FROM tasks t
WHERE t.parent_task_id = _task_id
AND t.archived IS FALSE
),
subtask_with_values AS (
SELECT
CASE
-- For completed tasks, always use 100%
WHEN is_complete IS TRUE THEN 100
-- For tasks with progress value set, use it regardless of manual_progress flag
WHEN progress_value IS NOT NULL THEN progress_value
-- Default to 0 for incomplete tasks with no progress value
ELSE 0
END AS progress_value,
weight
FROM subtask_progress
)
SELECT COALESCE(
SUM(progress_value * weight) / NULLIF(SUM(weight), 0),
0
)
FROM subtask_with_values
INTO _ratio;
-- If project uses time-based progress, calculate based on estimated time
ELSIF _use_time_progress IS TRUE THEN
WITH subtask_progress AS (
SELECT
t.id,
t.manual_progress,
t.progress_value,
EXISTS(
SELECT 1
FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = t.id
AND is_done IS TRUE
) AS is_complete,
COALESCE(t.total_minutes, 0) AS estimated_minutes
FROM tasks t
WHERE t.parent_task_id = _task_id
AND t.archived IS FALSE
),
subtask_with_values AS (
SELECT
CASE
-- For completed tasks, always use 100%
WHEN is_complete IS TRUE THEN 100
-- For tasks with progress value set, use it regardless of manual_progress flag
WHEN progress_value IS NOT NULL THEN progress_value
-- Default to 0 for incomplete tasks with no progress value
ELSE 0
END AS progress_value,
estimated_minutes
FROM subtask_progress
)
SELECT COALESCE(
SUM(progress_value * estimated_minutes) / NULLIF(SUM(estimated_minutes), 0),
0
)
FROM subtask_with_values
INTO _ratio;
ELSE
-- Traditional calculation based on completion status
SELECT (CASE WHEN _task_complete IS TRUE THEN 1 ELSE 0 END)
INTO _parent_task_done;
SELECT COUNT(*)
FROM tasks_with_status_view
WHERE parent_task_id = _task_id
AND is_done IS TRUE
INTO _sub_tasks_done;
_total_completed = _parent_task_done + _sub_tasks_done;
_total_tasks = _sub_tasks_count + 1; -- +1 for the parent task
IF _total_tasks = 0 THEN
_ratio = 0;
ELSE
_ratio = (_total_completed / _total_tasks) * 100;
END IF;
END IF;
END IF;
-- Ensure ratio is between 0 and 100
IF _ratio < 0 THEN
_ratio = 0;
ELSIF _ratio > 100 THEN
_ratio = 100;
END IF;
RETURN JSON_BUILD_OBJECT(
'ratio', _ratio,
'total_completed', _total_completed,
'total_tasks', _total_tasks,
'is_manual', _is_manual
);
END
$$;
COMMIT;

View File

@@ -0,0 +1,289 @@
-- Migration: Improve parent task progress calculation using weights and time estimation
-- Date: 2025-04-26
-- Version: 1.0.0
BEGIN;
-- Update function to better calculate parent task progress based on subtask weights or time estimations
CREATE OR REPLACE FUNCTION get_task_complete_ratio(_task_id uuid) RETURNS json
LANGUAGE plpgsql
AS
$$
DECLARE
_parent_task_done FLOAT = 0;
_sub_tasks_done FLOAT = 0;
_sub_tasks_count FLOAT = 0;
_total_completed FLOAT = 0;
_total_tasks FLOAT = 0;
_ratio FLOAT = 0;
_is_manual BOOLEAN = FALSE;
_manual_value INTEGER = NULL;
_project_id UUID;
_use_manual_progress BOOLEAN = FALSE;
_use_weighted_progress BOOLEAN = FALSE;
_use_time_progress BOOLEAN = FALSE;
BEGIN
-- Check if manual progress is set for this task
SELECT manual_progress, progress_value, project_id
FROM tasks
WHERE id = _task_id
INTO _is_manual, _manual_value, _project_id;
-- Check if the project uses manual progress
IF _project_id IS NOT NULL THEN
SELECT COALESCE(use_manual_progress, FALSE),
COALESCE(use_weighted_progress, FALSE),
COALESCE(use_time_progress, FALSE)
FROM projects
WHERE id = _project_id
INTO _use_manual_progress, _use_weighted_progress, _use_time_progress;
END IF;
-- Get all subtasks
SELECT COUNT(*)
FROM tasks
WHERE parent_task_id = _task_id AND archived IS FALSE
INTO _sub_tasks_count;
-- Only respect manual progress for tasks without subtasks
IF _is_manual IS TRUE AND _manual_value IS NOT NULL AND _sub_tasks_count = 0 THEN
RETURN JSON_BUILD_OBJECT(
'ratio', _manual_value,
'total_completed', 0,
'total_tasks', 0,
'is_manual', TRUE
);
END IF;
-- If there are no subtasks, just use the parent task's status
IF _sub_tasks_count = 0 THEN
-- For tasks without subtasks in time-based mode
IF _use_time_progress IS TRUE THEN
SELECT
CASE
WHEN EXISTS(
SELECT 1
FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = _task_id
AND is_done IS TRUE
) THEN 100
ELSE COALESCE(_manual_value, 0)
END
INTO _ratio;
ELSE
-- Traditional calculation for non-time-based tasks
SELECT (CASE
WHEN EXISTS(SELECT 1
FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = _task_id
AND is_done IS TRUE) THEN 1
ELSE 0 END)
INTO _parent_task_done;
_ratio = _parent_task_done * 100;
END IF;
ELSE
-- For parent tasks with subtasks, always use the appropriate calculation based on project mode
-- If project uses manual progress, calculate based on subtask manual progress values
IF _use_manual_progress IS TRUE THEN
WITH subtask_progress AS (
SELECT
CASE
-- If subtask has manual progress, use that value
WHEN manual_progress IS TRUE AND progress_value IS NOT NULL THEN
progress_value
-- Otherwise use completion status (0 or 100)
ELSE
CASE
WHEN EXISTS(
SELECT 1
FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = t.id
AND is_done IS TRUE
) THEN 100
ELSE 0
END
END AS progress_value
FROM tasks t
WHERE t.parent_task_id = _task_id
AND t.archived IS FALSE
)
SELECT COALESCE(AVG(progress_value), 0)
FROM subtask_progress
INTO _ratio;
-- If project uses weighted progress, calculate based on subtask weights
ELSIF _use_weighted_progress IS TRUE THEN
WITH subtask_progress AS (
SELECT
CASE
-- If subtask has manual progress, use that value
WHEN manual_progress IS TRUE AND progress_value IS NOT NULL THEN
progress_value
-- Otherwise use completion status (0 or 100)
ELSE
CASE
WHEN EXISTS(
SELECT 1
FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = t.id
AND is_done IS TRUE
) THEN 100
ELSE 0
END
END AS progress_value,
COALESCE(weight, 100) AS weight -- Default weight is 100 if not specified
FROM tasks t
WHERE t.parent_task_id = _task_id
AND t.archived IS FALSE
)
SELECT COALESCE(
SUM(progress_value * weight) / NULLIF(SUM(weight), 0),
0
)
FROM subtask_progress
INTO _ratio;
-- If project uses time-based progress, calculate based on estimated time (total_minutes)
ELSIF _use_time_progress IS TRUE THEN
WITH subtask_progress AS (
SELECT
CASE
-- If subtask has manual progress, use that value
WHEN manual_progress IS TRUE AND progress_value IS NOT NULL THEN
progress_value
-- Otherwise use completion status (0 or 100)
ELSE
CASE
WHEN EXISTS(
SELECT 1
FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = t.id
AND is_done IS TRUE
) THEN 100
ELSE 0
END
END AS progress_value,
COALESCE(total_minutes, 0) AS estimated_minutes -- Use time estimation for weighting
FROM tasks t
WHERE t.parent_task_id = _task_id
AND t.archived IS FALSE
)
SELECT COALESCE(
SUM(progress_value * estimated_minutes) / NULLIF(SUM(estimated_minutes), 0),
0
)
FROM subtask_progress
INTO _ratio;
ELSE
-- Traditional calculation based on completion status when no special mode is enabled
SELECT (CASE
WHEN EXISTS(SELECT 1
FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = _task_id
AND is_done IS TRUE) THEN 1
ELSE 0 END)
INTO _parent_task_done;
SELECT COUNT(*)
FROM tasks_with_status_view
WHERE parent_task_id = _task_id
AND is_done IS TRUE
INTO _sub_tasks_done;
_total_completed = _parent_task_done + _sub_tasks_done;
_total_tasks = _sub_tasks_count + 1; -- +1 for the parent task
IF _total_tasks = 0 THEN
_ratio = 0;
ELSE
_ratio = (_total_completed / _total_tasks) * 100;
END IF;
END IF;
END IF;
-- Ensure ratio is between 0 and 100
IF _ratio < 0 THEN
_ratio = 0;
ELSIF _ratio > 100 THEN
_ratio = 100;
END IF;
RETURN JSON_BUILD_OBJECT(
'ratio', _ratio,
'total_completed', _total_completed,
'total_tasks', _total_tasks,
'is_manual', _is_manual
);
END
$$;
-- Make sure we recalculate parent task progress when subtask progress changes
CREATE OR REPLACE FUNCTION update_parent_task_progress() RETURNS TRIGGER AS
$$
DECLARE
_parent_task_id UUID;
_project_id UUID;
_ratio FLOAT;
BEGIN
-- Check if this is a subtask
IF NEW.parent_task_id IS NOT NULL THEN
_parent_task_id := NEW.parent_task_id;
-- Force any parent task with subtasks to NOT use manual progress
UPDATE tasks
SET manual_progress = FALSE
WHERE id = _parent_task_id;
END IF;
-- If this task has progress value of 100 and doesn't have subtasks, we might want to prompt the user
-- to mark it as done. We'll annotate this in a way that the socket handler can detect.
IF NEW.progress_value = 100 OR NEW.weight = 100 OR NEW.total_minutes > 0 THEN
-- Check if task has status in "done" category
SELECT project_id FROM tasks WHERE id = NEW.id INTO _project_id;
-- Get the progress ratio for this task
SELECT get_task_complete_ratio(NEW.id)->>'ratio' INTO _ratio;
IF _ratio::FLOAT >= 100 THEN
-- Log that this task is at 100% progress
RAISE NOTICE 'Task % progress is at 100%%, may need status update', NEW.id;
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Create trigger for updates to task progress
DROP TRIGGER IF EXISTS update_parent_task_progress_trigger ON tasks;
CREATE TRIGGER update_parent_task_progress_trigger
AFTER UPDATE OF progress_value, weight, total_minutes ON tasks
FOR EACH ROW
EXECUTE FUNCTION update_parent_task_progress();
-- Create a function to ensure parent tasks never have manual progress when they have subtasks
CREATE OR REPLACE FUNCTION ensure_parent_task_without_manual_progress() RETURNS TRIGGER AS
$$
BEGIN
-- If this is a new subtask being created or a task is being converted to a subtask
IF NEW.parent_task_id IS NOT NULL THEN
-- Force the parent task to NOT use manual progress
UPDATE tasks
SET manual_progress = FALSE
WHERE id = NEW.parent_task_id;
-- Log that we've reset manual progress for a parent task
RAISE NOTICE 'Reset manual progress for parent task % because it has subtasks', NEW.parent_task_id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Create trigger for when tasks are created or updated with a parent_task_id
DROP TRIGGER IF EXISTS ensure_parent_task_without_manual_progress_trigger ON tasks;
CREATE TRIGGER ensure_parent_task_without_manual_progress_trigger
AFTER INSERT OR UPDATE OF parent_task_id ON tasks
FOR EACH ROW
EXECUTE FUNCTION ensure_parent_task_without_manual_progress();
COMMIT;

View File

@@ -0,0 +1,150 @@
-- Migration: Update socket event handlers to set progress-mode handlers
-- Date: 2025-04-26
-- Version: 1.0.0
BEGIN;
-- Create ENUM type for progress modes
CREATE TYPE progress_mode_type AS ENUM ('manual', 'weighted', 'time', 'default');
-- Alter tasks table to use ENUM type
ALTER TABLE tasks
ALTER COLUMN progress_mode TYPE progress_mode_type
USING progress_mode::text::progress_mode_type;
-- Update the on_update_task_progress function to set progress_mode
CREATE OR REPLACE FUNCTION on_update_task_progress(_body json) RETURNS json
LANGUAGE plpgsql
AS
$$
DECLARE
_task_id UUID;
_progress_value INTEGER;
_parent_task_id UUID;
_project_id UUID;
_current_mode progress_mode_type;
BEGIN
_task_id = (_body ->> 'task_id')::UUID;
_progress_value = (_body ->> 'progress_value')::INTEGER;
_parent_task_id = (_body ->> 'parent_task_id')::UUID;
-- Get the project ID and determine the current progress mode
SELECT project_id INTO _project_id FROM tasks WHERE id = _task_id;
IF _project_id IS NOT NULL THEN
SELECT
CASE
WHEN use_manual_progress IS TRUE THEN 'manual'
WHEN use_weighted_progress IS TRUE THEN 'weighted'
WHEN use_time_progress IS TRUE THEN 'time'
ELSE 'default'
END
INTO _current_mode
FROM projects
WHERE id = _project_id;
ELSE
_current_mode := 'default';
END IF;
-- Update the task with progress value and set the progress mode
UPDATE tasks
SET progress_value = _progress_value,
manual_progress = TRUE,
progress_mode = _current_mode,
updated_at = CURRENT_TIMESTAMP
WHERE id = _task_id;
-- Return the updated task info
RETURN JSON_BUILD_OBJECT(
'task_id', _task_id,
'progress_value', _progress_value,
'progress_mode', _current_mode
);
END;
$$;
-- Update the on_update_task_weight function to set progress_mode when weight is updated
CREATE OR REPLACE FUNCTION on_update_task_weight(_body json) RETURNS json
LANGUAGE plpgsql
AS
$$
DECLARE
_task_id UUID;
_weight INTEGER;
_parent_task_id UUID;
_project_id UUID;
BEGIN
_task_id = (_body ->> 'task_id')::UUID;
_weight = (_body ->> 'weight')::INTEGER;
_parent_task_id = (_body ->> 'parent_task_id')::UUID;
-- Get the project ID
SELECT project_id INTO _project_id FROM tasks WHERE id = _task_id;
-- Update the task with weight value and set progress_mode to 'weighted'
UPDATE tasks
SET weight = _weight,
progress_mode = 'weighted',
updated_at = CURRENT_TIMESTAMP
WHERE id = _task_id;
-- Return the updated task info
RETURN JSON_BUILD_OBJECT(
'task_id', _task_id,
'weight', _weight
);
END;
$$;
-- Create a function to reset progress values when switching project progress modes
CREATE OR REPLACE FUNCTION reset_project_progress_values() RETURNS TRIGGER
LANGUAGE plpgsql
AS
$$
DECLARE
_old_mode progress_mode_type;
_new_mode progress_mode_type;
_project_id UUID;
BEGIN
_project_id := NEW.id;
-- Determine old and new modes
_old_mode :=
CASE
WHEN OLD.use_manual_progress IS TRUE THEN 'manual'
WHEN OLD.use_weighted_progress IS TRUE THEN 'weighted'
WHEN OLD.use_time_progress IS TRUE THEN 'time'
ELSE 'default'
END;
_new_mode :=
CASE
WHEN NEW.use_manual_progress IS TRUE THEN 'manual'
WHEN NEW.use_weighted_progress IS TRUE THEN 'weighted'
WHEN NEW.use_time_progress IS TRUE THEN 'time'
ELSE 'default'
END;
-- If mode has changed, reset progress values for tasks with the old mode
IF _old_mode <> _new_mode THEN
-- Reset progress values for tasks that were set in the old mode
UPDATE tasks
SET progress_value = NULL,
progress_mode = NULL
WHERE project_id = _project_id
AND progress_mode = _old_mode;
END IF;
RETURN NEW;
END;
$$;
-- Create trigger to reset progress values when project progress mode changes
DROP TRIGGER IF EXISTS reset_progress_on_mode_change ON projects;
CREATE TRIGGER reset_progress_on_mode_change
AFTER UPDATE OF use_manual_progress, use_weighted_progress, use_time_progress
ON projects
FOR EACH ROW
EXECUTE FUNCTION reset_project_progress_values();
COMMIT;

View File

@@ -0,0 +1,160 @@
-- Migration: Fix progress_mode_type ENUM and casting issues
-- Date: 2025-04-27
-- Version: 1.0.0
BEGIN;
-- First, let's ensure the ENUM type exists with the correct values
DO $$
BEGIN
-- Check if the type exists
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'progress_mode_type') THEN
CREATE TYPE progress_mode_type AS ENUM ('manual', 'weighted', 'time', 'default');
ELSE
-- Add any missing values to the existing ENUM
BEGIN
ALTER TYPE progress_mode_type ADD VALUE IF NOT EXISTS 'manual';
ALTER TYPE progress_mode_type ADD VALUE IF NOT EXISTS 'weighted';
ALTER TYPE progress_mode_type ADD VALUE IF NOT EXISTS 'time';
ALTER TYPE progress_mode_type ADD VALUE IF NOT EXISTS 'default';
EXCEPTION
WHEN duplicate_object THEN
-- Ignore if values already exist
NULL;
END;
END IF;
END $$;
-- Update functions to use proper type casting
CREATE OR REPLACE FUNCTION on_update_task_progress(_body json) RETURNS json
LANGUAGE plpgsql
AS
$$
DECLARE
_task_id UUID;
_progress_value INTEGER;
_parent_task_id UUID;
_project_id UUID;
_current_mode progress_mode_type;
BEGIN
_task_id = (_body ->> 'task_id')::UUID;
_progress_value = (_body ->> 'progress_value')::INTEGER;
_parent_task_id = (_body ->> 'parent_task_id')::UUID;
-- Get the project ID and determine the current progress mode
SELECT project_id INTO _project_id FROM tasks WHERE id = _task_id;
IF _project_id IS NOT NULL THEN
SELECT
CASE
WHEN use_manual_progress IS TRUE THEN 'manual'::progress_mode_type
WHEN use_weighted_progress IS TRUE THEN 'weighted'::progress_mode_type
WHEN use_time_progress IS TRUE THEN 'time'::progress_mode_type
ELSE 'default'::progress_mode_type
END
INTO _current_mode
FROM projects
WHERE id = _project_id;
ELSE
_current_mode := 'default'::progress_mode_type;
END IF;
-- Update the task with progress value and set the progress mode
UPDATE tasks
SET progress_value = _progress_value,
manual_progress = TRUE,
progress_mode = _current_mode,
updated_at = CURRENT_TIMESTAMP
WHERE id = _task_id;
-- Return the updated task info
RETURN JSON_BUILD_OBJECT(
'task_id', _task_id,
'progress_value', _progress_value,
'progress_mode', _current_mode
);
END;
$$;
-- Update the on_update_task_weight function to use proper type casting
CREATE OR REPLACE FUNCTION on_update_task_weight(_body json) RETURNS json
LANGUAGE plpgsql
AS
$$
DECLARE
_task_id UUID;
_weight INTEGER;
_parent_task_id UUID;
_project_id UUID;
BEGIN
_task_id = (_body ->> 'task_id')::UUID;
_weight = (_body ->> 'weight')::INTEGER;
_parent_task_id = (_body ->> 'parent_task_id')::UUID;
-- Get the project ID
SELECT project_id INTO _project_id FROM tasks WHERE id = _task_id;
-- Update the task with weight value and set progress_mode to 'weighted'
UPDATE tasks
SET weight = _weight,
progress_mode = 'weighted'::progress_mode_type,
updated_at = CURRENT_TIMESTAMP
WHERE id = _task_id;
-- Return the updated task info
RETURN JSON_BUILD_OBJECT(
'task_id', _task_id,
'weight', _weight
);
END;
$$;
-- Update the reset_project_progress_values function to use proper type casting
CREATE OR REPLACE FUNCTION reset_project_progress_values() RETURNS TRIGGER
LANGUAGE plpgsql
AS
$$
DECLARE
_old_mode progress_mode_type;
_new_mode progress_mode_type;
_project_id UUID;
BEGIN
_project_id := NEW.id;
-- Determine old and new modes with proper type casting
_old_mode :=
CASE
WHEN OLD.use_manual_progress IS TRUE THEN 'manual'::progress_mode_type
WHEN OLD.use_weighted_progress IS TRUE THEN 'weighted'::progress_mode_type
WHEN OLD.use_time_progress IS TRUE THEN 'time'::progress_mode_type
ELSE 'default'::progress_mode_type
END;
_new_mode :=
CASE
WHEN NEW.use_manual_progress IS TRUE THEN 'manual'::progress_mode_type
WHEN NEW.use_weighted_progress IS TRUE THEN 'weighted'::progress_mode_type
WHEN NEW.use_time_progress IS TRUE THEN 'time'::progress_mode_type
ELSE 'default'::progress_mode_type
END;
-- If mode has changed, reset progress values for tasks with the old mode
IF _old_mode <> _new_mode THEN
-- Reset progress values for tasks that were set in the old mode
UPDATE tasks
SET progress_value = NULL,
progress_mode = NULL
WHERE project_id = _project_id
AND progress_mode::text::progress_mode_type = _old_mode;
END IF;
RETURN NEW;
END;
$$;
-- Update the tasks table to ensure proper type casting for existing values
UPDATE tasks
SET progress_mode = progress_mode::text::progress_mode_type
WHERE progress_mode IS NOT NULL;
COMMIT;

View File

@@ -0,0 +1,166 @@
-- Migration: Fix multilevel subtask progress calculation for weighted and manual progress
-- Date: 2025-05-06
-- Version: 1.0.0
BEGIN;
-- Update the trigger function to recursively recalculate parent task progress up the entire hierarchy
CREATE OR REPLACE FUNCTION update_parent_task_progress() RETURNS TRIGGER AS
$$
DECLARE
_parent_task_id UUID;
_project_id UUID;
_ratio FLOAT;
BEGIN
-- Check if this is a subtask
IF NEW.parent_task_id IS NOT NULL THEN
_parent_task_id := NEW.parent_task_id;
-- Force any parent task with subtasks to NOT use manual progress
UPDATE tasks
SET manual_progress = FALSE
WHERE id = _parent_task_id;
-- Calculate and update the parent's progress value
SELECT (get_task_complete_ratio(_parent_task_id)->>'ratio')::FLOAT INTO _ratio;
-- Update the parent's progress value
UPDATE tasks
SET progress_value = _ratio
WHERE id = _parent_task_id;
-- Recursively propagate changes up the hierarchy by using a recursive CTE
WITH RECURSIVE task_hierarchy AS (
-- Base case: Start with the parent task
SELECT
id,
parent_task_id
FROM tasks
WHERE id = _parent_task_id
UNION ALL
-- Recursive case: Go up to each ancestor
SELECT
t.id,
t.parent_task_id
FROM tasks t
JOIN task_hierarchy th ON t.id = th.parent_task_id
WHERE t.id IS NOT NULL
)
-- For each ancestor, recalculate its progress
UPDATE tasks
SET
manual_progress = FALSE,
progress_value = (SELECT (get_task_complete_ratio(task_hierarchy.id)->>'ratio')::FLOAT)
FROM task_hierarchy
WHERE tasks.id = task_hierarchy.id
AND task_hierarchy.parent_task_id IS NOT NULL;
-- Log the recalculation for debugging
RAISE NOTICE 'Updated progress for task % to %', _parent_task_id, _ratio;
END IF;
-- If this task has progress value of 100 and doesn't have subtasks, we might want to prompt the user
-- to mark it as done. We'll annotate this in a way that the socket handler can detect.
IF NEW.progress_value = 100 OR NEW.weight = 100 OR NEW.total_minutes > 0 THEN
-- Check if task has status in "done" category
SELECT project_id FROM tasks WHERE id = NEW.id INTO _project_id;
-- Get the progress ratio for this task
SELECT (get_task_complete_ratio(NEW.id)->>'ratio')::FLOAT INTO _ratio;
IF _ratio >= 100 THEN
-- Log that this task is at 100% progress
RAISE NOTICE 'Task % progress is at 100%%, may need status update', NEW.id;
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Update existing trigger or create a new one to handle more changes
DROP TRIGGER IF EXISTS update_parent_task_progress_trigger ON tasks;
CREATE TRIGGER update_parent_task_progress_trigger
AFTER UPDATE OF progress_value, weight, total_minutes, parent_task_id, manual_progress ON tasks
FOR EACH ROW
EXECUTE FUNCTION update_parent_task_progress();
-- Also add a trigger for when a new task is inserted
DROP TRIGGER IF EXISTS update_parent_task_progress_on_insert_trigger ON tasks;
CREATE TRIGGER update_parent_task_progress_on_insert_trigger
AFTER INSERT ON tasks
FOR EACH ROW
WHEN (NEW.parent_task_id IS NOT NULL)
EXECUTE FUNCTION update_parent_task_progress();
-- Add a comment to explain the fix
COMMENT ON FUNCTION update_parent_task_progress() IS
'This function recursively updates progress values for all ancestors when a task''s progress changes.
The previous version only updated the immediate parent, which led to incorrect progress values for
higher-level parent tasks when using weighted or manual progress calculations with multi-level subtasks.';
-- Add a function to immediately recalculate all task progress values in the correct order
-- This will fix existing data where parent tasks don't have proper progress values
CREATE OR REPLACE FUNCTION recalculate_all_task_progress() RETURNS void AS
$$
BEGIN
-- First, reset manual_progress flag for all tasks that have subtasks
UPDATE tasks AS t
SET manual_progress = FALSE
WHERE EXISTS (
SELECT 1
FROM tasks
WHERE parent_task_id = t.id
AND archived IS FALSE
);
-- Start recalculation from leaf tasks (no subtasks) and propagate upward
-- This ensures calculations are done in the right order
WITH RECURSIVE task_hierarchy AS (
-- Base case: Start with all leaf tasks (no subtasks)
SELECT
id,
parent_task_id,
0 AS level
FROM tasks
WHERE NOT EXISTS (
SELECT 1 FROM tasks AS sub
WHERE sub.parent_task_id = tasks.id
AND sub.archived IS FALSE
)
AND archived IS FALSE
UNION ALL
-- Recursive case: Move up to parent tasks, but only after processing all their children
SELECT
t.id,
t.parent_task_id,
th.level + 1
FROM tasks t
JOIN task_hierarchy th ON t.id = th.parent_task_id
WHERE t.archived IS FALSE
)
-- Sort by level to ensure we calculate in the right order (leaves first, then parents)
-- This ensures we're using already updated progress values
UPDATE tasks
SET progress_value = (SELECT (get_task_complete_ratio(tasks.id)->>'ratio')::FLOAT)
FROM (
SELECT id, level
FROM task_hierarchy
ORDER BY level
) AS ordered_tasks
WHERE tasks.id = ordered_tasks.id
AND (manual_progress IS FALSE OR manual_progress IS NULL);
-- Log the completion of the recalculation
RAISE NOTICE 'Finished recalculating all task progress values';
END;
$$ LANGUAGE plpgsql;
-- Execute the function to fix existing data
SELECT recalculate_all_task_progress();
COMMIT;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,300 @@
-- Fix Duplicate Sort Orders Script
-- This script detects and fixes duplicate sort order values that break task ordering
-- 1. DETECTION QUERIES - Run these first to see the scope of the problem
-- Check for duplicates in main sort_order column
SELECT
project_id,
sort_order,
COUNT(*) as duplicate_count,
STRING_AGG(id::text, ', ') as task_ids
FROM tasks
WHERE project_id IS NOT NULL
GROUP BY project_id, sort_order
HAVING COUNT(*) > 1
ORDER BY project_id, sort_order;
-- Check for duplicates in status_sort_order
SELECT
project_id,
status_sort_order,
COUNT(*) as duplicate_count,
STRING_AGG(id::text, ', ') as task_ids
FROM tasks
WHERE project_id IS NOT NULL
GROUP BY project_id, status_sort_order
HAVING COUNT(*) > 1
ORDER BY project_id, status_sort_order;
-- Check for duplicates in priority_sort_order
SELECT
project_id,
priority_sort_order,
COUNT(*) as duplicate_count,
STRING_AGG(id::text, ', ') as task_ids
FROM tasks
WHERE project_id IS NOT NULL
GROUP BY project_id, priority_sort_order
HAVING COUNT(*) > 1
ORDER BY project_id, priority_sort_order;
-- Check for duplicates in phase_sort_order
SELECT
project_id,
phase_sort_order,
COUNT(*) as duplicate_count,
STRING_AGG(id::text, ', ') as task_ids
FROM tasks
WHERE project_id IS NOT NULL
GROUP BY project_id, phase_sort_order
HAVING COUNT(*) > 1
ORDER BY project_id, phase_sort_order;
-- Note: member_sort_order removed - no longer used
-- 2. CLEANUP FUNCTIONS
-- Fix duplicates in main sort_order column
CREATE OR REPLACE FUNCTION fix_sort_order_duplicates() RETURNS void
LANGUAGE plpgsql
AS
$$
DECLARE
_project RECORD;
_task RECORD;
_counter INTEGER;
BEGIN
-- For each project, reassign sort_order values to ensure uniqueness
FOR _project IN
SELECT DISTINCT project_id
FROM tasks
WHERE project_id IS NOT NULL
LOOP
_counter := 0;
-- Reassign sort_order values sequentially for this project
FOR _task IN
SELECT id
FROM tasks
WHERE project_id = _project.project_id
ORDER BY sort_order, created_at
LOOP
UPDATE tasks
SET sort_order = _counter
WHERE id = _task.id;
_counter := _counter + 1;
END LOOP;
END LOOP;
RAISE NOTICE 'Fixed sort_order duplicates for all projects';
END
$$;
-- Fix duplicates in status_sort_order column
CREATE OR REPLACE FUNCTION fix_status_sort_order_duplicates() RETURNS void
LANGUAGE plpgsql
AS
$$
DECLARE
_project RECORD;
_task RECORD;
_counter INTEGER;
BEGIN
FOR _project IN
SELECT DISTINCT project_id
FROM tasks
WHERE project_id IS NOT NULL
LOOP
_counter := 0;
FOR _task IN
SELECT id
FROM tasks
WHERE project_id = _project.project_id
ORDER BY status_sort_order, created_at
LOOP
UPDATE tasks
SET status_sort_order = _counter
WHERE id = _task.id;
_counter := _counter + 1;
END LOOP;
END LOOP;
RAISE NOTICE 'Fixed status_sort_order duplicates for all projects';
END
$$;
-- Fix duplicates in priority_sort_order column
CREATE OR REPLACE FUNCTION fix_priority_sort_order_duplicates() RETURNS void
LANGUAGE plpgsql
AS
$$
DECLARE
_project RECORD;
_task RECORD;
_counter INTEGER;
BEGIN
FOR _project IN
SELECT DISTINCT project_id
FROM tasks
WHERE project_id IS NOT NULL
LOOP
_counter := 0;
FOR _task IN
SELECT id
FROM tasks
WHERE project_id = _project.project_id
ORDER BY priority_sort_order, created_at
LOOP
UPDATE tasks
SET priority_sort_order = _counter
WHERE id = _task.id;
_counter := _counter + 1;
END LOOP;
END LOOP;
RAISE NOTICE 'Fixed priority_sort_order duplicates for all projects';
END
$$;
-- Fix duplicates in phase_sort_order column
CREATE OR REPLACE FUNCTION fix_phase_sort_order_duplicates() RETURNS void
LANGUAGE plpgsql
AS
$$
DECLARE
_project RECORD;
_task RECORD;
_counter INTEGER;
BEGIN
FOR _project IN
SELECT DISTINCT project_id
FROM tasks
WHERE project_id IS NOT NULL
LOOP
_counter := 0;
FOR _task IN
SELECT id
FROM tasks
WHERE project_id = _project.project_id
ORDER BY phase_sort_order, created_at
LOOP
UPDATE tasks
SET phase_sort_order = _counter
WHERE id = _task.id;
_counter := _counter + 1;
END LOOP;
END LOOP;
RAISE NOTICE 'Fixed phase_sort_order duplicates for all projects';
END
$$;
-- Note: fix_member_sort_order_duplicates() removed - no longer needed
-- Master function to fix all sort order duplicates
CREATE OR REPLACE FUNCTION fix_all_duplicate_sort_orders() RETURNS void
LANGUAGE plpgsql
AS
$$
BEGIN
RAISE NOTICE 'Starting sort order cleanup for all columns...';
PERFORM fix_sort_order_duplicates();
PERFORM fix_status_sort_order_duplicates();
PERFORM fix_priority_sort_order_duplicates();
PERFORM fix_phase_sort_order_duplicates();
RAISE NOTICE 'Completed sort order cleanup for all columns';
END
$$;
-- 3. VERIFICATION FUNCTION
-- Verify that duplicates have been fixed
CREATE OR REPLACE FUNCTION verify_sort_order_integrity() RETURNS TABLE(
column_name text,
project_id uuid,
duplicate_count bigint,
status text
)
LANGUAGE plpgsql
AS
$$
BEGIN
-- Check sort_order duplicates
RETURN QUERY
SELECT
'sort_order'::text as column_name,
t.project_id,
COUNT(*) as duplicate_count,
CASE WHEN COUNT(*) > 1 THEN 'DUPLICATES FOUND' ELSE 'OK' END as status
FROM tasks t
WHERE t.project_id IS NOT NULL
GROUP BY t.project_id, t.sort_order
HAVING COUNT(*) > 1;
-- Check status_sort_order duplicates
RETURN QUERY
SELECT
'status_sort_order'::text as column_name,
t.project_id,
COUNT(*) as duplicate_count,
CASE WHEN COUNT(*) > 1 THEN 'DUPLICATES FOUND' ELSE 'OK' END as status
FROM tasks t
WHERE t.project_id IS NOT NULL
GROUP BY t.project_id, t.status_sort_order
HAVING COUNT(*) > 1;
-- Check priority_sort_order duplicates
RETURN QUERY
SELECT
'priority_sort_order'::text as column_name,
t.project_id,
COUNT(*) as duplicate_count,
CASE WHEN COUNT(*) > 1 THEN 'DUPLICATES FOUND' ELSE 'OK' END as status
FROM tasks t
WHERE t.project_id IS NOT NULL
GROUP BY t.project_id, t.priority_sort_order
HAVING COUNT(*) > 1;
-- Check phase_sort_order duplicates
RETURN QUERY
SELECT
'phase_sort_order'::text as column_name,
t.project_id,
COUNT(*) as duplicate_count,
CASE WHEN COUNT(*) > 1 THEN 'DUPLICATES FOUND' ELSE 'OK' END as status
FROM tasks t
WHERE t.project_id IS NOT NULL
GROUP BY t.project_id, t.phase_sort_order
HAVING COUNT(*) > 1;
-- Note: member_sort_order verification removed - column no longer used
END
$$;
-- 4. USAGE INSTRUCTIONS
/*
USAGE:
1. First, run the detection queries to see which projects have duplicates
2. Then run this to fix all duplicates:
SELECT fix_all_duplicate_sort_orders();
3. Finally, verify the fix worked:
SELECT * FROM verify_sort_order_integrity();
If verification returns no rows, all duplicates have been fixed successfully.
WARNING: This will reassign sort order values based on current order + creation time.
Make sure to backup your database before running these functions.
*/

View File

@@ -0,0 +1,37 @@
-- Migration: Add separate sort order columns for different grouping types
-- This allows users to maintain different task orders when switching between grouping views
-- Add new sort order columns
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS status_sort_order INTEGER DEFAULT 0;
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS priority_sort_order INTEGER DEFAULT 0;
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS phase_sort_order INTEGER DEFAULT 0;
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS member_sort_order INTEGER DEFAULT 0;
-- Initialize new columns with current sort_order values
UPDATE tasks SET
status_sort_order = sort_order,
priority_sort_order = sort_order,
phase_sort_order = sort_order,
member_sort_order = sort_order
WHERE status_sort_order = 0
OR priority_sort_order = 0
OR phase_sort_order = 0
OR member_sort_order = 0;
-- Add constraints to ensure non-negative values
ALTER TABLE tasks ADD CONSTRAINT tasks_status_sort_order_check CHECK (status_sort_order >= 0);
ALTER TABLE tasks ADD CONSTRAINT tasks_priority_sort_order_check CHECK (priority_sort_order >= 0);
ALTER TABLE tasks ADD CONSTRAINT tasks_phase_sort_order_check CHECK (phase_sort_order >= 0);
ALTER TABLE tasks ADD CONSTRAINT tasks_member_sort_order_check CHECK (member_sort_order >= 0);
-- Add indexes for performance (since these will be used for ordering)
CREATE INDEX IF NOT EXISTS idx_tasks_status_sort_order ON tasks(project_id, status_sort_order);
CREATE INDEX IF NOT EXISTS idx_tasks_priority_sort_order ON tasks(project_id, priority_sort_order);
CREATE INDEX IF NOT EXISTS idx_tasks_phase_sort_order ON tasks(project_id, phase_sort_order);
CREATE INDEX IF NOT EXISTS idx_tasks_member_sort_order ON tasks(project_id, member_sort_order);
-- Update comments for documentation
COMMENT ON COLUMN tasks.status_sort_order IS 'Sort order when grouped by status';
COMMENT ON COLUMN tasks.priority_sort_order IS 'Sort order when grouped by priority';
COMMENT ON COLUMN tasks.phase_sort_order IS 'Sort order when grouped by phase';
COMMENT ON COLUMN tasks.member_sort_order IS 'Sort order when grouped by members/assignees';

View File

@@ -0,0 +1,172 @@
-- Migration: Update database functions to handle grouping-specific sort orders
-- Function to get the appropriate sort column name based on grouping type
CREATE OR REPLACE FUNCTION get_sort_column_name(_group_by TEXT) RETURNS TEXT
LANGUAGE plpgsql
AS
$$
BEGIN
CASE _group_by
WHEN 'status' THEN RETURN 'status_sort_order';
WHEN 'priority' THEN RETURN 'priority_sort_order';
WHEN 'phase' THEN RETURN 'phase_sort_order';
WHEN 'members' THEN RETURN 'member_sort_order';
ELSE RETURN 'sort_order'; -- fallback to general sort_order
END CASE;
END;
$$;
-- Updated bulk sort order function to handle different sort columns
CREATE OR REPLACE FUNCTION update_task_sort_orders_bulk(_updates json, _group_by text DEFAULT 'status') RETURNS void
LANGUAGE plpgsql
AS
$$
DECLARE
_update_record RECORD;
_sort_column TEXT;
_sql TEXT;
BEGIN
-- Get the appropriate sort column based on grouping
_sort_column := get_sort_column_name(_group_by);
-- Simple approach: update each task's sort_order from the provided array
FOR _update_record IN
SELECT
(item->>'task_id')::uuid as task_id,
(item->>'sort_order')::int as sort_order,
(item->>'status_id')::uuid as status_id,
(item->>'priority_id')::uuid as priority_id,
(item->>'phase_id')::uuid as phase_id
FROM json_array_elements(_updates) as item
LOOP
-- Update the appropriate sort column and other fields using dynamic SQL
-- Only update sort_order if we're using the default sorting
IF _sort_column = 'sort_order' THEN
UPDATE tasks SET
sort_order = _update_record.sort_order,
status_id = COALESCE(_update_record.status_id, status_id),
priority_id = COALESCE(_update_record.priority_id, priority_id)
WHERE id = _update_record.task_id;
ELSE
-- Update only the grouping-specific sort column, not the main sort_order
_sql := 'UPDATE tasks SET ' || _sort_column || ' = $1, ' ||
'status_id = COALESCE($2, status_id), ' ||
'priority_id = COALESCE($3, priority_id) ' ||
'WHERE id = $4';
EXECUTE _sql USING
_update_record.sort_order,
_update_record.status_id,
_update_record.priority_id,
_update_record.task_id;
END IF;
-- Handle phase updates separately since it's in a different table
IF _update_record.phase_id IS NOT NULL THEN
INSERT INTO task_phase (task_id, phase_id)
VALUES (_update_record.task_id, _update_record.phase_id)
ON CONFLICT (task_id) DO UPDATE SET phase_id = _update_record.phase_id;
END IF;
END LOOP;
END;
$$;
-- Updated main sort order change handler
CREATE OR REPLACE FUNCTION handle_task_list_sort_order_change(_body json) RETURNS void
LANGUAGE plpgsql
AS
$$
DECLARE
_from_index INT;
_to_index INT;
_task_id UUID;
_project_id UUID;
_from_group UUID;
_to_group UUID;
_group_by TEXT;
_batch_size INT := 100;
_sort_column TEXT;
_sql TEXT;
BEGIN
_project_id = (_body ->> 'project_id')::UUID;
_task_id = (_body ->> 'task_id')::UUID;
_from_index = (_body ->> 'from_index')::INT;
_to_index = (_body ->> 'to_index')::INT;
_from_group = (_body ->> 'from_group')::UUID;
_to_group = (_body ->> 'to_group')::UUID;
_group_by = (_body ->> 'group_by')::TEXT;
-- Get the appropriate sort column
_sort_column := get_sort_column_name(_group_by);
-- Handle group changes
IF (_from_group <> _to_group OR (_from_group <> _to_group) IS NULL) THEN
IF (_group_by = 'status') THEN
UPDATE tasks
SET status_id = _to_group
WHERE id = _task_id
AND status_id = _from_group
AND project_id = _project_id;
END IF;
IF (_group_by = 'priority') THEN
UPDATE tasks
SET priority_id = _to_group
WHERE id = _task_id
AND priority_id = _from_group
AND project_id = _project_id;
END IF;
IF (_group_by = 'phase') THEN
IF (is_null_or_empty(_to_group) IS FALSE) THEN
INSERT INTO task_phase (task_id, phase_id)
VALUES (_task_id, _to_group)
ON CONFLICT (task_id) DO UPDATE SET phase_id = _to_group;
ELSE
DELETE FROM task_phase WHERE task_id = _task_id;
END IF;
END IF;
END IF;
-- Handle sort order changes using dynamic SQL
IF (_from_index <> _to_index) THEN
-- For the main sort_order column, we need to be careful about unique constraints
IF _sort_column = 'sort_order' THEN
-- Use a transaction-safe approach for the main sort_order column
IF (_to_index > _from_index) THEN
-- Moving down: decrease sort_order for items between old and new position
UPDATE tasks SET sort_order = sort_order - 1
WHERE project_id = _project_id
AND sort_order > _from_index
AND sort_order <= _to_index;
ELSE
-- Moving up: increase sort_order for items between new and old position
UPDATE tasks SET sort_order = sort_order + 1
WHERE project_id = _project_id
AND sort_order >= _to_index
AND sort_order < _from_index;
END IF;
-- Set the new sort_order for the moved task
UPDATE tasks SET sort_order = _to_index WHERE id = _task_id;
ELSE
-- For grouping-specific columns, use dynamic SQL since there's no unique constraint
IF (_to_index > _from_index) THEN
-- Moving down: decrease sort_order for items between old and new position
_sql := 'UPDATE tasks SET ' || _sort_column || ' = ' || _sort_column || ' - 1 ' ||
'WHERE project_id = $1 AND ' || _sort_column || ' > $2 AND ' || _sort_column || ' <= $3';
EXECUTE _sql USING _project_id, _from_index, _to_index;
ELSE
-- Moving up: increase sort_order for items between new and old position
_sql := 'UPDATE tasks SET ' || _sort_column || ' = ' || _sort_column || ' + 1 ' ||
'WHERE project_id = $1 AND ' || _sort_column || ' >= $2 AND ' || _sort_column || ' < $3';
EXECUTE _sql USING _project_id, _to_index, _from_index;
END IF;
-- Set the new sort_order for the moved task
_sql := 'UPDATE tasks SET ' || _sort_column || ' = $1 WHERE id = $2';
EXECUTE _sql USING _to_index, _task_id;
END IF;
END IF;
END;
$$;

View File

@@ -0,0 +1,179 @@
-- Migration: Fix sort order constraint violations
-- First, let's ensure all existing tasks have unique sort_order values within each project
-- This is a one-time fix to ensure data consistency
DO $$
DECLARE
_project RECORD;
_task RECORD;
_counter INTEGER;
BEGIN
-- For each project, reassign sort_order values to ensure uniqueness
FOR _project IN
SELECT DISTINCT project_id
FROM tasks
WHERE project_id IS NOT NULL
LOOP
_counter := 0;
-- Reassign sort_order values sequentially for this project
FOR _task IN
SELECT id
FROM tasks
WHERE project_id = _project.project_id
ORDER BY sort_order, created_at
LOOP
UPDATE tasks
SET sort_order = _counter
WHERE id = _task.id;
_counter := _counter + 1;
END LOOP;
END LOOP;
END
$$;
-- Now create a better version of our functions that properly handles the constraints
-- Updated bulk sort order function that avoids sort_order conflicts
CREATE OR REPLACE FUNCTION update_task_sort_orders_bulk(_updates json, _group_by text DEFAULT 'status') RETURNS void
LANGUAGE plpgsql
AS
$$
DECLARE
_update_record RECORD;
_sort_column TEXT;
_sql TEXT;
BEGIN
-- Get the appropriate sort column based on grouping
_sort_column := get_sort_column_name(_group_by);
-- Process each update record
FOR _update_record IN
SELECT
(item->>'task_id')::uuid as task_id,
(item->>'sort_order')::int as sort_order,
(item->>'status_id')::uuid as status_id,
(item->>'priority_id')::uuid as priority_id,
(item->>'phase_id')::uuid as phase_id
FROM json_array_elements(_updates) as item
LOOP
-- Update the grouping-specific sort column and other fields
_sql := 'UPDATE tasks SET ' || _sort_column || ' = $1, ' ||
'status_id = COALESCE($2, status_id), ' ||
'priority_id = COALESCE($3, priority_id), ' ||
'updated_at = CURRENT_TIMESTAMP ' ||
'WHERE id = $4';
EXECUTE _sql USING
_update_record.sort_order,
_update_record.status_id,
_update_record.priority_id,
_update_record.task_id;
-- Handle phase updates separately since it's in a different table
IF _update_record.phase_id IS NOT NULL THEN
INSERT INTO task_phase (task_id, phase_id)
VALUES (_update_record.task_id, _update_record.phase_id)
ON CONFLICT (task_id) DO UPDATE SET phase_id = _update_record.phase_id;
END IF;
END LOOP;
END;
$$;
-- Also update the helper function to be more explicit
CREATE OR REPLACE FUNCTION get_sort_column_name(_group_by TEXT) RETURNS TEXT
LANGUAGE plpgsql
AS
$$
BEGIN
CASE _group_by
WHEN 'status' THEN RETURN 'status_sort_order';
WHEN 'priority' THEN RETURN 'priority_sort_order';
WHEN 'phase' THEN RETURN 'phase_sort_order';
WHEN 'members' THEN RETURN 'member_sort_order';
-- For backward compatibility, still support general sort_order but be explicit
WHEN 'general' THEN RETURN 'sort_order';
ELSE RETURN 'status_sort_order'; -- Default to status sorting
END CASE;
END;
$$;
-- Updated main sort order change handler that avoids conflicts
CREATE OR REPLACE FUNCTION handle_task_list_sort_order_change(_body json) RETURNS void
LANGUAGE plpgsql
AS
$$
DECLARE
_from_index INT;
_to_index INT;
_task_id UUID;
_project_id UUID;
_from_group UUID;
_to_group UUID;
_group_by TEXT;
_sort_column TEXT;
_sql TEXT;
BEGIN
_project_id = (_body ->> 'project_id')::UUID;
_task_id = (_body ->> 'task_id')::UUID;
_from_index = (_body ->> 'from_index')::INT;
_to_index = (_body ->> 'to_index')::INT;
_from_group = (_body ->> 'from_group')::UUID;
_to_group = (_body ->> 'to_group')::UUID;
_group_by = (_body ->> 'group_by')::TEXT;
-- Get the appropriate sort column
_sort_column := get_sort_column_name(_group_by);
-- Handle group changes first
IF (_from_group <> _to_group OR (_from_group <> _to_group) IS NULL) THEN
IF (_group_by = 'status') THEN
UPDATE tasks
SET status_id = _to_group, updated_at = CURRENT_TIMESTAMP
WHERE id = _task_id
AND project_id = _project_id;
END IF;
IF (_group_by = 'priority') THEN
UPDATE tasks
SET priority_id = _to_group, updated_at = CURRENT_TIMESTAMP
WHERE id = _task_id
AND project_id = _project_id;
END IF;
IF (_group_by = 'phase') THEN
IF (is_null_or_empty(_to_group) IS FALSE) THEN
INSERT INTO task_phase (task_id, phase_id)
VALUES (_task_id, _to_group)
ON CONFLICT (task_id) DO UPDATE SET phase_id = _to_group;
ELSE
DELETE FROM task_phase WHERE task_id = _task_id;
END IF;
END IF;
END IF;
-- Handle sort order changes for the grouping-specific column only
IF (_from_index <> _to_index) THEN
-- Update the grouping-specific sort order (no unique constraint issues)
IF (_to_index > _from_index) THEN
-- Moving down: decrease sort order for items between old and new position
_sql := 'UPDATE tasks SET ' || _sort_column || ' = ' || _sort_column || ' - 1, ' ||
'updated_at = CURRENT_TIMESTAMP ' ||
'WHERE project_id = $1 AND ' || _sort_column || ' > $2 AND ' || _sort_column || ' <= $3';
EXECUTE _sql USING _project_id, _from_index, _to_index;
ELSE
-- Moving up: increase sort order for items between new and old position
_sql := 'UPDATE tasks SET ' || _sort_column || ' = ' || _sort_column || ' + 1, ' ||
'updated_at = CURRENT_TIMESTAMP ' ||
'WHERE project_id = $1 AND ' || _sort_column || ' >= $2 AND ' || _sort_column || ' < $3';
EXECUTE _sql USING _project_id, _to_index, _from_index;
END IF;
-- Set the new sort order for the moved task
_sql := 'UPDATE tasks SET ' || _sort_column || ' = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2';
EXECUTE _sql USING _to_index, _task_id;
END IF;
END;
$$;

View File

@@ -0,0 +1,93 @@
-- Migration: Add survey tables for account setup questionnaire
-- Date: 2025-07-24
-- Description: Creates tables to store survey questions and user responses for account setup flow
BEGIN;
-- Create surveys table to define different types of surveys
CREATE TABLE IF NOT EXISTS surveys (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
description TEXT,
survey_type VARCHAR(50) DEFAULT 'account_setup' NOT NULL, -- 'account_setup', 'onboarding', 'feedback'
is_active BOOLEAN DEFAULT TRUE NOT NULL,
created_at TIMESTAMP DEFAULT now() NOT NULL,
updated_at TIMESTAMP DEFAULT now() NOT NULL
);
-- Create survey_questions table to store individual questions
CREATE TABLE IF NOT EXISTS survey_questions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
survey_id UUID REFERENCES surveys(id) ON DELETE CASCADE NOT NULL,
question_key VARCHAR(100) NOT NULL, -- Used for localization keys
question_type VARCHAR(50) NOT NULL, -- 'single_choice', 'multiple_choice', 'text'
is_required BOOLEAN DEFAULT FALSE NOT NULL,
sort_order INTEGER DEFAULT 0 NOT NULL,
options JSONB, -- For choice questions, store options as JSON array
created_at TIMESTAMP DEFAULT now() NOT NULL,
updated_at TIMESTAMP DEFAULT now() NOT NULL
);
-- Create survey_responses table to track user responses to surveys
CREATE TABLE IF NOT EXISTS survey_responses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
survey_id UUID REFERENCES surveys(id) ON DELETE CASCADE NOT NULL,
user_id UUID REFERENCES users(id) ON DELETE CASCADE NOT NULL,
is_completed BOOLEAN DEFAULT FALSE NOT NULL,
started_at TIMESTAMP DEFAULT now() NOT NULL,
completed_at TIMESTAMP,
created_at TIMESTAMP DEFAULT now() NOT NULL,
updated_at TIMESTAMP DEFAULT now() NOT NULL
);
-- Create survey_answers table to store individual question answers
CREATE TABLE IF NOT EXISTS survey_answers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
response_id UUID REFERENCES survey_responses(id) ON DELETE CASCADE NOT NULL,
question_id UUID REFERENCES survey_questions(id) ON DELETE CASCADE NOT NULL,
answer_text TEXT,
answer_json JSONB, -- For multiple choice answers stored as array
created_at TIMESTAMP DEFAULT now() NOT NULL,
updated_at TIMESTAMP DEFAULT now() NOT NULL
);
-- Add performance indexes
CREATE INDEX IF NOT EXISTS idx_surveys_type_active ON surveys(survey_type, is_active);
CREATE INDEX IF NOT EXISTS idx_survey_questions_survey_order ON survey_questions(survey_id, sort_order);
CREATE INDEX IF NOT EXISTS idx_survey_responses_user_survey ON survey_responses(user_id, survey_id);
CREATE INDEX IF NOT EXISTS idx_survey_responses_completed ON survey_responses(survey_id, is_completed);
CREATE INDEX IF NOT EXISTS idx_survey_answers_response ON survey_answers(response_id);
-- Add constraints
ALTER TABLE survey_questions ADD CONSTRAINT survey_questions_sort_order_check CHECK (sort_order >= 0);
ALTER TABLE survey_questions ADD CONSTRAINT survey_questions_type_check CHECK (question_type IN ('single_choice', 'multiple_choice', 'text'));
-- Add unique constraint to prevent duplicate responses per user per survey
ALTER TABLE survey_responses ADD CONSTRAINT unique_user_survey_response UNIQUE (user_id, survey_id);
-- Add unique constraint to prevent duplicate answers per question per response
ALTER TABLE survey_answers ADD CONSTRAINT unique_response_question_answer UNIQUE (response_id, question_id);
-- Insert the default account setup survey
INSERT INTO surveys (name, description, survey_type, is_active) VALUES
('Account Setup Survey', 'Initial questionnaire during account setup to understand user needs', 'account_setup', true)
ON CONFLICT DO NOTHING;
-- Get the survey ID for inserting questions
DO $$
DECLARE
survey_uuid UUID;
BEGIN
SELECT id INTO survey_uuid FROM surveys WHERE survey_type = 'account_setup' AND name = 'Account Setup Survey' LIMIT 1;
-- Insert survey questions
INSERT INTO survey_questions (survey_id, question_key, question_type, is_required, sort_order, options) VALUES
(survey_uuid, 'organization_type', 'single_choice', true, 1, '["freelancer", "startup", "small_medium_business", "agency", "enterprise", "other"]'),
(survey_uuid, 'user_role', 'single_choice', true, 2, '["founder_ceo", "project_manager", "software_developer", "designer", "operations", "other"]'),
(survey_uuid, 'main_use_cases', 'multiple_choice', true, 3, '["task_management", "team_collaboration", "resource_planning", "client_communication", "time_tracking", "other"]'),
(survey_uuid, 'previous_tools', 'text', false, 4, null),
(survey_uuid, 'how_heard_about', 'single_choice', false, 5, '["google_search", "twitter", "linkedin", "friend_colleague", "blog_article", "other"]')
ON CONFLICT DO NOTHING;
END $$;
COMMIT;

View File

@@ -0,0 +1,72 @@
# Node-pg-migrate Migrations
This directory contains database migrations managed by node-pg-migrate.
## Migration Commands
- `npm run migrate:create -- migration-name` - Create a new migration file
- `npm run migrate:up` - Run all pending migrations
- `npm run migrate:down` - Rollback the last migration
- `npm run migrate:redo` - Rollback and re-run the last migration
## Migration File Format
Migrations are JavaScript files with timestamp prefixes (e.g., `20250115000000_performance-indexes.js`).
Each migration file exports two functions:
- `exports.up` - Contains the forward migration logic
- `exports.down` - Contains the rollback logic
## Best Practices
1. **Always use IF EXISTS/IF NOT EXISTS checks** to make migrations idempotent
2. **Test migrations locally** before deploying to production
3. **Include rollback logic** in the `down` function for all changes
4. **Use descriptive names** for migration files
5. **Keep migrations focused** - one logical change per migration
## Example Migration
```javascript
exports.up = pgm => {
// Create table with IF NOT EXISTS
pgm.createTable('users', {
id: 'id',
name: { type: 'varchar(100)', notNull: true },
created_at: {
type: 'timestamp',
notNull: true,
default: pgm.func('current_timestamp')
}
}, { ifNotExists: true });
// Add index with IF NOT EXISTS
pgm.createIndex('users', 'name', {
name: 'idx_users_name',
ifNotExists: true
});
};
exports.down = pgm => {
// Drop in reverse order
pgm.dropIndex('users', 'name', {
name: 'idx_users_name',
ifExists: true
});
pgm.dropTable('users', { ifExists: true });
};
```
## Migration History
The `pgmigrations` table tracks which migrations have been run. Do not modify this table manually.
## Converting from SQL Migrations
When converting SQL migrations to node-pg-migrate format:
1. Wrap SQL statements in `pgm.sql()` calls
2. Use node-pg-migrate helper methods where possible (createTable, addColumns, etc.)
3. Always include `IF EXISTS/IF NOT EXISTS` checks
4. Ensure proper rollback logic in the `down` function

View File

@@ -12,7 +12,7 @@ CREATE TYPE DEPENDENCY_TYPE AS ENUM ('blocked_by');
CREATE TYPE SCHEDULE_TYPE AS ENUM ('daily', 'weekly', 'yearly', 'monthly', 'every_x_days', 'every_x_weeks', 'every_x_months');
CREATE TYPE LANGUAGE_TYPE AS ENUM ('en', 'es', 'pt');
CREATE TYPE LANGUAGE_TYPE AS ENUM ('en', 'es', 'pt', 'alb', 'de', 'zh_cn');
-- START: Users
CREATE SEQUENCE IF NOT EXISTS users_user_no_seq START 1;
@@ -1391,27 +1391,30 @@ ALTER TABLE task_work_log
CHECK (time_spent >= (0)::NUMERIC);
CREATE TABLE IF NOT EXISTS tasks (
id UUID DEFAULT uuid_generate_v4() NOT NULL,
name TEXT NOT NULL,
description TEXT,
done BOOLEAN DEFAULT FALSE NOT NULL,
total_minutes NUMERIC DEFAULT 0 NOT NULL,
archived BOOLEAN DEFAULT FALSE NOT NULL,
task_no BIGINT NOT NULL,
start_date TIMESTAMP WITH TIME ZONE,
end_date TIMESTAMP WITH TIME ZONE,
priority_id UUID NOT NULL,
project_id UUID NOT NULL,
reporter_id UUID NOT NULL,
parent_task_id UUID,
status_id UUID NOT NULL,
completed_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
sort_order INTEGER DEFAULT 0 NOT NULL,
roadmap_sort_order INTEGER DEFAULT 0 NOT NULL,
billable BOOLEAN DEFAULT TRUE,
schedule_id UUID
id UUID DEFAULT uuid_generate_v4() NOT NULL,
name TEXT NOT NULL,
description TEXT,
done BOOLEAN DEFAULT FALSE NOT NULL,
total_minutes NUMERIC DEFAULT 0 NOT NULL,
archived BOOLEAN DEFAULT FALSE NOT NULL,
task_no BIGINT NOT NULL,
start_date TIMESTAMP WITH TIME ZONE,
end_date TIMESTAMP WITH TIME ZONE,
priority_id UUID NOT NULL,
project_id UUID NOT NULL,
reporter_id UUID NOT NULL,
parent_task_id UUID,
status_id UUID NOT NULL,
completed_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
sort_order INTEGER DEFAULT 0 NOT NULL,
roadmap_sort_order INTEGER DEFAULT 0 NOT NULL,
status_sort_order INTEGER DEFAULT 0 NOT NULL,
priority_sort_order INTEGER DEFAULT 0 NOT NULL,
phase_sort_order INTEGER DEFAULT 0 NOT NULL,
billable BOOLEAN DEFAULT TRUE,
schedule_id UUID
);
ALTER TABLE tasks
@@ -1499,6 +1502,21 @@ ALTER TABLE tasks
ADD CONSTRAINT tasks_total_minutes_check
CHECK ((total_minutes >= (0)::NUMERIC) AND (total_minutes <= (999999)::NUMERIC));
-- Add constraints for new sort order columns
ALTER TABLE tasks ADD CONSTRAINT tasks_status_sort_order_check CHECK (status_sort_order >= 0);
ALTER TABLE tasks ADD CONSTRAINT tasks_priority_sort_order_check CHECK (priority_sort_order >= 0);
ALTER TABLE tasks ADD CONSTRAINT tasks_phase_sort_order_check CHECK (phase_sort_order >= 0);
-- Add indexes for performance on new sort order columns
CREATE INDEX IF NOT EXISTS idx_tasks_status_sort_order ON tasks(project_id, status_sort_order);
CREATE INDEX IF NOT EXISTS idx_tasks_priority_sort_order ON tasks(project_id, priority_sort_order);
CREATE INDEX IF NOT EXISTS idx_tasks_phase_sort_order ON tasks(project_id, phase_sort_order);
-- Add comments for documentation
COMMENT ON COLUMN tasks.status_sort_order IS 'Sort order when grouped by status';
COMMENT ON COLUMN tasks.priority_sort_order IS 'Sort order when grouped by priority';
COMMENT ON COLUMN tasks.phase_sort_order IS 'Sort order when grouped by phase';
CREATE TABLE IF NOT EXISTS tasks_assignees (
task_id UUID NOT NULL,
project_member_id UUID NOT NULL,

View File

@@ -32,3 +32,37 @@ SELECT u.avatar_url,
FROM team_members
LEFT JOIN users u ON team_members.user_id = u.id;
-- PERFORMANCE OPTIMIZATION: Create materialized view for team member info
-- This pre-calculates the expensive joins and subqueries from team_member_info_view
CREATE MATERIALIZED VIEW IF NOT EXISTS team_member_info_mv AS
SELECT
u.avatar_url,
COALESCE(u.email, ei.email) AS email,
COALESCE(u.name, ei.name) AS name,
u.id AS user_id,
tm.id AS team_member_id,
tm.team_id,
tm.active,
u.socket_id
FROM team_members tm
LEFT JOIN users u ON tm.user_id = u.id
LEFT JOIN email_invitations ei ON ei.team_member_id = tm.id
WHERE tm.active = TRUE;
-- Create unique index on the materialized view for fast lookups
CREATE UNIQUE INDEX IF NOT EXISTS idx_team_member_info_mv_team_member_id
ON team_member_info_mv(team_member_id);
CREATE INDEX IF NOT EXISTS idx_team_member_info_mv_team_user
ON team_member_info_mv(team_id, user_id);
-- Function to refresh the materialized view
CREATE OR REPLACE FUNCTION refresh_team_member_info_mv()
RETURNS void
LANGUAGE plpgsql
AS $$
BEGIN
REFRESH MATERIALIZED VIEW CONCURRENTLY team_member_info_mv;
END;
$$;

View File

@@ -3351,15 +3351,15 @@ BEGIN
SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON)
FROM (SELECT team_member_id,
project_member_id,
(SELECT name FROM team_member_info_view WHERE team_member_info_view.team_member_id = tm.id),
(SELECT email_notifications_enabled
COALESCE((SELECT name FROM team_member_info_view WHERE team_member_info_view.team_member_id = tm.id), '') as name,
COALESCE((SELECT email_notifications_enabled
FROM notification_settings
WHERE team_id = tm.team_id
AND notification_settings.user_id = u.id) AS email_notifications_enabled,
u.avatar_url,
AND notification_settings.user_id = u.id), false) AS email_notifications_enabled,
COALESCE(u.avatar_url, '') as avatar_url,
u.id AS user_id,
u.email,
u.socket_id AS socket_id,
COALESCE(u.email, '') as email,
COALESCE(u.socket_id, '') as socket_id,
tm.team_id AS team_id
FROM tasks_assignees
INNER JOIN team_members tm ON tm.id = tasks_assignees.team_member_id
@@ -4066,14 +4066,14 @@ DECLARE
_schedule_id JSON;
_task_completed_at TIMESTAMPTZ;
BEGIN
SELECT name FROM tasks WHERE id = _task_id INTO _task_name;
SELECT COALESCE(name, '') FROM tasks WHERE id = _task_id INTO _task_name;
SELECT name
SELECT COALESCE(name, '')
FROM task_statuses
WHERE id = (SELECT status_id FROM tasks WHERE id = _task_id)
INTO _previous_status_name;
SELECT name FROM task_statuses WHERE id = _status_id INTO _new_status_name;
SELECT COALESCE(name, '') FROM task_statuses WHERE id = _status_id INTO _new_status_name;
IF (_previous_status_name != _new_status_name)
THEN
@@ -4081,14 +4081,22 @@ BEGIN
SELECT get_task_complete_info(_task_id, _status_id) INTO _task_info;
SELECT name FROM users WHERE id = _user_id INTO _updater_name;
SELECT COALESCE(name, '') FROM users WHERE id = _user_id INTO _updater_name;
_message = CONCAT(_updater_name, ' transitioned "', _task_name, '" from ', _previous_status_name, '',
_new_status_name);
END IF;
SELECT completed_at FROM tasks WHERE id = _task_id INTO _task_completed_at;
SELECT schedule_id FROM tasks WHERE id = _task_id INTO _schedule_id;
-- Handle schedule_id properly for recurring tasks
SELECT CASE
WHEN schedule_id IS NULL THEN 'null'::json
ELSE json_build_object('id', schedule_id)
END
FROM tasks
WHERE id = _task_id
INTO _schedule_id;
SELECT COALESCE(ROW_TO_JSON(r), '{}'::JSON)
FROM (SELECT is_done, is_doing, is_todo
@@ -4097,7 +4105,7 @@ BEGIN
INTO _status_category;
RETURN JSON_BUILD_OBJECT(
'message', _message,
'message', COALESCE(_message, ''),
'project_id', (SELECT project_id FROM tasks WHERE id = _task_id),
'parent_done', (CASE
WHEN EXISTS(SELECT 1
@@ -4105,14 +4113,14 @@ BEGIN
WHERE tasks_with_status_view.task_id = _task_id
AND is_done IS TRUE) THEN 1
ELSE 0 END),
'color_code', (_task_info ->> 'color_code')::TEXT,
'color_code_dark', (_task_info ->> 'color_code_dark')::TEXT,
'total_tasks', (_task_info ->> 'total_tasks')::INT,
'total_completed', (_task_info ->> 'total_completed')::INT,
'members', (_task_info ->> 'members')::JSON,
'color_code', COALESCE((_task_info ->> 'color_code')::TEXT, ''),
'color_code_dark', COALESCE((_task_info ->> 'color_code_dark')::TEXT, ''),
'total_tasks', COALESCE((_task_info ->> 'total_tasks')::INT, 0),
'total_completed', COALESCE((_task_info ->> 'total_completed')::INT, 0),
'members', COALESCE((_task_info ->> 'members')::JSON, '[]'::JSON),
'completed_at', _task_completed_at,
'status_category', _status_category,
'schedule_id', _schedule_id
'status_category', COALESCE(_status_category, '{}'::JSON),
'schedule_id', COALESCE(_schedule_id, 'null'::JSON)
);
END
$$;
@@ -4305,6 +4313,24 @@ BEGIN
END
$$;
-- Helper function to get the appropriate sort column name based on grouping type
CREATE OR REPLACE FUNCTION get_sort_column_name(_group_by TEXT) RETURNS TEXT
LANGUAGE plpgsql
AS
$$
BEGIN
CASE _group_by
WHEN 'status' THEN RETURN 'status_sort_order';
WHEN 'priority' THEN RETURN 'priority_sort_order';
WHEN 'phase' THEN RETURN 'phase_sort_order';
WHEN 'members' THEN RETURN 'member_sort_order';
-- For backward compatibility, still support general sort_order but be explicit
WHEN 'general' THEN RETURN 'sort_order';
ELSE RETURN 'status_sort_order'; -- Default to status sorting
END CASE;
END;
$$;
CREATE OR REPLACE FUNCTION handle_task_list_sort_order_change(_body json) RETURNS void
LANGUAGE plpgsql
AS
@@ -4317,54 +4343,67 @@ DECLARE
_from_group UUID;
_to_group UUID;
_group_by TEXT;
_sort_column TEXT;
_sql TEXT;
BEGIN
_project_id = (_body ->> 'project_id')::UUID;
_task_id = (_body ->> 'task_id')::UUID;
_from_index = (_body ->> 'from_index')::INT; -- from sort_order
_to_index = (_body ->> 'to_index')::INT; -- to sort_order
_from_index = (_body ->> 'from_index')::INT;
_to_index = (_body ->> 'to_index')::INT;
_from_group = (_body ->> 'from_group')::UUID;
_to_group = (_body ->> 'to_group')::UUID;
_group_by = (_body ->> 'group_by')::TEXT;
IF (_from_group <> _to_group OR (_from_group <> _to_group) IS NULL)
THEN
IF (_group_by = 'status')
THEN
UPDATE tasks SET status_id = _to_group WHERE id = _task_id AND status_id = _from_group;
-- Get the appropriate sort column
_sort_column := get_sort_column_name(_group_by);
-- Handle group changes first
IF (_from_group <> _to_group OR (_from_group <> _to_group) IS NULL) THEN
IF (_group_by = 'status') THEN
UPDATE tasks
SET status_id = _to_group, updated_at = CURRENT_TIMESTAMP
WHERE id = _task_id
AND project_id = _project_id;
END IF;
IF (_group_by = 'priority')
THEN
UPDATE tasks SET priority_id = _to_group WHERE id = _task_id AND priority_id = _from_group;
IF (_group_by = 'priority') THEN
UPDATE tasks
SET priority_id = _to_group, updated_at = CURRENT_TIMESTAMP
WHERE id = _task_id
AND project_id = _project_id;
END IF;
IF (_group_by = 'phase')
THEN
IF (is_null_or_empty(_to_group) IS FALSE)
THEN
IF (_group_by = 'phase') THEN
IF (is_null_or_empty(_to_group) IS FALSE) THEN
INSERT INTO task_phase (task_id, phase_id)
VALUES (_task_id, _to_group)
ON CONFLICT (task_id) DO UPDATE SET phase_id = _to_group;
END IF;
IF (is_null_or_empty(_to_group) IS TRUE)
THEN
DELETE
FROM task_phase
WHERE task_id = _task_id;
ELSE
DELETE FROM task_phase WHERE task_id = _task_id;
END IF;
END IF;
END IF;
IF ((_body ->> 'to_last_index')::BOOLEAN IS TRUE AND _from_index < _to_index)
THEN
PERFORM handle_task_list_sort_inside_group(_from_index, _to_index, _task_id, _project_id);
-- Handle sort order changes for the grouping-specific column only
IF (_from_index <> _to_index) THEN
-- Update the grouping-specific sort order (no unique constraint issues)
IF (_to_index > _from_index) THEN
-- Moving down: decrease sort order for items between old and new position
_sql := 'UPDATE tasks SET ' || _sort_column || ' = ' || _sort_column || ' - 1, ' ||
'updated_at = CURRENT_TIMESTAMP ' ||
'WHERE project_id = $1 AND ' || _sort_column || ' > $2 AND ' || _sort_column || ' <= $3';
EXECUTE _sql USING _project_id, _from_index, _to_index;
ELSE
PERFORM handle_task_list_sort_between_groups(_from_index, _to_index, _task_id, _project_id);
-- Moving up: increase sort order for items between new and old position
_sql := 'UPDATE tasks SET ' || _sort_column || ' = ' || _sort_column || ' + 1, ' ||
'updated_at = CURRENT_TIMESTAMP ' ||
'WHERE project_id = $1 AND ' || _sort_column || ' >= $2 AND ' || _sort_column || ' < $3';
EXECUTE _sql USING _project_id, _to_index, _from_index;
END IF;
ELSE
PERFORM handle_task_list_sort_inside_group(_from_index, _to_index, _task_id, _project_id);
-- Set the new sort order for the moved task
_sql := 'UPDATE tasks SET ' || _sort_column || ' = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2';
EXECUTE _sql USING _to_index, _task_id;
END IF;
END
$$;
@@ -4569,31 +4608,31 @@ BEGIN
INSERT INTO project_task_list_cols (project_id, name, key, index, pinned)
VALUES (_project_id, 'Progress', 'PROGRESS', 3, TRUE);
INSERT INTO project_task_list_cols (project_id, name, key, index, pinned)
VALUES (_project_id, 'Members', 'ASSIGNEES', 4, TRUE);
VALUES (_project_id, 'Status', 'STATUS', 4, TRUE);
INSERT INTO project_task_list_cols (project_id, name, key, index, pinned)
VALUES (_project_id, 'Labels', 'LABELS', 5, TRUE);
VALUES (_project_id, 'Members', 'ASSIGNEES', 5, TRUE);
INSERT INTO project_task_list_cols (project_id, name, key, index, pinned)
VALUES (_project_id, 'Status', 'STATUS', 6, TRUE);
VALUES (_project_id, 'Labels', 'LABELS', 6, TRUE);
INSERT INTO project_task_list_cols (project_id, name, key, index, pinned)
VALUES (_project_id, 'Priority', 'PRIORITY', 7, TRUE);
VALUES (_project_id, 'Phase', 'PHASE', 7, TRUE);
INSERT INTO project_task_list_cols (project_id, name, key, index, pinned)
VALUES (_project_id, 'Time Tracking', 'TIME_TRACKING', 8, TRUE);
VALUES (_project_id, 'Priority', 'PRIORITY', 8, TRUE);
INSERT INTO project_task_list_cols (project_id, name, key, index, pinned)
VALUES (_project_id, 'Estimation', 'ESTIMATION', 9, FALSE);
VALUES (_project_id, 'Time Tracking', 'TIME_TRACKING', 9, TRUE);
INSERT INTO project_task_list_cols (project_id, name, key, index, pinned)
VALUES (_project_id, 'Start Date', 'START_DATE', 10, FALSE);
VALUES (_project_id, 'Estimation', 'ESTIMATION', 10, FALSE);
INSERT INTO project_task_list_cols (project_id, name, key, index, pinned)
VALUES (_project_id, 'Due Date', 'DUE_DATE', 11, TRUE);
VALUES (_project_id, 'Start Date', 'START_DATE', 11, FALSE);
INSERT INTO project_task_list_cols (project_id, name, key, index, pinned)
VALUES (_project_id, 'Completed Date', 'COMPLETED_DATE', 12, FALSE);
VALUES (_project_id, 'Due Date', 'DUE_DATE', 12, TRUE);
INSERT INTO project_task_list_cols (project_id, name, key, index, pinned)
VALUES (_project_id, 'Created Date', 'CREATED_DATE', 13, FALSE);
VALUES (_project_id, 'Completed Date', 'COMPLETED_DATE', 13, FALSE);
INSERT INTO project_task_list_cols (project_id, name, key, index, pinned)
VALUES (_project_id, 'Last Updated', 'LAST_UPDATED', 14, FALSE);
VALUES (_project_id, 'Created Date', 'CREATED_DATE', 14, FALSE);
INSERT INTO project_task_list_cols (project_id, name, key, index, pinned)
VALUES (_project_id, 'Reporter', 'REPORTER', 15, FALSE);
VALUES (_project_id, 'Last Updated', 'LAST_UPDATED', 15, FALSE);
INSERT INTO project_task_list_cols (project_id, name, key, index, pinned)
VALUES (_project_id, 'Phase', 'PHASE', 16, FALSE);
VALUES (_project_id, 'Reporter', 'REPORTER', 16, FALSE);
END
$$;
@@ -5477,8 +5516,15 @@ $$
DECLARE
_iterator NUMERIC := 0;
_status_id TEXT;
_project_id UUID;
_base_sort_order NUMERIC;
BEGIN
-- Get the project_id from the first status to ensure we update all statuses in the same project
SELECT project_id INTO _project_id
FROM task_statuses
WHERE id = (SELECT TRIM(BOTH '"' FROM JSON_ARRAY_ELEMENTS(_status_ids)::TEXT) LIMIT 1)::UUID;
-- Update the sort_order for statuses in the provided order
FOR _status_id IN SELECT * FROM JSON_ARRAY_ELEMENTS((_status_ids)::JSON)
LOOP
UPDATE task_statuses
@@ -5487,6 +5533,29 @@ BEGIN
_iterator := _iterator + 1;
END LOOP;
-- Get the base sort order for remaining statuses (simple count approach)
SELECT COUNT(*) INTO _base_sort_order
FROM task_statuses ts2
WHERE ts2.project_id = _project_id
AND ts2.id = ANY(SELECT (TRIM(BOTH '"' FROM JSON_ARRAY_ELEMENTS(_status_ids)::TEXT))::UUID);
-- Update remaining statuses with simple sequential numbering
-- Reset iterator to start from base_sort_order
_iterator := _base_sort_order;
-- Use a cursor approach to avoid window functions
FOR _status_id IN
SELECT id::TEXT FROM task_statuses
WHERE project_id = _project_id
AND id NOT IN (SELECT (TRIM(BOTH '"' FROM JSON_ARRAY_ELEMENTS(_status_ids)::TEXT))::UUID)
ORDER BY sort_order
LOOP
UPDATE task_statuses
SET sort_order = _iterator
WHERE id = _status_id::UUID;
_iterator := _iterator + 1;
END LOOP;
RETURN;
END
$$;
@@ -6148,3 +6217,434 @@ BEGIN
RETURN v_new_id;
END;
$$;
CREATE OR REPLACE FUNCTION transfer_team_ownership(_team_id UUID, _new_owner_id UUID) RETURNS json
LANGUAGE plpgsql
AS
$$
DECLARE
_old_owner_id UUID;
_owner_role_id UUID;
_admin_role_id UUID;
_old_org_id UUID;
_new_org_id UUID;
_has_license BOOLEAN;
_old_owner_role_id UUID;
_new_owner_role_id UUID;
_has_active_coupon BOOLEAN;
_other_teams_count INTEGER;
_new_owner_org_id UUID;
_license_type_id UUID;
_has_valid_license BOOLEAN;
BEGIN
-- Get the current owner's ID and organization
SELECT t.user_id, t.organization_id
INTO _old_owner_id, _old_org_id
FROM teams t
WHERE t.id = _team_id;
IF _old_owner_id IS NULL THEN
RAISE EXCEPTION 'Team not found';
END IF;
-- Get the new owner's organization
SELECT organization_id INTO _new_owner_org_id
FROM organizations
WHERE user_id = _new_owner_id;
-- Get the old organization
SELECT id INTO _old_org_id
FROM organizations
WHERE id = _old_org_id;
IF _old_org_id IS NULL THEN
RAISE EXCEPTION 'Organization not found';
END IF;
-- Check if new owner has any valid license type
SELECT EXISTS (
SELECT 1
FROM (
-- Check regular subscriptions
SELECT lus.user_id, lus.status, lus.active
FROM licensing_user_subscriptions lus
WHERE lus.user_id = _new_owner_id
AND lus.active = TRUE
AND lus.status IN ('active', 'trialing')
UNION ALL
-- Check custom subscriptions
SELECT lcs.user_id, lcs.subscription_status as status, TRUE as active
FROM licensing_custom_subs lcs
WHERE lcs.user_id = _new_owner_id
AND lcs.end_date > CURRENT_DATE
UNION ALL
-- Check trial status in organizations
SELECT o.user_id, o.subscription_status as status, TRUE as active
FROM organizations o
WHERE o.user_id = _new_owner_id
AND o.trial_in_progress = TRUE
AND o.trial_expire_date > CURRENT_DATE
) valid_licenses
) INTO _has_valid_license;
IF NOT _has_valid_license THEN
RAISE EXCEPTION 'New owner does not have a valid license (subscription, custom subscription, or trial)';
END IF;
-- Check if new owner has any active coupon codes
SELECT EXISTS (
SELECT 1
FROM licensing_coupon_codes lcc
WHERE lcc.redeemed_by = _new_owner_id
AND lcc.is_redeemed = TRUE
AND lcc.is_refunded = FALSE
) INTO _has_active_coupon;
IF _has_active_coupon THEN
RAISE EXCEPTION 'New owner has active coupon codes that need to be handled before transfer';
END IF;
-- Count other teams in the organization for information purposes
SELECT COUNT(*) INTO _other_teams_count
FROM teams
WHERE organization_id = _old_org_id
AND id != _team_id;
-- If new owner has their own organization, move the team to their organization
IF _new_owner_org_id IS NOT NULL THEN
-- Update the team to use the new owner's organization
UPDATE teams
SET user_id = _new_owner_id,
organization_id = _new_owner_org_id
WHERE id = _team_id;
-- Create notification about organization change
PERFORM create_notification(
_old_owner_id,
_team_id,
NULL,
NULL,
CONCAT('Team <b>', (SELECT name FROM teams WHERE id = _team_id), '</b> has been moved to a different organization')
);
PERFORM create_notification(
_new_owner_id,
_team_id,
NULL,
NULL,
CONCAT('Team <b>', (SELECT name FROM teams WHERE id = _team_id), '</b> has been moved to your organization')
);
ELSE
-- If new owner doesn't have an organization, transfer the old organization to them
UPDATE organizations
SET user_id = _new_owner_id
WHERE id = _old_org_id;
-- Update the team to use the same organization
UPDATE teams
SET user_id = _new_owner_id,
organization_id = _old_org_id
WHERE id = _team_id;
-- Notify both users about organization ownership transfer
PERFORM create_notification(
_old_owner_id,
NULL,
NULL,
NULL,
CONCAT('You are no longer the owner of organization <b>', (SELECT organization_name FROM organizations WHERE id = _old_org_id), '</b>')
);
PERFORM create_notification(
_new_owner_id,
NULL,
NULL,
NULL,
CONCAT('You are now the owner of organization <b>', (SELECT organization_name FROM organizations WHERE id = _old_org_id), '</b>')
);
END IF;
-- Get the owner and admin role IDs
SELECT id INTO _owner_role_id FROM roles WHERE team_id = _team_id AND owner = TRUE;
SELECT id INTO _admin_role_id FROM roles WHERE team_id = _team_id AND admin_role = TRUE;
-- Get current role IDs for both users
SELECT role_id INTO _old_owner_role_id
FROM team_members
WHERE team_id = _team_id AND user_id = _old_owner_id;
SELECT role_id INTO _new_owner_role_id
FROM team_members
WHERE team_id = _team_id AND user_id = _new_owner_id;
-- Update the old owner's role to admin if they want to stay in the team
IF _old_owner_role_id IS NOT NULL THEN
UPDATE team_members
SET role_id = _admin_role_id
WHERE team_id = _team_id AND user_id = _old_owner_id;
END IF;
-- Update the new owner's role to owner
IF _new_owner_role_id IS NOT NULL THEN
UPDATE team_members
SET role_id = _owner_role_id
WHERE team_id = _team_id AND user_id = _new_owner_id;
ELSE
-- If new owner is not a team member yet, add them
INSERT INTO team_members (user_id, team_id, role_id)
VALUES (_new_owner_id, _team_id, _owner_role_id);
END IF;
-- Create notification for both users about team ownership
PERFORM create_notification(
_old_owner_id,
_team_id,
NULL,
NULL,
CONCAT('You are no longer the owner of team <b>', (SELECT name FROM teams WHERE id = _team_id), '</b>')
);
PERFORM create_notification(
_new_owner_id,
_team_id,
NULL,
NULL,
CONCAT('You are now the owner of team <b>', (SELECT name FROM teams WHERE id = _team_id), '</b>')
);
RETURN json_build_object(
'success', TRUE,
'old_owner_id', _old_owner_id,
'new_owner_id', _new_owner_id,
'team_id', _team_id,
'old_org_id', _old_org_id,
'new_org_id', COALESCE(_new_owner_org_id, _old_org_id),
'old_role_id', _old_owner_role_id,
'new_role_id', _new_owner_role_id,
'has_valid_license', _has_valid_license,
'has_active_coupon', _has_active_coupon,
'other_teams_count', _other_teams_count,
'org_ownership_transferred', _new_owner_org_id IS NULL,
'team_moved_to_new_org', _new_owner_org_id IS NOT NULL
);
END;
$$;
-- PERFORMANCE OPTIMIZATION: Optimized version with batching for large datasets
CREATE OR REPLACE FUNCTION handle_task_list_sort_between_groups_optimized(_from_index integer, _to_index integer, _task_id uuid, _project_id uuid, _batch_size integer DEFAULT 100) RETURNS void
LANGUAGE plpgsql
AS
$$
DECLARE
_offset INT := 0;
_affected_rows INT;
BEGIN
-- PERFORMANCE OPTIMIZATION: Use direct updates without CTE in UPDATE
IF (_to_index = -1)
THEN
_to_index = COALESCE((SELECT MAX(sort_order) + 1 FROM tasks WHERE project_id = _project_id), 0);
END IF;
-- PERFORMANCE OPTIMIZATION: Batch updates for large datasets
IF _to_index > _from_index
THEN
LOOP
UPDATE tasks
SET sort_order = sort_order - 1
WHERE project_id = _project_id
AND sort_order > _from_index
AND sort_order < _to_index
AND sort_order > _offset
AND sort_order <= _offset + _batch_size;
GET DIAGNOSTICS _affected_rows = ROW_COUNT;
EXIT WHEN _affected_rows = 0;
_offset := _offset + _batch_size;
END LOOP;
UPDATE tasks SET sort_order = _to_index - 1 WHERE id = _task_id AND project_id = _project_id;
END IF;
IF _to_index < _from_index
THEN
_offset := 0;
LOOP
UPDATE tasks
SET sort_order = sort_order + 1
WHERE project_id = _project_id
AND sort_order > _to_index
AND sort_order < _from_index
AND sort_order > _offset
AND sort_order <= _offset + _batch_size;
GET DIAGNOSTICS _affected_rows = ROW_COUNT;
EXIT WHEN _affected_rows = 0;
_offset := _offset + _batch_size;
END LOOP;
UPDATE tasks SET sort_order = _to_index + 1 WHERE id = _task_id AND project_id = _project_id;
END IF;
END
$$;
-- PERFORMANCE OPTIMIZATION: Optimized version with batching for large datasets
CREATE OR REPLACE FUNCTION handle_task_list_sort_inside_group_optimized(_from_index integer, _to_index integer, _task_id uuid, _project_id uuid, _batch_size integer DEFAULT 100) RETURNS void
LANGUAGE plpgsql
AS
$$
DECLARE
_offset INT := 0;
_affected_rows INT;
BEGIN
-- PERFORMANCE OPTIMIZATION: Batch updates for large datasets without CTE in UPDATE
IF _to_index > _from_index
THEN
LOOP
UPDATE tasks
SET sort_order = sort_order - 1
WHERE project_id = _project_id
AND sort_order > _from_index
AND sort_order <= _to_index
AND sort_order > _offset
AND sort_order <= _offset + _batch_size;
GET DIAGNOSTICS _affected_rows = ROW_COUNT;
EXIT WHEN _affected_rows = 0;
_offset := _offset + _batch_size;
END LOOP;
END IF;
IF _to_index < _from_index
THEN
_offset := 0;
LOOP
UPDATE tasks
SET sort_order = sort_order + 1
WHERE project_id = _project_id
AND sort_order >= _to_index
AND sort_order < _from_index
AND sort_order > _offset
AND sort_order <= _offset + _batch_size;
GET DIAGNOSTICS _affected_rows = ROW_COUNT;
EXIT WHEN _affected_rows = 0;
_offset := _offset + _batch_size;
END LOOP;
END IF;
UPDATE tasks SET sort_order = _to_index WHERE id = _task_id AND project_id = _project_id;
END
$$;
-- Updated bulk sort order function that avoids sort_order conflicts
CREATE OR REPLACE FUNCTION update_task_sort_orders_bulk(_updates json, _group_by text DEFAULT 'status') RETURNS void
LANGUAGE plpgsql
AS
$$
DECLARE
_update_record RECORD;
_sort_column TEXT;
_sql TEXT;
BEGIN
-- Get the appropriate sort column based on grouping
_sort_column := get_sort_column_name(_group_by);
-- Process each update record
FOR _update_record IN
SELECT
(item->>'task_id')::uuid as task_id,
(item->>'sort_order')::int as sort_order,
(item->>'status_id')::uuid as status_id,
(item->>'priority_id')::uuid as priority_id,
(item->>'phase_id')::uuid as phase_id
FROM json_array_elements(_updates) as item
LOOP
-- Update the grouping-specific sort column and other fields
_sql := 'UPDATE tasks SET ' || _sort_column || ' = $1, ' ||
'status_id = COALESCE($2, status_id), ' ||
'priority_id = COALESCE($3, priority_id), ' ||
'updated_at = CURRENT_TIMESTAMP ' ||
'WHERE id = $4';
EXECUTE _sql USING
_update_record.sort_order,
_update_record.status_id,
_update_record.priority_id,
_update_record.task_id;
-- Handle phase updates separately since it's in a different table
IF _update_record.phase_id IS NOT NULL THEN
INSERT INTO task_phase (task_id, phase_id)
VALUES (_update_record.task_id, _update_record.phase_id)
ON CONFLICT (task_id) DO UPDATE SET phase_id = _update_record.phase_id;
END IF;
END LOOP;
END
$$;
-- Function to get the appropriate sort column name based on grouping type
CREATE OR REPLACE FUNCTION get_sort_column_name(_group_by TEXT) RETURNS TEXT
LANGUAGE plpgsql
AS
$$
BEGIN
CASE _group_by
WHEN 'status' THEN RETURN 'status_sort_order';
WHEN 'priority' THEN RETURN 'priority_sort_order';
WHEN 'phase' THEN RETURN 'phase_sort_order';
-- For backward compatibility, still support general sort_order but be explicit
WHEN 'general' THEN RETURN 'sort_order';
ELSE RETURN 'status_sort_order'; -- Default to status sorting
END CASE;
END;
$$;
-- Updated bulk sort order function to handle different sort columns
CREATE OR REPLACE FUNCTION update_task_sort_orders_bulk(_updates json, _group_by text DEFAULT 'status') RETURNS void
LANGUAGE plpgsql
AS
$$
DECLARE
_update_record RECORD;
_sort_column TEXT;
_sql TEXT;
BEGIN
-- Get the appropriate sort column based on grouping
_sort_column := get_sort_column_name(_group_by);
-- Process each update record
FOR _update_record IN
SELECT
(item->>'task_id')::uuid as task_id,
(item->>'sort_order')::int as sort_order,
(item->>'status_id')::uuid as status_id,
(item->>'priority_id')::uuid as priority_id,
(item->>'phase_id')::uuid as phase_id
FROM json_array_elements(_updates) as item
LOOP
-- Update the grouping-specific sort column and other fields
_sql := 'UPDATE tasks SET ' || _sort_column || ' = $1, ' ||
'status_id = COALESCE($2, status_id), ' ||
'priority_id = COALESCE($3, priority_id), ' ||
'updated_at = CURRENT_TIMESTAMP ' ||
'WHERE id = $4';
EXECUTE _sql USING
_update_record.sort_order,
_update_record.status_id,
_update_record.priority_id,
_update_record.task_id;
-- Handle phase updates separately since it's in a different table
IF _update_record.phase_id IS NOT NULL THEN
INSERT INTO task_phase (task_id, phase_id)
VALUES (_update_record.task_id, _update_record.phase_id)
ON CONFLICT (task_id) DO UPDATE SET phase_id = _update_record.phase_id;
END IF;
END LOOP;
END;
$$;

View File

@@ -132,3 +132,139 @@ CREATE INDEX IF NOT EXISTS projects_team_id_index
CREATE INDEX IF NOT EXISTS projects_team_id_name_index
ON projects (team_id, name);
-- Performance indexes for optimized tasks queries
-- From migration: 20250115000000-performance-indexes.sql
-- Composite index for main task filtering
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_project_archived_parent
ON tasks(project_id, archived, parent_task_id)
WHERE archived = FALSE;
-- Index for status joins
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_status_project
ON tasks(status_id, project_id)
WHERE archived = FALSE;
-- Index for assignees lookup
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_assignees_task_member
ON tasks_assignees(task_id, team_member_id);
-- Index for phase lookup
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_phase_task_phase
ON task_phase(task_id, phase_id);
-- Index for subtask counting
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_parent_archived
ON tasks(parent_task_id, archived)
WHERE parent_task_id IS NOT NULL AND archived = FALSE;
-- Index for labels
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_labels_task_label
ON task_labels(task_id, label_id);
-- Index for comments count
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_comments_task
ON task_comments(task_id);
-- Index for attachments count
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_attachments_task
ON task_attachments(task_id);
-- Index for work log aggregation
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_work_log_task
ON task_work_log(task_id);
-- Index for subscribers check
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_subscribers_task
ON task_subscribers(task_id);
-- Index for dependencies check
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_dependencies_task
ON task_dependencies(task_id);
-- Index for timers lookup
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_timers_task_user
ON task_timers(task_id, user_id);
-- Index for custom columns
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_cc_column_values_task
ON cc_column_values(task_id);
-- Index for team member info view optimization
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_team_members_team_user
ON team_members(team_id, user_id)
WHERE active = TRUE;
-- Index for notification settings
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_notification_settings_user_team
ON notification_settings(user_id, team_id);
-- Index for task status categories
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_statuses_category
ON task_statuses(category_id, project_id);
-- Index for project phases
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_project_phases_project_sort
ON project_phases(project_id, sort_index);
-- Index for task priorities
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_priorities_value
ON task_priorities(value);
-- Index for team labels
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_team_labels_team
ON team_labels(team_id);
-- Advanced performance indexes for task optimization
-- Composite index for task main query optimization (covers most WHERE conditions)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_performance_main
ON tasks(project_id, archived, parent_task_id, status_id, priority_id)
WHERE archived = FALSE;
-- Index for sorting by sort_order with project filter
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_project_sort_order
ON tasks(project_id, sort_order)
WHERE archived = FALSE;
-- Index for email_invitations to optimize team_member_info_view
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_email_invitations_team_member
ON email_invitations(team_member_id);
-- Covering index for task status with category information
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_statuses_covering
ON task_statuses(id, category_id, project_id);
-- Index for task aggregation queries (parent task progress calculation)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_parent_status_archived
ON tasks(parent_task_id, status_id, archived)
WHERE archived = FALSE;
-- Index for project team member filtering
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_team_members_project_lookup
ON team_members(team_id, active, user_id)
WHERE active = TRUE;
-- Covering index for tasks with frequently accessed columns
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_covering_main
ON tasks(id, project_id, archived, parent_task_id, status_id, priority_id, sort_order, name)
WHERE archived = FALSE;
-- Index for task search functionality
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_name_search
ON tasks USING gin(to_tsvector('english', name))
WHERE archived = FALSE;
-- Index for date-based filtering (if used)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_dates
ON tasks(project_id, start_date, end_date)
WHERE archived = FALSE;
-- Index for task timers with user filtering
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_timers_user_task
ON task_timers(user_id, task_id);
-- Index for sys_task_status_categories lookups
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_sys_task_status_categories_covering
ON sys_task_status_categories(id, color_code, color_code_dark, is_done, is_doing, is_todo);

View File

@@ -1,28 +0,0 @@
module.exports = {
brotli_js: {
options: {
mode: "brotli",
brotli: {
mode: 1
}
},
expand: true,
cwd: "build/public",
src: ["**/*.js"],
dest: "build/public",
extDot: "last",
ext: ".js.br"
},
gzip_js: {
options: {
mode: "gzip"
},
files: [{
expand: true,
cwd: "build/public",
src: ["**/*.js"],
dest: "build/public",
ext: ".js.gz"
}]
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -4,23 +4,37 @@
"private": true,
"engines": {
"npm": ">=8.11.0",
"node": ">=16.13.0",
"node": ">=20.0.0",
"yarn": "WARNING: Please use npm package manager instead of yarn"
},
"main": "build/bin/www",
"repository": "GITHUB_REPO_HERE",
"author": "worklenz.com",
"scripts": {
"start": "node ./build/bin/www",
"tcs": "grunt build:tsc",
"build": "grunt build",
"watch": "grunt watch",
"dev": "grunt dev",
"es": "esbuild `find src -type f -name '*.ts'` --platform=node --minify=true --watch=true --target=esnext --format=cjs --tsconfig=tsconfig.prod.json --outdir=dist",
"copy": "grunt copy",
"test": "jest",
"start": "node build/bin/www.js",
"dev": "npm run build:dev && npm run watch",
"build": "npm run clean && npm run compile && npm run copy && npm run compress",
"build:dev": "npm run clean && npm run compile:dev && npm run copy",
"build:prod": "npm run clean && npm run compile:prod && npm run copy && npm run minify && npm run compress",
"clean": "rimraf build",
"compile": "tsc --build tsconfig.prod.json",
"compile:dev": "tsc --build tsconfig.json",
"compile:prod": "tsc --build tsconfig.prod.json",
"copy": "npm run copy:assets && npm run copy:views && npm run copy:config && npm run copy:shared",
"copy:assets": "npx cpx2 \"src/public/**\" build/public",
"copy:views": "npx cpx2 \"src/views/**\" build/views",
"copy:config": "npx cpx2 \".env\" build && npx cpx2 \"package.json\" build",
"copy:shared": "npx cpx2 \"src/shared/postgresql-error-codes.json\" build/shared && npx cpx2 \"src/shared/sample-data.json\" build/shared && npx cpx2 \"src/shared/templates/**\" build/shared/templates",
"watch": "concurrently \"npm run watch:ts\" \"npm run watch:assets\"",
"watch:ts": "tsc --build tsconfig.json --watch",
"watch:assets": "npx cpx2 \"src/{public,views}/**\" build --watch",
"minify": "terser build/**/*.js --compress --mangle --output-dir build",
"compress": "node scripts/compress.js",
"swagger": "node ./cli/swagger",
"inline-queries": "node ./cli/inline-queries",
"sonar": "sonar-scanner -Dproject.settings=sonar-project-dev.properties",
"tsc": "tsc",
"test": "jest --setupFiles dotenv/config",
"test:watch": "jest --watch --setupFiles dotenv/config"
},
"jestSonar": {
@@ -45,6 +59,7 @@
"cors": "^2.8.5",
"cron": "^2.4.0",
"crypto-js": "^4.1.1",
"csrf-sync": "^4.2.1",
"csurf": "^1.11.0",
"debug": "^4.3.4",
"dotenv": "^16.3.1",
@@ -70,7 +85,6 @@
"passport-local": "^1.0.0",
"path": "^0.12.7",
"pg": "^8.14.1",
"pg-native": "^3.3.0",
"pug": "^3.0.2",
"redis": "^4.6.7",
"sanitize-html": "^2.11.0",
@@ -78,8 +92,10 @@
"sharp": "^0.32.6",
"slugify": "^1.6.6",
"socket.io": "^4.7.1",
"tinymce": "^7.8.0",
"uglify-js": "^3.17.4",
"winston": "^3.10.0",
"worklenz-backend": "file:",
"xss-filters": "^1.2.7"
},
"devDependencies": {
@@ -87,15 +103,17 @@
"@babel/preset-typescript": "^7.22.5",
"@types/bcrypt": "^5.0.0",
"@types/bluebird": "^3.5.38",
"@types/body-parser": "^1.19.2",
"@types/compression": "^1.7.2",
"@types/connect-flash": "^0.0.37",
"@types/cookie-parser": "^1.4.3",
"@types/cron": "^2.0.1",
"@types/crypto-js": "^4.2.2",
"@types/csurf": "^1.11.2",
"@types/express": "^4.17.17",
"@types/express": "^4.17.21",
"@types/express-brute": "^1.0.2",
"@types/express-brute-redis": "^0.0.4",
"@types/express-serve-static-core": "^4.17.34",
"@types/express-session": "^1.17.7",
"@types/fs-extra": "^9.0.13",
"@types/hpp": "^0.2.2",
@@ -120,26 +138,22 @@
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"chokidar": "^3.5.3",
"concurrently": "^9.1.2",
"cpx2": "^8.0.0",
"esbuild": "^0.17.19",
"esbuild-envfile-plugin": "^1.0.5",
"esbuild-node-externals": "^1.8.0",
"eslint": "^8.45.0",
"eslint-plugin-security": "^1.7.1",
"fs-extra": "^10.1.0",
"grunt": "^1.6.1",
"grunt-contrib-clean": "^2.0.1",
"grunt-contrib-compress": "^2.0.0",
"grunt-contrib-copy": "^1.0.0",
"grunt-contrib-uglify": "^5.2.2",
"grunt-contrib-watch": "^1.1.0",
"grunt-shell": "^4.0.0",
"grunt-sync": "^0.8.2",
"highcharts": "^11.1.0",
"jest": "^28.1.3",
"jest-sonar-reporter": "^2.0.0",
"ncp": "^2.0.0",
"nodeman": "^1.1.2",
"rimraf": "^6.0.1",
"swagger-jsdoc": "^6.2.8",
"terser": "^5.40.0",
"ts-jest": "^28.0.8",
"ts-node": "^10.9.1",
"tslint": "^6.1.3",

View File

@@ -0,0 +1,53 @@
const fs = require('fs');
const path = require('path');
const { createGzip } = require('zlib');
const { pipeline } = require('stream');
async function compressFile(inputPath, outputPath) {
return new Promise((resolve, reject) => {
const gzip = createGzip();
const source = fs.createReadStream(inputPath);
const destination = fs.createWriteStream(outputPath);
pipeline(source, gzip, destination, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
async function compressDirectory(dir) {
const files = fs.readdirSync(dir, { withFileTypes: true });
for (const file of files) {
const fullPath = path.join(dir, file.name);
if (file.isDirectory()) {
await compressDirectory(fullPath);
} else if (file.name.endsWith('.js') || file.name.endsWith('.css')) {
const gzPath = fullPath + '.gz';
await compressFile(fullPath, gzPath);
console.log(`Compressed: ${fullPath} -> ${gzPath}`);
}
}
}
async function main() {
try {
const buildDir = path.join(__dirname, '../build');
if (fs.existsSync(buildDir)) {
await compressDirectory(buildDir);
console.log('Compression complete!');
} else {
console.log('Build directory not found. Run build first.');
}
} catch (error) {
console.error('Compression failed:', error);
process.exit(1);
}
}
main();

View File

@@ -6,7 +6,7 @@ import logger from "morgan";
import helmet from "helmet";
import compression from "compression";
import passport from "passport";
import csurf from "csurf";
import { csrfSync } from "csrf-sync";
import rateLimit from "express-rate-limit";
import cors from "cors";
import flash from "connect-flash";
@@ -112,17 +112,13 @@ function isLoggedIn(req: Request, _res: Response, next: NextFunction) {
return req.user ? next() : next(createError(401));
}
// CSRF configuration
const csrfProtection = csurf({
cookie: {
key: "XSRF-TOKEN",
path: "/",
httpOnly: false,
secure: isProduction(), // Only secure in production
sameSite: isProduction() ? "none" : "lax", // Different settings for dev vs prod
domain: isProduction() ? ".worklenz.com" : undefined // Only set domain in production
},
ignoreMethods: ["HEAD", "OPTIONS"]
// CSRF configuration using csrf-sync for session-based authentication
const {
invalidCsrfTokenError,
generateToken,
csrfSynchronisedProtection,
} = csrfSync({
getTokenFromRequest: (req: Request) => req.headers["x-csrf-token"] as string || (req.body && req.body["_csrf"])
});
// Apply CSRF selectively (exclude webhooks and public routes)
@@ -135,38 +131,25 @@ app.use((req, res, next) => {
) {
next();
} else {
csrfProtection(req, res, next);
csrfSynchronisedProtection(req, res, next);
}
});
// Set CSRF token cookie
// Set CSRF token method on request object for compatibility
app.use((req: Request, res: Response, next: NextFunction) => {
if (req.csrfToken) {
const token = req.csrfToken();
res.cookie("XSRF-TOKEN", token, {
httpOnly: false,
secure: isProduction(),
sameSite: isProduction() ? "none" : "lax",
domain: isProduction() ? ".worklenz.com" : undefined,
path: "/"
});
// Add csrfToken method to request object for compatibility
if (!req.csrfToken && generateToken) {
req.csrfToken = (overwrite?: boolean) => generateToken(req, overwrite);
}
next();
});
// CSRF token refresh endpoint
app.get("/csrf-token", (req: Request, res: Response) => {
if (req.csrfToken) {
const token = req.csrfToken();
res.cookie("XSRF-TOKEN", token, {
httpOnly: false,
secure: isProduction(),
sameSite: isProduction() ? "none" : "lax",
domain: isProduction() ? ".worklenz.com" : undefined,
path: "/"
});
res.status(200).json({ done: true, message: "CSRF token refreshed" });
} else {
try {
const token = generateToken(req);
res.status(200).json({ done: true, message: "CSRF token refreshed", token });
} catch (error) {
res.status(500).json({ done: false, message: "Failed to generate CSRF token" });
}
});
@@ -219,7 +202,7 @@ if (isInternalServer()) {
// CSRF error handler
app.use((err: any, req: Request, res: Response, next: NextFunction) => {
if (err.code === "EBADCSRFTOKEN") {
if (err === invalidCsrfTokenError) {
return res.status(403).json({
done: false,
message: "Invalid CSRF token",

View File

@@ -5,7 +5,7 @@ import db from "../config/db";
import {ServerResponse} from "../models/server-response";
import WorklenzControllerBase from "./worklenz-controller-base";
import HandleExceptions from "../decorators/handle-exceptions";
import {calculateMonthDays, getColor, megabytesToBytes} from "../shared/utils";
import {calculateMonthDays, getColor, log_error, megabytesToBytes} from "../shared/utils";
import moment from "moment";
import {calculateStorage} from "../shared/s3";
import {checkTeamSubscriptionStatus, getActiveTeamMemberCount, getCurrentProjectsCount, getFreePlanSettings, getOwnerIdByTeam, getTeamMemberCount, getUsedStorage} from "../shared/paddle-utils";
@@ -232,7 +232,11 @@ export default class AdminCenterController extends WorklenzControllerBase {
FROM team_member_info_view
WHERE team_member_info_view.team_member_id = tm.id),
role_id,
r.name AS role_name
r.name AS role_name,
EXISTS(SELECT email
FROM email_invitations
WHERE team_member_id = tm.id
AND email_invitations.team_id = tm.team_id) AS pending_invitation
FROM team_members tm
LEFT JOIN users u on tm.user_id = u.id
LEFT JOIN roles r on tm.role_id = r.id
@@ -255,22 +259,33 @@ export default class AdminCenterController extends WorklenzControllerBase {
const {id} = req.params;
const {name, teamMembers} = req.body;
const updateNameQuery = `UPDATE teams
SET name = $1
WHERE id = $2;`;
await db.query(updateNameQuery, [name, id]);
try {
// Update team name
const updateNameQuery = `UPDATE teams SET name = $1 WHERE id = $2 RETURNING id;`;
const nameResult = await db.query(updateNameQuery, [name, id]);
if (!nameResult.rows.length) {
return res.status(404).send(new ServerResponse(false, null, "Team not found"));
}
if (teamMembers.length) {
teamMembers.forEach(async (element: { role_name: string; user_id: string; }) => {
const q = `UPDATE team_members
SET role_id = (SELECT id FROM roles WHERE roles.team_id = $1 AND name = $2)
WHERE user_id = $3
AND team_id = $1;`;
await db.query(q, [id, element.role_name, element.user_id]);
});
// Update team member roles if provided
if (teamMembers?.length) {
// Use Promise.all to handle all role updates concurrently
await Promise.all(teamMembers.map(async (member: { role_name: string; user_id: string; }) => {
const roleQuery = `
UPDATE team_members
SET role_id = (SELECT id FROM roles WHERE roles.team_id = $1 AND name = $2)
WHERE user_id = $3 AND team_id = $1
RETURNING id;`;
await db.query(roleQuery, [id, member.role_name, member.user_id]);
}));
}
return res.status(200).send(new ServerResponse(true, null, "Team updated successfully"));
} catch (error) {
log_error("Error updating team:", error);
return res.status(500).send(new ServerResponse(false, null, "Failed to update team"));
}
return res.status(200).send(new ServerResponse(true, [], "Team updated successfully"));
}
@HandleExceptions()

View File

@@ -35,8 +35,18 @@ export default class AuthController extends WorklenzControllerBase {
const auth_error = errors.length > 0 ? errors[0] : null;
const message = messages.length > 0 ? messages[0] : null;
const midTitle = req.query.strategy === "login" ? "Login Failed!" : "Signup Failed!";
const title = req.query.strategy ? midTitle : null;
// Determine title based on authentication status and strategy
let title = null;
if (req.query.strategy) {
if (auth_error) {
// Show failure title only when there's an actual error
title = req.query.strategy === "login" ? "Login Failed!" : "Signup Failed!";
} else if (req.isAuthenticated() && message) {
// Show success title when authenticated and there's a success message
title = req.query.strategy === "login" ? "Login Successful!" : "Signup Successful!";
}
// If no error and not authenticated, don't show any title (this might be a redirect without completion)
}
if (req.user)
req.user.build_v = FileConstants.getRelease();

View File

@@ -137,6 +137,10 @@ export default class HomePageController extends WorklenzControllerBase {
WHERE category_id NOT IN (SELECT id
FROM sys_task_status_categories
WHERE is_done IS FALSE))
AND NOT EXISTS(SELECT project_id
FROM archived_projects
WHERE project_id = p.id
AND user_id = $2)
${groupByClosure}
ORDER BY t.end_date ASC`;
@@ -158,9 +162,13 @@ export default class HomePageController extends WorklenzControllerBase {
WHERE category_id NOT IN (SELECT id
FROM sys_task_status_categories
WHERE is_done IS FALSE))
AND NOT EXISTS(SELECT project_id
FROM archived_projects
WHERE project_id = p.id
AND user_id = $3)
${groupByClosure}`;
const result = await db.query(q, [teamId, userId]);
const result = await db.query(q, [teamId, userId, userId]);
const [row] = result.rows;
return row;
}

View File

@@ -322,7 +322,7 @@ export default class ProjectInsightsController extends WorklenzControllerBase {
(SELECT get_task_assignees(tasks.id)) AS assignees
FROM tasks
JOIN work_log ON work_log.task_id = tasks.id
WHERE project_id = $1
WHERE project_id = $1 AND total_minutes <> 0 AND (total_minutes * 60) <> work_log.total_time_spent
AND CASE
WHEN ($2 IS TRUE) THEN project_id IS NOT NULL
ELSE archived IS FALSE END

View File

@@ -71,7 +71,7 @@ export default class ProjectsController extends WorklenzControllerBase {
return res.status(200).send(new ServerResponse(false, [], `Sorry, the free plan cannot have more than ${projectsLimit} projects.`));
}
}
const q = `SELECT create_project($1) AS project`;
req.body.team_id = req.user?.team_id || null;
@@ -317,65 +317,58 @@ export default class ProjectsController extends WorklenzControllerBase {
@HandleExceptions()
public static async getMembersByProjectId(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const {sortField, sortOrder, size, offset} = this.toPaginationOptions(req.query, "name");
const search = (req.query.search || "").toString().trim();
let searchFilter = "";
const params = [req.params.id, req.user?.team_id ?? null, size, offset];
if (search) {
searchFilter = `
AND (
(SELECT name FROM team_member_info_view WHERE team_member_info_view.team_member_id = tm.id) ILIKE '%' || $5 || '%'
OR (SELECT email FROM team_member_info_view WHERE team_member_info_view.team_member_id = tm.id) ILIKE '%' || $5 || '%'
)
`;
params.push(search);
}
const q = `
SELECT ROW_TO_JSON(rec) AS members
FROM (SELECT COUNT(*) AS total,
(SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(t))), '[]'::JSON)
FROM (SELECT project_members.id,
team_member_id,
(SELECT name
FROM team_member_info_view
WHERE team_member_info_view.team_member_id = tm.id),
(SELECT email
FROM team_member_info_view
WHERE team_member_info_view.team_member_id = tm.id) AS email,
u.avatar_url,
(SELECT COUNT(*)
FROM tasks
WHERE archived IS FALSE
AND project_id = project_members.project_id
AND id IN (SELECT task_id
FROM tasks_assignees
WHERE tasks_assignees.project_member_id = project_members.id)) AS all_tasks_count,
(SELECT COUNT(*)
FROM tasks
WHERE archived IS FALSE
AND project_id = project_members.project_id
AND id IN (SELECT task_id
FROM tasks_assignees
WHERE tasks_assignees.project_member_id = project_members.id)
AND status_id IN (SELECT id
FROM task_statuses
WHERE category_id = (SELECT id
FROM sys_task_status_categories
WHERE is_done IS TRUE))) AS completed_tasks_count,
EXISTS(SELECT email
FROM email_invitations
WHERE team_member_id = project_members.team_member_id
AND email_invitations.team_id = $2) AS pending_invitation,
(SELECT project_access_levels.name
FROM project_access_levels
WHERE project_access_levels.id = project_members.project_access_level_id) AS access,
(SELECT name FROM job_titles WHERE id = tm.job_title_id) AS job_title
FROM project_members
INNER JOIN team_members tm ON project_members.team_member_id = tm.id
LEFT JOIN users u ON tm.user_id = u.id
WHERE project_id = $1
ORDER BY ${sortField} ${sortOrder}
LIMIT $3 OFFSET $4) t) AS data
FROM project_members
WHERE project_id = $1) rec;
WITH filtered_members AS (
SELECT project_members.id,
team_member_id,
(SELECT name FROM team_member_info_view WHERE team_member_info_view.team_member_id = tm.id) AS name,
(SELECT email FROM team_member_info_view WHERE team_member_info_view.team_member_id = tm.id) AS email,
u.avatar_url,
(SELECT COUNT(*) FROM tasks WHERE archived IS FALSE AND project_id = project_members.project_id AND id IN (SELECT task_id FROM tasks_assignees WHERE tasks_assignees.project_member_id = project_members.id)) AS all_tasks_count,
(SELECT COUNT(*) FROM tasks WHERE archived IS FALSE AND project_id = project_members.project_id AND id IN (SELECT task_id FROM tasks_assignees WHERE tasks_assignees.project_member_id = project_members.id) AND status_id IN (SELECT id FROM task_statuses WHERE category_id = (SELECT id FROM sys_task_status_categories WHERE is_done IS TRUE))) AS completed_tasks_count,
EXISTS(SELECT email FROM email_invitations WHERE team_member_id = project_members.team_member_id AND email_invitations.team_id = $2) AS pending_invitation,
(SELECT project_access_levels.name FROM project_access_levels WHERE project_access_levels.id = project_members.project_access_level_id) AS access,
(SELECT name FROM job_titles WHERE id = tm.job_title_id) AS job_title
FROM project_members
INNER JOIN team_members tm ON project_members.team_member_id = tm.id
LEFT JOIN users u ON tm.user_id = u.id
WHERE project_id = $1
${search ? searchFilter : ""}
)
SELECT
(SELECT COUNT(*) FROM filtered_members) AS total,
(SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(t))), '[]'::JSON)
FROM (
SELECT * FROM filtered_members
ORDER BY ${sortField} ${sortOrder}
LIMIT $3 OFFSET $4
) t
) AS data
`;
const result = await db.query(q, [req.params.id, req.user?.team_id ?? null, size, offset]);
const result = await db.query(q, params);
const [data] = result.rows;
for (const member of data?.members.data || []) {
for (const member of data?.data || []) {
member.progress = member.all_tasks_count > 0
? ((member.completed_tasks_count / member.all_tasks_count) * 100).toFixed(0) : 0;
}
return res.status(200).send(new ServerResponse(true, data?.members || this.paginatedDatasetDefaultStruct));
return res.status(200).send(new ServerResponse(true, data || this.paginatedDatasetDefaultStruct));
}
@HandleExceptions()
@@ -408,6 +401,9 @@ export default class ProjectsController extends WorklenzControllerBase {
sps.color_code AS status_color,
sps.icon AS status_icon,
(SELECT name FROM clients WHERE id = projects.client_id) AS client_name,
projects.use_manual_progress,
projects.use_weighted_progress,
projects.use_time_progress,
(SELECT COALESCE(ROW_TO_JSON(pm), '{}'::JSON)
FROM (SELECT team_member_id AS id,
@@ -753,4 +749,186 @@ export default class ProjectsController extends WorklenzControllerBase {
}
@HandleExceptions()
public static async getGrouped(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
// Use qualified field name for projects to avoid ambiguity
const {searchQuery, sortField, sortOrder, size, offset} = this.toPaginationOptions(req.query, ["projects.name"]);
const groupBy = req.query.groupBy as string || "category";
const filterByMember = !req.user?.owner && !req.user?.is_admin ?
` AND is_member_of_project(projects.id, '${req.user?.id}', $1) ` : "";
const isFavorites = req.query.filter === "1" ? ` AND EXISTS(SELECT user_id FROM favorite_projects WHERE user_id = '${req.user?.id}' AND project_id = projects.id)` : "";
const isArchived = req.query.filter === "2"
? ` AND EXISTS(SELECT user_id FROM archived_projects WHERE user_id = '${req.user?.id}' AND project_id = projects.id)`
: ` AND NOT EXISTS(SELECT user_id FROM archived_projects WHERE user_id = '${req.user?.id}' AND project_id = projects.id)`;
const categories = this.getFilterByCategoryWhereClosure(req.query.categories as string);
const statuses = this.getFilterByStatusWhereClosure(req.query.statuses as string);
// Determine grouping field and join based on groupBy parameter
let groupField = "";
let groupName = "";
let groupColor = "";
let groupJoin = "";
let groupByFields = "";
let groupOrderBy = "";
switch (groupBy) {
case "client":
groupField = "COALESCE(projects.client_id::text, 'no-client')";
groupName = "COALESCE(clients.name, 'No Client')";
groupColor = "'#688'";
groupJoin = "LEFT JOIN clients ON projects.client_id = clients.id";
groupByFields = "projects.client_id, clients.name";
groupOrderBy = "COALESCE(clients.name, 'No Client')";
break;
case "status":
groupField = "COALESCE(projects.status_id::text, 'no-status')";
groupName = "COALESCE(sys_project_statuses.name, 'No Status')";
groupColor = "COALESCE(sys_project_statuses.color_code, '#888')";
groupJoin = "LEFT JOIN sys_project_statuses ON projects.status_id = sys_project_statuses.id";
groupByFields = "projects.status_id, sys_project_statuses.name, sys_project_statuses.color_code";
groupOrderBy = "COALESCE(sys_project_statuses.name, 'No Status')";
break;
case "category":
default:
groupField = "COALESCE(projects.category_id::text, 'uncategorized')";
groupName = "COALESCE(project_categories.name, 'Uncategorized')";
groupColor = "COALESCE(project_categories.color_code, '#888')";
groupJoin = "LEFT JOIN project_categories ON projects.category_id = project_categories.id";
groupByFields = "projects.category_id, project_categories.name, project_categories.color_code";
groupOrderBy = "COALESCE(project_categories.name, 'Uncategorized')";
}
// Ensure sortField is properly qualified for the inner project query
let qualifiedSortField = sortField;
if (Array.isArray(sortField)) {
qualifiedSortField = sortField[0]; // Take the first field if it's an array
}
// Replace "projects." with "p2." for the inner query
const innerSortField = qualifiedSortField.replace("projects.", "p2.");
const q = `
SELECT ROW_TO_JSON(rec) AS groups
FROM (
SELECT COUNT(DISTINCT ${groupField}) AS total_groups,
(SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(group_data))), '[]'::JSON)
FROM (
SELECT ${groupField} AS group_key,
${groupName} AS group_name,
${groupColor} AS group_color,
COUNT(*) AS project_count,
(SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(project_data))), '[]'::JSON)
FROM (
SELECT p2.id,
p2.name,
(SELECT sys_project_statuses.name FROM sys_project_statuses WHERE sys_project_statuses.id = p2.status_id) AS status,
(SELECT sys_project_statuses.color_code FROM sys_project_statuses WHERE sys_project_statuses.id = p2.status_id) AS status_color,
(SELECT sys_project_statuses.icon FROM sys_project_statuses WHERE sys_project_statuses.id = p2.status_id) AS status_icon,
EXISTS(SELECT user_id
FROM favorite_projects
WHERE user_id = '${req.user?.id}'
AND project_id = p2.id) AS favorite,
EXISTS(SELECT user_id
FROM archived_projects
WHERE user_id = '${req.user?.id}'
AND project_id = p2.id) AS archived,
p2.color_code,
p2.start_date,
p2.end_date,
p2.category_id,
(SELECT COUNT(*)
FROM tasks
WHERE archived IS FALSE
AND project_id = p2.id) AS all_tasks_count,
(SELECT COUNT(*)
FROM tasks
WHERE archived IS FALSE
AND project_id = p2.id
AND status_id IN (SELECT task_statuses.id
FROM task_statuses
WHERE task_statuses.project_id = p2.id
AND task_statuses.category_id IN
(SELECT sys_task_status_categories.id FROM sys_task_status_categories WHERE sys_task_status_categories.is_done IS TRUE))) AS completed_tasks_count,
(SELECT COUNT(*)
FROM project_members
WHERE project_members.project_id = p2.id) AS members_count,
(SELECT get_project_members(p2.id)) AS names,
(SELECT clients.name FROM clients WHERE clients.id = p2.client_id) AS client_name,
(SELECT users.name FROM users WHERE users.id = p2.owner_id) AS project_owner,
(SELECT project_categories.name FROM project_categories WHERE project_categories.id = p2.category_id) AS category_name,
(SELECT project_categories.color_code
FROM project_categories
WHERE project_categories.id = p2.category_id) AS category_color,
((SELECT project_members.team_member_id as team_member_id
FROM project_members
WHERE project_members.project_id = p2.id
AND project_members.project_access_level_id = (SELECT project_access_levels.id FROM project_access_levels WHERE project_access_levels.key = 'PROJECT_MANAGER'))) AS project_manager_team_member_id,
(SELECT project_members.default_view
FROM project_members
WHERE project_members.project_id = p2.id
AND project_members.team_member_id = '${req.user?.team_member_id}') AS team_member_default_view,
(SELECT CASE
WHEN ((SELECT MAX(tasks.updated_at)
FROM tasks
WHERE tasks.archived IS FALSE
AND tasks.project_id = p2.id) >
p2.updated_at)
THEN (SELECT MAX(tasks.updated_at)
FROM tasks
WHERE tasks.archived IS FALSE
AND tasks.project_id = p2.id)
ELSE p2.updated_at END) AS updated_at
FROM projects p2
${groupJoin.replace("projects.", "p2.")}
WHERE p2.team_id = $1
AND ${groupField.replace("projects.", "p2.")} = ${groupField}
${categories.replace("projects.", "p2.")}
${statuses.replace("projects.", "p2.")}
${isArchived.replace("projects.", "p2.")}
${isFavorites.replace("projects.", "p2.")}
${filterByMember.replace("projects.", "p2.")}
${searchQuery.replace("projects.", "p2.")}
ORDER BY ${innerSortField} ${sortOrder}
) project_data
) AS projects
FROM projects
${groupJoin}
WHERE projects.team_id = $1 ${categories} ${statuses} ${isArchived} ${isFavorites} ${filterByMember} ${searchQuery}
GROUP BY ${groupByFields}
ORDER BY ${groupOrderBy}
LIMIT $2 OFFSET $3
) group_data
) AS data
FROM projects
${groupJoin}
WHERE projects.team_id = $1 ${categories} ${statuses} ${isArchived} ${isFavorites} ${filterByMember} ${searchQuery}
) rec;
`;
const result = await db.query(q, [req.user?.team_id || null, size, offset]);
const [data] = result.rows;
// Process the grouped data
for (const group of data?.groups.data || []) {
for (const project of group.projects || []) {
project.progress = project.all_tasks_count > 0
? ((project.completed_tasks_count / project.all_tasks_count) * 100).toFixed(0) : 0;
project.updated_at_string = moment(project.updated_at).fromNow();
project.names = this.createTagList(project?.names);
project.names.map((a: any) => a.color_code = getColor(a.name));
if (project.project_manager_team_member_id) {
project.project_manager = {
id: project.project_manager_team_member_id
};
}
}
}
return res.status(200).send(new ServerResponse(true, data?.groups || { total_groups: 0, data: [] }));
}
}

View File

@@ -0,0 +1,179 @@
// Example of updated getMemberTimeSheets method with timezone support
// This shows the key changes needed to handle timezones properly
import moment from "moment-timezone";
import db from "../../config/db";
import { IWorkLenzRequest } from "../../interfaces/worklenz-request";
import { IWorkLenzResponse } from "../../interfaces/worklenz-response";
import { ServerResponse } from "../../models/server-response";
import { DATE_RANGES } from "../../shared/constants";
export async function getMemberTimeSheets(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const archived = req.query.archived === "true";
const teams = (req.body.teams || []) as string[];
const teamIds = teams.map(id => `'${id}'`).join(",");
const projects = (req.body.projects || []) as string[];
const projectIds = projects.map(p => `'${p}'`).join(",");
const {billable} = req.body;
// Get user timezone from request or database
const userTimezone = req.body.timezone || await getUserTimezone(req.user?.id || "");
if (!teamIds || !projectIds.length)
return res.status(200).send(new ServerResponse(true, { users: [], projects: [] }));
const { duration, date_range } = req.body;
// Calculate date range with timezone support
let startDate: moment.Moment;
let endDate: moment.Moment;
if (date_range && date_range.length === 2) {
// Convert user's local dates to their timezone's start/end of day
startDate = moment.tz(date_range[0], userTimezone).startOf("day");
endDate = moment.tz(date_range[1], userTimezone).endOf("day");
} else if (duration === DATE_RANGES.ALL_TIME) {
const minDateQuery = `SELECT MIN(COALESCE(start_date, created_at)) as min_date FROM projects WHERE id IN (${projectIds})`;
const minDateResult = await db.query(minDateQuery, []);
const minDate = minDateResult.rows[0]?.min_date;
startDate = minDate ? moment.tz(minDate, userTimezone) : moment.tz("2000-01-01", userTimezone);
endDate = moment.tz(userTimezone);
} else {
// Calculate ranges based on user's timezone
const now = moment.tz(userTimezone);
switch (duration) {
case DATE_RANGES.YESTERDAY:
startDate = now.clone().subtract(1, "day").startOf("day");
endDate = now.clone().subtract(1, "day").endOf("day");
break;
case DATE_RANGES.LAST_WEEK:
startDate = now.clone().subtract(1, "week").startOf("isoWeek");
endDate = now.clone().subtract(1, "week").endOf("isoWeek");
break;
case DATE_RANGES.LAST_MONTH:
startDate = now.clone().subtract(1, "month").startOf("month");
endDate = now.clone().subtract(1, "month").endOf("month");
break;
case DATE_RANGES.LAST_QUARTER:
startDate = now.clone().subtract(3, "months").startOf("day");
endDate = now.clone().endOf("day");
break;
default:
startDate = now.clone().startOf("day");
endDate = now.clone().endOf("day");
}
}
// Convert to UTC for database queries
const startUtc = startDate.utc().format("YYYY-MM-DD HH:mm:ss");
const endUtc = endDate.utc().format("YYYY-MM-DD HH:mm:ss");
// Calculate working days in user's timezone
const totalDays = endDate.diff(startDate, "days") + 1;
let workingDays = 0;
const current = startDate.clone();
while (current.isSameOrBefore(endDate, "day")) {
if (current.isoWeekday() >= 1 && current.isoWeekday() <= 5) {
workingDays++;
}
current.add(1, "day");
}
// Updated SQL query with proper timezone handling
const billableQuery = buildBillableQuery(billable);
const archivedClause = archived ? "" : `AND projects.id NOT IN (SELECT project_id FROM archived_projects WHERE project_id = projects.id AND user_id = '${req.user?.id}')`;
const q = `
WITH project_hours AS (
SELECT
id,
COALESCE(hours_per_day, 8) as hours_per_day
FROM projects
WHERE id IN (${projectIds})
),
total_working_hours AS (
SELECT
SUM(hours_per_day) * ${workingDays} as total_hours
FROM project_hours
)
SELECT
u.id,
u.email,
tm.name,
tm.color_code,
COALESCE(SUM(twl.time_spent), 0) as logged_time,
COALESCE(SUM(twl.time_spent), 0) / 3600.0 as value,
(SELECT total_hours FROM total_working_hours) as total_working_hours,
CASE
WHEN (SELECT total_hours FROM total_working_hours) > 0
THEN ROUND((COALESCE(SUM(twl.time_spent), 0) / 3600.0) / (SELECT total_hours FROM total_working_hours) * 100, 2)
ELSE 0
END as utilization_percent,
ROUND(COALESCE(SUM(twl.time_spent), 0) / 3600.0, 2) as utilized_hours,
ROUND(COALESCE(SUM(twl.time_spent), 0) / 3600.0 - (SELECT total_hours FROM total_working_hours), 2) as over_under_utilized_hours,
'${userTimezone}' as user_timezone,
'${startDate.format("YYYY-MM-DD")}' as report_start_date,
'${endDate.format("YYYY-MM-DD")}' as report_end_date
FROM team_members tm
LEFT JOIN users u ON tm.user_id = u.id
LEFT JOIN task_work_log twl ON twl.user_id = u.id
LEFT JOIN tasks t ON twl.task_id = t.id ${billableQuery}
LEFT JOIN projects p ON t.project_id = p.id
WHERE tm.team_id IN (${teamIds})
AND (
twl.id IS NULL
OR (
p.id IN (${projectIds})
AND twl.created_at >= '${startUtc}'::TIMESTAMP
AND twl.created_at <= '${endUtc}'::TIMESTAMP
${archivedClause}
)
)
GROUP BY u.id, u.email, tm.name, tm.color_code
ORDER BY logged_time DESC`;
const result = await db.query(q, []);
// Add timezone context to response
const response = {
data: result.rows,
timezone_info: {
user_timezone: userTimezone,
report_period: {
start: startDate.format("YYYY-MM-DD HH:mm:ss z"),
end: endDate.format("YYYY-MM-DD HH:mm:ss z"),
working_days: workingDays,
total_days: totalDays
}
}
};
return res.status(200).send(new ServerResponse(true, response));
}
async function getUserTimezone(userId: string): Promise<string> {
const q = `SELECT tz.name as timezone
FROM users u
JOIN timezones tz ON u.timezone_id = tz.id
WHERE u.id = $1`;
const result = await db.query(q, [userId]);
return result.rows[0]?.timezone || "UTC";
}
function buildBillableQuery(billable: { billable: boolean; nonBillable: boolean }): string {
if (!billable) return "";
const { billable: isBillable, nonBillable } = billable;
if (isBillable && nonBillable) {
return "";
} else if (isBillable) {
return " AND tasks.billable IS TRUE";
} else if (nonBillable) {
return " AND tasks.billable IS FALSE";
}
return "";
}

View File

@@ -408,6 +408,65 @@ export default class ReportingAllocationController extends ReportingControllerBa
const { duration, date_range } = req.body;
// Calculate the date range (start and end)
let startDate: moment.Moment;
let endDate: moment.Moment;
if (date_range && date_range.length === 2) {
startDate = moment(date_range[0]);
endDate = moment(date_range[1]);
} else if (duration === DATE_RANGES.ALL_TIME) {
// Fetch the earliest start_date (or created_at if null) from selected projects
const minDateQuery = `SELECT MIN(COALESCE(start_date, created_at)) as min_date FROM projects WHERE id IN (${projectIds})`;
const minDateResult = await db.query(minDateQuery, []);
const minDate = minDateResult.rows[0]?.min_date;
startDate = minDate ? moment(minDate) : moment('2000-01-01');
endDate = moment();
} else {
switch (duration) {
case DATE_RANGES.YESTERDAY:
startDate = moment().subtract(1, "day");
endDate = moment().subtract(1, "day");
break;
case DATE_RANGES.LAST_WEEK:
startDate = moment().subtract(1, "week").startOf("isoWeek");
endDate = moment().subtract(1, "week").endOf("isoWeek");
break;
case DATE_RANGES.LAST_MONTH:
startDate = moment().subtract(1, "month").startOf("month");
endDate = moment().subtract(1, "month").endOf("month");
break;
case DATE_RANGES.LAST_QUARTER:
startDate = moment().subtract(3, "months").startOf("quarter");
endDate = moment().subtract(1, "quarter").endOf("quarter");
break;
default:
startDate = moment().startOf("day");
endDate = moment().endOf("day");
}
}
// Count only weekdays (Mon-Fri) in the period
let workingDays = 0;
let current = startDate.clone();
while (current.isSameOrBefore(endDate, 'day')) {
const day = current.isoWeekday();
if (day >= 1 && day <= 5) workingDays++;
current.add(1, 'day');
}
// Get hours_per_day for all selected projects
const projectHoursQuery = `SELECT id, hours_per_day FROM projects WHERE id IN (${projectIds})`;
const projectHoursResult = await db.query(projectHoursQuery, []);
const projectHoursMap: Record<string, number> = {};
for (const row of projectHoursResult.rows) {
projectHoursMap[row.id] = row.hours_per_day || 8;
}
// Sum total working hours for all selected projects
let totalWorkingHours = 0;
for (const pid of Object.keys(projectHoursMap)) {
totalWorkingHours += workingDays * projectHoursMap[pid];
}
const durationClause = this.getDateRangeClause(duration || DATE_RANGES.LAST_WEEK, date_range);
const archivedClause = archived
? ""
@@ -430,6 +489,12 @@ export default class ReportingAllocationController extends ReportingControllerBa
for (const member of result.rows) {
member.value = member.logged_time ? parseFloat(moment.duration(member.logged_time, "seconds").asHours().toFixed(2)) : 0;
member.color_code = getColor(member.name);
member.total_working_hours = totalWorkingHours;
member.utilization_percent = (totalWorkingHours > 0 && member.logged_time) ? ((parseFloat(member.logged_time) / (totalWorkingHours * 3600)) * 100).toFixed(2) : '0.00';
member.utilized_hours = member.logged_time ? (parseFloat(member.logged_time) / 3600).toFixed(2) : '0.00';
// Over/under utilized hours: utilized_hours - total_working_hours
const overUnder = member.utilized_hours && member.total_working_hours ? (parseFloat(member.utilized_hours) - member.total_working_hours) : 0;
member.over_under_utilized_hours = overUnder.toFixed(2);
}
return res.status(200).send(new ServerResponse(true, result.rows));

View File

@@ -0,0 +1,117 @@
import WorklenzControllerBase from "../worklenz-controller-base";
import { IWorkLenzRequest } from "../../interfaces/worklenz-request";
import db from "../../config/db";
import moment from "moment-timezone";
import { DATE_RANGES } from "../../shared/constants";
export default abstract class ReportingControllerBaseWithTimezone extends WorklenzControllerBase {
/**
* Get the user's timezone from the database or request
* @param userId - The user ID
* @returns The user's timezone or 'UTC' as default
*/
protected static async getUserTimezone(userId: string): Promise<string> {
const q = `SELECT tz.name as timezone
FROM users u
JOIN timezones tz ON u.timezone_id = tz.id
WHERE u.id = $1`;
const result = await db.query(q, [userId]);
return result.rows[0]?.timezone || 'UTC';
}
/**
* Generate date range clause with timezone support
* @param key - Date range key (e.g., YESTERDAY, LAST_WEEK)
* @param dateRange - Array of date strings
* @param userTimezone - User's timezone (e.g., 'America/New_York')
* @returns SQL clause for date filtering
*/
protected static getDateRangeClauseWithTimezone(key: string, dateRange: string[], userTimezone: string) {
// For custom date ranges
if (dateRange.length === 2) {
// Convert dates to user's timezone start/end of day
const start = moment.tz(dateRange[0], userTimezone).startOf('day');
const end = moment.tz(dateRange[1], userTimezone).endOf('day');
// Convert to UTC for database comparison
const startUtc = start.utc().format("YYYY-MM-DD HH:mm:ss");
const endUtc = end.utc().format("YYYY-MM-DD HH:mm:ss");
if (start.isSame(end, 'day')) {
// Single day selection
return `AND task_work_log.created_at >= '${startUtc}'::TIMESTAMP AND task_work_log.created_at <= '${endUtc}'::TIMESTAMP`;
}
return `AND task_work_log.created_at >= '${startUtc}'::TIMESTAMP AND task_work_log.created_at <= '${endUtc}'::TIMESTAMP`;
}
// For predefined ranges, calculate based on user's timezone
const now = moment.tz(userTimezone);
let startDate, endDate;
switch (key) {
case DATE_RANGES.YESTERDAY:
startDate = now.clone().subtract(1, 'day').startOf('day');
endDate = now.clone().subtract(1, 'day').endOf('day');
break;
case DATE_RANGES.LAST_WEEK:
startDate = now.clone().subtract(1, 'week').startOf('week');
endDate = now.clone().subtract(1, 'week').endOf('week');
break;
case DATE_RANGES.LAST_MONTH:
startDate = now.clone().subtract(1, 'month').startOf('month');
endDate = now.clone().subtract(1, 'month').endOf('month');
break;
case DATE_RANGES.LAST_QUARTER:
startDate = now.clone().subtract(3, 'months').startOf('day');
endDate = now.clone().endOf('day');
break;
default:
return "";
}
if (startDate && endDate) {
const startUtc = startDate.utc().format("YYYY-MM-DD HH:mm:ss");
const endUtc = endDate.utc().format("YYYY-MM-DD HH:mm:ss");
return `AND task_work_log.created_at >= '${startUtc}'::TIMESTAMP AND task_work_log.created_at <= '${endUtc}'::TIMESTAMP`;
}
return "";
}
/**
* Format dates for display in user's timezone
* @param date - Date to format
* @param userTimezone - User's timezone
* @param format - Moment format string
* @returns Formatted date string
*/
protected static formatDateInTimezone(date: string | Date, userTimezone: string, format: string = "YYYY-MM-DD HH:mm:ss") {
return moment.tz(date, userTimezone).format(format);
}
/**
* Get working days count between two dates in user's timezone
* @param startDate - Start date
* @param endDate - End date
* @param userTimezone - User's timezone
* @returns Number of working days
*/
protected static getWorkingDaysInTimezone(startDate: string, endDate: string, userTimezone: string): number {
const start = moment.tz(startDate, userTimezone);
const end = moment.tz(endDate, userTimezone);
let workingDays = 0;
const current = start.clone();
while (current.isSameOrBefore(end, 'day')) {
// Monday = 1, Friday = 5
if (current.isoWeekday() >= 1 && current.isoWeekday() <= 5) {
workingDays++;
}
current.add(1, 'day');
}
return workingDays;
}
}

View File

@@ -6,10 +6,69 @@ import { IWorkLenzResponse } from "../../interfaces/worklenz-response";
import { ServerResponse } from "../../models/server-response";
import { DATE_RANGES, TASK_PRIORITY_COLOR_ALPHA } from "../../shared/constants";
import { formatDuration, getColor, int } from "../../shared/utils";
import ReportingControllerBase from "./reporting-controller-base";
import ReportingControllerBaseWithTimezone from "./reporting-controller-base-with-timezone";
import Excel from "exceljs";
export default class ReportingMembersController extends ReportingControllerBase {
export default class ReportingMembersController extends ReportingControllerBaseWithTimezone {
protected static getPercentage(n: number, total: number) {
return +(n ? (n / total) * 100 : 0).toFixed();
}
protected static getCurrentTeamId(req: IWorkLenzRequest): string | null {
return req.user?.team_id ?? null;
}
public static convertMinutesToHoursAndMinutes(totalMinutes: number) {
const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60;
return `${hours}h ${minutes}m`;
}
public static convertSecondsToHoursAndMinutes(seconds: number) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
return `${hours}h ${minutes}m`;
}
protected static formatEndDate(endDate: string) {
const end = moment(endDate).format("YYYY-MM-DD");
const fEndDate = moment(end);
return fEndDate;
}
protected static formatCurrentDate() {
const current = moment().format("YYYY-MM-DD");
const fCurrentDate = moment(current);
return fCurrentDate;
}
protected static getDaysLeft(endDate: string): number | null {
if (!endDate) return null;
const fCurrentDate = this.formatCurrentDate();
const fEndDate = this.formatEndDate(endDate);
return fEndDate.diff(fCurrentDate, "days");
}
protected static isOverdue(endDate: string): boolean {
if (!endDate) return false;
const fCurrentDate = this.formatCurrentDate();
const fEndDate = this.formatEndDate(endDate);
return fEndDate.isBefore(fCurrentDate);
}
protected static isToday(endDate: string): boolean {
if (!endDate) return false;
const fCurrentDate = this.formatCurrentDate();
const fEndDate = this.formatEndDate(endDate);
return fEndDate.isSame(fCurrentDate);
}
private static async getMembers(
teamId: string, searchQuery = "",
@@ -487,7 +546,9 @@ export default class ReportingMembersController extends ReportingControllerBase
dateRange = date_range.split(",");
}
const durationClause = ReportingMembersController.getDateRangeClauseMembers(duration as string || DATE_RANGES.LAST_WEEK, dateRange, "twl");
// Get user timezone for proper date filtering
const userTimezone = await this.getUserTimezone(req.user?.id as string);
const durationClause = this.getDateRangeClauseWithTimezone(duration as string || DATE_RANGES.LAST_WEEK, dateRange, userTimezone);
const minMaxDateClause = this.getMinMaxDates(duration as string || DATE_RANGES.LAST_WEEK, dateRange, "task_work_log");
const memberName = (req.query.member_name as string)?.trim() || null;
@@ -1038,7 +1099,9 @@ export default class ReportingMembersController extends ReportingControllerBase
public static async getMemberTimelogs(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const { team_member_id, team_id, duration, date_range, archived, billable } = req.body;
const durationClause = ReportingMembersController.getDateRangeClauseMembers(duration || DATE_RANGES.LAST_WEEK, date_range, "twl");
// Get user timezone for proper date filtering
const userTimezone = await this.getUserTimezone(req.user?.id as string);
const durationClause = this.getDateRangeClauseWithTimezone(duration || DATE_RANGES.LAST_WEEK, date_range, userTimezone);
const minMaxDateClause = this.getMinMaxDates(duration || DATE_RANGES.LAST_WEEK, date_range, "task_work_log");
const billableQuery = this.buildBillableQuery(billable);
@@ -1230,8 +1293,8 @@ public static async getSingleMemberProjects(req: IWorkLenzRequest, res: IWorkLen
row.actual_time = int(row.actual_time);
row.estimated_time_string = this.convertMinutesToHoursAndMinutes(int(row.estimated_time));
row.actual_time_string = this.convertSecondsToHoursAndMinutes(int(row.actual_time));
row.days_left = ReportingControllerBase.getDaysLeft(row.end_date);
row.is_overdue = ReportingControllerBase.isOverdue(row.end_date);
row.days_left = this.getDaysLeft(row.end_date);
row.is_overdue = this.isOverdue(row.end_date);
if (row.days_left && row.is_overdue) {
row.days_left = row.days_left.toString().replace(/-/g, "");
}

View File

@@ -0,0 +1,167 @@
import { IWorkLenzRequest } from "../interfaces/worklenz-request";
import { IWorkLenzResponse } from "../interfaces/worklenz-response";
import { ServerResponse } from "../models/server-response";
import WorklenzControllerBase from "./worklenz-controller-base";
import HandleExceptions from "../decorators/handle-exceptions";
import { ISurveySubmissionRequest } from "../interfaces/survey";
import db from "../config/db";
export default class SurveyController extends WorklenzControllerBase {
@HandleExceptions()
public static async getAccountSetupSurvey(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const q = `
SELECT
s.id,
s.name,
s.description,
s.survey_type,
s.is_active,
COALESCE(
json_agg(
json_build_object(
'id', sq.id,
'survey_id', sq.survey_id,
'question_key', sq.question_key,
'question_type', sq.question_type,
'is_required', sq.is_required,
'sort_order', sq.sort_order,
'options', sq.options
) ORDER BY sq.sort_order
) FILTER (WHERE sq.id IS NOT NULL),
'[]'
) AS questions
FROM surveys s
LEFT JOIN survey_questions sq ON s.id = sq.survey_id
WHERE s.survey_type = 'account_setup' AND s.is_active = true
GROUP BY s.id, s.name, s.description, s.survey_type, s.is_active
LIMIT 1;
`;
const result = await db.query(q);
const [survey] = result.rows;
if (!survey) {
return res.status(200).send(new ServerResponse(false, null, "Account setup survey not found"));
}
return res.status(200).send(new ServerResponse(true, survey));
}
@HandleExceptions()
public static async submitSurveyResponse(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const userId = req.user?.id;
const body = req.body as ISurveySubmissionRequest;
if (!userId) {
return res.status(200).send(new ServerResponse(false, null, "User not authenticated"));
}
if (!body.survey_id || !body.answers || !Array.isArray(body.answers)) {
return res.status(200).send(new ServerResponse(false, null, "Invalid survey submission data"));
}
// Check if user has already submitted a response for this survey
const existingResponseQuery = `
SELECT id FROM survey_responses
WHERE user_id = $1 AND survey_id = $2;
`;
const existingResult = await db.query(existingResponseQuery, [userId, body.survey_id]);
let responseId: string;
if (existingResult.rows.length > 0) {
// Update existing response
responseId = existingResult.rows[0].id;
const updateResponseQuery = `
UPDATE survey_responses
SET is_completed = true, completed_at = NOW(), updated_at = NOW()
WHERE id = $1;
`;
await db.query(updateResponseQuery, [responseId]);
// Delete existing answers
const deleteAnswersQuery = `DELETE FROM survey_answers WHERE response_id = $1;`;
await db.query(deleteAnswersQuery, [responseId]);
} else {
// Create new response
const createResponseQuery = `
INSERT INTO survey_responses (survey_id, user_id, is_completed, completed_at)
VALUES ($1, $2, true, NOW())
RETURNING id;
`;
const responseResult = await db.query(createResponseQuery, [body.survey_id, userId]);
responseId = responseResult.rows[0].id;
}
// Insert new answers
if (body.answers.length > 0) {
const answerValues: string[] = [];
const params: any[] = [];
body.answers.forEach((answer, index) => {
const baseIndex = index * 4;
answerValues.push(`($${baseIndex + 1}, $${baseIndex + 2}, $${baseIndex + 3}, $${baseIndex + 4})`);
params.push(
responseId,
answer.question_id,
answer.answer_text || null,
answer.answer_json ? JSON.stringify(answer.answer_json) : null
);
});
const insertAnswersQuery = `
INSERT INTO survey_answers (response_id, question_id, answer_text, answer_json)
VALUES ${answerValues.join(', ')};
`;
await db.query(insertAnswersQuery, params);
}
return res.status(200).send(new ServerResponse(true, { response_id: responseId }));
}
@HandleExceptions()
public static async getUserSurveyResponse(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const userId = req.user?.id;
const surveyId = req.params.survey_id;
if (!userId) {
return res.status(200).send(new ServerResponse(false, null, "User not authenticated"));
}
const q = `
SELECT
sr.id,
sr.survey_id,
sr.user_id,
sr.is_completed,
sr.started_at,
sr.completed_at,
COALESCE(
json_agg(
json_build_object(
'question_id', sa.question_id,
'answer_text', sa.answer_text,
'answer_json', sa.answer_json
)
) FILTER (WHERE sa.id IS NOT NULL),
'[]'
) AS answers
FROM survey_responses sr
LEFT JOIN survey_answers sa ON sr.id = sa.response_id
WHERE sr.user_id = $1 AND sr.survey_id = $2
GROUP BY sr.id, sr.survey_id, sr.user_id, sr.is_completed, sr.started_at, sr.completed_at;
`;
const result = await db.query(q, [userId, surveyId]);
const [response] = result.rows;
if (!response) {
return res.status(200).send(new ServerResponse(false, null, "Survey response not found"));
}
return res.status(200).send(new ServerResponse(true, response));
}
}

View File

@@ -16,19 +16,23 @@ export default class TaskPhasesController extends WorklenzControllerBase {
if (!req.query.id)
return res.status(400).send(new ServerResponse(false, null, "Invalid request"));
// Use custom name if provided, otherwise use default naming pattern
const phaseName = req.body.name?.trim() ||
`Untitled Phase (${(await db.query("SELECT COUNT(*) FROM project_phases WHERE project_id = $1", [req.query.id])).rows[0].count + 1})`;
const q = `
INSERT INTO project_phases (name, color_code, project_id, sort_index)
VALUES (
CONCAT('Untitled Phase (', (SELECT COUNT(*) FROM project_phases WHERE project_id = $2) + 1, ')'),
$1,
$2,
(SELECT COUNT(*) FROM project_phases WHERE project_id = $2) + 1)
$3,
(SELECT COUNT(*) FROM project_phases WHERE project_id = $3) + 1)
RETURNING id, name, color_code, sort_index;
`;
req.body.color_code = this.DEFAULT_PHASE_COLOR;
const result = await db.query(q, [req.body.color_code, req.query.id]);
const result = await db.query(q, [phaseName, req.body.color_code, req.query.id]);
const [data] = result.rows;
data.color_code = getColor(data.name) + TASK_STATUS_COLOR_ALPHA;

View File

@@ -134,6 +134,25 @@ export default class TaskStatusesController extends WorklenzControllerBase {
return res.status(200).send(new ServerResponse(true, data));
}
@HandleExceptions()
public static async updateCategory(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const hasMoreCategories = await TaskStatusesController.hasMoreCategories(req.params.id, req.query.current_project_id as string);
if (!hasMoreCategories)
return res.status(200).send(new ServerResponse(false, null, existsErrorMessage).withTitle("Status category update failed!"));
const q = `
UPDATE task_statuses
SET category_id = $2
WHERE id = $1
AND project_id = $3
RETURNING (SELECT color_code FROM sys_task_status_categories WHERE id = task_statuses.category_id), (SELECT color_code_dark FROM sys_task_status_categories WHERE id = task_statuses.category_id);
`;
const result = await db.query(q, [req.params.id, req.body.category_id, req.query.current_project_id]);
const [data] = result.rows;
return res.status(200).send(new ServerResponse(true, data));
}
@HandleExceptions()
public static async updateStatusOrder(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const q = `SELECT update_status_order($1);`;

View File

@@ -1,6 +1,6 @@
import WorklenzControllerBase from "./worklenz-controller-base";
import {getColor} from "../shared/utils";
import {PriorityColorCodes, TASK_PRIORITY_COLOR_ALPHA, TASK_STATUS_COLOR_ALPHA} from "../shared/constants";
import { getColor } from "../shared/utils";
import { PriorityColorCodes, TASK_PRIORITY_COLOR_ALPHA, TASK_STATUS_COLOR_ALPHA } from "../shared/constants";
import moment from "moment/moment";
export const GroupBy = {
@@ -16,6 +16,7 @@ export interface ITaskGroup {
start_date?: string;
end_date?: string;
color_code: string;
color_code_dark: string;
category_id: string | null;
old_category_id?: string;
todo_progress?: number;
@@ -32,10 +33,46 @@ export default class TasksControllerBase extends WorklenzControllerBase {
}
public static updateTaskViewModel(task: any) {
task.progress = ~~(task.total_minutes_spent / task.total_minutes * 100);
// For parent tasks (with subtasks), always use calculated progress from subtasks
if (task.sub_tasks_count > 0) {
// Ensure progress matches complete_ratio for consistency
task.progress = task.complete_ratio || 0;
// Important: Parent tasks should not have manual progress
// If they somehow do, reset it
if (task.manual_progress) {
task.manual_progress = false;
task.progress_value = null;
}
}
// For tasks without subtasks, respect manual progress if set
else if (task.manual_progress === true && task.progress_value !== null && task.progress_value !== undefined) {
// For manually set progress, use that value directly
task.progress = parseInt(task.progress_value);
task.complete_ratio = parseInt(task.progress_value);
}
// For tasks with no subtasks and no manual progress
else {
// Only calculate progress based on time if time-based progress is enabled for the project
if (task.project_use_time_progress && task.total_minutes_spent && task.total_minutes) {
// Cap the progress at 100% to prevent showing more than 100% progress
task.progress = Math.min(~~(task.total_minutes_spent / task.total_minutes * 100), 100);
} else {
// Default to 0% progress when time-based calculation is not enabled
task.progress = 0;
}
// Set complete_ratio to match progress
task.complete_ratio = task.progress;
}
// Ensure numeric values
task.progress = parseInt(task.progress) || 0;
task.complete_ratio = parseInt(task.complete_ratio) || 0;
task.overdue = task.total_minutes < task.total_minutes_spent;
task.time_spent = {hours: ~~(task.total_minutes_spent / 60), minutes: task.total_minutes_spent % 60};
task.time_spent = { hours: ~~(task.total_minutes_spent / 60), minutes: task.total_minutes_spent % 60 };
task.comments_count = Number(task.comments_count) ? +task.comments_count : 0;
task.attachments_count = Number(task.attachments_count) ? +task.attachments_count : 0;
@@ -73,9 +110,9 @@ export default class TasksControllerBase extends WorklenzControllerBase {
if (task.timer_start_time)
task.timer_start_time = moment(task.timer_start_time).valueOf();
// Set completed_count and total_tasks_count regardless of progress calculation method
const totalCompleted = (+task.completed_sub_tasks + +task.parent_task_completed) || 0;
const totalTasks = +task.sub_tasks_count || 0; // if needed add +1 for parent
task.complete_ratio = TasksControllerBase.calculateTaskCompleteRatio(totalCompleted, totalTasks);
const totalTasks = +task.sub_tasks_count || 0;
task.completed_count = totalCompleted;
task.total_tasks_count = totalTasks;

File diff suppressed because it is too large Load Diff

View File

@@ -34,29 +34,24 @@ export default abstract class WorklenzControllerBase {
const offset = queryParams.search ? 0 : (index - 1) * size;
const paging = queryParams.paging || "true";
// let s = "";
// if (typeof searchField === "string") {
// s = `${searchField} || ' ' || id::TEXT`;
// } else if (Array.isArray(searchField)) {
// s = searchField.join(" || ' ' || ");
// }
// const search = (queryParams.search as string || "").trim();
// const searchQuery = search ? `AND TO_TSVECTOR(${s}) @@ TO_TSQUERY('${toTsQuery(search)}')` : "";
const search = (queryParams.search as string || "").trim();
let s = "";
if (typeof searchField === "string") {
s = ` ${searchField} ILIKE '%${search}%'`;
} else if (Array.isArray(searchField)) {
s = searchField.map(index => ` ${index} ILIKE '%${search}%'`).join(" OR ");
}
let searchQuery = "";
if (search) {
searchQuery = isMemberFilter ? ` (${s}) AND ` : ` AND (${s}) `;
// Properly escape single quotes to prevent SQL syntax errors
const escapedSearch = search.replace(/'/g, "''");
let s = "";
if (typeof searchField === "string") {
s = ` ${searchField} ILIKE '%${escapedSearch}%'`;
} else if (Array.isArray(searchField)) {
s = searchField.map(field => ` ${field} ILIKE '%${escapedSearch}%'`).join(" OR ");
}
if (s) {
searchQuery = isMemberFilter ? ` (${s}) AND ` : ` AND (${s}) `;
}
}
// Sort

View File

@@ -1,11 +1,11 @@
import {startDailyDigestJob} from "./daily-digest-job";
import {startNotificationsJob} from "./notifications-job";
import {startProjectDigestJob} from "./project-digest-job";
import { startRecurringTasksJob } from "./recurring-tasks";
import {startRecurringTasksJob} from "./recurring-tasks";
export function startCronJobs() {
startNotificationsJob();
startDailyDigestJob();
startProjectDigestJob();
// startRecurringTasksJob();
if (process.env.ENABLE_RECURRING_JOBS === "true") startRecurringTasksJob();
}

View File

@@ -7,12 +7,90 @@ import TasksController from "../controllers/tasks-controller";
// At 11:00+00 (4.30pm+530) on every day-of-month if it's on every day-of-week from Monday through Friday.
// const TIME = "0 11 */1 * 1-5";
const TIME = "*/2 * * * *";
const TIME = process.env.RECURRING_JOBS_INTERVAL || "0 11 */1 * 1-5";
const TIME_FORMAT = "YYYY-MM-DD";
// const TIME = "0 0 * * *"; // Runs at midnight every day
const log = (value: any) => console.log("recurring-task-cron-job:", value);
// Define future limits for different schedule types
// More conservative limits to prevent task list clutter
const FUTURE_LIMITS = {
daily: moment.duration(3, "days"),
weekly: moment.duration(1, "week"),
monthly: moment.duration(1, "month"),
every_x_days: (interval: number) => moment.duration(interval, "days"),
every_x_weeks: (interval: number) => moment.duration(interval, "weeks"),
every_x_months: (interval: number) => moment.duration(interval, "months")
};
// Helper function to get the future limit based on schedule type
function getFutureLimit(scheduleType: string, interval?: number): moment.Duration {
switch (scheduleType) {
case "daily":
return FUTURE_LIMITS.daily;
case "weekly":
return FUTURE_LIMITS.weekly;
case "monthly":
return FUTURE_LIMITS.monthly;
case "every_x_days":
return FUTURE_LIMITS.every_x_days(interval || 1);
case "every_x_weeks":
return FUTURE_LIMITS.every_x_weeks(interval || 1);
case "every_x_months":
return FUTURE_LIMITS.every_x_months(interval || 1);
default:
return moment.duration(3, "days"); // Default to 3 days
}
}
// Helper function to batch create tasks
async function createBatchTasks(template: ITaskTemplate & IRecurringSchedule, endDates: moment.Moment[]) {
const createdTasks = [];
for (const nextEndDate of endDates) {
const existingTaskQuery = `
SELECT id FROM tasks
WHERE schedule_id = $1 AND end_date::DATE = $2::DATE;
`;
const existingTaskResult = await db.query(existingTaskQuery, [template.schedule_id, nextEndDate.format(TIME_FORMAT)]);
if (existingTaskResult.rows.length === 0) {
const createTaskQuery = `SELECT create_quick_task($1::json) as task;`;
const taskData = {
name: template.name,
priority_id: template.priority_id,
project_id: template.project_id,
reporter_id: template.reporter_id,
status_id: template.status_id || null,
end_date: nextEndDate.format(TIME_FORMAT),
schedule_id: template.schedule_id
};
const createTaskResult = await db.query(createTaskQuery, [JSON.stringify(taskData)]);
const createdTask = createTaskResult.rows[0].task;
if (createdTask) {
createdTasks.push(createdTask);
for (const assignee of template.assignees) {
await TasksController.createTaskBulkAssignees(assignee.team_member_id, template.project_id, createdTask.id, assignee.assigned_by);
}
for (const label of template.labels) {
const q = `SELECT add_or_remove_task_label($1, $2) AS labels;`;
await db.query(q, [createdTask.id, label.label_id]);
}
console.log(`Created task for template ${template.name} with end date ${nextEndDate.format(TIME_FORMAT)}`);
}
} else {
console.log(`Skipped creating task for template ${template.name} with end date ${nextEndDate.format(TIME_FORMAT)} - task already exists`);
}
}
return createdTasks;
}
async function onRecurringTaskJobTick() {
try {
log("(cron) Recurring tasks job started.");
@@ -33,65 +111,44 @@ async function onRecurringTaskJobTick() {
? moment(template.last_task_end_date)
: moment(template.created_at);
const futureLimit = moment(template.last_checked_at || template.created_at).add(1, "week");
// Calculate future limit based on schedule type
const futureLimit = moment(template.last_checked_at || template.created_at)
.add(getFutureLimit(
template.schedule_type,
template.interval_days || template.interval_weeks || template.interval_months || 1
));
let nextEndDate = calculateNextEndDate(template, lastTaskEndDate);
const endDatesToCreate: moment.Moment[] = [];
// Find the next future occurrence
while (nextEndDate.isSameOrBefore(now)) {
// Find all future occurrences within the limit
while (nextEndDate.isSameOrBefore(futureLimit)) {
if (nextEndDate.isAfter(now)) {
endDatesToCreate.push(moment(nextEndDate));
}
nextEndDate = calculateNextEndDate(template, nextEndDate);
}
// Only create a task if it's within the future limit
if (nextEndDate.isSameOrBefore(futureLimit)) {
const existingTaskQuery = `
SELECT id FROM tasks
WHERE schedule_id = $1 AND end_date::DATE = $2::DATE;
// Batch create tasks for all future dates
if (endDatesToCreate.length > 0) {
const createdTasks = await createBatchTasks(template, endDatesToCreate);
createdTaskCount += createdTasks.length;
// Update the last_checked_at in the schedule
const updateScheduleQuery = `
UPDATE task_recurring_schedules
SET last_checked_at = $1::DATE,
last_created_task_end_date = $2
WHERE id = $3;
`;
const existingTaskResult = await db.query(existingTaskQuery, [template.schedule_id, nextEndDate.format(TIME_FORMAT)]);
if (existingTaskResult.rows.length === 0) {
const createTaskQuery = `SELECT create_quick_task($1::json) as task;`;
const taskData = {
name: template.name,
priority_id: template.priority_id,
project_id: template.project_id,
reporter_id: template.reporter_id,
status_id: template.status_id || null,
end_date: nextEndDate.format(TIME_FORMAT),
schedule_id: template.schedule_id
};
const createTaskResult = await db.query(createTaskQuery, [JSON.stringify(taskData)]);
const createdTask = createTaskResult.rows[0].task;
if (createdTask) {
createdTaskCount++;
for (const assignee of template.assignees) {
await TasksController.createTaskBulkAssignees(assignee.team_member_id, template.project_id, createdTask.id, assignee.assigned_by);
}
for (const label of template.labels) {
const q = `SELECT add_or_remove_task_label($1, $2) AS labels;`;
await db.query(q, [createdTask.id, label.label_id]);
}
console.log(`Created task for template ${template.name} with end date ${nextEndDate.format(TIME_FORMAT)}`);
}
} else {
console.log(`Skipped creating task for template ${template.name} with end date ${nextEndDate.format(TIME_FORMAT)} - task already exists`);
}
await db.query(updateScheduleQuery, [
moment().format(TIME_FORMAT),
endDatesToCreate[endDatesToCreate.length - 1].format(TIME_FORMAT),
template.schedule_id
]);
} else {
console.log(`No task created for template ${template.name} - next occurrence is beyond the future limit`);
console.log(`No tasks created for template ${template.name} - next occurrence is beyond the future limit`);
}
// Update the last_checked_at in the schedule
const updateScheduleQuery = `
UPDATE task_recurring_schedules
SET last_checked_at = $1::DATE, last_created_task_end_date = $2
WHERE id = $3;
`;
await db.query(updateScheduleQuery, [moment(template.last_checked_at || template.created_at).add(1, "day").format(TIME_FORMAT), nextEndDate.format(TIME_FORMAT), template.schedule_id]);
}
log(`(cron) Recurring tasks job ended with ${createdTaskCount} new tasks created.`);

View File

@@ -0,0 +1,37 @@
export interface ISurveyQuestion {
id: string;
survey_id: string;
question_key: string;
question_type: 'single_choice' | 'multiple_choice' | 'text';
is_required: boolean;
sort_order: number;
options?: string[];
}
export interface ISurvey {
id: string;
name: string;
description?: string;
survey_type: 'account_setup' | 'onboarding' | 'feedback';
is_active: boolean;
questions?: ISurveyQuestion[];
}
export interface ISurveyAnswer {
question_id: string;
answer_text?: string;
answer_json?: string[];
}
export interface ISurveyResponse {
id?: string;
survey_id: string;
user_id?: string;
is_completed: boolean;
answers: ISurveyAnswer[];
}
export interface ISurveySubmissionRequest {
survey_id: string;
answers: ISurveyAnswer[];
}

View File

@@ -0,0 +1,56 @@
import { NextFunction } from "express";
import { IWorkLenzRequest } from "../../interfaces/worklenz-request";
import { IWorkLenzResponse } from "../../interfaces/worklenz-response";
import { ServerResponse } from "../../models/server-response";
import { ISurveySubmissionRequest } from "../../interfaces/survey";
export default function surveySubmissionValidator(req: IWorkLenzRequest, res: IWorkLenzResponse, next: NextFunction): IWorkLenzResponse | void {
const body = req.body as ISurveySubmissionRequest;
if (!body) {
return res.status(200).send(new ServerResponse(false, null, "Request body is required"));
}
if (!body.survey_id || typeof body.survey_id !== 'string') {
return res.status(200).send(new ServerResponse(false, null, "Survey ID is required and must be a string"));
}
if (!body.answers || !Array.isArray(body.answers)) {
return res.status(200).send(new ServerResponse(false, null, "Answers are required and must be an array"));
}
// Validate each answer
for (let i = 0; i < body.answers.length; i++) {
const answer = body.answers[i];
if (!answer.question_id || typeof answer.question_id !== 'string') {
return res.status(200).send(new ServerResponse(false, null, `Answer ${i + 1}: Question ID is required and must be a string`));
}
// At least one of answer_text or answer_json should be provided
if (!answer.answer_text && !answer.answer_json) {
return res.status(200).send(new ServerResponse(false, null, `Answer ${i + 1}: Either answer_text or answer_json is required`));
}
// Validate answer_text if provided
if (answer.answer_text && typeof answer.answer_text !== 'string') {
return res.status(200).send(new ServerResponse(false, null, `Answer ${i + 1}: answer_text must be a string`));
}
// Validate answer_json if provided
if (answer.answer_json && !Array.isArray(answer.answer_json)) {
return res.status(200).send(new ServerResponse(false, null, `Answer ${i + 1}: answer_json must be an array`));
}
// Validate answer_json items are strings
if (answer.answer_json) {
for (let j = 0; j < answer.answer_json.length; j++) {
if (typeof answer.answer_json[j] !== 'string') {
return res.status(200).send(new ServerResponse(false, null, `Answer ${i + 1}: answer_json items must be strings`));
}
}
}
}
return next();
}

View File

@@ -3,13 +3,16 @@ import { Strategy as LocalStrategy } from "passport-local";
import { log_error } from "../../shared/utils";
import db from "../../config/db";
import { Request } from "express";
import { ERROR_KEY, SUCCESS_KEY } from "./passport-constants";
async function handleLogin(req: Request, email: string, password: string, done: any) {
console.log("Login attempt for:", email);
// Clear any existing flash messages
(req.session as any).flash = {};
if (!email || !password) {
console.log("Missing credentials");
return done(null, false, { message: "Please enter both email and password" });
const errorMsg = "Please enter both email and password";
req.flash(ERROR_KEY, errorMsg);
return done(null, false);
}
try {
@@ -19,23 +22,27 @@ async function handleLogin(req: Request, email: string, password: string, done:
AND google_id IS NULL
AND is_deleted IS FALSE;`;
const result = await db.query(q, [email]);
console.log("User query result count:", result.rowCount);
const [data] = result.rows;
if (!data?.password) {
console.log("No account found");
return done(null, false, { message: "No account found with this email" });
const errorMsg = "No account found with this email";
req.flash(ERROR_KEY, errorMsg);
return done(null, false);
}
const passwordMatch = bcrypt.compareSync(password, data.password);
console.log("Password match:", passwordMatch);
if (passwordMatch && email === data.email) {
delete data.password;
return done(null, data, {message: "User successfully logged in"});
const successMsg = "User successfully logged in";
req.flash(SUCCESS_KEY, successMsg);
return done(null, data);
}
return done(null, false, { message: "Incorrect email or password" });
const errorMsg = "Incorrect email or password";
req.flash(ERROR_KEY, errorMsg);
return done(null, false);
} catch (error) {
console.error("Login error:", error);
log_error(error, req.body);

View File

@@ -0,0 +1,4 @@
{
"doesNotExistText": "Na vjen keq, faqja që kërkoni nuk ekziston.",
"backHomeButton": "Kthehu në Faqen Kryesore"
}

View File

@@ -0,0 +1,31 @@
{
"continue": "Vazhdo",
"setupYourAccount": "Konfiguro Llogarinë Tënde në Worklenz.",
"organizationStepTitle": "Emërtoni Organizatën Tuaj",
"organizationStepLabel": "Zgjidhni një emër për llogarinë tuaj në Worklenz.",
"projectStepTitle": "Krijoni projektin tuaj të parë",
"projectStepLabel": "Në cilin projekt po punoni aktualisht?",
"projectStepPlaceholder": "p.sh. Plani i Marketingut",
"tasksStepTitle": "Krijoni detyrat tuaja të para",
"tasksStepLabel": "Shkruani disa detyra që do të kryeni në",
"tasksStepAddAnother": "Shto një tjetër",
"emailPlaceholder": "Adresa email",
"invalidEmail": "Ju lutemi vendosni një adresë email të vlefshme",
"or": "ose",
"templateButton": "Importo nga shablloni",
"goBack": "Kthehu Mbrapa",
"cancel": "Anulo",
"create": "Krijo",
"templateDrawerTitle": "Zgjidh nga shabllonet",
"step3InputLabel": "Fto me email",
"addAnother": "Shto një tjetër",
"skipForNow": "Kalo tani për tani",
"formTitle": "Krijoni detyrën tuaj të parë.",
"step3Title": "Fto ekipin tënd të punojë me",
"maxMembers": " (Mund të ftoni deri në 5 anëtarë)",
"maxTasks": " (Mund të krijoni deri në 5 detyra)"
}

View File

@@ -0,0 +1,113 @@
{
"title": "Faturimet",
"currentBill": "Fatura Aktuale",
"configuration": "Konfigurimi",
"currentPlanDetails": "Detajet e Planit Aktual",
"upgradePlan": "Përmirëso Planin",
"cardBodyText01": "Provë falas",
"cardBodyText02": "(Plani juaj i provës skadon në 1 muaj 19 ditë)",
"redeemCode": "Kodi i Zbritjes",
"accountStorage": "Depozita e Llogarisë",
"used": "Përdorur:",
"remaining": "E mbetur:",
"charges": "Tarifat",
"tooltip": "Tarifat për ciklin aktual të faturimit",
"description": "Përshkrimi",
"billingPeriod": "Periudha e Faturimit",
"billStatus": "Statusi i Faturës",
"perUserValue": "Vlera për Përdorues",
"users": "Përdoruesit",
"amount": "Shuma",
"invoices": "Faturat",
"transactionId": "ID e Transaksionit",
"transactionDate": "Data e Transaksionit",
"paymentMethod": "Metoda e Pagesës",
"status": "Statusi",
"ltdUsers": "Mund të shtoni deri në {{ltd_users}} përdorues.",
"totalSeats": "Vende totale",
"availableSeats": "Vende të disponueshme",
"addMoreSeats": "Shto më shumë vende",
"drawerTitle": "Kodi i Zbritjes",
"label": "Kodi i Zbritjes",
"drawerPlaceholder": "Vendosni kodin tuaj të zbritjes",
"redeemSubmit": "Paraqit",
"modalTitle": "Zgjidhni planin më të mirë për ekipin tuaj",
"seatLabel": "Numri i vendeve",
"freePlan": "Plan Falas",
"startup": "Startup",
"business": "Biznes",
"tag": "Më i Popullarizuar",
"enterprise": "Ndërmarrje",
"freeSubtitle": "falas përgjithmonë",
"freeUsers": "Më e mira për përdorim personal",
"freeText01": "100MB depozitë",
"freeText02": "3 projekte",
"freeText03": "5 anëtarë të ekipit",
"startupSubtitle": "ÇMIM I RASTËSISHËM / muaj",
"startupUsers": "Deri në 15 përdorues",
"startupText01": "25GB depozitë",
"startupText02": "Projekte të pakufizuara aktive",
"startupText03": "Orar",
"startupText04": "Raportim",
"startupText05": "Abonohu në projekte",
"businessSubtitle": "përdorues / muaj",
"businessUsers": "16 - 200 përdorues",
"enterpriseUsers": "200 - 500+ përdorues",
"footerTitle": "Ju lutemi na jepni një numër kontakti që mund të përdorim për t'ju kontaktuar.",
"footerLabel": "Numri i Kontaktit",
"footerButton": "Na kontaktoni",
"redeemCodePlaceHolder": "Vendosni kodin tuaj të zbritjes",
"submit": "Paraqit",
"trialPlan": "Provë Falas",
"trialExpireDate": "E vlefshme deri më {{trial_expire_date}}",
"trialExpired": "Provat tuaja falas skaduan {{trial_expire_string}}",
"trialInProgress": "Provat tuaja falas skadojnë {{trial_expire_string}}",
"required": "Kjo fushë është e detyrueshme",
"invalidCode": "Kod i pavlefshëm",
"selectPlan": "Zgjidhni planin më të mirë për ekipin tuaj",
"changeSubscriptionPlan": "Ndryshoni planin tuaj të abonimit",
"noOfSeats": "Numri i vendeve",
"annualPlan": "Pro - Vjetor",
"monthlyPlan": "Pro - Mujor",
"freeForever": "Falas Përgjithmonë",
"bestForPersonalUse": "Më e mira për përdorim personal",
"storage": "Depozitë",
"projects": "Projekte",
"teamMembers": "Anëtarët e Ekipit",
"unlimitedTeamMembers": "Anëtarë të pakufizuar të ekipit",
"unlimitedActiveProjects": "Projekte të pakufizuara aktive",
"schedule": "Orar",
"reporting": "Raportim",
"subscribeToProjects": "Abonohu në projekte",
"billedAnnually": "Faturuar çdo vit",
"billedMonthly": "Faturuar çdo muaj",
"pausePlan": "Pauzë Planin",
"resumePlan": "Rifillo Planin",
"changePlan": "Ndrysho Planin",
"cancelPlan": "Anulo Planin",
"perMonthPerUser": "për përdorues/muaj",
"viewInvoice": "Shiko Faturën",
"switchToFreePlan": "Kalo në Planin Falas",
"expirestoday": "sot",
"expirestomorrow": "nesër",
"expiredDaysAgo": "{{days}} ditë më parë",
"continueWith": "Vazhdo me {{plan}}",
"changeToPlan": "Ndrysho në {{plan}}"
}

View File

@@ -0,0 +1,8 @@
{
"overview": "Përmbledhje",
"name": "Emri i Organizatës",
"owner": "Pronari i Organizatës",
"admins": "Administruesit e Organizatës",
"contactNumber": "Shto Numrin e Kontaktit",
"edit": "Redakto"
}

View File

@@ -0,0 +1,12 @@
{
"membersCount": "Numri i Anëtarëve",
"createdAt": "Krijuar më",
"projectName": "Emri i Projektit",
"teamName": "Emri i Ekipit",
"refreshProjects": "Rifresko Projektet",
"searchPlaceholder": "Kërkoni sipas emrit të projektit",
"deleteProject": "Jeni i sigurt që dëshironi të fshini këtë projekt?",
"confirm": "Konfirmo",
"cancel": "Anulo",
"delete": "Fshi Projektin"
}

View File

@@ -0,0 +1,8 @@
{
"overview": "Përmbledhje",
"users": "Përdoruesit",
"teams": "Ekipet",
"billing": "Faturimi",
"projects": "Projektet",
"adminCenter": "Qendra Administrative"
}

View File

@@ -0,0 +1,33 @@
{
"title": "Ekipet",
"subtitle": "ekipet",
"tooltip": "Rifresko ekipet",
"placeholder": "Kërko sipas emrit",
"addTeam": "Shto Ekip",
"team": "Ekipi",
"membersCount": "Numri i Anëtarëve",
"members": "Anëtarët",
"drawerTitle": "Krijo Ekip të Ri",
"label": "Emri i Ekipit",
"drawerPlaceholder": "Emri",
"create": "Krijo",
"delete": "Fshi",
"settings": "Cilësimet",
"popTitle": "Jeni i sigurt?",
"message": "Ju lutemi shkruani një Emër",
"teamSettings": "Cilësimet e Ekipit",
"teamName": "Emri i Ekipit",
"teamDescription": "Përshkrimi i Ekipit",
"teamMembers": "Anëtarët e Ekipit",
"teamMembersCount": "Numri i Anëtarëve të Ekipit",
"teamMembersPlaceholder": "Kërko sipas emrit",
"addMember": "Shto Anëtar",
"add": "Shto",
"update": "Përditëso",
"teamNamePlaceholder": "Emri i ekipit",
"user": "Përdoruesi",
"role": "Roli",
"owner": "Pronari",
"admin": "Administruesi",
"member": "Anëtari"
}

View File

@@ -0,0 +1,9 @@
{
"title": "Përdoruesit",
"subTitle": "përdoruesit",
"placeholder": "Kërko sipas emrit",
"user": "Përdoruesi",
"email": "Email",
"lastActivity": "Aktiviteti i Fundit",
"refresh": "Rifresko përdoruesit"
}

View File

@@ -0,0 +1,34 @@
{
"name": "Emri",
"client": "Klienti",
"category": "Kategoria",
"status": "Statusi",
"tasksProgress": "Përparimi i Detyrave",
"updated_at": "E Përditësuar së Fundi",
"members": "Anëtarët",
"setting": "Cilësimet",
"projects": "Projektet",
"refreshProjects": "Rifresko projektet",
"all": "Të gjitha",
"favorites": "Të preferuarit",
"archived": "E arkivuar",
"placeholder": "Kërko sipas emrit",
"archive": "Arkivo",
"unarchive": "Çarkivo",
"archiveConfirm": "Jeni i sigurt që dëshironi të arkivoni këtë projekt?",
"unarchiveConfirm": "Jeni i sigurt që dëshironi të çarkivoni këtë projekt?",
"yes": "Po",
"no": "Jo",
"clickToFilter": "Kliko për të filtruar sipas",
"noProjects": "Nuk u gjetën projekte",
"addToFavourites": "Shto te të preferuarit",
"list": "Lista",
"group": "Grupi",
"listView": "Pamja e Listës",
"groupView": "Pamja e Grupit",
"groupBy": {
"category": "Kategoria",
"client": "Klienti"
},
"noPermission": "Nuk keni leje për të kryer këtë veprim"
}

View File

@@ -0,0 +1,5 @@
{
"loggingOut": "Po dilni...",
"authenticating": "Po autentikoheni...",
"gettingThingsReady": "Po përgatiten gjërat për ju..."
}

View File

@@ -0,0 +1,12 @@
{
"headerDescription": "Rivendosni fjalëkalimin tuaj",
"emailLabel": "Email",
"emailPlaceholder": "Vendosni email-in tuaj",
"emailRequired": "Ju lutemi vendosni Email-in tuaj!",
"resetPasswordButton": "Rivendos Fjalëkalimin",
"returnToLoginButton": "Kthehu te Hyrja",
"passwordResetSuccessMessage": "Një lidhje për rivendosjen e fjalëkalimit është dërguar në email-in tuaj.",
"orText": "OSE",
"successTitle": "U dërguan udhëzimet për rivendosje!",
"successMessage": "Informacioni për rivendosje është dërguar në email-in tuaj. Ju lutemi kontrolloni email-in."
}

View File

@@ -0,0 +1,27 @@
{
"headerDescription": "Hyni në llogarinë tuaj",
"emailLabel": "Email",
"emailPlaceholder": "Vendosni email-in tuaj",
"emailRequired": "Ju lutemi vendosni Email-in tuaj!",
"passwordLabel": "Fjalëkalimi",
"passwordPlaceholder": "Vendosni fjalëkalimin",
"passwordRequired": "Ju lutemi vendosni Fjalëkalimin!",
"rememberMe": "Më mbaj mend",
"loginButton": "Hyr",
"signupButton": "Regjistrohu",
"forgotPasswordButton": "Keni harruar fjalëkalimin?",
"signInWithGoogleButton": "Hyr me Google",
"dontHaveAccountText": "Nuk keni llogari?",
"orText": "OSE",
"successMessage": "Jeni futur me sukses!",
"loginError": "Hyrja dështoi",
"googleLoginError": "Hyrja përmes Google dështoi",
"validationMessages": {
"email": "Ju lutemi vendosni një adresë email të vlefshme",
"password": "Fjalëkalimi duhet të jetë së paku 8 karaktere"
},
"errorMessages": {
"loginErrorTitle": "Hyrja dështoi",
"loginErrorMessage": "Ju lutemi kontrolloni email-in dhe fjalëkalimin dhe provoni përsëri"
}
}

View File

@@ -0,0 +1,29 @@
{
"headerDescription": "Regjistrohuni për të filluar",
"nameLabel": "Emri i Plotë",
"namePlaceholder": "Shkruani emrin tuaj të plotë",
"nameRequired": "Ju lutemi shkruani emrin tuaj të plotë!",
"nameMinCharacterRequired": "Emri duhet të jetë së paku 4 karaktere!",
"emailLabel": "Email",
"emailPlaceholder": "Shkruani email-in tuaj",
"emailRequired": "Ju lutemi shkruani Email-in tuaj!",
"passwordLabel": "Fjalëkalimi",
"passwordPlaceholder": "Krijoni një fjalëkalim",
"passwordRequired": "Ju lutemi krijoni një Fjalëkalim!",
"passwordMinCharacterRequired": "Fjalëkalimi duhet të jetë së paku 8 karaktere!",
"passwordPatternRequired": "Fjalëkalimi nuk plotëson kërkesat!",
"strongPasswordPlaceholder": "Vendosni një fjalëkalim më të fortë",
"passwordValidationAltText": "Fjalëkalimi duhet të përmbajë së paku 8 karaktere me shkronja të mëdha dhe të vogla, një numër dhe një simbol.",
"signupSuccessMessage": "Jeni regjistruar me sukses!",
"privacyPolicyLink": "Politika e Privatësisë",
"termsOfUseLink": "Kushtet e Përdorimit",
"bySigningUpText": "Duke u regjistruar, ju pranoni",
"andText": "dhe",
"signupButton": "Regjistrohu",
"signInWithGoogleButton": "Hyr me Google",
"alreadyHaveAccountText": "Keni tashmë një llogari?",
"loginButton": "Hyr",
"orText": "OSE",
"reCAPTCHAVerificationError": "Gabim në Verifikimin e reCAPTCHA",
"reCAPTCHAVerificationErrorMessage": "Nuk mundëm të verifikojmë reCAPTCHA-n tuaj. Ju lutemi provoni përsëri."
}

View File

@@ -0,0 +1,14 @@
{
"title": "Verifikoni Email-in për Rivendosje",
"description": "Vendosni fjalëkalimin tuaj të ri",
"placeholder": "Vendosni fjalëkalimin tuaj të ri",
"confirmPasswordPlaceholder": "Konfirmoni fjalëkalimin e ri",
"passwordHint": "Të paktën 8 karaktere, me shkronja të mëdha dhe të vogla, një numër dhe një simbol.",
"resetPasswordButton": "Rivendos fjalëkalimin",
"orText": "Ose",
"resendResetEmail": "Dërgo përsëri email-in e rivendosjes",
"passwordRequired": "Ju lutemi vendosni fjalëkalimin e ri",
"returnToLoginButton": "Kthehu te Hyrja",
"confirmPasswordRequired": "Ju lutemi konfirmoni fjalëkalimin e ri",
"passwordMismatch": "Fjalëkalimet nuk përputhen"
}

View File

@@ -0,0 +1,9 @@
{
"login-success": "Hyrja u krye me sukses!",
"login-failed": "Hyrja dështoi. Ju lutemi kontrolloni kredencialet dhe provoni përsëri.",
"signup-success": "Regjistrimi u krye me sukses! Mirë se erdhët.",
"signup-failed": "Regjistrimi dështoi. Ju lutemi sigurohuni që të gjitha fushat e nevojshme janë plotësuar dhe provoni përsëri.",
"reconnecting": "Jeni shkëputur nga serveri.",
"connection-lost": "Lidhja me serverin dështoi. Ju lutemi kontrolloni lidhjen tuaj me internet.",
"connection-restored": "U lidhët me serverin me sukses"
}

View File

@@ -0,0 +1,13 @@
{
"formTitle": "Krijoni projektin tuaj të parë",
"inputLabel": "Në cilin projekt po punoni aktualisht?",
"or": "ose",
"templateButton": "Importo nga shablloni",
"createFromTemplate": "Krijo nga shablloni",
"goBack": "Kthehu Mbrapa",
"continue": "Vazhdo",
"cancel": "Anulo",
"create": "Krijo",
"templateDrawerTitle": "Zgjidh nga shabllonet",
"createProject": "Krijo Projekt"
}

View File

@@ -0,0 +1,7 @@
{
"formTitle": "Krijo detyrën tënde të parë.",
"inputLabel": "Shkruaj disa detyra që do të kryesh në",
"addAnother": "Shto një tjetër",
"goBack": "Kthehu mbrapa",
"continue": "Vazhdo"
}

View File

@@ -0,0 +1,46 @@
{
"todoList": {
"title": "Lista e Detyrave",
"refreshTasks": "Rifresko detyrat",
"addTask": "+ Shto Detyrë",
"noTasks": "Asnjë detyrë",
"pressEnter": "Shtyp",
"toCreate": "për të krijuar.",
"markAsDone": "Shëno si të përfunduar"
},
"projects": {
"title": "Projektet",
"refreshProjects": "Rifresko projektet",
"noRecentProjects": "Aktualisht nuk jeni caktuar në asnjë projekt.",
"noFavouriteProjects": "Asnjë projekt i shënuar si i preferuar.",
"recent": "Të Fundit",
"favourites": "Të Preferuarat"
},
"tasks": {
"assignedToMe": "Më janë caktuar",
"assignedByMe": "I kam caktuar",
"all": "Të Gjitha",
"today": "Sot",
"upcoming": "Ardhj",
"overdue": "Të vonuara",
"noDueDate": "Pa afat",
"noTasks": "Asnjë detyrë për të shfaqur.",
"addTask": "+ Shto detyrë",
"name": "Emri",
"project": "Projekti",
"status": "Statusi",
"dueDate": "Afati",
"dueDatePlaceholder": "Cakto Afatin",
"tomorrow": "Nesër",
"nextWeek": "Javën e Ardhshme",
"nextMonth": "Muajin e Ardhshëm",
"projectRequired": "Ju lutemi zgjidhni një projekt",
"pressTabToSelectDueDateAndProject": "Shtyp Tab për të zgjedhur afatin dhe projektin",
"dueOn": "Detyrat me afat më",
"taskRequired": "Ju lutemi shtoni një detyrë",
"list": "Listë",
"calendar": "Kalendar",
"tasks": "Detyrat",
"refresh": "Rifresko"
}
}

View File

@@ -0,0 +1,8 @@
{
"formTitle": "Fto ekipin tënd të punojë me",
"inputLabel": "Fto me email",
"addAnother": "Shto një tjetër",
"goBack": "Kthehu mbrapa",
"continue": "Vazhdo",
"skipForNow": "Anashkalo tani për tani"
}

View File

@@ -0,0 +1,30 @@
{
"rename": "Riemërto",
"delete": "Fshi",
"addTask": "Shto Detyrë",
"addSectionButton": "Shto Seksion",
"changeCategory": "Ndrysho kategorinë",
"deleteTooltip": "Fshi",
"deleteConfirmationTitle": "Jeni i sigurt?",
"deleteConfirmationOk": "Po",
"deleteConfirmationCancel": "Anulo",
"dueDate": "Data e përfundimit",
"cancel": "Anulo",
"today": "Sot",
"tomorrow": "Nesër",
"assignToMe": "Cakto mua",
"archive": "Arkivo",
"newTaskNamePlaceholder": "Shkruaj emrin e detyrës",
"newSubtaskNamePlaceholder": "Shkruaj emrin e nëndetyrës",
"untitledSection": "Seksion pa titull",
"unmapped": "Pa hartë",
"clickToChangeDate": "Klikoni për të ndryshuar datën",
"noDueDate": "Pa datë përfundimi",
"save": "Ruaj",
"clear": "Pastro",
"nextWeek": "Javën e ardhshme"
}

View File

@@ -0,0 +1,6 @@
{
"title": "Prova juaj e Worklenz ka skaduar!",
"subtitle": "Ju lutemi përmirësoni tani.",
"button": "Përmirëso tani",
"checking": "Po kontrollohet statusi i abonimit..."
}

View File

@@ -0,0 +1,31 @@
{
"logoAlt": "Logoja e Worklenz",
"home": "Kryefaqja",
"projects": "Projektet",
"schedule": "Orari",
"reporting": "Raportimi",
"clients": "Klientët",
"teams": "Ekipet",
"labels": "Etiketa",
"jobTitles": "Tituj Pune",
"upgradePlan": "Përmirëso Abonimin",
"upgradePlanTooltip": "Përmirëso abonimin",
"invite": "Fto",
"inviteTooltip": "Fto anëtarë të ekipit të bashkohen",
"switchTeamTooltip": "Ndrysho ekipin",
"help": "Ndihmë",
"notificationTooltip": "Shiko njoftimet",
"profileTooltip": "Shiko profilin",
"adminCenter": "Qendra Administrative",
"settings": "Cilësimet",
"logOut": "Dil",
"notificationsDrawer": {
"read": "Lexuara e njoftimet ",
"unread": "Njoftimet e palexuara",
"markAsRead": "Shëno si të lexuara",
"readAndJoin": "Lexo & Bashkohu",
"accept": "Prano",
"acceptAndJoin": "Prano & Bashkohu",
"noNotifications": "Asnjë njoftim"
}
}

View File

@@ -0,0 +1,5 @@
{
"nameYourOrganization": "Emërtoni organizatën tuaj.",
"worklenzAccountTitle": "Zgjidhni një emër për llogarinë tuaj në Worklenz.",
"continue": "Vazhdo"
}

View File

@@ -0,0 +1,19 @@
{
"configurePhases": "Konfiguro Fazat",
"phaseLabel": "Etiketa e Fazës",
"enterPhaseName": "Vendosni një emër për etiketën e fazës",
"addOption": "Shto Opsion",
"phaseOptions": "Opsionet e Fazës:",
"dragToReorderPhases": "Zvarrit fazat për t'i rirenditur. Çdo fazë mund të ketë një ngjyrë të ndryshme.",
"enterNewPhaseName": "Shkruani emrin e fazës së re...",
"addPhase": "Shto Fazë",
"noPhasesFound": "Nuk u gjetën faza. Krijoni fazën tuaj të parë më sipër.",
"deletePhase": "Fshi Fazën",
"deletePhaseConfirm": "Jeni të sigurt që doni të fshini këtë fazë? Ky veprim nuk mund të zhbëhet.",
"rename": "Riemëro",
"delete": "Fshi",
"enterPhaseName": "Shkruani emrin e fazës",
"selectColor": "Zgjidh ngjyrën",
"managePhases": "Menaxho Fazat",
"close": "Mbyll"
}

View File

@@ -0,0 +1,42 @@
{
"createProject": "Krijo Projekt",
"editProject": "Modifiko Projektin",
"enterCategoryName": "Vendosni emër për kategorinë",
"hitEnterToCreate": "Shtyp Enter për të krijuar!",
"enterNotes": "Shënime",
"youCanManageClientsUnderSettings": "Mund të menaxhoni klientët nën Cilësimet",
"addCategory": "Shto kategori projektit",
"newCategory": "Kategori e Re",
"notes": "Shënime",
"startDate": "Data e Fillimit",
"endDate": "Data e Përfundimit",
"estimateWorkingDays": "Vlerëso ditët e punës",
"estimateManDays": "Vlerëso ditët e punëtorëve",
"hoursPerDay": "Orë në ditë",
"create": "Krijo",
"update": "Përditëso",
"delete": "Fshi",
"typeToSearchClients": "Shkruani për të kërkuar klientë",
"projectColor": "Ngjyra e Projektit",
"pleaseEnterAName": "Ju lutemi vendosni një emër",
"enterProjectName": "Vendosni emrin e projektit",
"name": "Emri",
"status": "Statusi",
"health": "Gjendja",
"category": "Kategoria",
"projectManager": "Menaxheri i Projektit",
"client": "Klienti",
"deleteConfirmation": "Jeni i sigurt që doni të fshini?",
"deleteConfirmationDescription": "Kjo do të fshijë të gjitha të dhënat e lidhura dhe nuk mund të zhbëhet.",
"yes": "Po",
"no": "Jo",
"createdAt": "Krijuar më",
"updatedAt": "Përditësuar më",
"by": "nga",
"add": "Shto",
"asClient": "si klient",
"createClient": "Krijo klient",
"searchInputPlaceholder": "Kërko sipas emrit ose emailit",
"hoursPerDayValidationMessage": "Orët në ditë duhet të jenë një numër midis 1 dhe 24",
"noPermission": "Nuk ka leje"
}

View File

@@ -0,0 +1,14 @@
{
"nameColumn": "Emri",
"attachedTaskColumn": "Detyra e Bashkangjitur",
"sizeColumn": "Madhësia",
"uploadedByColumn": "Ngarkuar Nga",
"uploadedAtColumn": "Ngarkuar Më",
"fileIconAlt": "Ikona e skedarit",
"titleDescriptionText": "Të gjitha bashkëngjitjet e detyrave në këtë projekt do të shfahen këtu.",
"deleteConfirmationTitle": "Jeni i sigurt?",
"deleteConfirmationOk": "Po",
"deleteConfirmationCancel": "Anulo",
"segmentedTooltip": "Së shpejti! Kaloni midis pamjes listë dhe pamjes miniaturash.",
"emptyText": "Nuk ka bashkëngjitje në projekt."
}

View File

@@ -0,0 +1,41 @@
{
"overview": {
"title": "Përmbledhje",
"statusOverview": "Përmbledhje Statusi",
"priorityOverview": "Përmbledhje Prioriteti",
"lastUpdatedTasks": "Detyrat e Përditësuara Së Fundi"
},
"members": {
"title": "Anëtarët",
"tooltip": "Anëtarët",
"tasksByMembers": "Detyrat sipas anëtarëve",
"tasksByMembersTooltip": "Detyrat sipas anëtarëve",
"name": "Emri",
"taskCount": "Numri i Detyrave",
"contribution": "Kontributi",
"completed": "Të Përfunduara",
"incomplete": "Të Papërfunduara",
"overdue": "Të Vonuara",
"progress": "Progresi"
},
"tasks": {
"overdueTasks": "Detyrat e Vonuara",
"overLoggedTasks": "Detyrat me regjistrim të tepërt",
"tasksCompletedEarly": "Detyrat e përfunduara para afatit",
"tasksCompletedLate": "Detyrat e përfunduara pas afatit",
"overLoggedTasksTooltip": "Detyrat me kohë të regjistruar mbi kohën e vlerësuar",
"overdueTasksTooltip": "Detyrat që kanë kaluar afatin e tyre"
},
"common": {
"seeAll": "Shiko të gjitha",
"totalLoggedHours": "Orët totale të regjistruara",
"totalEstimation": "Vlerësimi total",
"completedTasks": "Detyrat e përfunduara",
"incompleteTasks": "Detyrat e papërfunduara",
"overdueTasks": "Detyrat e vonuara",
"overdueTasksTooltip": "Detyrat që kanë kaluar afatin e tyre",
"totalLoggedHoursTooltip": "Vlerësimi dhe koha e regjistruar për detyrat.",
"includeArchivedTasks": "Përfshi Detyrat e Arkivuara",
"export": "Eksporto"
}
}

View File

@@ -0,0 +1,17 @@
{
"nameColumn": "Emri",
"jobTitleColumn": "Titulli i Punës",
"emailColumn": "Email",
"tasksColumn": "Detyrat",
"taskProgressColumn": "Progresi i Detyrave",
"accessColumn": "Qasja",
"fileIconAlt": "Ikona e skedarit",
"deleteConfirmationTitle": "Jeni i sigurt?",
"deleteConfirmationOk": "Po",
"deleteConfirmationCancel": "Anulo",
"refreshButtonTooltip": "Rifresko anëtarët",
"deleteButtonTooltip": "Hiq nga projekti",
"memberCount": "Anëtar",
"membersCountPlural": "Anëtarë",
"emptyText": "Nuk ka bashkëngjitje në projekt."
}

View File

@@ -0,0 +1,6 @@
{
"inputPlaceholder": "Shto një koment..",
"addButton": "Shto",
"cancelButton": "Anulo",
"deleteButton": "Fshi"
}

View File

@@ -0,0 +1,14 @@
{
"taskList": "Lista e Detyrave",
"board": "Tabela Kanban",
"insights": "Analiza",
"files": "Skedarë",
"members": "Anëtarë",
"updates": "Përditësime",
"projectView": "Pamja e Projektit",
"loading": "Duke ngarkuar projektin...",
"error": "Gabim në ngarkimin e projektit",
"pinnedTab": "E fiksuar si tab i parazgjedhur",
"pinTab": "Fikso si tab i parazgjedhur",
"unpinTab": "Hiqe fiksimin e tab-it të parazgjedhur"
}

Some files were not shown because too many files have changed in this diff Show More