diff --git a/backup.sh b/backup.sh new file mode 100644 index 00000000..8f16e1c7 --- /dev/null +++ b/backup.sh @@ -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" diff --git a/docker-compose.yml b/docker-compose.yml index 363f2006..6522ddff 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -83,7 +83,11 @@ 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 @@ -93,23 +97,65 @@ services: 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: diff --git a/docs/enhanced-task-management-technical-guide.md b/docs/enhanced-task-management-technical-guide.md new file mode 100644 index 00000000..af808cd6 --- /dev/null +++ b/docs/enhanced-task-management-technical-guide.md @@ -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([]); + +// 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' && ( + + + +)} +``` + +## 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 ? ( + +) : ( + +); +``` + +### 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 +
+ +
+``` + +### 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(); + + 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 \ No newline at end of file diff --git a/docs/enhanced-task-management-user-guide.md b/docs/enhanced-task-management-user-guide.md new file mode 100644 index 00000000..34a50e85 --- /dev/null +++ b/docs/enhanced-task-management-user-guide.md @@ -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 \ No newline at end of file diff --git a/docs/recurring-tasks-user-guide.md b/docs/recurring-tasks-user-guide.md new file mode 100644 index 00000000..3d91572a --- /dev/null +++ b/docs/recurring-tasks-user-guide.md @@ -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. \ No newline at end of file diff --git a/docs/recurring-tasks.md b/docs/recurring-tasks.md new file mode 100644 index 00000000..71448719 --- /dev/null +++ b/docs/recurring-tasks.md @@ -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. \ No newline at end of file diff --git a/docs/task-progress-guide-for-users.md b/docs/task-progress-guide-for-users.md new file mode 100644 index 00000000..1eeb15c1 --- /dev/null +++ b/docs/task-progress-guide-for-users.md @@ -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. \ No newline at end of file diff --git a/docs/task-progress-methods.md b/docs/task-progress-methods.md new file mode 100644 index 00000000..b931b7f5 --- /dev/null +++ b/docs/task-progress-methods.md @@ -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. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..57245686 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "worklenz", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/task-progress-methods.md b/task-progress-methods.md new file mode 100644 index 00000000..11b18ef5 --- /dev/null +++ b/task-progress-methods.md @@ -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 : ( + + + + ); +}; +``` + +**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. \ No newline at end of file diff --git a/worklenz-backend/.env.template b/worklenz-backend/.env.template index 145f2411..fdd8fe44 100644 --- a/worklenz-backend/.env.template +++ b/worklenz-backend/.env.template @@ -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 \ No newline at end of file +ENABLE_EMAIL_CRONJOBS=true + +# RECURRING_JOBS +ENABLE_RECURRING_JOBS=true +RECURRING_JOBS_INTERVAL="0 11 */1 * 1-5" \ No newline at end of file diff --git a/worklenz-backend/Gruntfile.js b/worklenz-backend/Gruntfile.js deleted file mode 100644 index b621cbc0..00000000 --- a/worklenz-backend/Gruntfile.js +++ /dev/null @@ -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", []); -}; diff --git a/worklenz-backend/database/00-init-db.sh b/worklenz-backend/database/00-init-db.sh deleted file mode 100644 index 9743d435..00000000 --- a/worklenz-backend/database/00-init-db.sh +++ /dev/null @@ -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" \ No newline at end of file diff --git a/worklenz-backend/database/00_init.sh b/worklenz-backend/database/00_init.sh new file mode 100644 index 00000000..afd8562a --- /dev/null +++ b/worklenz-backend/database/00_init.sh @@ -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." diff --git a/worklenz-backend/database/migrations/20250115000000-performance-indexes.sql b/worklenz-backend/database/migrations/20250115000000-performance-indexes.sql new file mode 100644 index 00000000..791c6f02 --- /dev/null +++ b/worklenz-backend/database/migrations/20250115000000-performance-indexes.sql @@ -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); \ No newline at end of file diff --git a/worklenz-backend/database/migrations/20250128000000-fix-window-function-error.sql b/worklenz-backend/database/migrations/20250128000000-fix-window-function-error.sql new file mode 100644 index 00000000..9a20e173 --- /dev/null +++ b/worklenz-backend/database/migrations/20250128000000-fix-window-function-error.sql @@ -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 +$$; \ No newline at end of file diff --git a/worklenz-backend/database/migrations/20250422132400-manual-task-progress.sql b/worklenz-backend/database/migrations/20250422132400-manual-task-progress.sql new file mode 100644 index 00000000..c45d34af --- /dev/null +++ b/worklenz-backend/database/migrations/20250422132400-manual-task-progress.sql @@ -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; \ No newline at end of file diff --git a/worklenz-backend/database/migrations/20250423000000-subtask-manual-progress.sql b/worklenz-backend/database/migrations/20250423000000-subtask-manual-progress.sql new file mode 100644 index 00000000..b4650dc7 --- /dev/null +++ b/worklenz-backend/database/migrations/20250423000000-subtask-manual-progress.sql @@ -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; \ No newline at end of file diff --git a/worklenz-backend/database/migrations/20250424000000-add-progress-and-weight-activity-types.sql b/worklenz-backend/database/migrations/20250424000000-add-progress-and-weight-activity-types.sql new file mode 100644 index 00000000..53eafe20 --- /dev/null +++ b/worklenz-backend/database/migrations/20250424000000-add-progress-and-weight-activity-types.sql @@ -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; \ No newline at end of file diff --git a/worklenz-backend/database/migrations/20250425000000-update-time-based-progress.sql b/worklenz-backend/database/migrations/20250425000000-update-time-based-progress.sql new file mode 100644 index 00000000..7b02bef7 --- /dev/null +++ b/worklenz-backend/database/migrations/20250425000000-update-time-based-progress.sql @@ -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; \ No newline at end of file diff --git a/worklenz-backend/database/migrations/20250426000000-improve-parent-task-progress-calculation.sql b/worklenz-backend/database/migrations/20250426000000-improve-parent-task-progress-calculation.sql new file mode 100644 index 00000000..e1d5d1f2 --- /dev/null +++ b/worklenz-backend/database/migrations/20250426000000-improve-parent-task-progress-calculation.sql @@ -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; \ No newline at end of file diff --git a/worklenz-backend/database/migrations/20250426000000-update-progress-mode-handlers.sql b/worklenz-backend/database/migrations/20250426000000-update-progress-mode-handlers.sql new file mode 100644 index 00000000..7f16670b --- /dev/null +++ b/worklenz-backend/database/migrations/20250426000000-update-progress-mode-handlers.sql @@ -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; \ No newline at end of file diff --git a/worklenz-backend/database/migrations/20250427000000-fix-progress-mode-type.sql b/worklenz-backend/database/migrations/20250427000000-fix-progress-mode-type.sql new file mode 100644 index 00000000..6e5efc9d --- /dev/null +++ b/worklenz-backend/database/migrations/20250427000000-fix-progress-mode-type.sql @@ -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; \ No newline at end of file diff --git a/worklenz-backend/database/migrations/20250506000000-fix-multilevel-subtask-progress-calculation.sql b/worklenz-backend/database/migrations/20250506000000-fix-multilevel-subtask-progress-calculation.sql new file mode 100644 index 00000000..1a8d4cba --- /dev/null +++ b/worklenz-backend/database/migrations/20250506000000-fix-multilevel-subtask-progress-calculation.sql @@ -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; \ No newline at end of file diff --git a/worklenz-backend/database/migrations/consolidated-progress-migrations.sql b/worklenz-backend/database/migrations/consolidated-progress-migrations.sql new file mode 100644 index 00000000..ef89a923 --- /dev/null +++ b/worklenz-backend/database/migrations/consolidated-progress-migrations.sql @@ -0,0 +1,1140 @@ +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 + 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 progress_mode PROGRESS_MODE_TYPE DEFAULT 'default', + ADD COLUMN IF NOT EXISTS weight INTEGER DEFAULT NULL; + +-- 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; + +-- Add progress-related fields to projects table +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; + +-- 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; + _project_id UUID; + _use_manual_progress BOOLEAN = FALSE; + _use_weighted_progress BOOLEAN = FALSE; + _use_time_progress BOOLEAN = FALSE; + _task_complete BOOLEAN = FALSE; + _progress_mode VARCHAR(20) = NULL; +BEGIN + -- Check if manual progress is set for this task + SELECT manual_progress, progress_value, project_id, progress_mode, + 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, _progress_mode, _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; + + -- Determine current active mode + DECLARE + _current_mode VARCHAR(20) = 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; + BEGIN + -- Only use manual progress value if it was set in the current active mode + -- and time progress is not enabled + IF _use_time_progress IS FALSE AND + ((_is_manual IS TRUE AND _manual_value IS NOT NULL AND + (_progress_mode IS NULL OR _progress_mode = _current_mode)) OR + (_use_manual_progress IS TRUE AND _manual_value IS NOT NULL AND + (_progress_mode IS NULL OR _progress_mode = 'manual'))) THEN + RETURN JSON_BUILD_OBJECT( + 'ratio', _manual_value, + 'total_completed', 0, + 'total_tasks', 0, + 'is_manual', TRUE + ); + END IF; + END; + + -- If there are no subtasks, calculate based on the task itself + IF _sub_tasks_count = 0 THEN + -- Use time-based estimation if enabled + IF _use_time_progress IS TRUE THEN + -- Calculate progress based on logged time vs estimated time + WITH task_time_info AS ( + SELECT + COALESCE(t.total_minutes, 0) as estimated_minutes, + COALESCE(( + SELECT SUM(time_spent) + FROM task_work_log + WHERE task_id = t.id + ), 0) as logged_minutes + FROM tasks t + WHERE t.id = _task_id + ) + SELECT + CASE + WHEN _task_complete IS TRUE THEN 100 + WHEN estimated_minutes > 0 THEN + LEAST((logged_minutes / estimated_minutes) * 100, 100) + ELSE 0 + END + INTO _ratio + FROM task_time_info; + 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 AND _use_time_progress IS FALSE THEN + WITH subtask_progress AS ( + SELECT + t.id, + t.manual_progress, + t.progress_value, + t.progress_mode, + 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 + WHEN is_complete IS TRUE THEN 100 + WHEN progress_value IS NOT NULL AND (progress_mode = 'manual' OR progress_mode IS NULL) THEN 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 AND _use_time_progress IS FALSE THEN + WITH subtask_progress AS ( + SELECT + t.id, + t.manual_progress, + t.progress_value, + t.progress_mode, + 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 + WHEN is_complete IS TRUE THEN 100 + WHEN progress_value IS NOT NULL AND (progress_mode = 'weighted' OR progress_mode IS NULL) THEN 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 actual logged time + ELSIF _use_time_progress IS TRUE THEN + WITH task_time_info AS ( + SELECT + t.id, + COALESCE(t.total_minutes, 0) as estimated_minutes, + COALESCE(( + SELECT SUM(time_spent) + FROM task_work_log + WHERE task_id = t.id + ), 0) as logged_minutes, + 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 + ) + SELECT COALESCE( + SUM( + CASE + WHEN is_complete IS TRUE THEN estimated_minutes + ELSE LEAST(logged_minutes, estimated_minutes) + END + ) / NULLIF(SUM(estimated_minutes), 0) * 100, + 0 + ) + FROM task_time_info + 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 +$$; + +-- Update project functions to handle progress-related fields +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; +$$; + +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; +$$; + +COMMIT; + +BEGIN; + +-- 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 VARCHAR(20); +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 VARCHAR(20); + _new_mode VARCHAR(20); + _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; + +BEGIN; + +-- Add progress_mode column to tasks table to track which mode the progress was set in +ALTER TABLE tasks + ADD COLUMN IF NOT EXISTS progress_mode VARCHAR(20) DEFAULT NULL; + +-- Update function to use time-based progress for all tasks and respect mode changes +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; + _progress_mode VARCHAR(20) = NULL; +BEGIN + -- Check if manual progress is set for this task + SELECT manual_progress, + progress_value, + project_id, + progress_mode, + 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, _progress_mode, _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; + + -- Determine current active mode + DECLARE + _current_mode VARCHAR(20) = 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; + BEGIN + -- Only use manual progress value if it was set in the current active mode + -- or if the task is explicitly marked for manual progress + IF (_is_manual IS TRUE AND _manual_value IS NOT NULL AND + (_progress_mode IS NULL OR _progress_mode = _current_mode)) OR + (_use_manual_progress IS TRUE AND _manual_value IS NOT NULL AND + (_progress_mode IS NULL OR _progress_mode = 'manual')) + THEN + RETURN JSON_BUILD_OBJECT( + 'ratio', _manual_value, + 'total_completed', 0, + 'total_tasks', 0, + 'is_manual', TRUE + ); + END IF; + END; + + -- 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 in the correct mode, or 0 + SELECT CASE + WHEN _task_complete IS TRUE THEN 100 + WHEN _manual_value IS NOT NULL AND (_progress_mode = 'time' OR _progress_mode IS NULL) + THEN _manual_value + ELSE 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, + t.progress_mode, + 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 in the correct mode, use it + WHEN progress_value IS NOT NULL AND + (progress_mode = 'manual' OR progress_mode IS NULL) + THEN progress_value + -- Default to 0 for incomplete tasks with no progress value or wrong mode + 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, + t.progress_mode, + 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 in the correct mode, use it + WHEN progress_value IS NOT NULL AND + (progress_mode = 'weighted' OR progress_mode IS NULL) + THEN progress_value + -- Default to 0 for incomplete tasks with no progress value or wrong mode + 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 actual logged time + ELSIF _use_time_progress IS TRUE + THEN + WITH task_time_info AS ( + SELECT + t.id, + COALESCE(t.total_minutes, 0) as estimated_minutes, + COALESCE(( + SELECT SUM(time_spent) + FROM task_work_log + WHERE task_id = t.id + ), 0) as logged_minutes, + 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 + ) + SELECT COALESCE( + SUM( + CASE + WHEN is_complete IS TRUE THEN estimated_minutes + ELSE LEAST(logged_minutes, estimated_minutes) + END + ) / NULLIF(SUM(estimated_minutes), 0) * 100, + 0 + ) + FROM task_time_info + 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 +$$; + +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; +$$; + +COMMIT; + +BEGIN; + +-- 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 VARCHAR(20); +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 VARCHAR(20); + _new_mode VARCHAR(20); + _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; + + diff --git a/worklenz-backend/database/sql/1_tables.sql b/worklenz-backend/database/sql/1_tables.sql index e9fc31c4..21f498f1 100644 --- a/worklenz-backend/database/sql/1_tables.sql +++ b/worklenz-backend/database/sql/1_tables.sql @@ -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', 'alb', 'de'); +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; diff --git a/worklenz-backend/database/sql/3_views.sql b/worklenz-backend/database/sql/3_views.sql index 15e36e23..f29291de 100644 --- a/worklenz-backend/database/sql/3_views.sql +++ b/worklenz-backend/database/sql/3_views.sql @@ -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; +$$; + diff --git a/worklenz-backend/database/sql/4_functions.sql b/worklenz-backend/database/sql/4_functions.sql index 189f8ac7..2c57d3c4 100644 --- a/worklenz-backend/database/sql/4_functions.sql +++ b/worklenz-backend/database/sql/4_functions.sql @@ -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 $$; @@ -4317,6 +4325,7 @@ DECLARE _from_group UUID; _to_group UUID; _group_by TEXT; + _batch_size INT := 100; -- PERFORMANCE OPTIMIZATION: Batch size for large updates BEGIN _project_id = (_body ->> 'project_id')::UUID; _task_id = (_body ->> 'task_id')::UUID; @@ -4329,16 +4338,26 @@ BEGIN _group_by = (_body ->> 'group_by')::TEXT; + -- PERFORMANCE OPTIMIZATION: Use CTE for better query planning IF (_from_group <> _to_group OR (_from_group <> _to_group) IS NULL) THEN + -- PERFORMANCE OPTIMIZATION: Batch update group changes IF (_group_by = 'status') THEN - UPDATE tasks SET status_id = _to_group WHERE id = _task_id AND status_id = _from_group; + 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; + 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') @@ -4357,14 +4376,15 @@ BEGIN END IF; END IF; + -- PERFORMANCE OPTIMIZATION: Optimized sort order handling 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); + PERFORM handle_task_list_sort_inside_group_optimized(_from_index, _to_index, _task_id, _project_id, _batch_size); ELSE - PERFORM handle_task_list_sort_between_groups(_from_index, _to_index, _task_id, _project_id); + PERFORM handle_task_list_sort_between_groups_optimized(_from_index, _to_index, _task_id, _project_id, _batch_size); END IF; ELSE - PERFORM handle_task_list_sort_inside_group(_from_index, _to_index, _task_id, _project_id); + PERFORM handle_task_list_sort_inside_group_optimized(_from_index, _to_index, _task_id, _project_id, _batch_size); END IF; END $$; @@ -5477,8 +5497,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 +5514,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 +6198,360 @@ 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 ', (SELECT name FROM teams WHERE id = _team_id), ' has been moved to a different organization') + ); + + PERFORM create_notification( + _new_owner_id, + _team_id, + NULL, + NULL, + CONCAT('Team ', (SELECT name FROM teams WHERE id = _team_id), ' 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 ', (SELECT organization_name FROM organizations WHERE id = _old_org_id), '') + ); + + PERFORM create_notification( + _new_owner_id, + NULL, + NULL, + NULL, + CONCAT('You are now the owner of organization ', (SELECT organization_name FROM organizations WHERE id = _old_org_id), '') + ); + 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 ', (SELECT name FROM teams WHERE id = _team_id), '') + ); + + PERFORM create_notification( + _new_owner_id, + _team_id, + NULL, + NULL, + CONCAT('You are now the owner of team ', (SELECT name FROM teams WHERE id = _team_id), '') + ); + + 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 +$$; + +-- Simple function to update task sort orders in bulk +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 +$$; diff --git a/worklenz-backend/package-lock.json b/worklenz-backend/package-lock.json index 2953defa..1a0f78d3 100644 --- a/worklenz-backend/package-lock.json +++ b/worklenz-backend/package-lock.json @@ -24,6 +24,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", @@ -32,6 +33,7 @@ "express-rate-limit": "^6.8.0", "express-session": "^1.17.3", "express-validator": "^6.15.0", + "grunt-cli": "^1.5.0", "helmet": "^6.2.0", "hpp": "^0.2.3", "http-errors": "^2.0.0", @@ -49,7 +51,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", @@ -57,8 +58,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": { @@ -66,15 +69,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", @@ -99,26 +104,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", @@ -130,23 +131,15 @@ "yarn": "WARNING: Please use npm package manager instead of yarn" } }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -157,6 +150,7 @@ "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", "dev": true, + "license": "MIT", "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.6", @@ -169,6 +163,7 @@ "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } @@ -177,13 +172,15 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@apidevtools/swagger-parser": { "version": "10.0.3", "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", "dev": true, + "license": "MIT", "dependencies": { "@apidevtools/json-schema-ref-parser": "^9.0.6", "@apidevtools/openapi-schemas": "^2.0.4", @@ -197,792 +194,924 @@ } }, "node_modules/@aws-crypto/crc32": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", - "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", "dependencies": { - "@aws-crypto/util": "^3.0.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-crypto/crc32/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, "node_modules/@aws-crypto/crc32c": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-3.0.0.tgz", - "integrity": "sha512-ENNPPManmnVJ4BTXlOjAgD7URidbAznURqD0KvfREyc4o20DPYdEldU1f5cQ7Jbj0CJJSPaMIk/9ZshdB3210w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "license": "Apache-2.0", "dependencies": { - "@aws-crypto/util": "^3.0.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" + "tslib": "^2.6.2" } }, - "node_modules/@aws-crypto/crc32c/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@aws-crypto/ie11-detection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", - "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", - "dependencies": { - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/ie11-detection/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, "node_modules/@aws-crypto/sha1-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-3.0.0.tgz", - "integrity": "sha512-NJth5c997GLHs6nOYTzFKTbYdMNA6/1XlKVgnZoaZcQ7z7UJlOgj2JdbHE8tiYLS3fzXNCguct77SPGat2raSw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", "dependencies": { - "@aws-crypto/ie11-detection": "^3.0.0", - "@aws-crypto/supports-web-crypto": "^3.0.0", - "@aws-crypto/util": "^3.0.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/sha1-browser/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@aws-crypto/sha256-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", - "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", - "dependencies": { - "@aws-crypto/ie11-detection": "^3.0.0", - "@aws-crypto/sha256-js": "^3.0.0", - "@aws-crypto/supports-web-crypto": "^3.0.0", - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@aws-crypto/sha256-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", - "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", - "dependencies": { - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@aws-crypto/supports-web-crypto": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", - "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", - "dependencies": { - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@aws-crypto/util": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", - "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/util/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@aws-sdk/client-s3": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.378.0.tgz", - "integrity": "sha512-FW1CFT6Kt2Y+IiFPCd70VapcZBkS1ZhpPZttpJeugE8T2Hye1fwQDDvAwd3Slo4zMkTL+cWQkfFJSNB1Dez/pQ==", - "dependencies": { - "@aws-crypto/sha1-browser": "3.0.0", - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.378.0", - "@aws-sdk/credential-provider-node": "3.378.0", - "@aws-sdk/middleware-bucket-endpoint": "3.378.0", - "@aws-sdk/middleware-expect-continue": "3.378.0", - "@aws-sdk/middleware-flexible-checksums": "3.378.0", - "@aws-sdk/middleware-host-header": "3.378.0", - "@aws-sdk/middleware-location-constraint": "3.378.0", - "@aws-sdk/middleware-logger": "3.378.0", - "@aws-sdk/middleware-recursion-detection": "3.378.0", - "@aws-sdk/middleware-sdk-s3": "3.378.0", - "@aws-sdk/middleware-signing": "3.378.0", - "@aws-sdk/middleware-ssec": "3.378.0", - "@aws-sdk/middleware-user-agent": "3.378.0", - "@aws-sdk/signature-v4-multi-region": "3.378.0", - "@aws-sdk/types": "3.378.0", - "@aws-sdk/util-endpoints": "3.378.0", - "@aws-sdk/util-user-agent-browser": "3.378.0", - "@aws-sdk/util-user-agent-node": "3.378.0", - "@aws-sdk/xml-builder": "3.310.0", - "@smithy/config-resolver": "^2.0.1", - "@smithy/eventstream-serde-browser": "^2.0.1", - "@smithy/eventstream-serde-config-resolver": "^2.0.1", - "@smithy/eventstream-serde-node": "^2.0.1", - "@smithy/fetch-http-handler": "^2.0.1", - "@smithy/hash-blob-browser": "^2.0.1", - "@smithy/hash-node": "^2.0.1", - "@smithy/hash-stream-node": "^2.0.1", - "@smithy/invalid-dependency": "^2.0.1", - "@smithy/md5-js": "^2.0.1", - "@smithy/middleware-content-length": "^2.0.1", - "@smithy/middleware-endpoint": "^2.0.1", - "@smithy/middleware-retry": "^2.0.1", - "@smithy/middleware-serde": "^2.0.1", - "@smithy/middleware-stack": "^2.0.0", - "@smithy/node-config-provider": "^2.0.1", - "@smithy/node-http-handler": "^2.0.1", - "@smithy/protocol-http": "^2.0.1", - "@smithy/smithy-client": "^2.0.1", - "@smithy/types": "^2.0.2", - "@smithy/url-parser": "^2.0.1", - "@smithy/util-base64": "^2.0.0", - "@smithy/util-body-length-browser": "^2.0.0", - "@smithy/util-body-length-node": "^2.0.0", - "@smithy/util-defaults-mode-browser": "^2.0.1", - "@smithy/util-defaults-mode-node": "^2.0.1", - "@smithy/util-retry": "^2.0.0", - "@smithy/util-stream": "^2.0.1", "@smithy/util-utf8": "^2.0.0", - "@smithy/util-waiter": "^2.0.1", - "fast-xml-parser": "4.2.5", - "tslib": "^2.5.0" + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.840.0.tgz", + "integrity": "sha512-dRuo03EqGBbl9+PTogpwY9bYmGWIjn8nB82HN5Qj20otgjUvhLOdEkkip9mroYsrvqNoKbMedWdCudIcB/YY1w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/credential-provider-node": "3.840.0", + "@aws-sdk/middleware-bucket-endpoint": "3.840.0", + "@aws-sdk/middleware-expect-continue": "3.840.0", + "@aws-sdk/middleware-flexible-checksums": "3.840.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-location-constraint": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-sdk-s3": "3.840.0", + "@aws-sdk/middleware-ssec": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.840.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/signature-v4-multi-region": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.840.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.840.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.6.0", + "@smithy/eventstream-serde-browser": "^4.0.4", + "@smithy/eventstream-serde-config-resolver": "^4.1.2", + "@smithy/eventstream-serde-node": "^4.0.4", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-blob-browser": "^4.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/hash-stream-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/md5-js": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/middleware-retry": "^4.1.14", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.21", + "@smithy/util-defaults-mode-node": "^4.0.21", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "@smithy/util-waiter": "^4.0.6", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@aws-sdk/client-ses": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.378.0.tgz", - "integrity": "sha512-9mFGS1AYOjPcIfGeOdP8YGQSLSv+pldxTUcGytST9L8Vdj7yPY2bv/gLi+UUckTU1mKP98cQFcrBpzxawmTnDA==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.840.0.tgz", + "integrity": "sha512-RTIVFrAGDAOJ0xWFgCf9q0y1QrfPOCn1O6fKfjqwGig0XjwQH/YbxwC6wfV24/JAPrt2qRjkSU6SvBSVcHp9+w==", + "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.378.0", - "@aws-sdk/credential-provider-node": "3.378.0", - "@aws-sdk/middleware-host-header": "3.378.0", - "@aws-sdk/middleware-logger": "3.378.0", - "@aws-sdk/middleware-recursion-detection": "3.378.0", - "@aws-sdk/middleware-signing": "3.378.0", - "@aws-sdk/middleware-user-agent": "3.378.0", - "@aws-sdk/types": "3.378.0", - "@aws-sdk/util-endpoints": "3.378.0", - "@aws-sdk/util-user-agent-browser": "3.378.0", - "@aws-sdk/util-user-agent-node": "3.378.0", - "@smithy/config-resolver": "^2.0.1", - "@smithy/fetch-http-handler": "^2.0.1", - "@smithy/hash-node": "^2.0.1", - "@smithy/invalid-dependency": "^2.0.1", - "@smithy/middleware-content-length": "^2.0.1", - "@smithy/middleware-endpoint": "^2.0.1", - "@smithy/middleware-retry": "^2.0.1", - "@smithy/middleware-serde": "^2.0.1", - "@smithy/middleware-stack": "^2.0.0", - "@smithy/node-config-provider": "^2.0.1", - "@smithy/node-http-handler": "^2.0.1", - "@smithy/protocol-http": "^2.0.1", - "@smithy/smithy-client": "^2.0.1", - "@smithy/types": "^2.0.2", - "@smithy/url-parser": "^2.0.1", - "@smithy/util-base64": "^2.0.0", - "@smithy/util-body-length-browser": "^2.0.0", - "@smithy/util-body-length-node": "^2.0.0", - "@smithy/util-defaults-mode-browser": "^2.0.1", - "@smithy/util-defaults-mode-node": "^2.0.1", - "@smithy/util-retry": "^2.0.0", - "@smithy/util-utf8": "^2.0.0", - "@smithy/util-waiter": "^2.0.1", - "fast-xml-parser": "4.2.5", - "tslib": "^2.5.0" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/credential-provider-node": "3.840.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.840.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.840.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.840.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.6.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/middleware-retry": "^4.1.14", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.21", + "@smithy/util-defaults-mode-node": "^4.0.21", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "@smithy/util-waiter": "^4.0.6", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.378.0.tgz", - "integrity": "sha512-xQ2myljd4T0W46WQVHnT61PLiIoGqcIJA6euClvSQndKqXt8fnJP6/kn2r+APIsjey823pjkEP4mZq8gYDiOOw==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.840.0.tgz", + "integrity": "sha512-3Zp+FWN2hhmKdpS0Ragi5V2ZPsZNScE3jlbgoJjzjI/roHZqO+e3/+XFN4TlM0DsPKYJNp+1TAjmhxN6rOnfYA==", + "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/middleware-host-header": "3.378.0", - "@aws-sdk/middleware-logger": "3.378.0", - "@aws-sdk/middleware-recursion-detection": "3.378.0", - "@aws-sdk/middleware-user-agent": "3.378.0", - "@aws-sdk/types": "3.378.0", - "@aws-sdk/util-endpoints": "3.378.0", - "@aws-sdk/util-user-agent-browser": "3.378.0", - "@aws-sdk/util-user-agent-node": "3.378.0", - "@smithy/config-resolver": "^2.0.1", - "@smithy/fetch-http-handler": "^2.0.1", - "@smithy/hash-node": "^2.0.1", - "@smithy/invalid-dependency": "^2.0.1", - "@smithy/middleware-content-length": "^2.0.1", - "@smithy/middleware-endpoint": "^2.0.1", - "@smithy/middleware-retry": "^2.0.1", - "@smithy/middleware-serde": "^2.0.1", - "@smithy/middleware-stack": "^2.0.0", - "@smithy/node-config-provider": "^2.0.1", - "@smithy/node-http-handler": "^2.0.1", - "@smithy/protocol-http": "^2.0.1", - "@smithy/smithy-client": "^2.0.1", - "@smithy/types": "^2.0.2", - "@smithy/url-parser": "^2.0.1", - "@smithy/util-base64": "^2.0.0", - "@smithy/util-body-length-browser": "^2.0.0", - "@smithy/util-body-length-node": "^2.0.0", - "@smithy/util-defaults-mode-browser": "^2.0.1", - "@smithy/util-defaults-mode-node": "^2.0.1", - "@smithy/util-retry": "^2.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.5.0" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.840.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.840.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.840.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.6.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/middleware-retry": "^4.1.14", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.21", + "@smithy/util-defaults-mode-node": "^4.0.21", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.378.0.tgz", - "integrity": "sha512-+IcXH/W/TVzE0lMHuACgARgM/WxVbujGJzYUmDwj4E3uXjhTrRz69aeDk5z2EUggxKON9NOzHGZpm06VoS8uPA==", + "node_modules/@aws-sdk/core": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.840.0.tgz", + "integrity": "sha512-x3Zgb39tF1h2XpU+yA4OAAQlW6LVEfXNlSedSYJ7HGKXqA/E9h3rWQVpYfhXXVVsLdYXdNw5KBUkoAoruoZSZA==", + "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/middleware-host-header": "3.378.0", - "@aws-sdk/middleware-logger": "3.378.0", - "@aws-sdk/middleware-recursion-detection": "3.378.0", - "@aws-sdk/middleware-user-agent": "3.378.0", - "@aws-sdk/types": "3.378.0", - "@aws-sdk/util-endpoints": "3.378.0", - "@aws-sdk/util-user-agent-browser": "3.378.0", - "@aws-sdk/util-user-agent-node": "3.378.0", - "@smithy/config-resolver": "^2.0.1", - "@smithy/fetch-http-handler": "^2.0.1", - "@smithy/hash-node": "^2.0.1", - "@smithy/invalid-dependency": "^2.0.1", - "@smithy/middleware-content-length": "^2.0.1", - "@smithy/middleware-endpoint": "^2.0.1", - "@smithy/middleware-retry": "^2.0.1", - "@smithy/middleware-serde": "^2.0.1", - "@smithy/middleware-stack": "^2.0.0", - "@smithy/node-config-provider": "^2.0.1", - "@smithy/node-http-handler": "^2.0.1", - "@smithy/protocol-http": "^2.0.1", - "@smithy/smithy-client": "^2.0.1", - "@smithy/types": "^2.0.2", - "@smithy/url-parser": "^2.0.1", - "@smithy/util-base64": "^2.0.0", - "@smithy/util-body-length-browser": "^2.0.0", - "@smithy/util-body-length-node": "^2.0.0", - "@smithy/util-defaults-mode-browser": "^2.0.1", - "@smithy/util-defaults-mode-node": "^2.0.1", - "@smithy/util-retry": "^2.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.840.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/core": "^3.6.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-utf8": "^4.0.0", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sts": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.378.0.tgz", - "integrity": "sha512-u7y1I5BVjKEDK6ybA4c5smkbuoSFTBQqYX9qbCFYRErIA3qCICZB3duApcVRpdypKBzwYxUkLT/qKj4s9QTvrQ==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/credential-provider-node": "3.378.0", - "@aws-sdk/middleware-host-header": "3.378.0", - "@aws-sdk/middleware-logger": "3.378.0", - "@aws-sdk/middleware-recursion-detection": "3.378.0", - "@aws-sdk/middleware-sdk-sts": "3.378.0", - "@aws-sdk/middleware-signing": "3.378.0", - "@aws-sdk/middleware-user-agent": "3.378.0", - "@aws-sdk/types": "3.378.0", - "@aws-sdk/util-endpoints": "3.378.0", - "@aws-sdk/util-user-agent-browser": "3.378.0", - "@aws-sdk/util-user-agent-node": "3.378.0", - "@smithy/config-resolver": "^2.0.1", - "@smithy/fetch-http-handler": "^2.0.1", - "@smithy/hash-node": "^2.0.1", - "@smithy/invalid-dependency": "^2.0.1", - "@smithy/middleware-content-length": "^2.0.1", - "@smithy/middleware-endpoint": "^2.0.1", - "@smithy/middleware-retry": "^2.0.1", - "@smithy/middleware-serde": "^2.0.1", - "@smithy/middleware-stack": "^2.0.0", - "@smithy/node-config-provider": "^2.0.1", - "@smithy/node-http-handler": "^2.0.1", - "@smithy/protocol-http": "^2.0.1", - "@smithy/smithy-client": "^2.0.1", - "@smithy/types": "^2.0.2", - "@smithy/url-parser": "^2.0.1", - "@smithy/util-base64": "^2.0.0", - "@smithy/util-body-length-browser": "^2.0.0", - "@smithy/util-body-length-node": "^2.0.0", - "@smithy/util-defaults-mode-browser": "^2.0.1", - "@smithy/util-defaults-mode-node": "^2.0.1", - "@smithy/util-retry": "^2.0.0", - "@smithy/util-utf8": "^2.0.0", - "fast-xml-parser": "4.2.5", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.378.0.tgz", - "integrity": "sha512-B2OVdO9kBClDwGgWTBLAQwFV8qYTYGyVujg++1FZFSFMt8ORFdZ5fNpErvJtiSjYiOOQMzyBeSNhKyYNXCiJjQ==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.840.0.tgz", + "integrity": "sha512-EzF6VcJK7XvQ/G15AVEfJzN2mNXU8fcVpXo4bRyr1S6t2q5zx6UPH/XjDbn18xyUmOq01t+r8gG+TmHEVo18fA==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.378.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@aws-sdk/core": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.840.0.tgz", + "integrity": "sha512-wbnUiPGLVea6mXbUh04fu+VJmGkQvmToPeTYdHE8eRZq3NRDi3t3WltT+jArLBKD/4NppRpMjf2ju4coMCz91g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.378.0.tgz", - "integrity": "sha512-R34ELLCBTb+QkmWCaukNkT4vGeAipcL2wFN7Q2/WVSnJnRPPZSxzDK5rr78TiOPhRBu1k+aLDRNfslTZDknIIQ==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.840.0.tgz", + "integrity": "sha512-7F290BsWydShHb+7InXd+IjJc3mlEIm9I0R57F/Pjl1xZB69MdkhVGCnuETWoBt4g53ktJd6NEjzm/iAhFXFmw==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.378.0", - "@aws-sdk/credential-provider-process": "3.378.0", - "@aws-sdk/credential-provider-sso": "3.378.0", - "@aws-sdk/credential-provider-web-identity": "3.378.0", - "@aws-sdk/types": "3.378.0", - "@smithy/credential-provider-imds": "^2.0.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/shared-ini-file-loader": "^2.0.0", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@aws-sdk/core": "3.840.0", + "@aws-sdk/credential-provider-env": "3.840.0", + "@aws-sdk/credential-provider-http": "3.840.0", + "@aws-sdk/credential-provider-process": "3.840.0", + "@aws-sdk/credential-provider-sso": "3.840.0", + "@aws-sdk/credential-provider-web-identity": "3.840.0", + "@aws-sdk/nested-clients": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.378.0.tgz", - "integrity": "sha512-vULsOsmcqSD+Prp/yl/o1gvQAKd2oHuqI8snh4G0RAkEvoyb7vx2l0ShCoXOVY/wM9PQH8nxBHmVbiAQfSndNg==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.840.0.tgz", + "integrity": "sha512-KufP8JnxA31wxklLm63evUPSFApGcH8X86z3mv9SRbpCm5ycgWIGVCTXpTOdgq6rPZrwT9pftzv2/b4mV/9clg==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.378.0", - "@aws-sdk/credential-provider-ini": "3.378.0", - "@aws-sdk/credential-provider-process": "3.378.0", - "@aws-sdk/credential-provider-sso": "3.378.0", - "@aws-sdk/credential-provider-web-identity": "3.378.0", - "@aws-sdk/types": "3.378.0", - "@smithy/credential-provider-imds": "^2.0.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/shared-ini-file-loader": "^2.0.0", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@aws-sdk/credential-provider-env": "3.840.0", + "@aws-sdk/credential-provider-http": "3.840.0", + "@aws-sdk/credential-provider-ini": "3.840.0", + "@aws-sdk/credential-provider-process": "3.840.0", + "@aws-sdk/credential-provider-sso": "3.840.0", + "@aws-sdk/credential-provider-web-identity": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.378.0.tgz", - "integrity": "sha512-KFTIy7u+wXj3eDua4rgS0tODzMnXtXhAm1RxzCW9FL5JLBBrd82ymCj1Dp72217Sw5Do6NjCnDTTNkCHZMA77w==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.840.0.tgz", + "integrity": "sha512-HkDQWHy8tCI4A0Ps2NVtuVYMv9cB4y/IuD/TdOsqeRIAT12h8jDb98BwQPNLAImAOwOWzZJ8Cu0xtSpX7CQhMw==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.378.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/shared-ini-file-loader": "^2.0.0", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@aws-sdk/core": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.378.0.tgz", - "integrity": "sha512-lDPo/audYE/oERAef/VnHMe8THPCauH3Yu3DQYzCs+EWr1sIzp8vklWdMVQQI8cUlcLyYf4Dv9t8c+eJFZvrgw==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.840.0.tgz", + "integrity": "sha512-2qgdtdd6R0Z1y0KL8gzzwFUGmhBHSUx4zy85L2XV1CXhpRNwV71SVWJqLDVV5RVWVf9mg50Pm3AWrUC0xb0pcA==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.378.0", - "@aws-sdk/token-providers": "3.378.0", - "@aws-sdk/types": "3.378.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/shared-ini-file-loader": "^2.0.0", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@aws-sdk/client-sso": "3.840.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/token-providers": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.378.0.tgz", - "integrity": "sha512-GWjydOszhc4xDF8xuPtBvboglXQr0gwCW1oHAvmLcOT38+Hd6qnKywnMSeoXYRPgoKfF9TkWQgW1jxplzCG0UA==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.840.0.tgz", + "integrity": "sha512-dpEeVXG8uNZSmVXReE4WP0lwoioX2gstk4RnUgrdUE3YaPq8A+hJiVAyc3h+cjDeIqfbsQbZm9qFetKC2LF9dQ==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.378.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@aws-sdk/core": "3.840.0", + "@aws-sdk/nested-clients": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.378.0.tgz", - "integrity": "sha512-3o+AYU6JWUsPM49bWglCUOgNvySiHkbIma0J6F9a68e30vEDD0FUQtKzyHPZkF7iYDyesEl166gYjwVNAmASzw==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.840.0.tgz", + "integrity": "sha512-+gkQNtPwcSMmlwBHFd4saVVS11In6ID1HczNzpM3MXKXRBfSlbZJbCt6wN//AZ8HMklZEik4tcEOG0qa9UY8SQ==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.378.0", - "@aws-sdk/util-arn-parser": "3.310.0", - "@smithy/protocol-http": "^2.0.1", - "@smithy/types": "^2.0.2", - "@smithy/util-config-provider": "^2.0.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.378.0.tgz", - "integrity": "sha512-8maaNQvza3/IGDbIyVQkUbGlo+Oc6SY1gVG50UMcTUX8nwZrD1/ko+ft+pd2EDb2n+0JritoDj4bjr6pdesNBg==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.840.0.tgz", + "integrity": "sha512-iJg2r6FKsKKvdiU4oCOuCf7Ro/YE0Q2BT/QyEZN3/Rt8Nr4SAZiQOlcBXOCpGvuIKOEAhvDOUnW3aDHL01PdVw==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.378.0", - "@smithy/protocol-http": "^2.0.1", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.840.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.378.0.tgz", - "integrity": "sha512-pHkcVTu2T+x/1fpPHMpRDpXY5zxDsjijv3C6Nz/nm3gQrZvQ3fYDrQdV3Oj6Xeg40B3kkcp/bzgDo7MDzG088A==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.840.0.tgz", + "integrity": "sha512-Kg/o2G6o72sdoRH0J+avdcf668gM1bp6O4VeEXpXwUj/urQnV5qiB2q1EYT110INHUKWOLXPND3sQAqh6sTqHw==", + "license": "Apache-2.0", "dependencies": { - "@aws-crypto/crc32": "3.0.0", - "@aws-crypto/crc32c": "3.0.0", - "@aws-sdk/types": "3.378.0", - "@smithy/is-array-buffer": "^2.0.0", - "@smithy/protocol-http": "^2.0.1", - "@smithy/types": "^2.0.2", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.5.0" + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.378.0.tgz", - "integrity": "sha512-zzZZ8U3MxTgSW/bpr5wNbDuGUc/lPtB9c07bD/+F81KuGCOiPIl4PA4EyMI3tftPM9DbbcFX5ZwKi9vlZ4BWcw==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.840.0.tgz", + "integrity": "sha512-ub+hXJAbAje94+Ya6c6eL7sYujoE8D4Bumu1NUI8TXjUhVVn0HzVWQjpRLshdLsUp1AW7XyeJaxyajRaJQ8+Xg==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.378.0", - "@smithy/protocol-http": "^2.0.1", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.840.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.378.0.tgz", - "integrity": "sha512-Nn43avmhsDnCKtD1gQ7Xl2pvuxypnN7vvLWFeHb+7CCDKx/sK+ta+1UchNNOxh8hKL+rfBYOD2+/ZvwRRkAnAA==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.840.0.tgz", + "integrity": "sha512-KVLD0u0YMF3aQkVF8bdyHAGWSUY6N1Du89htTLgqCcIhSxxAJ9qifrosVZ9jkAzqRW99hcufyt2LylcVU2yoKQ==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.378.0", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.378.0.tgz", - "integrity": "sha512-l1DyaDLm3KeBMNMuANI3scWh8Xvu248x+vw6Z7ExWOhGXFmQ1MW7YvASg/SdxWkhlF9HmkkTif1LdMB22x6QDA==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.840.0.tgz", + "integrity": "sha512-lSV8FvjpdllpGaRspywss4CtXV8M7NNNH+2/j86vMH+YCOZ6fu2T/TyFd/tHwZ92vDfHctWkRbQxg0bagqwovA==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.378.0", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.378.0.tgz", - "integrity": "sha512-mUMfHAz0oGNIWiTZHTVJb+I515Hqs2zx1j36Le4MMiiaMkPW1SRUF1FIwGuc1wh6E8jB5q+XfEMriDjRi4TZRA==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.840.0.tgz", + "integrity": "sha512-Gu7lGDyfddyhIkj1Z1JtrY5NHb5+x/CRiB87GjaSrKxkDaydtX2CU977JIABtt69l9wLbcGDIQ+W0uJ5xPof7g==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.378.0", - "@smithy/protocol-http": "^2.0.1", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.840.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.378.0.tgz", - "integrity": "sha512-6PeZmQTG/GURC/fpCy71znSgn9brPSzMTIW1/cBLqW9RUB2CXb0ZsbsMPwcsN3lFgd2UHeIcZjg7wBRum/Xk/Q==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.840.0.tgz", + "integrity": "sha512-rOUji7CayWN3O09zvvgLzDVQe0HiJdZkxoTS6vzOS3WbbdT7joGdVtAJHtn+x776QT3hHzbKU5gnfhel0o6gQA==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.378.0", - "@aws-sdk/util-arn-parser": "3.310.0", - "@smithy/protocol-http": "^2.0.1", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@aws-sdk/core": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@smithy/core": "^3.6.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-sts": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.378.0.tgz", - "integrity": "sha512-uOoE4mvlJnR7NGIbCXQA3nI4qjWHfEETX4WzamjCQBTmoXBUlSU0hCRKvG5VHSpwI3XOu7dke9fFqbldseQzgw==", - "dependencies": { - "@aws-sdk/middleware-signing": "3.378.0", - "@aws-sdk/types": "3.378.0", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-signing": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.378.0.tgz", - "integrity": "sha512-XnEQUg1wkbakDMEcwpaPq4U1qn+jdGVyPLvcvcecw09yJj0+SIG5h3xWhBYVUxm9zEJUhIYc1DnNL2V5YFeCoQ==", - "dependencies": { - "@aws-sdk/types": "3.378.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/protocol-http": "^2.0.1", - "@smithy/signature-v4": "^2.0.0", - "@smithy/types": "^2.0.2", - "@smithy/util-middleware": "^2.0.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.378.0.tgz", - "integrity": "sha512-WDT2LOd6OxlY1zkrRG9ZtW2vFms/dsqMg9VyE88RKG2oATxSXEhkr5zLbNVh3TyuUKnV9jydate56d/ECwHOHg==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.840.0.tgz", + "integrity": "sha512-CBZP9t1QbjDFGOrtnUEHL1oAvmnCUUm7p0aPNbIdSzNtH42TNKjPRN3TuEIJDGjkrqpL3MXyDSmNayDcw/XW7Q==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.378.0", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.378.0.tgz", - "integrity": "sha512-gwMmJgfqFh0k/Tvb+agXcdbIp9pUmYRN868CfqpKiQ7UlN8DHNixuPYrdktLkUBoEvnxmZEKdt0EnkBCdBTIcw==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.840.0.tgz", + "integrity": "sha512-hiiMf7BP5ZkAFAvWRcK67Mw/g55ar7OCrvrynC92hunx/xhMkrgSLM0EXIZ1oTn3uql9kH/qqGF0nqsK6K555A==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.378.0", - "@aws-sdk/util-endpoints": "3.378.0", - "@smithy/protocol-http": "^2.0.1", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@aws-sdk/core": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.840.0", + "@smithy/core": "^3.6.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.840.0.tgz", + "integrity": "sha512-LXYYo9+n4hRqnRSIMXLBb+BLz+cEmjMtTudwK1BF6Bn2RfdDv29KuyeDRrPCS3TwKl7ZKmXUmE9n5UuHAPfBpA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.840.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.840.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.840.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.6.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/middleware-retry": "^4.1.14", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.21", + "@smithy/util-defaults-mode-node": "^4.0.21", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.840.0.tgz", + "integrity": "sha512-Qjnxd/yDv9KpIMWr90ZDPtRj0v75AqGC92Lm9+oHXZ8p1MjG5JE2CW0HL8JRgK9iKzgKBL7pPQRXI8FkvEVfrA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/s3-request-presigner": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.378.0.tgz", - "integrity": "sha512-bvrfZ+pUstwyfBZuZxG/xozfxGarldjjVX9HMxj49o1vvbGOhwRKO93XRUzV5WDk4TqKi5YcErCur0ZoqSgm4w==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.840.0.tgz", + "integrity": "sha512-1jcrhVoSZjiAQJGNswI0RGR36/+OG6yTV42wQamHdNHk+/68dn9MGTUVr+58AEFOyEAPE/EvkiYRD6n5WkUjMg==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/signature-v4-multi-region": "3.378.0", - "@aws-sdk/types": "3.378.0", - "@aws-sdk/util-format-url": "3.378.0", - "@smithy/middleware-endpoint": "^2.0.1", - "@smithy/protocol-http": "^2.0.1", - "@smithy/smithy-client": "^2.0.1", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@aws-sdk/signature-v4-multi-region": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-format-url": "3.840.0", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.378.0.tgz", - "integrity": "sha512-gtuABS7EeYZQeNzTrabY3Ruv4wWmoz4u8OMSGl47gYPDWA70WYEZ0aoi4zSGuKhXiqtVvTsO9wGEMIInwV5phQ==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.840.0.tgz", + "integrity": "sha512-8AoVgHrkSfhvGPtwx23hIUO4MmMnux2pjnso1lrLZGqxfElM6jm2w4jTNLlNXk8uKHGyX89HaAIuT0lL6dJj9g==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.378.0", - "@smithy/protocol-http": "^2.0.1", - "@smithy/signature-v4": "^2.0.0", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@aws-sdk/middleware-sdk-s3": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@aws-sdk/signature-v4-crt": "^3.118.0" - }, - "peerDependenciesMeta": { - "@aws-sdk/signature-v4-crt": { - "optional": true - } + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.378.0.tgz", - "integrity": "sha512-2J3XCwYcImKGSpv4YZ7wqt/H+P56/BAFAmZx/LqwZlkgg+arTGo76WbeM0CQCsgmKuS9xZEVlfH4z+d0H9aoyw==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.840.0.tgz", + "integrity": "sha512-6BuTOLTXvmgwjK7ve7aTg9JaWFdM5UoMolLVPMyh3wTv9Ufalh8oklxYHUBIxsKkBGO2WiHXytveuxH6tAgTYg==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso-oidc": "3.378.0", - "@aws-sdk/types": "3.378.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/shared-ini-file-loader": "^2.0.0", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@aws-sdk/core": "3.840.0", + "@aws-sdk/nested-clients": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/types": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.378.0.tgz", - "integrity": "sha512-qP0CvR/ItgktmN8YXpGQglzzR/6s0nrsQ4zIfx3HMwpsBTwuouYahcCtF1Vr82P4NFcoDA412EJahJ2pIqEd+w==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.840.0.tgz", + "integrity": "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/util-arn-parser": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.310.0.tgz", - "integrity": "sha512-jL8509owp/xB9+Or0pvn3Fe+b94qfklc2yPowZZIFAkFcCSIdkIglz18cPDWnYAcy9JGewpMS1COXKIUhZkJsA==", + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.804.0.tgz", + "integrity": "sha512-wmBJqn1DRXnZu3b4EkE6CWnoWMo1ZMvlfkqU5zPz67xx1GMaXlDCchFvKAXMjk4jn/L1O3tKnoFDNsoLV1kgNQ==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.378.0.tgz", - "integrity": "sha512-NU5C2l2xAXxpyB5nT0fIhahLPlJoJdzHWw4uC53KH9b4PrjHtgvgCN8beIsD3QxyfgeoM4A5J9Auo6WurfRnLw==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.840.0.tgz", + "integrity": "sha512-eqE9ROdg/Kk0rj3poutyRCFauPDXIf/WSvCqFiRDDVi6QOnCv/M0g2XW8/jSvkJlOyaXkNCptapIp6BeeFFGYw==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.378.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "@smithy/util-endpoints": "^3.0.6", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/util-format-url": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.378.0.tgz", - "integrity": "sha512-CtW2HnCq08ildVD7B5OPn1zOxBAMBjkDxqzOcLw3Rk9F6OKuMM9hawulU62tMtouJPC0QSS6eLoNOrYGch5ehQ==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.840.0.tgz", + "integrity": "sha512-VB1PWyI1TQPiPvg4w7tgUGGQER1xxXPNUqfh3baxUSFi1Oh8wHrDnFywkxLm3NMmgDmnLnSZ5Q326qAoyqKLSg==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.378.0", - "@smithy/querystring-builder": "^2.0.1", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.840.0", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/util-locate-window": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.310.0.tgz", - "integrity": "sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==", + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.804.0.tgz", + "integrity": "sha512-zVoRfpmBVPodYlnMjgVjfGoEZagyRF5IPn3Uo6ZvOZp24chnW/FRstH7ESDHDDRga4z3V+ElUQHKpFDXWyBW5A==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.378.0.tgz", - "integrity": "sha512-FSCpagzftK1W+m7Ar6lpX7/Gr9y5P56nhFYz8U4EYQ4PkufS6czWX9YW+/FA5OYV0vlQ/SvPqMnzoHIPUNhZrQ==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.840.0.tgz", + "integrity": "sha512-JdyZM3EhhL4PqwFpttZu1afDpPJCCc3eyZOLi+srpX11LsGj6sThf47TYQN75HT1CarZ7cCdQHGzP2uy3/xHfQ==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.378.0", - "@smithy/types": "^2.0.2", + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", "bowser": "^2.11.0", - "tslib": "^2.5.0" + "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.378.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.378.0.tgz", - "integrity": "sha512-IdwVJV0E96MkJeFte4dlWqvB+oiqCiZ5lOlheY3W9NynTuuX0GGYNC8Y9yIsV8Oava1+ujpJq0ww6qXdYxmO4A==", + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.840.0.tgz", + "integrity": "sha512-Fy5JUEDQU1tPm2Yw/YqRYYc27W5+QD/J4mYvQvdWjUGZLB5q3eLFMGD35Uc28ZFoGMufPr4OCxK/bRfWROBRHQ==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.378.0", - "@smithy/node-config-provider": "^2.0.1", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@aws-sdk/middleware-user-agent": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" }, "peerDependencies": { "aws-crt": ">=1.0.0" @@ -993,23 +1122,17 @@ } } }, - "node_modules/@aws-sdk/util-utf8-browser": { - "version": "3.259.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", - "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", - "dependencies": { - "tslib": "^2.3.1" - } - }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.310.0.tgz", - "integrity": "sha512-TqELu4mOuSIKQCqj63fGVs86Yh+vBx5nHRpWKNUNhB2nPTpfbziTs5c1X358be3peVWA4wPxW7Nt53KIg1tnNw==", + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.821.0.tgz", + "integrity": "sha512-DIIotRnefVL6DiaHtO6/21DhJ4JZnnIwdNbpwiAhdt/AVbttcE4yw925gsjur0OGv5BTYXQXU3YnANBYnZjuQA==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@azure/abort-controller": { @@ -1039,14 +1162,14 @@ } }, "node_modules/@azure/core-client": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.9.3.tgz", - "integrity": "sha512-/wGw8fJ4mdpJ1Cum7s1S+VQyXt1ihwKLzfabS1O/RDADnmzVc01dHn44qD0BvGH6KlZNzOMW95tEpKqhkCChPA==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.9.4.tgz", + "integrity": "sha512-f7IxTD15Qdux30s2qFARH+JxgwxWLG2Rlr4oSkPGuLWm+1p5y1+C04XGLA0vmX6EtqfutmjvpNmAfgwVIS5hpw==", "license": "MIT", "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.4.0", - "@azure/core-rest-pipeline": "^1.9.1", + "@azure/core-rest-pipeline": "^1.20.0", "@azure/core-tracing": "^1.0.0", "@azure/core-util": "^1.6.1", "@azure/logger": "^1.0.0", @@ -1057,14 +1180,14 @@ } }, "node_modules/@azure/core-http-compat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-2.2.0.tgz", - "integrity": "sha512-1kW8ZhN0CfbNOG6C688z5uh2yrzALE7dDXHiR9dY4vt+EbhGZQSbjDa5bQd2rf3X2pdWMsXbqbArxUyeNdvtmg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-2.3.0.tgz", + "integrity": "sha512-qLQujmUypBBG0gxHd0j6/Jdmul6ttl24c8WGiLXIk7IHXdBlfoBqW27hyz3Xn6xbfdyVSarl1Ttbk0AwnZBYCw==", "license": "MIT", "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-client": "^1.3.0", - "@azure/core-rest-pipeline": "^1.19.0" + "@azure/core-rest-pipeline": "^1.20.0" }, "engines": { "node": ">=18.0.0" @@ -1098,9 +1221,9 @@ } }, "node_modules/@azure/core-rest-pipeline": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.19.1.tgz", - "integrity": "sha512-zHeoI3NCs53lLBbWNzQycjnYKsA1CVKlnzSNuSFcUDwBp8HHVObePxrM7HaX+Ha5Ks639H7chNC9HOaIhNS03w==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.21.0.tgz", + "integrity": "sha512-a4MBwe/5WKbq9MIxikzgxLBbruC5qlkFYlBdI7Ev50Y7ib5Vo/Jvt5jnJo7NaWeJ908LCHL0S1Us4UMf1VoTfg==", "license": "MIT", "dependencies": { "@azure/abort-controller": "^2.0.0", @@ -1108,36 +1231,13 @@ "@azure/core-tracing": "^1.0.1", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.0.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", + "@typespec/ts-http-runtime": "^0.2.3", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@azure/core-rest-pipeline/node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/@azure/core-rest-pipeline/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/@azure/core-tracing": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.2.0.tgz", @@ -1151,12 +1251,13 @@ } }, "node_modules/@azure/core-util": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.11.0.tgz", - "integrity": "sha512-DxOSLua+NdpWoSqULhjDyAZTXFdP/LKkqtYuxxz1SCN289zk3OG8UOpnCQAz/tygyACBtWp/BoO72ptK7msY8g==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.12.0.tgz", + "integrity": "sha512-13IyjTQgABPARvG90+N2dXpC+hwp466XCdQXPCRlbWHgd3SJd5Q1VvaBGv6k1BIa4MQm6hAF1UBU1m8QUxV8sQ==", "license": "MIT", "dependencies": { "@azure/abort-controller": "^2.0.0", + "@typespec/ts-http-runtime": "^0.2.2", "tslib": "^2.6.2" }, "engines": { @@ -1177,9 +1278,9 @@ } }, "node_modules/@azure/core-xml/node_modules/fast-xml-parser": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.0.9.tgz", - "integrity": "sha512-2mBwCiuW3ycKQQ6SOesSB8WeF+fIGb6I/GG5vU5/XEptwFFhp9PE8b9O7fbs2dpq9fXn4ULR3UsfydNUCntf5A==", + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", "funding": [ { "type": "github", @@ -1188,16 +1289,16 @@ ], "license": "MIT", "dependencies": { - "strnum": "^2.0.5" + "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "node_modules/@azure/core-xml/node_modules/strnum": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.0.5.tgz", - "integrity": "sha512-YAT3K/sgpCUxhxNMrrdhtod3jckkpYwH6JAuwmUdXZsmzH1wUyzTMrrK2wYCEEqlKwrWDd35NeuUkbBy/1iK+Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", "funding": [ { "type": "github", @@ -1207,11 +1308,12 @@ "license": "MIT" }, "node_modules/@azure/logger": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.1.4.tgz", - "integrity": "sha512-4IXXzcCdLdlXuCG+8UKEwLA1T1NHqUfanhXYHiQTn+6sfWCZXduqbtXDGceg3Ce5QxTGo7EqmbV6Bi+aqKuClQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.2.0.tgz", + "integrity": "sha512-0hKEzLhpw+ZTAfNJyRrn6s+V0nDWzXk9OjBr2TiGIu0OfMr5s2V4FpKLTAK3Ca5r5OKLbf4hkOGDPyiRjie/jA==", "license": "MIT", "dependencies": { + "@typespec/ts-http-runtime": "^0.2.2", "tslib": "^2.6.2" }, "engines": { @@ -1243,47 +1345,51 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", - "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.7.tgz", + "integrity": "sha512-xgu/ySj2mTiUFmdE9yCMfBxLp4DHd5DwmbbD05YAuICfodYT3VvRxbrh81LGQ/8UpSdtMdfKMn3KouYDX59DGQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.9.tgz", - "integrity": "sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.7.tgz", + "integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==", "dev": true, + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.9", - "@babel/helper-compilation-targets": "^7.22.9", - "@babel/helper-module-transforms": "^7.22.9", - "@babel/helpers": "^7.22.6", - "@babel/parser": "^7.22.7", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.8", - "@babel/types": "^7.22.5", - "convert-source-map": "^1.7.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.27.7", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.7", + "@babel/types": "^7.27.7", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", + "json5": "^2.2.3", "semver": "^6.3.1" }, "engines": { @@ -1295,77 +1401,65 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.23.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" + "@babel/parser": "^7.27.5", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz", - "integrity": "sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz", - "integrity": "sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.5", - "browserslist": "^4.21.9", + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.9.tgz", - "integrity": "sha512-Pwyi89uO4YrGKxL/eNJ8lfEH55DnRloGPOseaA8NFNL6jAUnn+KccaISiFazCj5IolPPDjGSdzQzXVzODVRqUQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", + "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.27.1", "semver": "^6.3.1" }, "engines": { @@ -1376,13 +1470,14 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.9.tgz", - "integrity": "sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", + "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "regexpu-core": "^5.3.1", + "@babel/helper-annotate-as-pure": "^7.27.1", + "regexpu-core": "^6.2.0", "semver": "^6.3.1" }, "engines": { @@ -1393,90 +1488,60 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.2.tgz", - "integrity": "sha512-k0qnnOqHn5dK9pZpfD5XXZ9SojAITdCKRn2Lp6rnDGzIbaP0rHyMPk/4wsSxVBVz4RfN0q6VpXWP2pDGIoQ7hw==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", - "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", - "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz", - "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.5" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" }, "engines": { "node": ">=6.9.0" @@ -1486,35 +1551,38 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.9.tgz", - "integrity": "sha512-8WWC4oR4Px+tr+Fp0X3RHDVfINGpF3ad1HIbrc8A77epiR6eMMc6jsgozkzT2uDiOOdoS9cLIQ+XD2XvI2WSmQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-wrap-function": "^7.22.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1524,14 +1592,15 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz", - "integrity": "sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5" + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1540,113 +1609,85 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", - "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.9.tgz", - "integrity": "sha512-sZ+QzfauuUEfxSEjKFmi3qDSHgLsTPK/pEpoD/qonZKOtTPTLbf59oabPQ4rKekt9lFcj/hTZaOhWwFYrgjk+Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", + "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-function-name": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz", - "integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.6", - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz", + "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.7" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -1654,13 +1695,47 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz", - "integrity": "sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==", + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", + "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1670,14 +1745,15 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz", - "integrity": "sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1686,11 +1762,29 @@ "@babel/core": "^7.13.0" } }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", + "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-proposal-private-property-in-object": { "version": "7.21.0-placeholder-for-preset-env.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" }, @@ -1698,27 +1792,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1731,6 +1810,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1743,6 +1823,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, @@ -1755,6 +1836,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -1765,37 +1847,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", - "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1805,12 +1864,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", - "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1824,6 +1884,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -1836,6 +1897,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1844,12 +1906,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", - "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1863,6 +1926,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -1875,6 +1939,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1887,6 +1952,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -1899,6 +1965,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1911,6 +1978,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1923,6 +1991,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1935,6 +2004,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -1950,6 +2020,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -1961,12 +2032,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", - "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1980,6 +2052,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -1992,12 +2065,13 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", - "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2007,15 +2081,15 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.22.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.7.tgz", - "integrity": "sha512-7HmE7pk/Fmke45TODvxvkxRMV9RazV+ZZzhOL9AG8G29TLrr3jkjwF7uJfxZ30EoXpO+LJkq4oA8NjO2DTnEDg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.27.1.tgz", + "integrity": "sha512-eST9RrwlpaoJBDHShc+DS2SG4ATTi2MYNb4OxYkf3n+7eb49LWpnS+HSpVfW4x927qQwgk8A2hGNVaajAEw0EA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.5", - "@babel/plugin-syntax-async-generators": "^7.8.4" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2025,14 +2099,15 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", - "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.5" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2042,12 +2117,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", - "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2057,12 +2133,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz", - "integrity": "sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg==", + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.5.tgz", + "integrity": "sha512-JF6uE2s67f0y2RZcm2kpAUEbD50vH62TyWVebxwHAlbSdM49VqPz8t4a1uIjp4NIOIZ4xzLfjY5emt/RCyC7TQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2072,13 +2149,14 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", - "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2088,14 +2166,14 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz", - "integrity": "sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", + "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-class-static-block": "^7.14.5" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2105,19 +2183,17 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz", - "integrity": "sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.7.tgz", + "integrity": "sha512-CuLkokN1PEZ0Fsjtq+001aog/C2drDK9nTfK/NRK0n6rBin6cBrvM+zfQjDE+UllhR6/J4a6w8Xq9i4yi3mQrw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.27.7", "globals": "^11.1.0" }, "engines": { @@ -2128,13 +2204,14 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", - "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2144,12 +2221,14 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz", - "integrity": "sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.7.tgz", + "integrity": "sha512-pg3ZLdIKWCP0CrJm0O4jYjVthyBeioVfvz9nwt6o5paUxsgJ/8GucSMAIaj6M7xA4WY+SrvtGu2LijzkdyecWQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.7" }, "engines": { "node": ">=6.9.0" @@ -2159,13 +2238,14 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", - "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2175,12 +2255,13 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", - "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2189,14 +2270,31 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz", - "integrity": "sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ==", + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2206,13 +2304,13 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", - "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", + "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2222,13 +2320,13 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz", - "integrity": "sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2238,12 +2336,14 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz", - "integrity": "sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2253,14 +2353,15 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", - "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2270,13 +2371,13 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz", - "integrity": "sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-json-strings": "^7.8.3" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2286,12 +2387,13 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", - "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2301,13 +2403,13 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz", - "integrity": "sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", + "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2317,12 +2419,13 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", - "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2332,13 +2435,14 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", - "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2348,14 +2452,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz", - "integrity": "sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2365,15 +2469,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz", - "integrity": "sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", + "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2383,13 +2488,14 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", - "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2399,13 +2505,14 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2415,12 +2522,13 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", - "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2430,13 +2538,13 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz", - "integrity": "sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2446,13 +2554,13 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz", - "integrity": "sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2462,16 +2570,17 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz", - "integrity": "sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.7.tgz", + "integrity": "sha512-201B1kFTWhckclcXpWHc8uUpYziDX/Pl4rxl0ZX0DiCZ3jknwfSUALL3QCYeeXXB37yWxJbo+g+Vfq8pAaHi3w==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.22.5" + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.27.7", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.27.7" }, "engines": { "node": ">=6.9.0" @@ -2481,13 +2590,14 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", - "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2497,13 +2607,13 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz", - "integrity": "sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2513,14 +2623,14 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.6.tgz", - "integrity": "sha512-Vd5HiWml0mDVtcLHIoEU5sw6HOUW/Zk0acLs/SAeuLzkGNOPc9DB4nkUajemhCmTIz3eiaKREZn2hQQqF79YTg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2530,12 +2640,13 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz", - "integrity": "sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2545,13 +2656,14 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", - "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2561,15 +2673,15 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz", - "integrity": "sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2579,12 +2691,13 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", - "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2594,13 +2707,13 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz", - "integrity": "sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw==", + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.5.tgz", + "integrity": "sha512-uhB8yHerfe3MWnuLAhEbeQ4afVoqv8BQsPqrTv7e/jZ9y00kJL6l9a/f4OWaKxotmjzewfEyXE1vgDJenkQ2/Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "regenerator-transform": "^0.15.1" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2609,13 +2722,31 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", - "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2625,12 +2756,13 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", - "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2640,13 +2772,14 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", - "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2656,12 +2789,13 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", - "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2671,12 +2805,13 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", - "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2686,12 +2821,13 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", - "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2701,15 +2837,17 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.9.tgz", - "integrity": "sha512-BnVR1CpKiuD0iobHPaM1iLvcwPYN2uVFAqoLVSpEDKWuOikoCv5HbKLxclhKYUXlWkX86DoZGtqI4XhbOsyrMg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.1.tgz", + "integrity": "sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.9", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-typescript": "^7.22.5" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2719,12 +2857,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz", - "integrity": "sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2734,13 +2873,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", - "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2750,13 +2890,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", - "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2766,13 +2907,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", - "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2782,90 +2924,80 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.9.tgz", - "integrity": "sha512-wNi5H/Emkhll/bqPjsjQorSykrlfY5OWakd6AulLvMEytpKasMVUpVy8RL4qBIBs5Ac6/5i0/Rv0b/Fg6Eag/g==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.27.2.tgz", + "integrity": "sha512-Ma4zSuYSlGNRlCLO+EAzLnCmJK2vdstgv+n7aUP+/IKZrOfWHOJVdSJtuub8RzHTj3ahD37k5OKJWvzf16TQyQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-compilation-targets": "^7.22.9", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.5", + "@babel/compat-data": "^7.27.2", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.22.5", - "@babel/plugin-syntax-import-attributes": "^7.22.5", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.22.5", - "@babel/plugin-transform-async-generator-functions": "^7.22.7", - "@babel/plugin-transform-async-to-generator": "^7.22.5", - "@babel/plugin-transform-block-scoped-functions": "^7.22.5", - "@babel/plugin-transform-block-scoping": "^7.22.5", - "@babel/plugin-transform-class-properties": "^7.22.5", - "@babel/plugin-transform-class-static-block": "^7.22.5", - "@babel/plugin-transform-classes": "^7.22.6", - "@babel/plugin-transform-computed-properties": "^7.22.5", - "@babel/plugin-transform-destructuring": "^7.22.5", - "@babel/plugin-transform-dotall-regex": "^7.22.5", - "@babel/plugin-transform-duplicate-keys": "^7.22.5", - "@babel/plugin-transform-dynamic-import": "^7.22.5", - "@babel/plugin-transform-exponentiation-operator": "^7.22.5", - "@babel/plugin-transform-export-namespace-from": "^7.22.5", - "@babel/plugin-transform-for-of": "^7.22.5", - "@babel/plugin-transform-function-name": "^7.22.5", - "@babel/plugin-transform-json-strings": "^7.22.5", - "@babel/plugin-transform-literals": "^7.22.5", - "@babel/plugin-transform-logical-assignment-operators": "^7.22.5", - "@babel/plugin-transform-member-expression-literals": "^7.22.5", - "@babel/plugin-transform-modules-amd": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.22.5", - "@babel/plugin-transform-modules-systemjs": "^7.22.5", - "@babel/plugin-transform-modules-umd": "^7.22.5", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.22.5", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5", - "@babel/plugin-transform-numeric-separator": "^7.22.5", - "@babel/plugin-transform-object-rest-spread": "^7.22.5", - "@babel/plugin-transform-object-super": "^7.22.5", - "@babel/plugin-transform-optional-catch-binding": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.6", - "@babel/plugin-transform-parameters": "^7.22.5", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.5", - "@babel/plugin-transform-property-literals": "^7.22.5", - "@babel/plugin-transform-regenerator": "^7.22.5", - "@babel/plugin-transform-reserved-words": "^7.22.5", - "@babel/plugin-transform-shorthand-properties": "^7.22.5", - "@babel/plugin-transform-spread": "^7.22.5", - "@babel/plugin-transform-sticky-regex": "^7.22.5", - "@babel/plugin-transform-template-literals": "^7.22.5", - "@babel/plugin-transform-typeof-symbol": "^7.22.5", - "@babel/plugin-transform-unicode-escapes": "^7.22.5", - "@babel/plugin-transform-unicode-property-regex": "^7.22.5", - "@babel/plugin-transform-unicode-regex": "^7.22.5", - "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.4", - "babel-plugin-polyfill-corejs3": "^0.8.2", - "babel-plugin-polyfill-regenerator": "^0.5.1", - "core-js-compat": "^3.31.0", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.27.1", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.27.1", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.27.1", + "@babel/plugin-transform-classes": "^7.27.1", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.27.1", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.27.2", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.1", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.27.1", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.40.0", "semver": "^6.3.1" }, "engines": { @@ -2876,14 +3008,13 @@ } }, "node_modules/@babel/preset-modules": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6.tgz", - "integrity": "sha512-ID2yj6K/4lKfhuU3+EX4UvNbIt7eACFbHmNUjzA+ep+B5971CknnA/9DEWKbRokfbbtblxxxXFJJrH47UEAMVg==", + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", "@babel/types": "^7.4.4", "esutils": "^2.0.2" }, @@ -2892,16 +3023,17 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.22.5.tgz", - "integrity": "sha512-YbPaal9LxztSGhmndR46FmAbkJ/1fAsw293tSU+I5E5h+cnJ3d4GTwyUgGYmOXJYdGA+uNePle4qbaRzj2NISQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.22.5", - "@babel/plugin-transform-typescript": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2910,53 +3042,34 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", - "dev": true - }, - "node_modules/@babel/runtime": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", - "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.13.11" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.7.tgz", + "integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.5", + "@babel/parser": "^7.27.7", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.7", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -2964,13 +3077,13 @@ } }, "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz", + "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==", + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2980,12 +3093,14 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", "engines": { "node": ">=0.1.90" } @@ -2995,6 +3110,7 @@ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -3007,6 +3123,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -3016,6 +3133,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "license": "MIT", "dependencies": { "colorspace": "1.1.x", "enabled": "2.0.x", @@ -3030,6 +3148,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -3046,6 +3165,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -3062,6 +3182,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -3078,6 +3199,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -3094,6 +3216,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -3110,6 +3233,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -3126,6 +3250,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -3142,6 +3267,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3158,6 +3284,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3174,6 +3301,7 @@ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3190,6 +3318,7 @@ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3206,6 +3335,7 @@ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3222,6 +3352,7 @@ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3238,6 +3369,7 @@ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3254,6 +3386,7 @@ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3270,6 +3403,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3286,6 +3420,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -3302,6 +3437,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -3318,6 +3454,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" @@ -3334,6 +3471,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -3350,6 +3488,7 @@ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -3366,6 +3505,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -3375,34 +3515,40 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "dev": true, + "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "node_modules/@eslint-community/regexpp": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", - "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz", - "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -3422,10 +3568,11 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -3436,11 +3583,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/eslintrc/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -3449,10 +3610,11 @@ } }, "node_modules/@eslint/js": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz", - "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -3461,6 +3623,7 @@ "version": "4.3.5", "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz", "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", + "license": "MIT", "dependencies": { "@types/node": "^14.0.1", "lodash.escaperegexp": "^4.1.2", @@ -3471,14 +3634,16 @@ } }, "node_modules/@fast-csv/format/node_modules/@types/node": { - "version": "14.18.54", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.54.tgz", - "integrity": "sha512-uq7O52wvo2Lggsx1x21tKZgqkJpvwCseBBPtX/nKQfpVlEsLOb11zZ1CRsWUKvJF0+lzuA9jwvA7Pr2Wt7i3xw==" + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" }, "node_modules/@fast-csv/parse": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz", "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "license": "MIT", "dependencies": { "@types/node": "^14.0.1", "lodash.escaperegexp": "^4.1.2", @@ -3490,29 +3655,46 @@ } }, "node_modules/@fast-csv/parse/node_modules/@types/node": { - "version": "14.18.54", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.54.tgz", - "integrity": "sha512-uq7O52wvo2Lggsx1x21tKZgqkJpvwCseBBPtX/nKQfpVlEsLOb11zZ1CRsWUKvJF0+lzuA9jwvA7Pr2Wt7i3xw==" + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", - "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -3522,16 +3704,89 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, + "license": "ISC", "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", @@ -3548,6 +3803,7 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, + "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" } @@ -3557,6 +3813,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -3570,6 +3827,7 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -3583,6 +3841,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, @@ -3595,6 +3854,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, + "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, @@ -3610,6 +3870,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, @@ -3622,21 +3883,17 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -3646,6 +3903,7 @@ "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^28.1.3", "@types/node": "*", @@ -3658,81 +3916,12 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@jest/console/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/console/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/console/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/core": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/console": "^28.1.3", "@jest/reporters": "^28.1.3", @@ -3776,74 +3965,56 @@ } } }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@jest/core/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { - "color-convert": "^2.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=8" + "node": "*" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@jest/core/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10" + "node": "*" + } + }, + "node_modules/@jest/core/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/core/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/core/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@jest/environment": { @@ -3851,6 +4022,7 @@ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/fake-timers": "^28.1.3", "@jest/types": "^28.1.3", @@ -3866,6 +4038,7 @@ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", "dev": true, + "license": "MIT", "dependencies": { "expect": "^28.1.3", "jest-snapshot": "^28.1.3" @@ -3879,6 +4052,7 @@ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", "dev": true, + "license": "MIT", "dependencies": { "jest-get-type": "^28.0.2" }, @@ -3891,6 +4065,7 @@ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^28.1.3", "@sinonjs/fake-timers": "^9.1.2", @@ -3908,6 +4083,7 @@ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/environment": "^28.1.3", "@jest/expect": "^28.1.3", @@ -3922,6 +4098,7 @@ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", "dev": true, + "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^28.1.3", @@ -3961,74 +4138,39 @@ } } }, - "node_modules/@jest/reporters/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { - "color-convert": "^2.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=8" + "node": "*" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@jest/reporters/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@jest/reporters/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/reporters/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "node": "*" } }, "node_modules/@jest/schemas": { @@ -4036,6 +4178,7 @@ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", "dev": true, + "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.24.1" }, @@ -4048,6 +4191,7 @@ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.13", "callsites": "^3.0.0", @@ -4062,6 +4206,7 @@ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", "dev": true, + "license": "MIT", "dependencies": { "@jest/console": "^28.1.3", "@jest/types": "^28.1.3", @@ -4077,6 +4222,7 @@ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/test-result": "^28.1.3", "graceful-fs": "^4.2.9", @@ -4092,6 +4238,7 @@ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^28.1.3", @@ -4113,81 +4260,19 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@jest/transform/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@jest/transform/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/transform/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/transform/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/transform/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, "node_modules/@jest/types": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/schemas": "^28.1.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -4200,140 +4285,68 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/types/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/types/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.11.tgz", + "integrity": "sha512-C512c1ytBTio4MrpWKlJpyFHT6+qfFL8SZ58zBzJ1OOzUEjHeF1BtjY2fH7n4x/g2OV/KiiMLAivOp1DXmiMMw==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "node_modules/@jridgewell/source-map": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.9.tgz", + "integrity": "sha512-amBU75CKOOkcQLfyM6J+DnWwz41yTsWI7o8MQ003LwUIWb4NYX/evAblTx1oBBYJySqL/zHPxHXDw5ewpQaUFw==", "dev": true, - "engines": { - "node": ">=6.0.0" + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.3.tgz", + "integrity": "sha512-AiR5uKpFxP3PjO4R19kQGIMwxyRyPuXmKEEy301V1C0+1rVjS94EZQXf1QKZYN8Q0YM+estSPhmx5JwNftv6nw==", + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "version": "0.3.28", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.28.tgz", + "integrity": "sha512-KNNHHwW3EIp4EDYOvYFGyIFfx36R2dNJYH4knnZlF8T5jdbD5Wx8xmSaQ2gP9URkJ04LGEtlcCtwArKcmFcwKw==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@mapbox/node-pre-gyp": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", "dependencies": { "detect-libc": "^2.0.0", "https-proxy-agent": "^5.0.0", @@ -4349,24 +4362,85 @@ "node-pre-gyp": "bin/node-pre-gyp" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/@mapbox/node-pre-gyp/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "debug": "4" }, "engines": { - "node": ">=10" + "node": ">= 6.0.0" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -4374,16 +4448,12 @@ "node": ">=10" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -4397,6 +4467,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -4406,6 +4477,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -4418,14 +4490,16 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "license": "MIT", "peerDependencies": { "@redis/client": "^1.0.0" } }, "node_modules/@redis/client": { - "version": "1.5.8", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.8.tgz", - "integrity": "sha512-xzElwHIO6rBAqzPeVnCzgvrnBEcFL1P0w8P65VNLRkdVW8rOE58f52hdj0BDgmsdOm4f1EoXPZtH4Fh7M/qUpw==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", + "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", + "license": "MIT", "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", @@ -4438,36 +4512,41 @@ "node_modules/@redis/client/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" }, "node_modules/@redis/graph": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.0.tgz", - "integrity": "sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "license": "MIT", "peerDependencies": { "@redis/client": "^1.0.0" } }, "node_modules/@redis/json": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.4.tgz", - "integrity": "sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", + "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", + "license": "MIT", "peerDependencies": { "@redis/client": "^1.0.0" } }, "node_modules/@redis/search": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.3.tgz", - "integrity": "sha512-4Dg1JjvCevdiCBTZqjhKkGoC5/BcB7k9j99kdMnaXFXg8x4eyOIVg9487CMv7/BUVkFLZCaIh8ead9mU15DNng==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", + "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", + "license": "MIT", "peerDependencies": { "@redis/client": "^1.0.0" } }, "node_modules/@redis/time-series": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.4.tgz", - "integrity": "sha512-ThUIgo2U/g7cCuZavucQTQzA9g9JbDDY2f64u3AbAoz/8vE2lt2U37LamDUVChhaDA3IRT9R6VvJwqnUfTJzng==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", + "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", + "license": "MIT", "peerDependencies": { "@redis/client": "^1.0.0" } @@ -4476,13 +4555,15 @@ "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@sinonjs/commons": { "version": "1.8.6", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "type-detect": "4.0.8" } @@ -4492,635 +4573,770 @@ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^1.7.0" } }, "node_modules/@smithy/abort-controller": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.0.1.tgz", - "integrity": "sha512-0s7XjIbsTwZyUW9OwXQ8J6x1UiA1TNCh60Vaw56nHahL7kUZsLhmTlWiaxfLkFtO2Utkj8YewcpHTYpxaTzO+w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/chunked-blob-reader": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-2.0.0.tgz", - "integrity": "sha512-k+J4GHJsMSAIQPChGBrjEmGS+WbPonCXesoqP9fynIqjn7rdOThdH8FAeCmokP9mxTYKQAKoHCLPzNlm6gh7Wg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.0.0.tgz", + "integrity": "sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/chunked-blob-reader-native": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-2.0.0.tgz", - "integrity": "sha512-HM8V2Rp1y8+1343tkZUKZllFhEQPNmpNdgFAncbTsxkZ18/gqjk23XXv3qGyXWp412f3o43ZZ1UZHVcHrpRnCQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.0.0.tgz", + "integrity": "sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==", + "license": "Apache-2.0", "dependencies": { - "@smithy/util-base64": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/config-resolver": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.0.1.tgz", - "integrity": "sha512-l83Pm7hV+8CBQOCmBRopWDtF+CURUJol7NsuPYvimiDhkC2F8Ba9T1imSFE+pD1UIJ9jlsDPAnZfPJT5cjnuEw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.4.tgz", + "integrity": "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.0.2", - "@smithy/util-config-provider": "^2.0.0", - "@smithy/util-middleware": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.6.0.tgz", + "integrity": "sha512-Pgvfb+TQ4wUNLyHzvgCP4aYZMh16y7GcfF59oirRHcgGgkH1e/s9C0nv/v3WP+Quymyr5je71HeFQCwh+44XLg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.0.8", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/credential-provider-imds": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.0.1.tgz", - "integrity": "sha512-8VxriuRINNEfVZjEFKBY75y9ZWAx73DZ5K/u+3LmB6r8WR2h3NaFxFKMlwlq0uzNdGhD1ouKBn9XWEGYHKiPLw==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", + "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^2.0.1", - "@smithy/property-provider": "^2.0.1", - "@smithy/types": "^2.0.2", - "@smithy/url-parser": "^2.0.1", - "tslib": "^2.5.0" + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/eventstream-codec": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-2.0.1.tgz", - "integrity": "sha512-/IiNB7gQM2y2ZC/GAWOWDa8+iXfhr1g9Xe5979cQEOdCWDISvrAiv18cn3OtIQUhbYOR3gm7QtCpkq1to2takQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.0.4.tgz", + "integrity": "sha512-7XoWfZqWb/QoR/rAU4VSi0mWnO2vu9/ltS6JZ5ZSZv0eovLVfDfu0/AX4ub33RsJTOth3TiFWSHS5YdztvFnig==", + "license": "Apache-2.0", "dependencies": { - "@aws-crypto/crc32": "3.0.0", - "@smithy/types": "^2.0.2", - "@smithy/util-hex-encoding": "^2.0.0", - "tslib": "^2.5.0" + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/eventstream-serde-browser": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-2.0.1.tgz", - "integrity": "sha512-9E1/6ZGF7nB/Td3G1kcatU7VjjP8eZ/p/Q+0KsZc1AUPyv4lR15pmWnWj3iGBEGYI9qZBJ/7a/wPEPayabmA3Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.0.4.tgz", + "integrity": "sha512-3fb/9SYaYqbpy/z/H3yIi0bYKyAa89y6xPmIqwr2vQiUT2St+avRt8UKwsWt9fEdEasc5d/V+QjrviRaX1JRFA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^2.0.1", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@smithy/eventstream-serde-universal": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-2.0.1.tgz", - "integrity": "sha512-J8a+8HH8oDPIgq8Px/nPLfu9vpIjQ7XUPtP3orbs8KUh0GznNthSTy1xZP5RXjRqGQEkxPvsHf1po2+QOsgNFw==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.1.2.tgz", + "integrity": "sha512-JGtambizrWP50xHgbzZI04IWU7LdI0nh/wGbqH3sJesYToMi2j/DcoElqyOcqEIG/D4tNyxgRuaqBXWE3zOFhQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/eventstream-serde-node": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-2.0.1.tgz", - "integrity": "sha512-wklowUz0zXJuqC7FMpriz66J8OAko3z6INTg+iMJWYB1bWv4pc5V7q36PxlZ0RKRbj0u+EThlozWgzE7Stz2Sw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.0.4.tgz", + "integrity": "sha512-RD6UwNZ5zISpOWPuhVgRz60GkSIp0dy1fuZmj4RYmqLVRtejFqQ16WmfYDdoSoAjlp1LX+FnZo+/hkdmyyGZ1w==", + "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^2.0.1", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@smithy/eventstream-serde-universal": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/eventstream-serde-universal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-2.0.1.tgz", - "integrity": "sha512-WPPylIgVZ6wOYVgpF0Rs1LlocYyj248MRtKEEehnDvC+0tV7wmGt7H/SchCh10W4y4YUxuzPlW+mUvVMGmLSVg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.0.4.tgz", + "integrity": "sha512-UeJpOmLGhq1SLox79QWw/0n2PFX+oPRE1ZyRMxPIaFEfCqWaqpB7BU9C8kpPOGEhLF7AwEqfFbtwNxGy4ReENA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-codec": "^2.0.1", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@smithy/eventstream-codec": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/fetch-http-handler": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.0.1.tgz", - "integrity": "sha512-/SoU/ClazgcdOxgE4zA7RX8euiELwpsrKCSvulVQvu9zpmqJRyEJn8ZTWYFV17/eHOBdHTs9kqodhNhsNT+cUw==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.4.tgz", + "integrity": "sha512-AMtBR5pHppYMVD7z7G+OlHHAcgAN7v0kVKEpHuTO4Gb199Gowh0taYi9oDStFeUhetkeP55JLSVlTW1n9rFtUw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^2.0.1", - "@smithy/querystring-builder": "^2.0.1", - "@smithy/types": "^2.0.2", - "@smithy/util-base64": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/hash-blob-browser": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-2.0.1.tgz", - "integrity": "sha512-i/o2+sHb4jDRz5nf2ilTTbC0nVmm4LO//FbODCAB7pbzMdywxbZ6z+q56FmEa8R+aFbtApxQ1SJ3umEiNz6IPg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.0.4.tgz", + "integrity": "sha512-WszRiACJiQV3QG6XMV44i5YWlkrlsM5Yxgz4jvsksuu7LDXA6wAtypfPajtNTadzpJy3KyJPoWehYpmZGKUFIQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/chunked-blob-reader": "^2.0.0", - "@smithy/chunked-blob-reader-native": "^2.0.0", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@smithy/chunked-blob-reader": "^5.0.0", + "@smithy/chunked-blob-reader-native": "^4.0.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/hash-node": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.0.1.tgz", - "integrity": "sha512-oTKYimQdF4psX54ZonpcIE+MXjMUWFxLCNosjPkJPFQ9whRX0K/PFX/+JZGRQh3zO9RlEOEUIbhy9NO+Wha6hw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.4.tgz", + "integrity": "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.0.2", - "@smithy/util-buffer-from": "^2.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/types": "^4.3.1", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/hash-stream-node": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-2.0.1.tgz", - "integrity": "sha512-AequnQdPRuXf4AuvvFlSjnkWI460xxhAd6y362gFtOE4jjJLLXblbMAXVFrkV8/pDMGNjpVegVSpRmHXZsbKhg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.0.4.tgz", + "integrity": "sha512-wHo0d8GXyVmpmMh/qOR0R7Y46/G1y6OR8U+bSTB4ppEzRxd1xVAQ9xOE9hOc0bSjhz0ujCPAbfNLkLrpa6cevg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.0.2", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/types": "^4.3.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/invalid-dependency": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.0.1.tgz", - "integrity": "sha512-2q/Eb0AE662zwyMV+z+TL7deBwcHCgaZZGc0RItamBE8kak3MzCi/EZCNoFWoBfxgQ4jfR12wm8KKsSXhJzJtQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.4.tgz", + "integrity": "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/is-array-buffer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz", - "integrity": "sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/md5-js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-2.0.1.tgz", - "integrity": "sha512-8WWOtwWMmIDgTkRv1o3opy3ABsRXs4/XunETK53ckxQRAiOML1PlnqLBK9Uwk9bvOD6cpmsC6dioIfmKGpJ25w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.0.4.tgz", + "integrity": "sha512-uGLBVqcOwrLvGh/v/jw423yWHq/ofUGK1W31M2TNspLQbUV1Va0F5kTxtirkoHawODAZcjXTSGi7JwbnPcDPJg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.0.2", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/types": "^4.3.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/middleware-content-length": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.0.1.tgz", - "integrity": "sha512-IZhRSk5GkVBcrKaqPXddBS2uKhaqwBgaSgbBb1OJyGsKe7SxRFbclWS0LqOR9fKUkDl+3lL8E2ffpo6EQg0igw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.4.tgz", + "integrity": "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==", + "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^2.0.1", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/middleware-endpoint": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-2.0.1.tgz", - "integrity": "sha512-uz/KI1MBd9WHrrkVFZO4L4Wyv24raf0oR4EsOYEeG5jPJO5U+C7MZGLcMxX8gWERDn1sycBDqmGv8fjUMLxT6w==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.13.tgz", + "integrity": "sha512-xg3EHV/Q5ZdAO5b0UiIMj3RIOCobuS40pBBODguUDVdko6YK6QIzCVRrHTogVuEKglBWqWenRnZ71iZnLL3ZAQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^2.0.1", - "@smithy/types": "^2.0.2", - "@smithy/url-parser": "^2.0.1", - "@smithy/util-middleware": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/core": "^3.6.0", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/middleware-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-2.0.1.tgz", - "integrity": "sha512-NKHF4i0gjSyjO6C0ZyjEpNqzGgIu7s8HOK6oT/1Jqws2Q1GynR1xV8XTUs1gKXeaNRzbzKQRewHHmfPwZjOtHA==", + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.14.tgz", + "integrity": "sha512-eoXaLlDGpKvdmvt+YBfRXE7HmIEtFF+DJCbTPwuLunP0YUnrydl+C4tS+vEM0+nyxXrX3PSUFqC+lP1+EHB1Tw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^2.0.1", - "@smithy/service-error-classification": "^2.0.0", - "@smithy/types": "^2.0.2", - "@smithy/util-middleware": "^2.0.0", - "@smithy/util-retry": "^2.0.0", - "tslib": "^2.5.0", - "uuid": "^8.3.2" + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/service-error-classification": "^4.0.6", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/middleware-serde": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.0.1.tgz", - "integrity": "sha512-uKxPaC6ItH9ZXdpdqNtf8sda7GcU4SPMp0tomq/5lUg9oiMa/Q7+kD35MUrpKaX3IVXVrwEtkjCU9dogZ/RAUA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", + "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/middleware-stack": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.0.0.tgz", - "integrity": "sha512-31XC1xNF65nlbc16yuh3wwTudmqs6qy4EseQUGF8A/p2m/5wdd/cnXJqpniy/XvXVwkHPz/GwV36HqzHtIKATQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", + "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/node-config-provider": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.0.1.tgz", - "integrity": "sha512-Zoel4CPkKRTQ2XxmozZUfqBYqjPKL53/SvTDhJHj+VBSiJy6MXRav1iDCyFPS92t40Uh+Yi+Km5Ch3hQ+c/zSA==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^2.0.1", - "@smithy/shared-ini-file-loader": "^2.0.1", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/node-http-handler": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.0.1.tgz", - "integrity": "sha512-Zv3fxk3p9tsmPT2CKMsbuwbbxnq2gzLDIulxv+yI6aE+02WPYorObbbe9gh7SW3weadMODL1vTfOoJ9yFypDzg==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", + "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^2.0.1", - "@smithy/protocol-http": "^2.0.1", - "@smithy/querystring-builder": "^2.0.1", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@smithy/abort-controller": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/property-provider": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.0.1.tgz", - "integrity": "sha512-pmJRyY9SF6sutWIktIhe+bUdSQDxv/qZ4mYr3/u+u45riTPN7nmRxPo+e4sjWVoM0caKFjRSlj3tf5teRFy0Vg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/protocol-http": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-2.0.1.tgz", - "integrity": "sha512-mrkMAp0wtaDEIkgRObWYxI1Kun1tm6Iu6rK+X4utb6Ah7Uc3Kk4VIWwK/rBHdYGReiLIrxFCB1rq4a2gyZnSgg==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/querystring-builder": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.0.1.tgz", - "integrity": "sha512-bp+93WFzx1FojVEIeFPtG0A1pKsFdCUcZvVdZdRlmNooOUrz9Mm9bneRd8hDwAQ37pxiZkCOxopSXXRQN10mYw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", + "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.0.2", - "@smithy/util-uri-escape": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/types": "^4.3.1", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/querystring-parser": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-2.0.1.tgz", - "integrity": "sha512-h+e7k1z+IvI2sSbUBG9Aq46JsgLl4UqIUl6aigAlRBj+P6ocNXpM6Yn1vMBw5ijtXeZbYpd1YvCxwDgdw3jhmg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", + "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/service-error-classification": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-2.0.0.tgz", - "integrity": "sha512-2z5Nafy1O0cTf69wKyNjGW/sNVMiqDnb4jgwfMG8ye8KnFJ5qmJpDccwIbJNhXIfbsxTg9SEec2oe1cexhMJvw==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.6.tgz", + "integrity": "sha512-RRoTDL//7xi4tn5FrN2NzH17jbgmnKidUqd4KvquT0954/i6CXXkh1884jBiunq24g9cGtPBEXlU40W6EpNOOg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1" + }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.0.1.tgz", - "integrity": "sha512-a463YiZrPGvM+F336rIF8pLfQsHAdCRAn/BiI/EWzg5xLoxbC7GSxIgliDDXrOu0z8gT3nhVsif85eU6jyct3A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/signature-v4": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.0.1.tgz", - "integrity": "sha512-jztv5Mirca42ilxmMDjzLdXcoAmRhZskGafGL49sRo5u7swEZcToEFrq6vtX5YMbSyTVrE9Teog5EFexY5Ff2Q==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.2.tgz", + "integrity": "sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-codec": "^2.0.1", - "@smithy/is-array-buffer": "^2.0.0", - "@smithy/types": "^2.0.2", - "@smithy/util-hex-encoding": "^2.0.0", - "@smithy/util-middleware": "^2.0.0", - "@smithy/util-uri-escape": "^2.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/smithy-client": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.0.1.tgz", - "integrity": "sha512-LHC5m6tYpEu1iNbONfvMbwtErboyTZJfEIPoD78Ei5MVr36vZQCaCla5mvo36+q/a2NAk2//fA5Rx3I1Kf7+lQ==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.5.tgz", + "integrity": "sha512-+lynZjGuUFJaMdDYSTMnP/uPBBXXukVfrJlP+1U/Dp5SFTEI++w6NMga8DjOENxecOF71V9Z2DllaVDYRnGlkg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-stack": "^2.0.0", - "@smithy/types": "^2.0.2", - "@smithy/util-stream": "^2.0.1", - "tslib": "^2.5.0" + "@smithy/core": "^3.6.0", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.0.2.tgz", - "integrity": "sha512-wcymEjIXQ9+NEfE5Yt5TInAqe1o4n+Nh+rh00AwoazppmUt8tdo6URhc5gkDcOYrcvlDVAZE7uG69nDpEGUKxw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/url-parser": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-2.0.1.tgz", - "integrity": "sha512-NpHVOAwddo+OyyIoujDL9zGL96piHWrTNXqltWmBvlUoWgt1HPyBuKs6oHjioyFnNZXUqveTOkEEq0U5w6Uv8A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", + "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^2.0.1", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@smithy/querystring-parser": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/util-base64": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.0.0.tgz", - "integrity": "sha512-Zb1E4xx+m5Lud8bbeYi5FkcMJMnn+1WUnJF3qD7rAdXpaL7UjkFQLdmW5fHadoKbdHpwH9vSR8EyTJFHJs++tA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-body-length-browser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-2.0.0.tgz", - "integrity": "sha512-JdDuS4ircJt+FDnaQj88TzZY3+njZ6O+D3uakS32f2VNnDo3vyEuNdBOh/oFd8Df1zSZOuH1HEChk2AOYDezZg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/util-body-length-node": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-2.0.0.tgz", - "integrity": "sha512-ZV7Z/WHTMxHJe/xL/56qZwSUcl63/5aaPAGjkfynJm4poILjdD4GmFI+V+YWabh2WJIjwTKZ5PNsuvPQKt93Mg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-buffer-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz", - "integrity": "sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-config-provider": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.0.0.tgz", - "integrity": "sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.0.1.tgz", - "integrity": "sha512-w72Qwsb+IaEYEFtYICn0Do42eFju78hTaBzzJfT107lFOPdbjWjKnFutV+6GL/nZd5HWXY7ccAKka++C3NrjHw==", + "version": "4.0.21", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.21.tgz", + "integrity": "sha512-wM0jhTytgXu3wzJoIqpbBAG5U6BwiubZ6QKzSbP7/VbmF1v96xlAbX2Am/mz0Zep0NLvLh84JT0tuZnk3wmYQA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^2.0.1", - "@smithy/types": "^2.0.2", + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", "bowser": "^2.11.0", - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">= 10.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.0.1.tgz", - "integrity": "sha512-dNF45caelEBambo0SgkzQ0v76m4YM+aFKZNTtSafy7P5dVF8TbjZuR2UX1A5gJABD9XK6lzN+v/9Yfzj/EDgGg==", + "version": "4.0.21", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.21.tgz", + "integrity": "sha512-/F34zkoU0GzpUgLJydHY8Rxu9lBn8xQC/s/0M0U9lLBkYbA1htaAFjWYJzpzsbXPuri5D1H8gjp2jBum05qBrA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^2.0.1", - "@smithy/credential-provider-imds": "^2.0.1", - "@smithy/node-config-provider": "^2.0.1", - "@smithy/property-provider": "^2.0.1", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@smithy/config-resolver": "^4.1.4", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 10.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", + "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/util-hex-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.0.0.tgz", - "integrity": "sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-middleware": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.0.0.tgz", - "integrity": "sha512-eCWX4ECuDHn1wuyyDdGdUWnT4OGyIzV0LN1xRttBFMPI9Ff/4heSHVxneyiMtOB//zpXWCha1/SWHJOZstG7kA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", + "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-retry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-2.0.0.tgz", - "integrity": "sha512-/dvJ8afrElasuiiIttRJeoS2sy8YXpksQwiM/TcepqdRVp7u4ejd9C4IQURHNjlfPUT7Y6lCDSa2zQJbdHhVTg==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.6.tgz", + "integrity": "sha512-+YekoF2CaSMv6zKrA6iI/N9yva3Gzn4L6n35Luydweu5MMPYpiGZlWqehPHDHyNbnyaYlz/WJyYAZnC+loBDZg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/service-error-classification": "^4.0.6", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.0.1.tgz", - "integrity": "sha512-2a0IOtwIKC46EEo7E7cxDN8u2jwOiYYJqcFKA6rd5rdXqKakHT2Gc+AqHWngr0IEHUfW92zX12wRQKwyoqZf2Q==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.2.tgz", + "integrity": "sha512-aI+GLi7MJoVxg24/3J1ipwLoYzgkB4kUfogZfnslcYlynj3xsQ0e7vk4TnTro9hhsS5PvX1mwmkRqqHQjwcU7w==", + "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^2.0.1", - "@smithy/node-http-handler": "^2.0.1", - "@smithy/types": "^2.0.2", - "@smithy/util-base64": "^2.0.0", - "@smithy/util-buffer-from": "^2.0.0", - "@smithy/util-hex-encoding": "^2.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-uri-escape": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.0.0.tgz", - "integrity": "sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-utf8": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.0.0.tgz", - "integrity": "sha512-rctU1VkziY84n5OXe3bPNpKR001ZCME2JCaBBFgtiM2hfKbHFudc/BkMuPab8hRbLd0j3vbnBTTZ1igBf0wgiQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^2.0.0", - "tslib": "^2.5.0" + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-waiter": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-2.0.1.tgz", - "integrity": "sha512-bSyGFicPRYuGFFWAr72UvYI7tE7KmEeFJJ5iaLuTTdo8RGaNBZ2kE25coGtzrejYh9AhwSfckBvbxgEDxIxhlA==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.0.6.tgz", + "integrity": "sha512-slcr1wdRbX7NFphXZOxtxRNA7hXAAtJAXJDE/wdoMAos27SIquVCKiSqfB6/28YzQ8FCsB5NKkhdM5gMADbqxg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^2.0.1", - "@smithy/types": "^2.0.2", - "tslib": "^2.5.0" + "@smithy/abort-controller": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@socket.io/component-emitter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" }, "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/babel__core": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", - "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -5130,72 +5346,81 @@ } }, "node_modules/@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.0.0" } }, "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, + "license": "MIT", "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "node_modules/@types/babel__traverse": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz", - "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.20.7" } }, "node_modules/@types/bcrypt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.0.tgz", - "integrity": "sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/bluebird": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.38.tgz", - "integrity": "sha512-yR/Kxc0dd4FfwtEoLZMoqJbM/VE/W7hXn/MIjb+axcwag0iFmSPK7OBUZq1YWLynJUoWQkfUrI7T0HDqGApNSg==", - "dev": true + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.42.tgz", + "integrity": "sha512-Jhy+MWRlro6UjVi578V/4ZGNfeCOcNCp0YaFNIUGFKlImowqwb1O/22wDVk3FDGMLqxdpOV3qQHD5fPEH4hK6A==", + "dev": true, + "license": "MIT" }, "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", "dev": true, + "license": "MIT", "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "node_modules/@types/compression": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.7.2.tgz", - "integrity": "sha512-lwEL4M/uAGWngWFLSG87ZDr2kLrbuR8p7X+QZB1OQlT+qkHsCPDVFnHPyXf4Vyl4yDDorNY+mAhosxkCvppatg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-kCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+HVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q==", "dev": true, + "license": "MIT", "dependencies": { - "@types/express": "*" + "@types/express": "*", + "@types/node": "*" } }, "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -5205,29 +5430,25 @@ "resolved": "https://registry.npmjs.org/@types/connect-flash/-/connect-flash-0.0.37.tgz", "integrity": "sha512-SfmGGYpKvPfZeA+v74FS0HlYqVsx8Inb4d3px99kz2xSMx/IQiz/K/i+7MHTmk/OkE+0suZX108tHrQJ8QEGag==", "dev": true, + "license": "MIT", "dependencies": { "@types/express": "*" } }, - "node_modules/@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", - "license": "MIT" - }, "node_modules/@types/cookie-parser": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.3.tgz", - "integrity": "sha512-CqSKwFwefj4PzZ5n/iwad/bow2hTCh0FlNAeWLtQM3JA/NX/iYagIpWG2cf1bQKQ2c9gU2log5VUCrn7LDOs0w==", + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.9.tgz", + "integrity": "sha512-tGZiZ2Gtc4m3wIdLkZ8mkj1T6CEHb35+VApbL2T14Dew8HA7c+04dmKqsKRNC+8RJPm16JEK0tFSwdZqubfc4g==", "dev": true, - "dependencies": { + "license": "MIT", + "peerDependencies": { "@types/express": "*" } }, "node_modules/@types/cors": { - "version": "2.8.17", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", - "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -5238,6 +5459,7 @@ "resolved": "https://registry.npmjs.org/@types/cron/-/cron-2.0.1.tgz", "integrity": "sha512-WHa/1rtNtD2Q/H0+YTTZoty+/5rcE66iAFX2IY+JuUoOACsevYyFkSYu/2vdw+G5LrmO7Lxowrqm0av4k3qWNQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/luxon": "*", "@types/node": "*" @@ -5247,22 +5469,25 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/csurf": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@types/csurf/-/csurf-1.11.2.tgz", - "integrity": "sha512-9bc98EnwmC1S0aSJiA8rWwXtgXtXHHOQOsGHptImxFgqm6CeH+mIOunHRg6+/eg2tlmDMX3tY7XrWxo2M/nUNQ==", + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@types/csurf/-/csurf-1.11.5.tgz", + "integrity": "sha512-5rw87+5YGixyL2W8wblSUl5DSZi5YOlXE6Awwn2ofLvqKr/1LruKffrQipeJKUX44VaxKj8m5es3vfhltJTOoA==", "dev": true, + "license": "MIT", "dependencies": { "@types/express-serve-static-core": "*" } }, "node_modules/@types/express": { - "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", - "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", + "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -5271,10 +5496,11 @@ } }, "node_modules/@types/express-brute": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/express-brute/-/express-brute-1.0.2.tgz", - "integrity": "sha512-p+3ks+pW04poJobPxyEK3FLnBhEbEAVYhc6QXXBoVBzw5yfW+HobKvgCnaQ6d/egBym+tDXGKIuGoAAZbaJadw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/express-brute/-/express-brute-1.0.6.tgz", + "integrity": "sha512-yKVX0N9dTR4CNSMEMlSLfNkDgkNws2DMfRJZD6EsqZbtpDp4wAPSLo6N2e+c4OMPC72q2V82YWDtvYUCmBfvvA==", "dev": true, + "license": "MIT", "dependencies": { "@types/express": "*" } @@ -5284,24 +5510,17 @@ "resolved": "https://registry.npmjs.org/@types/express-brute-redis/-/express-brute-redis-0.0.4.tgz", "integrity": "sha512-pjarPr7id4sPuTMeltb8Z50rxbJxjAhLtkDbaiobeIcjnN1i+vwhq4YOeNTyAJneUPP0lossi0uvuvx9Of/zJg==", "dev": true, + "license": "MIT", "dependencies": { "@types/redis": "^2.8.0" } }, - "node_modules/@types/express-brute-redis/node_modules/@types/redis": { - "version": "2.8.32", - "resolved": "https://registry.npmjs.org/@types/redis/-/redis-2.8.32.tgz", - "integrity": "sha512-7jkMKxcGq9p242exlbsVzuJb57KqHRhNl4dHoQu2Y5v9bCAbtIXXH0R3HleSQW4CTOqpHIYUW3t6tpUj4BVQ+w==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.35", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", - "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -5310,10 +5529,11 @@ } }, "node_modules/@types/express-session": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.7.tgz", - "integrity": "sha512-L25080PBYoRLu472HY/HNCxaXY8AaGgqGC8/p/8+BYMhG0RDOLQ1wpXOpAzr4Gi5TGozTKyJv5BVODM5UNyVMw==", + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.2.tgz", + "integrity": "sha512-k+I0BxwVXsnEU2hV77cCobC08kIsn4y44C3gC0b46uxZVMaXA04lSPgRLR/bSL2w0t0ShJiG8o4jPzRG/nscFg==", "dev": true, + "license": "MIT", "dependencies": { "@types/express": "*" } @@ -5323,24 +5543,27 @@ "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/graceful-fs": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", - "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/hpp": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@types/hpp/-/hpp-0.2.2.tgz", - "integrity": "sha512-BLgsawqFFbS3tFUr+mcBRfst+DumnSfi4PgyNeJAGk0eIxm7lKX1axmHVlbgKNAZS0caZA5/LSopuj0T2LKRPw==", + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@types/hpp/-/hpp-0.2.6.tgz", + "integrity": "sha512-6gn1RuHA1/XFCVCqCkSV+AWy07YwtGg4re4SHhLMoiARTg9XlrbYMtVR+Uvws0VlERXzzcA+1UYvxEV6O+sgPg==", "dev": true, + "license": "MIT", "dependencies": { "@types/express": "*" } @@ -5349,28 +5572,32 @@ "version": "1.8.2", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.2.tgz", "integrity": "sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, + "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/istanbul-lib-report": "*" } @@ -5380,69 +5607,90 @@ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.8.tgz", "integrity": "sha512-8TJkV++s7B6XqnDrzR1m/TT0A0h948Pnl/097veySPN67VRAgQ4gZ7n2KfJo2rVq6njQjdxU3GCCyDvAeuHoiw==", "dev": true, + "license": "MIT", "dependencies": { "expect": "^28.0.0", "pretty-format": "^28.0.0" } }, "node_modules/@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", - "dev": true + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" }, "node_modules/@types/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==", + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", "dev": true, + "license": "MIT", "dependencies": { + "@types/ms": "*", "@types/node": "*" } }, "node_modules/@types/lodash": { - "version": "4.14.196", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.196.tgz", - "integrity": "sha512-22y3o88f4a94mKljsZcanlNWPzO0uBsBdzLAngf2tp533LzZcQzb6+eZPJ+vCTt+bqF2XnvT9gejTLsAcJAJyQ==", - "dev": true + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-NYqRyg/hIQrYPT9lbOeYc3kIRabJDn/k4qQHIXUpx88CBDww2fD15Sg5kbXlW86zm2XEW4g0QxkTI3/Kfkc7xQ==", + "dev": true, + "license": "MIT" }, "node_modules/@types/luxon": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.1.tgz", - "integrity": "sha512-XOS5nBcgEeP2PpcqJHjCWhUCAzGfXIU8ILOSLpx2FhxqMW9KdxgCGXNOEKGVBfveKtIpztHzKK5vSRVLyW/NqA==", - "dev": true + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.6.2.tgz", + "integrity": "sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", - "dev": true + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" }, "node_modules/@types/mime-types": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.1.tgz", - "integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==", - "dev": true + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", + "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", + "dev": true, + "license": "MIT" }, "node_modules/@types/morgan": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.4.tgz", - "integrity": "sha512-cXoc4k+6+YAllH3ZHmx4hf7La1dzUk6keTR4bF4b4Sc0mZxU/zK4wO7l+ZzezXm/jkYj/qC+uYGZrarZdIVvyQ==", + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.10.tgz", + "integrity": "sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { - "version": "18.17.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.1.tgz", - "integrity": "sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw==" + "version": "18.19.113", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.113.tgz", + "integrity": "sha512-TmSTE9vyebJ9vSEiU+P+0Sp4F5tMgjiEOZaQUW6wA3ODvi6uBgkHQ+EsIu0pbiKvf9QHEvyRCiaz03rV0b+IaA==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/oauth": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.1.tgz", - "integrity": "sha512-a1iY62/a3yhZ7qH7cNUsxoI3U/0Fe9+RnuFrpTKr+0WVOzbKlSLojShCKe20aOD1Sppv+i8Zlq0pLDuTJnwS4A==", + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.6.tgz", + "integrity": "sha512-H9TRCVKBNOhZZmyHLqFt9drPM9l+ShWiqqJijU1B8P3DX3ub84NjxDuy+Hjrz+fEca5Kwip3qPMKNyiLgNJtIA==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -5482,10 +5730,11 @@ } }, "node_modules/@types/passport-oauth2": { - "version": "1.4.12", - "resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.4.12.tgz", - "integrity": "sha512-RZg6cYTyEGinrZn/7REYQds6zrTxoBorX1/fdaz5UHzkG8xdFE7QQxkJagCr2ETzGII58FAFDmnmbTUVMrltNA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.8.0.tgz", + "integrity": "sha512-6//z+4orIOy/g3zx17HyQ71GSRK4bs7Sb+zFasRoc2xzlv7ZCJ+vkDBYFci8U6HY+or6Zy7ajf4mz4rK7nsWJQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/express": "*", "@types/oauth": "*", @@ -5493,84 +5742,103 @@ } }, "node_modules/@types/passport-strategy": { - "version": "0.2.35", - "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz", - "integrity": "sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==", + "version": "0.2.38", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.38.tgz", + "integrity": "sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==", "dev": true, + "license": "MIT", "dependencies": { "@types/express": "*", "@types/passport": "*" } }, "node_modules/@types/pg": { - "version": "8.11.11", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.11.tgz", - "integrity": "sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw==", + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.4.tgz", + "integrity": "sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==", "license": "MIT", "dependencies": { "@types/node": "*", "pg-protocol": "*", - "pg-types": "^4.0.1" + "pg-types": "^2.2.0" } }, "node_modules/@types/prettier": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/pug": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.6.tgz", - "integrity": "sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==", - "dev": true + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz", + "integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==", + "dev": true, + "license": "MIT" }, "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" }, "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/redis": { + "version": "2.8.32", + "resolved": "https://registry.npmjs.org/@types/redis/-/redis-2.8.32.tgz", + "integrity": "sha512-7jkMKxcGq9p242exlbsVzuJb57KqHRhNl4dHoQu2Y5v9bCAbtIXXH0R3HleSQW4CTOqpHIYUW3t6tpUj4BVQ+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } }, "node_modules/@types/sanitize-html": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.9.0.tgz", - "integrity": "sha512-4fP/kEcKNj2u39IzrxWYuf/FnCCwwQCpif6wwY6ROUS1EPRIfWJjGkY3HIowY1EX/VbX5e86yq8AAE7UPMgATg==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.16.0.tgz", + "integrity": "sha512-l6rX1MUXje5ztPT0cAFtUayXF06DqPhRyfVXareEN5gGCFaP/iwsxIyKODr9XDhfxPpN6vXUFNfo5kZMXCxBtw==", "dev": true, + "license": "MIT", "dependencies": { "htmlparser2": "^8.0.0" } }, "node_modules/@types/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", - "dev": true + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", + "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", + "dev": true, + "license": "MIT" }, "node_modules/@types/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", - "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", "dev": true, + "license": "MIT", "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "node_modules/@types/serve-static": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", - "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", "dev": true, + "license": "MIT", "dependencies": { "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" + "@types/node": "*", + "@types/send": "*" } }, "node_modules/@types/sharp": { @@ -5578,68 +5846,84 @@ "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.31.1.tgz", "integrity": "sha512-5nWwamN9ZFHXaYEincMSuza8nNfOof8nmO+mcI+Agx1uMUk4/pQnNIcix+9rLPXzKrm1pS34+6WRDbDV0Jn7ag==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/swagger-jsdoc": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.1.tgz", - "integrity": "sha512-+MUpcbyxD528dECUBCEVm6abNuORdbuGjbrUdHDeAQ+rkPuo2a+L4N02WJHF3bonSSE6SJ3dUJwF2V6+cHnf0w==", - "dev": true + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz", + "integrity": "sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==", + "dev": true, + "license": "MIT" }, "node_modules/@types/toobusy-js": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@types/toobusy-js/-/toobusy-js-0.5.2.tgz", - "integrity": "sha512-jK/CMvC5h/ECMhWRNjeFYIZzGFFvjt38+zbMndFKyDUHl1dm89SX7u8njoNHQKrW+OZoqxteBLvtSBFQOdGyHA==", - "dev": true + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@types/toobusy-js/-/toobusy-js-0.5.4.tgz", + "integrity": "sha512-hsKMbYiaL3ZWx7B3FYyN0rEJexw7I1HgKbNToX3ZZJv6373to954wlA7zrXR3/XoVwZnFwWqFguBs91sNzJGKQ==", + "dev": true, + "license": "MIT" }, "node_modules/@types/triple-beam": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.2.tgz", - "integrity": "sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g==" + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" }, "node_modules/@types/uglify-js": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.1.tgz", - "integrity": "sha512-GkewRA4i5oXacU/n4MA9+bLgt5/L3F1mKrYvFGm7r2ouLXhRKjuWwo9XHNnbx6WF3vlGW21S3fCvgqxvxXXc5g==", + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.5.tgz", + "integrity": "sha512-TU+fZFBTBcXj/GpDpDaBmgWk/gn96kMZ+uocaFUlV2f8a6WdMzzI44QBCmGcCiYR0Y6ZlNRiyUyKKt5nl/lbzQ==", "dev": true, + "license": "MIT", "dependencies": { "source-map": "^0.6.1" } }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "license": "MIT" + }, "node_modules/@types/xss-filters": { "version": "0.0.27", "resolved": "https://registry.npmjs.org/@types/xss-filters/-/xss-filters-0.0.27.tgz", "integrity": "sha512-ctN3f7vl4tBXa+W11hm0oDwp67K6SYK07h4OmNgaEoIOVJ/rksnc2prpbjK+Ju3/fYIa3HQaH4x9Y525CXFOow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "dev": true, + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.4.0", "@typescript-eslint/scope-manager": "5.62.0", @@ -5669,26 +5953,12 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -5696,17 +5966,12 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@typescript-eslint/parser": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -5734,6 +5999,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "dev": true, + "license": "MIT", "dependencies": { "@typescript-eslint/types": "5.62.0", "@typescript-eslint/visitor-keys": "5.62.0" @@ -5751,6 +6017,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "dev": true, + "license": "MIT", "dependencies": { "@typescript-eslint/typescript-estree": "5.62.0", "@typescript-eslint/utils": "5.62.0", @@ -5778,6 +6045,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -5791,6 +6059,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "@typescript-eslint/types": "5.62.0", "@typescript-eslint/visitor-keys": "5.62.0", @@ -5813,26 +6082,12 @@ } } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -5840,17 +6095,12 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@typescript-eslint/utils": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", @@ -5872,26 +6122,12 @@ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -5899,17 +6135,12 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/utils/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@typescript-eslint/visitor-keys": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, + "license": "MIT", "dependencies": { "@typescript-eslint/types": "5.62.0", "eslint-visitor-keys": "^3.3.0" @@ -5922,15 +6153,38 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.2.3.tgz", + "integrity": "sha512-oRhjSzcVjX8ExyaF8hC0zzTqxlVuRlgMHL/Bh4w3xB9+wjbm0FpXylVU/lBrn+kgphwYTrOk3tp+AVShGmlYCg==", + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC" }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -5939,11 +6193,21 @@ "node": ">= 0.6" } }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -5956,37 +6220,31 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, "engines": { "node": ">=0.4.0" } }, - "node_modules/adm-zip": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", - "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", - "dev": true, - "engines": { - "node": ">=6.0" - } - }, "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dependencies": { - "debug": "4" - }, + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", "engines": { - "node": ">= 6.0.0" + "node": ">= 14" } }, "node_modules/ajv": { @@ -5994,6 +6252,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6010,6 +6269,7 @@ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.21.3" }, @@ -6024,20 +6284,25 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/anymatch": { @@ -6045,6 +6310,7 @@ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -6056,18 +6322,20 @@ "node_modules/aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "license": "ISC" }, "node_modules/archiver": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.1.tgz", - "integrity": "sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "license": "MIT", "dependencies": { "archiver-utils": "^2.1.0", - "async": "^3.2.3", + "async": "^3.2.4", "buffer-crc32": "^0.2.1", "readable-stream": "^3.6.0", - "readdir-glob": "^1.0.0", + "readdir-glob": "^1.1.2", "tar-stream": "^2.2.0", "zip-stream": "^4.1.0" }, @@ -6079,6 +6347,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "license": "MIT", "dependencies": { "glob": "^7.1.4", "graceful-fs": "^4.2.0", @@ -6095,10 +6364,44 @@ "node": ">= 6" } }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/archiver-utils/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -6109,10 +6412,17 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/archiver-utils/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } @@ -6121,6 +6431,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" @@ -6133,19 +6445,21 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" }, "node_modules/array-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", - "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -6153,13 +6467,14 @@ "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" }, "node_modules/array-slice": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", - "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -6169,6 +6484,7 @@ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -6176,7 +6492,8 @@ "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT" }, "node_modules/assert-never": { "version": "1.4.0", @@ -6185,19 +6502,21 @@ "license": "MIT" }, "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" }, "node_modules/axios": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", - "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", + "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -6206,15 +6525,17 @@ } }, "node_modules/b4a": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", - "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==" + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "license": "Apache-2.0" }, "node_modules/babel-jest": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==", "dev": true, + "license": "MIT", "dependencies": { "@jest/transform": "^28.1.3", "@types/babel__core": "^7.1.14", @@ -6231,81 +6552,12 @@ "@babel/core": "^7.8.0" } }, - "node_modules/babel-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/babel-jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/babel-jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -6322,6 +6574,7 @@ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz", "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", @@ -6333,13 +6586,14 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz", - "integrity": "sha512-19hwUH5FKl49JEsvyTcoHakh6BE0wgXLLptIyKZ3PijHc/Ci521wygORCUCCred+E/twuqRyAkE02BAWPmsHOg==", + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.2", + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { @@ -6347,48 +6601,54 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.3.tgz", - "integrity": "sha512-z41XaniZL26WLrvjy7soabMXrfPWARN25PZoriDEiLMxAp50AUW3t35BGQUMg5xK3UrpVTtagIDklxYa+MhiNA==", + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", + "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.2", - "core-js-compat": "^3.31.0" + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.2.tgz", - "integrity": "sha512-tAlOptU0Xj34V1Y2PNTL4Y0FOJMDB6bZmoW39FeCQIhigGLkqu3Fj6uiXpxIf6Ij274ENdYx64y6Au+ZKlb1IA==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.2" + "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0" @@ -6399,6 +6659,7 @@ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz", "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==", "dev": true, + "license": "MIT", "dependencies": { "babel-plugin-jest-hoist": "^28.1.3", "babel-preset-current-node-syntax": "^1.0.0" @@ -6425,7 +6686,80 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/bare-events": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", + "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.5.tgz", + "integrity": "sha512-1zccWBMypln0jEE05LzZt+V/8y8AQsQQqxtklqaIyg5nu6OAYFhZxPXinJTSG+kU5qyNmeLgcn9AW7eHiCHVLA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", + "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", + "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } }, "node_modules/base64-js": { "version": "1.5.1", @@ -6444,7 +6778,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/base64id": { "version": "2.0.0", @@ -6459,6 +6794,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -6467,6 +6803,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", "dependencies": { "safe-buffer": "5.1.2" }, @@ -6474,13 +6811,20 @@ "node": ">= 0.8" } }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/bcrypt": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.0.tgz", - "integrity": "sha512-RHBS7HI5N5tEnGTmtR/pppX0mmDSBpQ4aCBsj7CEQfYXDcO74A8sIBYcJMuCsis2E81zDxeENYhv66oZwLiA+Q==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", "hasInstallScript": true, + "license": "MIT", "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.10", + "@mapbox/node-pre-gyp": "^1.0.11", "node-addon-api": "^5.0.0" }, "engines": { @@ -6488,9 +6832,10 @@ } }, "node_modules/big-integer": { - "version": "1.6.51", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", - "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "license": "Unlicense", "engines": { "node": ">=0.6" } @@ -6499,6 +6844,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "license": "MIT", "dependencies": { "buffers": "~0.1.1", "chainsaw": "~0.1.0" @@ -6508,18 +6854,23 @@ } }, "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", "dependencies": { "file-uri-to-path": "1.0.0" } @@ -6528,6 +6879,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -6537,19 +6889,8 @@ "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "node_modules/body": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", - "integrity": "sha512-chUsBxGRtuElD6fmw1gHLpvnKdVLK302peeFa9ZqAEk8TyzZ3fygLyUEDDPTJvL9+Bor0dIwn6ePOsRM2y0zQQ==", - "dev": true, - "dependencies": { - "continuable-cache": "^0.3.1", - "error": "^7.0.0", - "raw-body": "~1.1.0", - "safe-json-parse": "~1.0.1" - } + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "license": "MIT" }, "node_modules/body-parser": { "version": "1.20.3", @@ -6575,15 +6916,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/body-parser/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -6599,40 +6931,17 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/body/node_modules/bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", - "integrity": "sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ==", - "dev": true - }, - "node_modules/body/node_modules/raw-body": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", - "integrity": "sha512-WmJJU2e9Y6M5UzTOkHaM7xJGAPQD8PNzx3bAd2+uhZAim6wDk6dAZxPVYLF67XhbR4hmKGh33Lpmh4XWrCH5Mg==", - "dev": true, - "dependencies": { - "bytes": "1", - "string_decoder": "0.10" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/body/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", - "dev": true - }, "node_modules/bowser": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", - "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6642,7 +6951,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -6652,9 +6960,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.9", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", - "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", "dev": true, "funding": [ { @@ -6670,11 +6978,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001503", - "electron-to-chromium": "^1.4.431", - "node-releases": "^2.0.12", - "update-browserslist-db": "^1.0.11" + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -6688,6 +6997,7 @@ "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", "dev": true, + "license": "MIT", "dependencies": { "fast-json-stable-stringify": "2.x" }, @@ -6700,6 +7010,7 @@ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "node-int64": "^0.4.0" } @@ -6722,6 +7033,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -6731,6 +7043,7 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", "engines": { "node": "*" } @@ -6738,18 +7051,21 @@ "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/buffer-indexof-polyfill": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "license": "MIT", "engines": { "node": ">=0.10" } @@ -6767,34 +7083,24 @@ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", "integrity": "sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/call-bind-apply-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", - "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -6805,13 +7111,13 @@ } }, "node_modules/call-bound": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", - "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "get-intrinsic": "^1.2.6" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -6824,13 +7130,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -6840,14 +7148,15 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/caniuse-lite": { - "version": "1.0.30001517", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001517.tgz", - "integrity": "sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA==", + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", "dev": true, "funding": [ { @@ -6862,12 +7171,14 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chainsaw": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "license": "MIT/X11", "dependencies": { "traverse": ">=0.3.0 <0.4" }, @@ -6876,17 +7187,33 @@ } }, "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/char-regex": { @@ -6894,6 +7221,7 @@ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } @@ -6902,6 +7230,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", + "license": "MIT", "dependencies": { "is-regex": "^1.0.3" } @@ -6910,22 +7239,18 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/chartjs-to-image/-/chartjs-to-image-1.2.2.tgz", "integrity": "sha512-qnYedDlNSPsrISQyRhJk4gWciKMtK8mlx2VWbFMJIPLVokSHJBEUuoxE6LLDFGnOhdvLd3K5E6lmGap7/phWFQ==", + "license": "MIT", "dependencies": { "axios": "^1.6.0", "javascript-stringify": "^2.1.0" } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -6938,6 +7263,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -6946,14 +7274,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/ci-info": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, "funding": [ { @@ -6961,21 +7290,24 @@ "url": "https://github.com/sponsors/sibiraj-s" } ], + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/cjs-module-lexer": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", - "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", - "dev": true + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -6985,10 +7317,51 @@ "node": ">=12" } }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/cluster-key-slot": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", "engines": { "node": ">=0.10.0" } @@ -6998,6 +7371,7 @@ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, + "license": "MIT", "engines": { "iojs": ">= 1.0.0", "node": ">= 0.12.0" @@ -7007,12 +7381,14 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" @@ -7022,22 +7398,28 @@ } }, "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", "dependencies": { - "color-name": "1.1.3" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" }, "node_modules/color-string": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" @@ -7047,31 +7429,17 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", "bin": { "color-support": "bin.js" } }, - "node_modules/color/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, "node_modules/colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha512-ENwblkFQpqqia6b++zLD/KUWafYlVY/UNnAp7oz7LY7E924wmpye416wBOmvv/HMWzl8gL1kJlfvId/1Dg176w==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.1.90" } @@ -7080,6 +7448,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "license": "MIT", "dependencies": { "color": "^3.1.3", "text-hex": "1.0.x" @@ -7089,15 +7458,32 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", "dependencies": { "color-convert": "^1.9.3", "color-string": "^1.6.0" } }, + "node_modules/colorspace/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/colorspace/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -7110,14 +7496,16 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6" } }, "node_modules/compress-commons": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", - "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "license": "MIT", "dependencies": { "buffer-crc32": "^0.2.13", "crc32-stream": "^4.0.2", @@ -7132,6 +7520,7 @@ "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", "dependencies": { "mime-db": ">= 1.43.0 < 2" }, @@ -7140,16 +7529,17 @@ } }, "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", + "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", + "license": "MIT", "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", + "bytes": "3.1.2", + "compressible": "~2.0.18", "debug": "2.6.9", + "negotiator": "~0.6.4", "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", + "safe-buffer": "5.2.1", "vary": "~1.1.2" }, "engines": { @@ -7160,6 +7550,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -7167,12 +7558,40 @@ "node_modules/compression/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.0.tgz", + "integrity": "sha512-IsB/fiXTupmagMW4MNp2lx2cdSN2FfZq78vF90LBB+zZHArbIQZjQtzXCiXnvTxCZSvXanTqFLWBjw2UkLx1SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } }, "node_modules/connect-flash": { "version": "0.1.1", @@ -7186,6 +7605,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/connect-pg-simple/-/connect-pg-simple-7.0.0.tgz", "integrity": "sha512-fbNZUkxz8m+FRbctoxAU18DzRKp8GQSL+9gTJ0+LgSCElXLon2q8tDE8V74jUzf+w2ARZX8HKKyV0laX1NUZ/Q==", + "license": "MIT", "dependencies": { "@types/pg": "^8.6.1", "pg": "^8.7.1" @@ -7197,12 +7617,14 @@ "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC" }, "node_modules/constantinople": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", + "license": "MIT", "dependencies": { "@babel/parser": "^7.6.0", "@babel/types": "^7.6.1" @@ -7212,6 +7634,7 @@ "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" }, @@ -7219,25 +7642,6 @@ "node": ">= 0.6" } }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", @@ -7247,17 +7651,12 @@ "node": ">= 0.6" } }, - "node_modules/continuable-cache": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", - "integrity": "sha512-TF30kpKhTH8AGCG3dut0rdd/19B7Z+qCnrMoBLpyQu/2drZdNrrpcjPEoJeSVsQM+8KmWG5O56oPDjSSUsuTyA==", - "dev": true - }, "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" }, "node_modules/cookie": { "version": "0.7.2", @@ -7284,15 +7683,17 @@ "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" }, "node_modules/core-js-compat": { - "version": "3.31.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.1.tgz", - "integrity": "sha512-wIDWd2s5/5aJSdpOJHfSibxNODxoGoWOBHt8JSPB41NOE94M7kuTPZCYLOlTtuoXTsBPKobpJ6T+y0SSy5L9SA==", + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.43.0.tgz", + "integrity": "sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.21.9" + "browserslist": "^4.25.0" }, "funding": { "type": "opencollective", @@ -7302,12 +7703,14 @@ "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", "dependencies": { "object-assign": "^4", "vary": "^1" @@ -7316,10 +7719,65 @@ "node": ">= 0.10" } }, + "node_modules/cpx2": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/cpx2/-/cpx2-8.0.0.tgz", + "integrity": "sha512-RxD9jrSVNSOmfcbiPlr3XnKbUKH9K1w2HCv0skczUKhsZTueiDBecxuaSAKQkYSLQaGVA4ZQJZlTj5hVNNEvKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debounce": "^2.0.0", + "debug": "^4.1.1", + "duplexer": "^0.1.1", + "fs-extra": "^11.1.0", + "glob": "^11.0.0", + "glob2base": "0.0.12", + "ignore": "^6.0.2", + "minimatch": "^10.0.1", + "p-map": "^7.0.0", + "resolve": "^1.12.0", + "safe-buffer": "^5.2.0", + "shell-quote": "^1.8.0", + "subarg": "^1.0.0" + }, + "bin": { + "cpx": "bin/index.js" + }, + "engines": { + "node": "^20.0.0 || >=22.0.0", + "npm": ">=10" + } + }, + "node_modules/cpx2/node_modules/fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/cpx2/node_modules/ignore": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", + "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", "bin": { "crc32": "bin/crc32.njs" }, @@ -7328,9 +7786,10 @@ } }, "node_modules/crc32-stream": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", - "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "license": "MIT", "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^3.4.0" @@ -7343,16 +7802,25 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cron": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/cron/-/cron-2.4.0.tgz", - "integrity": "sha512-Cx77ic1TyIAtUggr0oAhtS8MLzPBUqGNIvdDM7jE3oFIxfe8LXWI9q3iQN/H2CebAiMir53LQKWOhEKnzkJTAQ==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/cron/-/cron-2.4.4.tgz", + "integrity": "sha512-MHlPImXJj3K7x7lyUHjtKEOl69CSlTOWxS89jiFgNkzXfvhVjhMz/nc7/EIfN9vgooZp8XTtXJ1FREdmbyXOiQ==", + "license": "MIT", "dependencies": { - "luxon": "^3.2.1" + "@types/luxon": "~3.3.0", + "luxon": "~3.3.0" } }, + "node_modules/cron/node_modules/@types/luxon": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.8.tgz", + "integrity": "sha512-jYvz8UMLDgy3a5SkGJne8H7VA7zPV2Lwohjx0V8V31+SqAjNmurWMkk9cQhfvlcnXWudBpK9xPM1n4rljOcHYQ==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -7378,6 +7846,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/csrf/-/csrf-3.1.0.tgz", "integrity": "sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==", + "license": "MIT", "dependencies": { "rndm": "1.2.0", "tsscmp": "1.0.6", @@ -7387,11 +7856,21 @@ "node": ">= 0.8" } }, + "node_modules/csrf-sync": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/csrf-sync/-/csrf-sync-4.2.1.tgz", + "integrity": "sha512-+q9tlUSCi/kbwr1NYwn5+MeuNhwxz3wSv1yl42BgIWfIuErZ3HajRwzvZTkfiyIqt1PZT8lQSlffhSYjCneN7g==", + "license": "ISC", + "dependencies": { + "http-errors": "^2.0.0" + } + }, "node_modules/csurf": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/csurf/-/csurf-1.11.0.tgz", "integrity": "sha512-UCtehyEExKTxgiu8UHdGvHj4tnpE/Qctue03Giq5gPgMQ9cg/ciod5blZQ5a4uCEenNQjxyGuzygLdKUmee/bQ==", - "deprecated": "Please use another csrf package", + "deprecated": "This package is archived and no longer maintained. For support, visit https://github.com/expressjs/express/discussions", + "license": "MIT", "dependencies": { "cookie": "0.4.0", "cookie-signature": "1.0.6", @@ -7406,6 +7885,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7414,6 +7894,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7422,6 +7903,7 @@ "version": "1.7.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "license": "MIT", "dependencies": { "depd": "~1.1.2", "inherits": "2.0.4", @@ -7436,12 +7918,14 @@ "node_modules/csurf/node_modules/setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "license": "ISC" }, "node_modules/csurf/node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7450,30 +7934,37 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "license": "MIT", "engines": { "node": ">=0.6" } }, - "node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, + "node_modules/debounce": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-2.2.0.tgz", + "integrity": "sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==", "dev": true, + "license": "MIT", "engines": { - "node": "*" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/dayjs": { - "version": "1.11.9", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", - "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" - }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -7488,6 +7979,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", "dependencies": { "mimic-response": "^3.1.0" }, @@ -7502,12 +7994,14 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", "engines": { "node": ">=4.0.0" } @@ -7516,12 +8010,14 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -7530,6 +8026,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -7537,12 +8034,14 @@ "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT" }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -7561,15 +8060,16 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", - "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", "engines": { "node": ">=8" } @@ -7579,6 +8079,7 @@ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -7588,6 +8089,7 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } @@ -7597,6 +8099,7 @@ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", "dev": true, + "license": "MIT", "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } @@ -7606,6 +8109,7 @@ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, + "license": "MIT", "dependencies": { "path-type": "^4.0.0" }, @@ -7618,6 +8122,7 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, + "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, @@ -7635,6 +8140,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -7653,12 +8159,14 @@ "type": "github", "url": "https://github.com/sponsors/fb55" } - ] + ], + "license": "BSD-2-Clause" }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.3.0" }, @@ -7670,9 +8178,10 @@ } }, "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -7683,14 +8192,15 @@ } }, "node_modules/dotenv": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", - "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" + "url": "https://dotenvx.com" } }, "node_modules/dunder-proto": { @@ -7711,12 +8221,14 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "license": "BSD-3-Clause", "dependencies": { "readable-stream": "^2.0.2" } @@ -7725,6 +8237,7 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -7735,18 +8248,33 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/duplexer2/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" } @@ -7754,19 +8282,22 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.4.473", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.473.tgz", - "integrity": "sha512-aVfC8+440vGfl06l8HKKn8/PD5jRfSnLkTTD65EFvU46igbpQRri1gxSzW9/+TeUlwYzrXk1sw867T96zlyECA==", - "dev": true + "version": "1.5.178", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.178.tgz", + "integrity": "sha512-wObbz/ar3Bc6e4X5vf0iO8xTN8YAjN/tgiAOJLr7yjYFtP9wAjq8Mb5h0yn6kResir+VYx2DXBj9NNobs0ETSA==", + "dev": true, + "license": "ISC" }, "node_modules/emittery": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -7775,14 +8306,17 @@ } }, "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" }, "node_modules/enabled": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", @@ -7794,20 +8328,20 @@ } }, "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", "dependencies": { "once": "^1.4.0" } }, "node_modules/engine.io": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", - "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", "license": "MIT", "dependencies": { - "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", @@ -7831,10 +8365,28 @@ "node": ">=10.0.0" } }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -7842,20 +8394,12 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/error": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", - "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", - "dev": true, - "dependencies": { - "string-template": "~0.2.1" - } - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, + "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } @@ -7879,9 +8423,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -7890,12 +8434,28 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -7928,44 +8488,50 @@ } }, "node_modules/esbuild-envfile-plugin": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/esbuild-envfile-plugin/-/esbuild-envfile-plugin-1.0.5.tgz", - "integrity": "sha512-AT6mUTI4pbVodwLRYOrXYeXmFCAKO3SpRqAktS0IlKvpohTgvw/cYWUgkOXpB46BhMwhjwucBKO5UVejNg/DPg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/esbuild-envfile-plugin/-/esbuild-envfile-plugin-1.0.7.tgz", + "integrity": "sha512-Qy2AUafFBY4T/OsMTlMab9h0ozostIqbJ/ZCLZXei3pgbxow3nyZixV0JTlpolNMQ56/g0UbcvPSuOgUnt4esg==", "dev": true, + "license": "ISC", "dependencies": { - "dotenv": "16.0.3" + "dotenv": "16.4.5" } }, "node_modules/esbuild-envfile-plugin/node_modules/dotenv": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", - "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" } }, "node_modules/esbuild-node-externals": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/esbuild-node-externals/-/esbuild-node-externals-1.8.0.tgz", - "integrity": "sha512-pYslmT8Bl383UnfxzHQQRpCgBNIOwAzDaYheuIeI4CODxelsN/eQroVn5STDow5QOpRalMgWUR+R8LfSgUROcw==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/esbuild-node-externals/-/esbuild-node-externals-1.18.0.tgz", + "integrity": "sha512-suFVX3SzZlXrGIS9Yqx+ZaHL4w1p0e/j7dQbOM9zk8SfFpnAGnDplHUKXIf9kcPEAfZRL66JuYeVSVlsSEQ5Eg==", "dev": true, + "license": "MIT", "dependencies": { - "find-up": "^5.0.0", - "tslib": "^2.4.1" + "find-up": "^5.0.0" }, "engines": { "node": ">=12" }, "peerDependencies": { - "esbuild": "0.12 - 0.18" + "esbuild": "0.12 - 0.25" } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -7977,36 +8543,42 @@ "license": "MIT" }, "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", "engines": { - "node": ">=0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.45.0.tgz", - "integrity": "sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.1.0", - "@eslint/js": "8.44.0", - "@humanwhocodes/config-array": "^0.11.10", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.1", - "espree": "^9.6.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -8044,6 +8616,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-1.7.1.tgz", "integrity": "sha512-sMStceig8AFglhhT2LqlU5r+/fn9OwsA72O5bBuQVTssPCdQAOQzL+oMn/ZcpeUY6KcNfLJArgcrsSULNjYYdQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "safe-regex": "^2.1.1" } @@ -8053,6 +8626,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -8062,10 +8636,11 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -8073,72 +8648,12 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.1.tgz", - "integrity": "sha512-CvefSOsDdaYYvxChovdrPo/ZGt8d5lrJWleAc1diXRKhHGiTYEI26cvo8Kle/wGnsizoCJjK73FMg1/IkIwiNA==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -8155,6 +8670,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -8164,6 +8680,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -8172,10 +8689,11 @@ } }, "node_modules/eslint/node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -8186,25 +8704,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { - "has-flag": "^4.0.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=8" + "node": "*" } }, "node_modules/eslint/node_modules/type-fest": { @@ -8212,6 +8722,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -8224,6 +8735,7 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -8241,6 +8753,7 @@ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, + "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -8250,10 +8763,11 @@ } }, "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -8266,6 +8780,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -8275,6 +8790,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -8287,6 +8803,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -8296,6 +8813,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -8305,6 +8823,7 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -8318,12 +8837,6 @@ "node": ">= 0.6" } }, - "node_modules/eventemitter2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", - "integrity": "sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==", - "dev": true - }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -8334,14 +8847,15 @@ } }, "node_modules/exceljs": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.3.0.tgz", - "integrity": "sha512-hTAeo5b5TPvf8Z02I2sKIT4kSfCnOO2bCxYX8ABqODCdAjppI3gI9VYiGCQQYVcBaBSKlFDMKlAQRqC+kV9O8w==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.4.0.tgz", + "integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==", + "license": "MIT", "dependencies": { "archiver": "^5.0.0", "dayjs": "^1.8.34", "fast-csv": "^4.3.1", - "jszip": "^3.5.0", + "jszip": "^3.10.1", "readable-stream": "^3.6.0", "saxes": "^5.0.1", "tmp": "^0.2.0", @@ -8352,11 +8866,21 @@ "node": ">=8.3.0" } }, + "node_modules/exceljs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, + "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -8375,17 +8899,12 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/execa/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "ISC" }, "node_modules/exit": { "version": "0.1.2", @@ -8400,6 +8919,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", "engines": { "node": ">=6" } @@ -8408,7 +8928,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", - "dev": true, + "license": "MIT", "dependencies": { "homedir-polyfill": "^1.0.1" }, @@ -8421,6 +8941,7 @@ "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", "dev": true, + "license": "MIT", "dependencies": { "@jest/expect-utils": "^28.1.3", "jest-get-type": "^28.0.2", @@ -8479,11 +9000,12 @@ } }, "node_modules/express-rate-limit": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.8.0.tgz", - "integrity": "sha512-yVeDWczkh8qgo9INJB1tT4j7LFu+n6ei/oqSMsqpsUIGYjTM+gk+Q3wv19TMUdo8chvus8XohAuOhG7RYRM9ZQ==", + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.11.2.tgz", + "integrity": "sha512-a7uwwfNTh1U60ssiIkuLFWHt4hAC5yxlLGU2VP0X4YNlyEDZAqF4tK3GD3NSitVBrCQmQ0++0uOyFOgC2y4DDw==", + "license": "MIT", "engines": { - "node": ">= 14.0.0" + "node": ">= 14" }, "peerDependencies": { "express": "^4 || ^5" @@ -8518,6 +9040,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -8525,31 +9048,14 @@ "node_modules/express-session/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/express-session/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/express-validator": { "version": "6.15.0", "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.15.0.tgz", "integrity": "sha512-r05VYoBL3i2pswuehoFSy+uM8NBuVaY7avp5qrYjQBDzagx2Z5A77FZqPT8/gNLF3HopWkIzaTFaC4JysWXLqg==", + "license": "MIT", "dependencies": { "lodash": "^4.17.21", "validator": "^13.9.0" @@ -8571,6 +9077,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -8578,37 +9085,20 @@ "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "license": "MIT" }, "node_modules/fast-csv": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz", "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", + "license": "MIT", "dependencies": { "@fast-csv/format": "4.3.5", "@fast-csv/parse": "4.3.6" @@ -8621,24 +9111,27 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-fifo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" }, "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -8648,28 +9141,31 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-xml-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz", - "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", "funding": [ - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - }, { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" } ], + "license": "MIT", "dependencies": { "strnum": "^1.0.5" }, @@ -8678,31 +9174,21 @@ } }, "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "dev": true, + "license": "ISC", "dependencies": { "reusify": "^1.0.4" } }, - "node_modules/faye-websocket": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", - "integrity": "sha512-Xhj93RXbMSq8urNCUq4p9l0P6hnySJ/7YNRhYNug0bLOuii7pKO7xQFb5mx9xZXWCar88pLPb805PvUkwrLZpQ==", - "dev": true, - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "bser": "2.1.1" } @@ -8710,28 +9196,15 @@ "node_modules/fecha": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", - "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, + "license": "MIT", "dependencies": { "flat-cache": "^3.0.4" }, @@ -8739,22 +9212,16 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/file-sync-cmp": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz", - "integrity": "sha512-0k45oWBokCqh2MOexeYKpyqmGKG+8mQ2Wd8iawx+uWd/weWJQAZ6SoPybagdCI4xFisag8iAR77WPm4h3pTfxA==", - "dev": true - }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -8796,11 +9263,19 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/find-index": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz", + "integrity": "sha512-uJ5vWrfBKMcE6y2Z8834dwEZj9mNGxYa3t3I53OwFeuZ8D9oc2E5zcsrkuhX6h4iYrjhiv0T3szQmxlAV9uxDg==", + "dev": true, + "license": "MIT" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -8813,25 +9288,25 @@ } }, "node_modules/findup-sync": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", - "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==", - "dev": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz", + "integrity": "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==", + "license": "MIT", "dependencies": { "detect-file": "^1.0.0", - "is-glob": "^4.0.3", - "micromatch": "^4.0.4", + "is-glob": "^4.0.0", + "micromatch": "^4.0.2", "resolve-dir": "^1.0.1" }, "engines": { - "node": ">= 10.13.0" + "node": ">= 8" } }, "node_modules/fined": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", - "dev": true, + "license": "MIT", "dependencies": { "expand-tilde": "^2.0.2", "is-plain-object": "^2.0.3", @@ -8847,34 +9322,90 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", - "dev": true, + "license": "MIT", "engines": { "node": ">= 0.10" } }, "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, + "license": "MIT", "dependencies": { - "flatted": "^3.1.0", + "flatted": "^3.2.9", + "keyv": "^4.5.3", "rimraf": "^3.0.2" }, "engines": { "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/flat-cache/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/flat-cache/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/flat-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" }, "node_modules/fn.name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" }, "node_modules/follow-redirects": { "version": "1.15.9", @@ -8900,7 +9431,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -8909,7 +9440,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", - "dev": true, + "license": "MIT", "dependencies": { "for-in": "^1.0.1" }, @@ -8917,13 +9448,33 @@ "node": ">=0.10.0" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -8934,6 +9485,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -8950,13 +9502,15 @@ "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -8970,6 +9524,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -8981,6 +9536,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -8991,19 +9547,22 @@ "node_modules/fs-minipass/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -9016,6 +9575,8 @@ "version": "1.0.12", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", "dependencies": { "graceful-fs": "^4.1.2", "inherits": "~2.0.0", @@ -9026,10 +9587,44 @@ "node": ">=0.6" } }, + "node_modules/fstream/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fstream/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/fstream/node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", "dependencies": { "minimist": "^1.2.6" }, @@ -9041,6 +9636,8 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -9061,6 +9658,8 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.2", @@ -9076,22 +9675,37 @@ "node": ">=10" } }, - "node_modules/gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", - "dev": true, + "node_modules/gauge/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/gauge/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/gauge/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { - "globule": "^1.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">= 4.0.0" + "node": ">=8" } }, "node_modules/generic-pool": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "license": "MIT", "engines": { "node": ">= 4" } @@ -9101,6 +9715,7 @@ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -9110,26 +9725,27 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, "node_modules/get-intrinsic": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", - "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "dunder-proto": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", + "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "math-intrinsics": "^1.0.0" + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -9143,15 +9759,30 @@ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -9159,34 +9790,31 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/getobject": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/getobject/-/getobject-1.0.2.tgz", - "integrity": "sha512-2zblDBaFcb3rB4rF77XVnuINOE2h2k/OnqXAiy0IrTxUfV1iFp3la33oAQVY9pCpWU268WFYVt2t71hlMuLsOg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" }, "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", + "dev": true, + "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.0.3", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": "*" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -9197,6 +9825,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -9204,11 +9833,23 @@ "node": ">= 6" } }, + "node_modules/glob2base": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz", + "integrity": "sha512-ZyqlgowMbfj2NPjxaZZ/EtsXlOch28FRXgMd64vqZWk1bT9+wvSRLYD1om9M7QfQru51zJPAT17qXm4/zd+9QA==", + "dev": true, + "dependencies": { + "find-index": "^0.1.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, + "license": "MIT", "dependencies": { "global-prefix": "^1.0.1", "is-windows": "^1.0.1", @@ -9222,7 +9863,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", - "dev": true, + "license": "MIT", "dependencies": { "expand-tilde": "^2.0.2", "homedir-polyfill": "^1.0.1", @@ -9238,7 +9879,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -9251,6 +9892,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -9260,6 +9902,7 @@ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, + "license": "MIT", "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -9275,52 +9918,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globule": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", - "integrity": "sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==", - "dev": true, - "dependencies": { - "glob": "~7.1.1", - "lodash": "^4.17.21", - "minimatch": "~3.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/globule/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globule/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -9336,52 +9933,27 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/grunt": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.6.1.tgz", - "integrity": "sha512-/ABUy3gYWu5iBmrUSRBP97JLpQUm0GgVveDCp6t3yRNIoltIYw7rEj3g5y1o2PGPR2vfTRGa7WC/LZHLTXnEzA==", "dev": true, - "dependencies": { - "dateformat": "~4.6.2", - "eventemitter2": "~0.4.13", - "exit": "~0.1.2", - "findup-sync": "~5.0.0", - "glob": "~7.1.6", - "grunt-cli": "~1.4.3", - "grunt-known-options": "~2.0.0", - "grunt-legacy-log": "~3.0.0", - "grunt-legacy-util": "~2.0.1", - "iconv-lite": "~0.6.3", - "js-yaml": "~3.14.0", - "minimatch": "~3.0.4", - "nopt": "~3.0.6" - }, - "bin": { - "grunt": "bin/grunt" - }, - "engines": { - "node": ">=16" - } + "license": "MIT" }, "node_modules/grunt-cli": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.4.3.tgz", - "integrity": "sha512-9Dtx/AhVeB4LYzsViCjUQkd0Kw0McN2gYpdmGYKtE2a5Yt7v1Q+HYZVWhqXc/kGnxlMtqKDxSwotiGeFmkrCoQ==", - "dev": true, + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.5.0.tgz", + "integrity": "sha512-rILKAFoU0dzlf22SUfDtq2R1fosChXXlJM5j7wI6uoW8gwmXDXzbUvirlKZSYCdXl3LXFbR+8xyS+WFo+b6vlA==", + "license": "MIT", "dependencies": { "grunt-known-options": "~2.0.0", "interpret": "~1.1.0", "liftup": "~3.0.1", - "nopt": "~4.0.1", - "v8flags": "~3.2.0" + "nopt": "~5.0.0", + "v8flags": "^4.0.1" }, "bin": { "grunt": "bin/grunt" @@ -9390,702 +9962,23 @@ "node": ">=10" } }, - "node_modules/grunt-cli/node_modules/nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", - "dev": true, - "dependencies": { - "abbrev": "1", - "osenv": "^0.1.4" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/grunt-contrib-clean": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-2.0.1.tgz", - "integrity": "sha512-uRvnXfhiZt8akb/ZRDHJpQQtkkVkqc/opWO4Po/9ehC2hPxgptB9S6JHDC/Nxswo4CJSM0iFPT/Iym3cEMWzKA==", - "dev": true, - "dependencies": { - "async": "^3.2.3", - "rimraf": "^2.6.2" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "grunt": ">=0.4.5" - } - }, - "node_modules/grunt-contrib-clean/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/grunt-contrib-compress": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-compress/-/grunt-contrib-compress-2.0.0.tgz", - "integrity": "sha512-r/dAGx4qG+rmBFF4lb/hTktW2huGMGxkSLf9msh3PPtq0+cdQRQerZJ30UKevX3BLQsohwLzO0p1z/LrH6aKXQ==", - "dev": true, - "dependencies": { - "adm-zip": "^0.5.1", - "archiver": "^5.1.0", - "chalk": "^4.1.0", - "lodash": "^4.17.20", - "pretty-bytes": "^5.4.1", - "stream-buffers": "^3.0.2" - }, - "engines": { - "node": ">=10.16" - } - }, - "node_modules/grunt-contrib-compress/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/grunt-contrib-compress/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/grunt-contrib-compress/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/grunt-contrib-compress/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/grunt-contrib-compress/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt-contrib-compress/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt-contrib-copy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz", - "integrity": "sha512-gFRFUB0ZbLcjKb67Magz1yOHGBkyU6uL29hiEW1tdQ9gQt72NuMKIy/kS6dsCbV0cZ0maNCb0s6y+uT1FKU7jA==", - "dev": true, - "dependencies": { - "chalk": "^1.1.1", - "file-sync-cmp": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-copy/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-copy/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-copy/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-copy/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-copy/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/grunt-contrib-uglify": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/grunt-contrib-uglify/-/grunt-contrib-uglify-5.2.2.tgz", - "integrity": "sha512-ITxiWxrjjP+RZu/aJ5GLvdele+sxlznh+6fK9Qckio5ma8f7Iv8woZjRkGfafvpuygxNefOJNc+hfjjBayRn2Q==", - "dev": true, - "dependencies": { - "chalk": "^4.1.2", - "maxmin": "^3.0.0", - "uglify-js": "^3.16.1", - "uri-path": "^1.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/grunt-contrib-uglify/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/grunt-contrib-uglify/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/grunt-contrib-uglify/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/grunt-contrib-uglify/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/grunt-contrib-uglify/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt-contrib-uglify/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt-contrib-watch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz", - "integrity": "sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg==", - "dev": true, - "dependencies": { - "async": "^2.6.0", - "gaze": "^1.1.0", - "lodash": "^4.17.10", - "tiny-lr": "^1.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-watch/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dev": true, - "dependencies": { - "lodash": "^4.17.14" - } - }, "node_modules/grunt-known-options": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-2.0.0.tgz", "integrity": "sha512-GD7cTz0I4SAede1/+pAbmJRG44zFLPipVtdL9o3vqx9IEyb7b4/Y3s7r6ofI3CchR5GvYJ+8buCSioDv5dQLiA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-legacy-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz", - "integrity": "sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA==", - "dev": true, - "dependencies": { - "colors": "~1.1.2", - "grunt-legacy-log-utils": "~2.1.0", - "hooker": "~0.2.3", - "lodash": "~4.17.19" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/grunt-legacy-log-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz", - "integrity": "sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw==", - "dev": true, - "dependencies": { - "chalk": "~4.1.0", - "lodash": "~4.17.19" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/grunt-legacy-log-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/grunt-legacy-log-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/grunt-legacy-log-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/grunt-legacy-log-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/grunt-legacy-log-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt-legacy-log-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt-legacy-util": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-2.0.1.tgz", - "integrity": "sha512-2bQiD4fzXqX8rhNdXkAywCadeqiPiay0oQny77wA2F3WF4grPJXCvAcyoWUJV+po/b15glGkxuSiQCK299UC2w==", - "dev": true, - "dependencies": { - "async": "~3.2.0", - "exit": "~0.1.2", - "getobject": "~1.0.0", - "hooker": "~0.2.3", - "lodash": "~4.17.21", - "underscore.string": "~3.3.5", - "which": "~2.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/grunt-shell": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/grunt-shell/-/grunt-shell-4.0.0.tgz", - "integrity": "sha512-dHFy8VZDfWGYLTeNvIHze4PKXGvIlDWuN0UE7hUZstTQeiEyv1VmW1MaDYQ3X5tE3bCi3bEia1gGKH8z/f1czQ==", - "dev": true, - "dependencies": { - "chalk": "^3.0.0", - "npm-run-path": "^2.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - }, - "peerDependencies": { - "grunt": ">=1" - } - }, - "node_modules/grunt-shell/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/grunt-shell/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt-shell/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/grunt-shell/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/grunt-shell/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt-shell/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt-sync": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/grunt-sync/-/grunt-sync-0.8.2.tgz", - "integrity": "sha512-PB+xKI9YPyZn3NZQXpKHfZVlxHdf1L8GMl+Wi0mLhYypWuOdZPW2EzTmSuhhFbXjkb0aIOxvII1zdZZEl9zqGg==", - "dev": true, - "dependencies": { - "fs-extra": "^6.0.1", - "glob": "^7.0.5", - "md5-file": "^2.0.3" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/grunt-sync/node_modules/fs-extra": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", - "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "node_modules/grunt-sync/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/grunt-sync/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/grunt/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/grunt/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/grunt/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/grunt/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/grunt/node_modules/nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", - "dev": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/grunt/node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/gzip-size": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", - "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", - "dev": true, - "dependencies": { - "duplexer": "^0.1.1", - "pify": "^4.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/has-symbols": { @@ -10101,11 +9994,12 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -10117,7 +10011,8 @@ "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC" }, "node_modules/hasown": { "version": "2.0.2", @@ -10135,6 +10030,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/helmet/-/helmet-6.2.0.tgz", "integrity": "sha512-DWlwuXLLqbrIOltR6tFQXShj/+7Cyp0gLi6uAb8qMdFh/YBBFbKSgQ6nbXmScYd8emMctuthmgIa7tUfo9Rtyg==", + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -10150,7 +10046,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, + "license": "MIT", "dependencies": { "parse-passwd": "^1.0.0" }, @@ -10158,19 +10054,11 @@ "node": ">=0.10.0" } }, - "node_modules/hooker": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", - "integrity": "sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/hpp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/hpp/-/hpp-0.2.3.tgz", "integrity": "sha512-4zDZypjQcxK/8pfFNR7jaON7zEUpXZxz4viyFmqjb3kWNWAHsLEUmWXcdn25c5l76ISvnD6hbOGO97cXUI3Ryw==", + "license": "ISC", "dependencies": { "lodash": "^4.17.12", "type-is": "^1.6.12" @@ -10183,7 +10071,8 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/htmlparser2": { "version": "8.0.2", @@ -10196,6 +10085,7 @@ "url": "https://github.com/sponsors/fb55" } ], + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", @@ -10207,6 +10097,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -10218,12 +10109,6 @@ "node": ">= 0.8" } }, - "node_modules/http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", - "dev": true - }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -10237,25 +10122,17 @@ "node": ">= 14" } }, - "node_modules/http-proxy-agent/node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", "dependencies": { - "agent-base": "6", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/human-signals": { @@ -10263,6 +10140,7 @@ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=10.17.0" } @@ -10296,13 +10174,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -10310,13 +10190,15 @@ "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -10329,10 +10211,11 @@ } }, "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "dev": true, + "license": "MIT", "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" @@ -10352,6 +10235,7 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.19" } @@ -10360,6 +10244,8 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -10368,23 +10254,26 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" }, "node_modules/interpret": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", "integrity": "sha512-CLM8SNMDu7C5psFCn6Wg/tgpj/bKAg7hc2gWqcuR9OD5Ft9PhBpIu8PLicPeis+xDd6YX2ncI8MCA64I9tftIA==", - "dev": true + "license": "MIT" }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", "engines": { "node": ">= 0.10" } @@ -10393,7 +10282,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "dev": true, + "license": "MIT", "dependencies": { "is-relative": "^1.0.0", "is-windows": "^1.0.1" @@ -10406,13 +10295,15 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, + "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" }, @@ -10421,11 +10312,15 @@ } }, "node_modules/is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -10435,6 +10330,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", + "license": "MIT", "dependencies": { "acorn": "^7.1.1", "object-assign": "^4.1.1" @@ -10444,6 +10340,7 @@ "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -10455,7 +10352,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -10464,6 +10361,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", "engines": { "node": ">=8" } @@ -10473,6 +10371,7 @@ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -10481,7 +10380,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -10493,7 +10392,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -10504,6 +10402,7 @@ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -10512,7 +10411,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, + "license": "MIT", "dependencies": { "isobject": "^3.0.1" }, @@ -10523,15 +10422,19 @@ "node_modules/is-promise": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "license": "MIT" }, "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -10544,7 +10447,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "dev": true, + "license": "MIT", "dependencies": { "is-unc-path": "^1.0.0" }, @@ -10556,6 +10459,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", "engines": { "node": ">=8" }, @@ -10567,7 +10471,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "dev": true, + "license": "MIT", "dependencies": { "unc-path-regex": "^0.1.2" }, @@ -10579,7 +10483,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -10587,28 +10491,30 @@ "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "license": "ISC" }, "node_modules/isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=8" } @@ -10618,6 +10524,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -10634,6 +10541,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", @@ -10643,32 +10551,12 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-lib-report/node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, + "license": "MIT", "dependencies": { "semver": "^7.5.3" }, @@ -10680,13 +10568,11 @@ } }, "node_modules/istanbul-lib-report/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -10699,6 +10585,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -10706,17 +10593,12 @@ "node": ">=8" } }, - "node_modules/istanbul-lib-report/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/istanbul-lib-source-maps": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -10727,10 +10609,11 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -10739,16 +10622,34 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/javascript-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz", - "integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==" + "integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==", + "license": "MIT" }, "node_modules/jest": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/core": "^28.1.3", "@jest/types": "^28.1.3", @@ -10775,6 +10676,7 @@ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", "integrity": "sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==", "dev": true, + "license": "MIT", "dependencies": { "execa": "^5.0.0", "p-limit": "^3.1.0" @@ -10788,6 +10690,7 @@ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz", "integrity": "sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==", "dev": true, + "license": "MIT", "dependencies": { "@jest/environment": "^28.1.3", "@jest/expect": "^28.1.3", @@ -10813,81 +10716,12 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-circus/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-circus/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-cli": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", "integrity": "sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/core": "^28.1.3", "@jest/test-result": "^28.1.3", @@ -10917,81 +10751,12 @@ } } }, - "node_modules/jest-cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-cli/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-cli/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-config": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^28.1.3", @@ -11032,74 +10797,39 @@ } } }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-config/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { - "color-convert": "^2.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=8" + "node": "*" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jest-config/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-config/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-config/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-config/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "node": "*" } }, "node_modules/jest-diff": { @@ -11107,6 +10837,7 @@ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^28.1.1", @@ -11117,81 +10848,12 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-diff/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-docblock": { "version": "28.1.1", "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz", "integrity": "sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==", "dev": true, + "license": "MIT", "dependencies": { "detect-newline": "^3.0.0" }, @@ -11204,6 +10866,7 @@ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", "integrity": "sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^28.1.3", "chalk": "^4.0.0", @@ -11215,81 +10878,12 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-each/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-each/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-each/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-environment-node": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", "integrity": "sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==", "dev": true, + "license": "MIT", "dependencies": { "@jest/environment": "^28.1.3", "@jest/fake-timers": "^28.1.3", @@ -11307,6 +10901,7 @@ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", "dev": true, + "license": "MIT", "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } @@ -11316,6 +10911,7 @@ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^28.1.3", "@types/graceful-fs": "^4.1.3", @@ -11341,6 +10937,7 @@ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", "dev": true, + "license": "MIT", "dependencies": { "jest-get-type": "^28.0.2", "pretty-format": "^28.1.3" @@ -11354,6 +10951,7 @@ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.0.0", "jest-diff": "^28.1.3", @@ -11364,81 +10962,12 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-matcher-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-message-util": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^28.1.3", @@ -11454,81 +10983,12 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-message-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-message-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-mock": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^28.1.3", "@types/node": "*" @@ -11542,6 +11002,7 @@ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" }, @@ -11559,6 +11020,7 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", "dev": true, + "license": "MIT", "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } @@ -11568,6 +11030,7 @@ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz", "integrity": "sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", @@ -11588,6 +11051,7 @@ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz", "integrity": "sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==", "dev": true, + "license": "MIT", "dependencies": { "jest-regex-util": "^28.0.2", "jest-snapshot": "^28.1.3" @@ -11596,81 +11060,12 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-resolve/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-resolve/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-resolve/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-runner": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz", "integrity": "sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/console": "^28.1.3", "@jest/environment": "^28.1.3", @@ -11698,81 +11093,12 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-runner/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runner/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-runner/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-runtime": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/environment": "^28.1.3", "@jest/fake-timers": "^28.1.3", @@ -11801,74 +11127,39 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-runtime/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-runtime/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { - "color-convert": "^2.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=8" + "node": "*" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jest-runtime/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-runtime/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runtime/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-runtime/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "node": "*" } }, "node_modules/jest-snapshot": { @@ -11876,6 +11167,7 @@ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", @@ -11905,84 +11197,12 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-snapshot/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -11990,29 +11210,12 @@ "node": ">=10" } }, - "node_modules/jest-snapshot/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/jest-sonar-reporter": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/jest-sonar-reporter/-/jest-sonar-reporter-2.0.0.tgz", "integrity": "sha512-ZervDCgEX5gdUbdtWsjdipLN3bKJwpxbvhkYNXTAYvAckCihobSLr9OT/IuyNIRT1EZMDDwR6DroWtrq+IL64w==", "dev": true, + "license": "MIT", "dependencies": { "xml": "^1.0.1" }, @@ -12025,6 +11228,7 @@ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^28.1.3", "@types/node": "*", @@ -12037,81 +11241,12 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-validate": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", "integrity": "sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^28.1.3", "camelcase": "^6.2.0", @@ -12124,26 +11259,12 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -12151,66 +11272,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-validate/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-validate/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-validate/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-watcher": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", "dev": true, + "license": "MIT", "dependencies": { "@jest/test-result": "^28.1.3", "@jest/types": "^28.1.3", @@ -12225,81 +11292,12 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-watcher/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-watcher/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-watcher/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-worker": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -12309,30 +11307,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/js-stringify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", @@ -12343,13 +11317,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -12358,40 +11334,52 @@ } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, + "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -12404,6 +11392,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -12412,46 +11401,41 @@ } }, "node_modules/jsonschema": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz", - "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.5.0.tgz", + "integrity": "sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw==", + "license": "MIT", "engines": { "node": "*" } }, "node_modules/jsonwebtoken": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", - "integrity": "sha512-K8wx7eJ5TPvEjuiVSkv167EVboBDv9PZdDoF7BgeQnBLVvZWW9clr2PsQHVJDTKaEIH5JBIwHujGcHp7GgI2eg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", "dependencies": { "jws": "^3.2.2", - "lodash": "^4.17.21", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", "ms": "^2.1.1", - "semver": "^7.3.8" + "semver": "^7.5.4" }, "engines": { "node": ">=12", "npm": ">=6" } }, - "node_modules/jsonwebtoken/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/jsonwebtoken/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -12459,15 +11443,11 @@ "node": ">=10" } }, - "node_modules/jsonwebtoken/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/jstransformer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==", + "license": "MIT", "dependencies": { "is-promise": "^2.0.0", "promise": "^7.0.1" @@ -12477,6 +11457,7 @@ "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", @@ -12488,6 +11469,7 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -12498,20 +11480,28 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/jszip/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } }, "node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", "dependencies": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } @@ -12520,16 +11510,27 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -12539,6 +11540,7 @@ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -12546,12 +11548,14 @@ "node_modules/kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" }, "node_modules/lazystream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", "dependencies": { "readable-stream": "^2.0.5" }, @@ -12563,6 +11567,7 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -12573,10 +11578,17 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/lazystream/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } @@ -12586,6 +11598,7 @@ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -12595,6 +11608,7 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -12603,21 +11617,11 @@ "node": ">= 0.8.0" } }, - "node_modules/libpq": { - "version": "1.8.14", - "resolved": "https://registry.npmjs.org/libpq/-/libpq-1.8.14.tgz", - "integrity": "sha512-/DDvQCiXP0KBMZ31U2mmURKaxoKt9kNqqgrSO2RuBKS+OJjw5b7uHi5jFoV8zPAUa2TNtq2XfcWL1OWDEyjwlg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "bindings": "1.5.0", - "nan": "2.22.0" - } - }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", "dependencies": { "immediate": "~3.0.5" } @@ -12626,7 +11630,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/liftup/-/liftup-3.0.1.tgz", "integrity": "sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw==", - "dev": true, + "license": "MIT", "dependencies": { "extend": "^3.0.2", "findup-sync": "^4.0.0", @@ -12641,43 +11645,25 @@ "node": ">=10" } }, - "node_modules/liftup/node_modules/findup-sync": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz", - "integrity": "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==", - "dev": true, - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^4.0.2", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/listenercount": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", - "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==" - }, - "node_modules/livereload-js": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz", - "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==", - "dev": true + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", + "license": "ISC" }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -12691,114 +11677,169 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" }, "node_modules/lodash.difference": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==" + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "license": "MIT" }, "node_modules/lodash.escaperegexp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", - "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==" + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "license": "MIT" }, "node_modules/lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "license": "MIT" }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "dev": true + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "dev": true, + "license": "MIT" }, "node_modules/lodash.groupby": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", - "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==" + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" }, "node_modules/lodash.isfunction": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", - "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" }, "node_modules/lodash.isnil": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", - "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==" + "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" }, "node_modules/lodash.isundefined": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", - "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==" + "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==", + "license": "MIT" }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.mergewith": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" }, "node_modules/lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", - "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==" + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "license": "MIT" }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" }, "node_modules/logform": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.5.1.tgz", - "integrity": "sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", "dependencies": { - "@colors/colors": "1.5.0", + "@colors/colors": "1.6.0", "@types/triple-beam": "^1.3.2", "fecha": "^4.2.0", "ms": "^2.1.1", "safe-stable-stringify": "^2.3.1", "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" } }, "node_modules/lru-cache": { @@ -12806,14 +11847,16 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^3.0.2" } }, "node_modules/luxon": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", - "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz", + "integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==", + "license": "MIT", "engines": { "node": ">=12" } @@ -12822,6 +11865,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", "dependencies": { "semver": "^6.0.0" }, @@ -12836,13 +11880,14 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", - "dev": true, + "license": "MIT", "dependencies": { "kind-of": "^6.0.2" }, @@ -12855,6 +11900,7 @@ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "tmpl": "1.0.5" } @@ -12863,7 +11909,7 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -12877,104 +11923,11 @@ "node": ">= 0.4" } }, - "node_modules/maxmin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/maxmin/-/maxmin-3.0.0.tgz", - "integrity": "sha512-wcahMInmGtg/7c6a75fr21Ch/Ks1Tb+Jtoan5Ft4bAI0ZvJqyOw8kkM7e7p8hDSzY805vmxwHT50KcjGwKyJ0g==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "figures": "^3.2.0", - "gzip-size": "^5.1.1", - "pretty-bytes": "^5.3.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/maxmin/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/maxmin/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/maxmin/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/maxmin/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/maxmin/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/maxmin/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/md5-file": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-2.0.7.tgz", - "integrity": "sha512-kWAICpJv8fIY0Ka/90iOX9nCJ407Fgj82ceWwcxi2HvVkKGHRMS/Y4caqBaju5skNYXiQohGUjwGZ7rVgzUhRw==", - "dev": true - }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -12992,13 +11945,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -13007,6 +11962,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -13015,7 +11971,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -13038,9 +11993,10 @@ } }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -13049,6 +12005,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -13056,11 +12013,21 @@ "node": ">= 0.6" } }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -13069,6 +12036,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -13077,36 +12045,45 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": "*" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/minizlib": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -13119,6 +12096,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -13129,12 +12107,14 @@ "node_modules/minizlib/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" }, @@ -13145,20 +12125,23 @@ "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" }, "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", "engines": { "node": "*" } }, "node_modules/moment-timezone": { - "version": "0.5.43", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.43.tgz", - "integrity": "sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==", + "version": "0.5.48", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz", + "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==", + "license": "MIT", "dependencies": { "moment": "^2.29.4" }, @@ -13170,6 +12153,7 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "license": "MIT", "dependencies": { "basic-auth": "~2.0.1", "debug": "2.6.9", @@ -13185,6 +12169,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -13192,12 +12177,14 @@ "node_modules/morgan/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/morgan/node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -13206,20 +12193,21 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/nan": { - "version": "2.22.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", - "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==", + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz", + "integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==", "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", @@ -13235,43 +12223,49 @@ } }, "node_modules/napi-build-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", - "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/natural-compare-lite": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ncp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", "integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==", "dev": true, + "license": "MIT", "bin": { "ncp": "bin/ncp" } }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/node-abi": { - "version": "3.45.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.45.0.tgz", - "integrity": "sha512-iwXuFrMAcFVi/ZoZiqq8BzAdsLw9kxDfTC0HMyjXfSL/6CSDAGD5UmR7azrAgWV1zKYq7dUUMj4owusBWKLsiQ==", + "version": "3.75.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", + "integrity": "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==", + "license": "MIT", "dependencies": { "semver": "^7.3.5" }, @@ -13279,24 +12273,11 @@ "node": ">=10" } }, - "node_modules/node-abi/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-abi/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -13304,20 +12285,17 @@ "node": ">=10" } }, - "node_modules/node-abi/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/node-addon-api": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", - "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "license": "MIT" }, "node_modules/node-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -13337,13 +12315,15 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "dev": true + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" }, "node_modules/nodeman": { "version": "1.1.2", @@ -13365,6 +12345,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", "dependencies": { "abbrev": "1" }, @@ -13379,35 +12360,30 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, + "license": "MIT", "dependencies": { - "path-key": "^2.0.0" + "path-key": "^3.0.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/npmlog": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", "dependencies": { "are-we-there-yet": "^2.0.0", "console-control-strings": "^1.1.0", @@ -13416,22 +12392,24 @@ } }, "node_modules/oauth": { - "version": "0.9.15", - "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", - "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==" + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.2.tgz", + "integrity": "sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==", + "license": "MIT" }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -13444,7 +12422,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", - "dev": true, + "license": "MIT", "dependencies": { "array-each": "^1.0.1", "array-slice": "^1.0.0", @@ -13459,7 +12437,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", "integrity": "sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w==", - "dev": true, + "license": "MIT", "dependencies": { "for-own": "^1.0.0", "make-iterator": "^1.0.0" @@ -13472,7 +12450,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "dev": true, + "license": "MIT", "dependencies": { "isobject": "^3.0.1" }, @@ -13480,11 +12458,6 @@ "node": ">=0.10.0" } }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" - }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -13501,6 +12474,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -13509,6 +12483,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", "dependencies": { "wrappy": "1" } @@ -13517,6 +12492,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", "dependencies": { "fn.name": "1.x.x" } @@ -13526,6 +12502,7 @@ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, + "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" }, @@ -13541,58 +12518,33 @@ "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "license": "MIT", "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "dev": true, - "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -13608,6 +12560,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^3.0.2" }, @@ -13618,25 +12571,48 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-map": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -13648,7 +12624,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", - "dev": true, + "license": "MIT", "dependencies": { "is-absolute": "^1.0.0", "map-cache": "^0.2.0", @@ -13663,6 +12639,7 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -13680,7 +12657,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", - "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -13688,12 +12665,14 @@ "node_modules/parse-srcset": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", - "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==", + "license": "MIT" }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -13720,6 +12699,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/passport-google-oauth2/-/passport-google-oauth2-0.2.0.tgz", "integrity": "sha512-62EdPtbfVdc55nIXi0p1WOa/fFMM8v/M8uQGnbcXA4OexZWCnfsEi3wo2buag+Is5oqpuHzOtI64JpHk0Xi5RQ==", + "license": "MIT", "dependencies": { "passport-oauth2": "^1.1.2" } @@ -13728,6 +12708,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz", "integrity": "sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==", + "license": "MIT", "dependencies": { "passport-oauth2": "1.x.x" }, @@ -13747,12 +12728,13 @@ } }, "node_modules/passport-oauth2": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.7.0.tgz", - "integrity": "sha512-j2gf34szdTF2Onw3+76alNnaAExlUmHvkc7cL+cmaS5NzHzDP/BvFHJruueQ9XAeNOdpI+CH+PWid8RA7KCwAQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz", + "integrity": "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==", + "license": "MIT", "dependencies": { "base64url": "3.x.x", - "oauth": "0.9.x", + "oauth": "0.10.x", "passport-strategy": "1.x.x", "uid2": "0.0.x", "utils-merge": "1.x.x" @@ -13777,6 +12759,7 @@ "version": "0.12.7", "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "license": "MIT", "dependencies": { "process": "^0.11.1", "util": "^0.10.3" @@ -13787,6 +12770,7 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -13795,6 +12779,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -13804,6 +12789,7 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -13811,13 +12797,14 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" }, "node_modules/path-root": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", - "dev": true, + "license": "MIT", "dependencies": { "path-root-regex": "^0.1.0" }, @@ -13829,11 +12816,38 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", - "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", @@ -13845,6 +12859,7 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -13855,22 +12870,22 @@ "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" }, "node_modules/pg": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.14.1.tgz", - "integrity": "sha512-0TdbqfjwIun9Fm/r89oB7RFQ0bLgduAhiIqIXOsyKoiC/L54DbuAAzIEN/9Op0f1Po9X7iCPXGoa/Ah+2aI8Xw==", + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", + "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "license": "MIT", "dependencies": { - "pg-connection-string": "^2.7.0", - "pg-pool": "^3.8.0", - "pg-protocol": "^1.8.0", - "pg-types": "^2.1.0", - "pgpass": "1.x" + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", + "pg-types": "2.2.0", + "pgpass": "1.0.5" }, "engines": { - "node": ">= 8.0.0" + "node": ">= 16.0.0" }, "optionalDependencies": { - "pg-cloudflare": "^1.1.1" + "pg-cloudflare": "^1.2.7" }, "peerDependencies": { "pg-native": ">=3.0.1" @@ -13882,134 +12897,47 @@ } }, "node_modules/pg-cloudflare": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", - "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "license": "MIT", "optional": true }, "node_modules/pg-connection-string": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz", - "integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", "license": "MIT" }, "node_modules/pg-int8": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", "engines": { "node": ">=4.0.0" } }, - "node_modules/pg-native": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/pg-native/-/pg-native-3.3.0.tgz", - "integrity": "sha512-8GHZOx20B/wceRebDG2KK2KZbmDmwkoLvWz4X7BQIF1fjRLCNp48oHsEHSk1lTw36GFGMksLiJ3qZcmSAgVdYA==", - "license": "MIT", - "dependencies": { - "libpq": "1.8.14", - "pg-types": "^2.1.0" - } - }, - "node_modules/pg-native/node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "license": "MIT", - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pg-native/node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/pg-native/node_modules/postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pg-native/node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pg-native/node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "license": "MIT", - "dependencies": { - "xtend": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pg-numeric": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz", - "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==", - "engines": { - "node": ">=4" - } - }, "node_modules/pg-pool": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.8.0.tgz", - "integrity": "sha512-VBw3jiVm6ZOdLBTIcXLNdSotb6Iy3uOCwDGFAksZCXmi10nyRvnP2v3jl4d+IsLYRyXf6o9hIm/ZtUzlByNUdw==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", "license": "MIT", "peerDependencies": { "pg": ">=8.0" } }, "node_modules/pg-protocol": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.8.0.tgz", - "integrity": "sha512-jvuYlEkL03NRvOoyoRktBK7+qU5kOvlAwvmrH8sr3wbLrOdVWsRxQfz8mMy9sZFsqJ1hEWNfdWKI4SAmoL+j7g==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", "license": "MIT" }, "node_modules/pg-types": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.0.1.tgz", - "integrity": "sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g==", - "dependencies": { - "pg-int8": "1.0.1", - "pg-numeric": "1.0.2", - "postgres-array": "~3.0.1", - "postgres-bytea": "~3.0.0", - "postgres-date": "~2.0.1", - "postgres-interval": "^3.0.0", - "postgres-range": "^1.1.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/pg/node_modules/pg-types": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", @@ -14021,59 +12949,26 @@ "node": ">=4" } }, - "node_modules/pg/node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/pg/node_modules/postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pg/node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pg/node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "dependencies": { - "xtend": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/pgpass": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", "dependencies": { "split2": "^4.1.0" } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -14081,20 +12976,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6" } @@ -14104,6 +12991,7 @@ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, + "license": "MIT", "dependencies": { "find-up": "^4.0.0" }, @@ -14116,6 +13004,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -14129,6 +13018,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, @@ -14141,6 +13031,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, + "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, @@ -14156,6 +13047,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, @@ -14164,9 +13056,9 @@ } }, "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "funding": [ { "type": "opencollective", @@ -14181,66 +13073,67 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/postgres-array": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.2.tgz", - "integrity": "sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=4" } }, "node_modules/postgres-bytea": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz", - "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", - "dependencies": { - "obuf": "~1.1.2" - }, + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=0.10.0" } }, "node_modules/postgres-date": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.0.1.tgz", - "integrity": "sha512-YtMKdsDt5Ojv1wQRvUhnyDJNSr2dGIC96mQVKz7xufp07nfuFONzdaowrMHjlAzY6GDLd4f+LUHHAAM1h4MdUw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, "node_modules/postgres-interval": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz", - "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "node_modules/postgres-range": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.3.tgz", - "integrity": "sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==" - }, "node_modules/prebuild-install": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", - "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", + "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", @@ -14255,32 +13148,40 @@ "node": ">=10" } }, + "node_modules/prebuild-install/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/prebuild-install/node_modules/tar-fs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", + "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, - "node_modules/pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/pretty-format": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", "dev": true, + "license": "MIT", "dependencies": { "@jest/schemas": "^28.1.3", "ansi-regex": "^5.0.1", @@ -14296,6 +13197,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -14307,6 +13209,7 @@ "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", "engines": { "node": ">= 0.6.0" } @@ -14314,12 +13217,14 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" }, "node_modules/promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "license": "MIT", "dependencies": { "asap": "~2.0.3" } @@ -14329,6 +13234,7 @@ "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "dev": true, + "license": "MIT", "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" @@ -14341,6 +13247,7 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -14352,7 +13259,8 @@ "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" }, "node_modules/pug": { "version": "3.0.3", @@ -14407,6 +13315,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", + "license": "MIT", "dependencies": { "constantinople": "^4.0.1", "jstransformer": "1.0.0", @@ -14419,6 +13328,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", + "license": "MIT", "dependencies": { "character-parser": "^2.2.0", "is-expression": "^4.0.0", @@ -14429,6 +13339,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", + "license": "MIT", "dependencies": { "pug-error": "^2.0.0", "pug-walk": "^2.0.0" @@ -14438,6 +13349,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", + "license": "MIT", "dependencies": { "object-assign": "^4.1.1", "pug-walk": "^2.0.0" @@ -14447,6 +13359,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", + "license": "MIT", "dependencies": { "pug-error": "^2.0.0", "token-stream": "1.0.0" @@ -14462,6 +13375,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", + "license": "MIT", "dependencies": { "pug-error": "^2.0.0" } @@ -14469,22 +13383,25 @@ "node_modules/pug-walk": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", - "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==" + "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==", + "license": "MIT" }, "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -14522,17 +13439,14 @@ "type": "consulting", "url": "https://feross.org/support" } - ] - }, - "node_modules/queue-tick": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", - "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" + ], + "license": "MIT" }, "node_modules/random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -14561,19 +13475,11 @@ "node": ">= 0.8" } }, - "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -14588,20 +13494,23 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -14615,14 +13524,16 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", "dependencies": { "minimatch": "^5.1.0" } }, "node_modules/readdir-glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -14631,6 +13542,7 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -14643,6 +13555,7 @@ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, + "license": "MIT", "dependencies": { "picomatch": "^2.2.1" }, @@ -14654,7 +13567,7 @@ "version": "0.7.1", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", - "dev": true, + "license": "MIT", "dependencies": { "resolve": "^1.9.0" }, @@ -14663,29 +13576,35 @@ } }, "node_modules/redis": { - "version": "4.6.7", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.7.tgz", - "integrity": "sha512-KrkuNJNpCwRm5vFJh0tteMxW8SaUzkm5fBH7eL5hd/D0fAkzvapxbfGPP/r+4JAXdQuX7nebsBkBqA2RHB7Usw==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.1.tgz", + "integrity": "sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==", + "license": "MIT", + "workspaces": [ + "./packages/*" + ], "dependencies": { "@redis/bloom": "1.2.0", - "@redis/client": "1.5.8", - "@redis/graph": "1.1.0", - "@redis/json": "1.0.4", - "@redis/search": "1.1.3", - "@redis/time-series": "1.0.4" + "@redis/client": "1.6.1", + "@redis/graph": "1.1.1", + "@redis/json": "1.0.7", + "@redis/search": "1.2.0", + "@redis/time-series": "1.1.0" } }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", "dev": true, + "license": "MIT", "dependencies": { "regenerate": "^1.4.2" }, @@ -14693,40 +13612,27 @@ "node": ">=4" } }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true - }, - "node_modules/regenerator-transform": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", - "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, "node_modules/regexp-tree": { "version": "0.1.27", "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", "dev": true, + "license": "MIT", "bin": { "regexp-tree": "bin/regexp-tree" } }, "node_modules/regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/regjsgen": "^0.8.0", "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.1.0" }, @@ -14734,25 +13640,37 @@ "node": ">=4" } }, - "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "jsesc": "~0.5.0" + "jsesc": "~3.0.2" }, "bin": { "regjsparser": "bin/parser" } }, "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "dev": true, + "license": "MIT", "bin": { "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" } }, "node_modules/require-directory": { @@ -14760,22 +13678,27 @@ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", "dependencies": { - "is-core-module": "^2.11.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -14785,6 +13708,7 @@ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, + "license": "MIT", "dependencies": { "resolve-from": "^5.0.0" }, @@ -14797,6 +13721,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -14805,7 +13730,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", - "dev": true, + "license": "MIT", "dependencies": { "expand-tilde": "^2.0.0", "global-modules": "^1.0.0" @@ -14819,6 +13744,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -14828,29 +13754,37 @@ "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz", "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } }, "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, + "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" } }, "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "license": "ISC", "dependencies": { - "glob": "^7.1.3" + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" }, "bin": { - "rimraf": "bin.js" + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -14859,7 +13793,8 @@ "node_modules/rndm": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz", - "integrity": "sha512-fJhQQI5tLrQvYIYFpOnFinzv9dwmR7hRnUz1XqP3OJ1jIweTNOd6aTO4jwQSgcBSFUB+/KHJxuGneime+FdzOw==" + "integrity": "sha512-fJhQQI5tLrQvYIYFpOnFinzv9dwmR7hRnUz1XqP3OJ1jIweTNOd6aTO4jwQSgcBSFUB+/KHJxuGneime+FdzOw==", + "license": "MIT" }, "node_modules/run-parallel": { "version": "1.2.0", @@ -14880,34 +13815,56 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } }, - "node_modules/safe-json-parse": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", - "integrity": "sha512-o0JmTu17WGUaUOHa1l0FPGXKBfijbxK6qoHzlkihsDXxzBHvJcA7zgviKR92Xs841rX9pK16unfphLq0/KqX7A==", - "dev": true + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" }, "node_modules/safe-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", "integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==", "dev": true, + "license": "MIT", "dependencies": { "regexp-tree": "~0.1.1" } }, "node_modules/safe-stable-stringify": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", - "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", "engines": { "node": ">=10" } @@ -14915,12 +13872,13 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" }, "node_modules/sanitize-html": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.14.0.tgz", - "integrity": "sha512-CafX+IUPxZshXqqRaG9ZClSlfPVjSxI0td7n07hk8QO2oO+9JDnlcL8iM8TWeOXOIBFgIOx6zioTzM53AOMn3g==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.0.tgz", + "integrity": "sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA==", "license": "MIT", "dependencies": { "deepmerge": "^4.2.2", @@ -14931,21 +13889,11 @@ "postcss": "^8.3.11" } }, - "node_modules/sanitize-html/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/sanitize-html/node_modules/is-plain-object": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -14954,6 +13902,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "license": "ISC", "dependencies": { "xmlchars": "^2.2.0" }, @@ -14966,6 +13915,7 @@ "resolved": "https://registry.npmjs.org/segfault-handler/-/segfault-handler-1.3.0.tgz", "integrity": "sha512-p7kVHo+4uoYkr0jmIiTBthwV5L2qmWtben/KDunDZ834mbos+tY+iO0//HpAJpOFSQZZ+wxKWuRo4DxV02B7Lg==", "hasInstallScript": true, + "license": "BSD-3-Clause", "dependencies": { "bindings": "^1.2.1", "nan": "^2.14.0" @@ -14975,6 +13925,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -15027,12 +13978,6 @@ "node": ">= 0.8" } }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", @@ -15051,23 +13996,27 @@ "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" }, "node_modules/sharp": { "version": "0.32.6", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.2", @@ -15085,29 +14034,17 @@ "url": "https://opencollective.com/libvips" } }, - "node_modules/sharp/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/sharp/node_modules/node-addon-api": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", - "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==" + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "license": "MIT" }, "node_modules/sharp/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -15115,36 +14052,12 @@ "node": ">=10" } }, - "node_modules/sharp/node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dependencies": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - }, - "node_modules/sharp/node_modules/tar-stream": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", - "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", - "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, - "node_modules/sharp/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -15157,10 +14070,24 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -15234,9 +14161,17 @@ } }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/simple-concat": { "version": "1.0.1", @@ -15255,7 +14190,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/simple-get": { "version": "4.0.1", @@ -15275,6 +14211,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", @@ -15285,6 +14222,7 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", "dependencies": { "is-arrayish": "^0.3.1" } @@ -15292,19 +14230,22 @@ "node_modules/simple-swizzle/node_modules/is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -15313,6 +14254,7 @@ "version": "1.6.6", "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==", + "license": "MIT", "engines": { "node": ">=8.0.0" } @@ -15345,10 +14287,28 @@ "ws": "~8.17.1" } }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/socket.io-parser": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" @@ -15357,19 +14317,55 @@ "node": ">=10.0.0" } }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -15379,6 +14375,7 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -15388,20 +14385,23 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", "engines": { "node": ">= 10.x" } }, "node_modules/sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", - "dev": true + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", "engines": { "node": "*" } @@ -15411,6 +14411,7 @@ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, + "license": "MIT", "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -15423,6 +14424,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -15431,60 +14433,39 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/stream-buffers": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz", - "integrity": "sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==", - "dev": true, - "engines": { - "node": ">= 0.10.0" - } - }, "node_modules/streamx": { - "version": "2.15.5", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.5.tgz", - "integrity": "sha512-9thPGMkKC2GctCzyCUjME3yR03x2xNo0GPKGkRw2UMYN+gqWa9uqpyNWhmsNCutU5zHmkUum0LsCRQTXUgUCAg==", + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", + "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "license": "MIT", "dependencies": { - "fast-fifo": "^1.1.0", - "queue-tick": "^1.0.1" + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" } }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" } }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, + "license": "MIT", "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" @@ -15493,16 +14474,31 @@ "node": ">=10" } }, - "node_modules/string-template": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", - "integrity": "sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==", - "dev": true - }, "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -15512,10 +14508,61 @@ "node": ">=8" } }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -15528,6 +14575,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -15537,6 +14585,7 @@ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -15546,6 +14595,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -15554,20 +14604,41 @@ } }, "node_modules/strnum": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/subarg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "integrity": "sha512-RIrIdRY0X1xojthNcVtgT9sjpOGagEUKpZdgBUi054OEPFo282yg+zE+t1Rj3+RqKq2xStL7uUHhY+AjbC4BXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.1.0" + } }, "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/supports-hyperlinks": { @@ -15575,6 +14646,7 @@ "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" @@ -15583,20 +14655,12 @@ "node": ">=8" } }, - "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/supports-hyperlinks/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -15608,6 +14672,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -15620,6 +14685,7 @@ "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", "dev": true, + "license": "MIT", "dependencies": { "commander": "6.2.0", "doctrine": "3.0.0", @@ -15639,7 +14705,9 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -15655,11 +14723,25 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/swagger-jsdoc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/swagger-parser": { "version": "10.0.3", "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", "dev": true, + "license": "MIT", "dependencies": { "@apidevtools/swagger-parser": "10.0.3" }, @@ -15685,25 +14767,35 @@ } }, "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz", + "integrity": "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==", + "license": "MIT", "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" } }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + "node_modules/tar-fs/node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -15715,16 +14807,27 @@ "node": ">=6" } }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, "node_modules/tar/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" }, "node_modules/terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-escapes": "^4.2.1", "supports-hyperlinks": "^2.0.0" @@ -15736,11 +14839,49 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/terser": { + "version": "5.43.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", + "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.14.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, + "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -15750,70 +14891,89 @@ "node": ">=8" } }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/tiny-lr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz", - "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==", "dev": true, - "dependencies": { - "body": "^5.1.0", - "debug": "^3.1.0", - "faye-websocket": "~0.10.0", - "livereload-js": "^2.3.0", - "object-assign": "^4.1.0", - "qs": "^6.4.0" - } + "license": "MIT" }, - "node_modules/tiny-lr/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } + "node_modules/tinymce": { + "version": "7.9.1", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-7.9.1.tgz", + "integrity": "sha512-zaOHwmiP1EqTeLRXAvVriDb00JYnfEjWGPdKEuac7MiZJ5aiDMZ4Unc98Gmajn+PBljOmO1GKV6G0KwWn3+k8A==", + "license": "GPL-2.0-or-later" }, "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dependencies": { - "rimraf": "^3.0.0" - }, + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "license": "MIT", "engines": { - "node": ">=8.17.0" + "node": ">=14.14" } }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "engines": { - "node": ">=4" - } + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -15826,6 +14986,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } @@ -15833,25 +14994,39 @@ "node_modules/token-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", - "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==" + "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==", + "license": "MIT" }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" }, "node_modules/traverse": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "license": "MIT/X11", "engines": { "node": "*" } }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", "engines": { "node": ">= 14.0.0" } @@ -15861,6 +15036,7 @@ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.8.tgz", "integrity": "sha512-5FaG0lXmRPzApix8oFG8RKjAz4ehtm8yMKOTy5HX3fY6W8kmvOrmcY0hKDElW52FJov+clhUbrKAqofnj4mXTg==", "dev": true, + "license": "MIT", "dependencies": { "bs-logger": "0.x", "fast-json-stable-stringify": "2.x", @@ -15899,26 +15075,12 @@ } } }, - "node_modules/ts-jest/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/ts-jest/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -15926,17 +15088,12 @@ "node": ">=10" } }, - "node_modules/ts-jest/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, + "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -15987,6 +15144,7 @@ "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", "dev": true, + "license": "Apache-2.0", "dependencies": { "@babel/code-frame": "^7.0.0", "builtin-modules": "^1.1.1", @@ -16012,26 +15170,116 @@ "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" } }, + "node_modules/tslint/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/tslint/node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, + "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" } }, + "node_modules/tslint/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslint/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/tslint/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, "node_modules/tslint/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/tslint/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/tslint/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tslint/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } }, "node_modules/tslint/node_modules/js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -16040,11 +15288,25 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/tslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/tslint/node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, + "license": "MIT", "dependencies": { "minimist": "^1.2.6" }, @@ -16057,27 +15319,37 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver" } }, - "node_modules/tslint/node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true + "node_modules/tslint/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } }, "node_modules/tslint/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "dev": true, + "license": "0BSD" }, "node_modules/tslint/node_modules/tsutils": { "version": "2.29.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^1.8.1" }, @@ -16089,6 +15361,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "license": "MIT", "engines": { "node": ">=0.6.x" } @@ -16098,6 +15371,7 @@ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^1.8.1" }, @@ -16112,12 +15386,14 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "dev": true, + "license": "0BSD" }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" }, @@ -16130,6 +15406,7 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -16142,6 +15419,7 @@ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -16151,6 +15429,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -16162,6 +15441,7 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -16175,6 +15455,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16184,9 +15465,10 @@ } }, "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "license": "BSD-2-Clause", "bin": { "uglifyjs": "bin/uglifyjs" }, @@ -16198,6 +15480,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "license": "MIT", "dependencies": { "random-bytes": "~1.0.0" }, @@ -16208,35 +15491,30 @@ "node_modules/uid2": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", - "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==" + "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==", + "license": "MIT" }, "node_modules/unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", - "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/underscore.string": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.6.tgz", - "integrity": "sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==", - "dev": true, - "dependencies": { - "sprintf-js": "^1.1.1", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": "*" - } + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -16246,6 +15524,7 @@ "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", "dev": true, + "license": "MIT", "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" @@ -16255,10 +15534,11 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -16268,15 +15548,17 @@ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10.0.0" } @@ -16294,6 +15576,7 @@ "version": "0.10.14", "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "license": "MIT", "dependencies": { "big-integer": "^1.6.17", "binary": "~0.3.0", @@ -16310,12 +15593,14 @@ "node_modules/unzipper/node_modules/bluebird": { "version": "3.4.7", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", - "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==" + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "license": "MIT" }, "node_modules/unzipper/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -16326,18 +15611,25 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/unzipper/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/unzipper/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } }, "node_modules/update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "funding": [ { @@ -16353,9 +15645,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -16369,23 +15662,16 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } }, - "node_modules/uri-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/uri-path/-/uri-path-1.0.0.tgz", - "integrity": "sha512-8pMuAn4KacYdGMkFaoQARicp4HSw24/DHOVKWqVRJ8LhhAwPPFpdGvdL9184JVmUwe7vz7Z9n6IqI6t5n2ELdg==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/util": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "license": "MIT", "dependencies": { "inherits": "2.0.3" } @@ -16393,25 +15679,33 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" }, "node_modules/util/node_modules/inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", "engines": { "node": ">= 0.4.0" } }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } @@ -16420,38 +15714,38 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/v8-to-istanbul": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", - "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, + "license": "ISC", "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" + "convert-source-map": "^2.0.0" }, "engines": { "node": ">=10.12.0" } }, "node_modules/v8flags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", - "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-4.0.1.tgz", + "integrity": "sha512-fcRLaS4H/hrZk9hYwbdRM35D0U8IYMfEClhXxCivOojl+yTRAZH3Zy2sSy6qVCiGbV9YAtPssP6jaChqC9vPCg==", + "license": "MIT", "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" } }, "node_modules/validator": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz", - "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==", + "version": "13.15.15", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz", + "integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==", + "license": "MIT", "engines": { "node": ">= 0.10" } @@ -16460,6 +15754,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -16478,6 +15773,7 @@ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "makeerror": "1.0.12" } @@ -16485,35 +15781,14 @@ "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -16524,6 +15799,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -16538,42 +15814,65 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, - "node_modules/winston": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.10.0.tgz", - "integrity": "sha512-nT6SIDaE9B7ZRO0u3UvdrimG0HkB7dSTAgInQnNR2SOPJ4bvq5q79+pXLftKmP52lJGW15+H5MCK0nM9D3KB/g==", + "node_modules/wide-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { - "@colors/colors": "1.5.0", + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/winston": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", "async": "^3.2.3", "is-stream": "^2.0.0", - "logform": "^2.4.0", + "logform": "^2.7.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", - "winston-transport": "^4.5.0" + "winston-transport": "^4.9.0" }, "engines": { "node": ">= 12.0.0" } }, "node_modules/winston-transport": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", - "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", "dependencies": { - "logform": "^2.3.2", - "readable-stream": "^3.6.0", + "logform": "^2.7.0", + "readable-stream": "^3.6.2", "triple-beam": "^1.3.0" }, "engines": { - "node": ">= 6.4.0" + "node": ">= 12.0.0" } }, "node_modules/with": { @@ -16591,11 +15890,45 @@ "node": ">= 10.0.0" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/worklenz-backend": { + "resolved": "", + "link": true + }, "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -16608,49 +15941,82 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=7.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" }, "node_modules/write-file-atomic": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, + "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" @@ -16659,6 +16025,13 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, "node_modules/ws": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", @@ -16684,12 +16057,14 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" }, "node_modules/xss-filters": { "version": "1.2.7", @@ -16700,6 +16075,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", "engines": { "node": ">=0.4" } @@ -16709,6 +16085,7 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } @@ -16717,13 +16094,15 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/yaml": { "version": "2.0.0-1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", "dev": true, + "license": "ISC", "engines": { "node": ">= 6" } @@ -16733,6 +16112,7 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, + "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -16751,15 +16131,39 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -16769,6 +16173,7 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -16781,6 +16186,7 @@ "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", "dev": true, + "license": "MIT", "dependencies": { "lodash.get": "^4.4.2", "lodash.isequal": "^4.5.0", @@ -16801,23 +16207,79 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "dev": true, + "license": "MIT", "optional": true, "engines": { "node": "^12.20.0 || >=14" } }, "node_modules/zip-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", - "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "license": "MIT", "dependencies": { - "archiver-utils": "^2.1.0", - "compress-commons": "^4.1.0", + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", "readable-stream": "^3.6.0" }, "engines": { "node": ">= 10" } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "license": "MIT", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/zip-stream/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } } } } diff --git a/worklenz-backend/package.json b/worklenz-backend/package.json index f3faaaec..5413ddf2 100644 --- a/worklenz-backend/package.json +++ b/worklenz-backend/package.json @@ -11,16 +11,30 @@ "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", @@ -53,6 +68,7 @@ "express-rate-limit": "^6.8.0", "express-session": "^1.17.3", "express-validator": "^6.15.0", + "grunt-cli": "^1.5.0", "helmet": "^6.2.0", "hpp": "^0.2.3", "http-errors": "^2.0.0", @@ -70,7 +86,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 +93,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 +104,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 +139,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", diff --git a/worklenz-backend/scripts/compress.js b/worklenz-backend/scripts/compress.js new file mode 100644 index 00000000..6a946163 --- /dev/null +++ b/worklenz-backend/scripts/compress.js @@ -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(); \ No newline at end of file diff --git a/worklenz-backend/src/app.ts b/worklenz-backend/src/app.ts index 4da00a0e..68f18af3 100644 --- a/worklenz-backend/src/app.ts +++ b/worklenz-backend/src/app.ts @@ -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", diff --git a/worklenz-backend/src/controllers/admin-center-controller.ts b/worklenz-backend/src/controllers/admin-center-controller.ts index 3c50c858..be334aff 100644 --- a/worklenz-backend/src/controllers/admin-center-controller.ts +++ b/worklenz-backend/src/controllers/admin-center-controller.ts @@ -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() diff --git a/worklenz-backend/src/controllers/auth-controller.ts b/worklenz-backend/src/controllers/auth-controller.ts index 8364d59c..4fea4f59 100644 --- a/worklenz-backend/src/controllers/auth-controller.ts +++ b/worklenz-backend/src/controllers/auth-controller.ts @@ -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(); diff --git a/worklenz-backend/src/controllers/project-insights-controller.ts b/worklenz-backend/src/controllers/project-insights-controller.ts index aeb3707d..6e8413dc 100644 --- a/worklenz-backend/src/controllers/project-insights-controller.ts +++ b/worklenz-backend/src/controllers/project-insights-controller.ts @@ -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 diff --git a/worklenz-backend/src/controllers/projects-controller.ts b/worklenz-backend/src/controllers/projects-controller.ts index e739bfb1..a350675e 100644 --- a/worklenz-backend/src/controllers/projects-controller.ts +++ b/worklenz-backend/src/controllers/projects-controller.ts @@ -408,6 +408,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 +756,186 @@ export default class ProjectsController extends WorklenzControllerBase { } + @HandleExceptions() + public static async getGrouped(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + // 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: [] })); + } + } diff --git a/worklenz-backend/src/controllers/reporting/reporting-allocation-controller.ts b/worklenz-backend/src/controllers/reporting/reporting-allocation-controller.ts index be79c4b8..4db8e3d5 100644 --- a/worklenz-backend/src/controllers/reporting/reporting-allocation-controller.ts +++ b/worklenz-backend/src/controllers/reporting/reporting-allocation-controller.ts @@ -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 = {}; + 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)); diff --git a/worklenz-backend/src/controllers/task-phases-controller.ts b/worklenz-backend/src/controllers/task-phases-controller.ts index e72fbbab..163ff250 100644 --- a/worklenz-backend/src/controllers/task-phases-controller.ts +++ b/worklenz-backend/src/controllers/task-phases-controller.ts @@ -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; diff --git a/worklenz-backend/src/controllers/task-statuses-controller.ts b/worklenz-backend/src/controllers/task-statuses-controller.ts index dbefe0dd..a20e0d7a 100644 --- a/worklenz-backend/src/controllers/task-statuses-controller.ts +++ b/worklenz-backend/src/controllers/task-statuses-controller.ts @@ -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 { + 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 { const q = `SELECT update_status_order($1);`; diff --git a/worklenz-backend/src/controllers/tasks-controller-base.ts b/worklenz-backend/src/controllers/tasks-controller-base.ts index 293ae3d8..58558c1e 100644 --- a/worklenz-backend/src/controllers/tasks-controller-base.ts +++ b/worklenz-backend/src/controllers/tasks-controller-base.ts @@ -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 = { @@ -32,10 +32,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 +109,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; diff --git a/worklenz-backend/src/controllers/tasks-controller-v2.ts b/worklenz-backend/src/controllers/tasks-controller-v2.ts index 3e1290f1..d941f824 100644 --- a/worklenz-backend/src/controllers/tasks-controller-v2.ts +++ b/worklenz-backend/src/controllers/tasks-controller-v2.ts @@ -97,15 +97,19 @@ export default class TasksControllerV2 extends TasksControllerBase { try { const result = await db.query("SELECT get_task_complete_ratio($1) AS info;", [taskId]); const [data] = result.rows; - data.info.ratio = +data.info.ratio.toFixed(); - return data.info; + if (data && data.info && data.info.ratio !== undefined) { + data.info.ratio = +((data.info.ratio || 0).toFixed()); + return data.info; + } + return null; } catch (error) { + log_error(`Error in getTaskCompleteRatio: ${error}`); return null; } } private static getQuery(userId: string, options: ParsedQs) { - const searchField = options.search ? "t.name" : "sort_order"; + const searchField = options.search ? ["t.name", "CONCAT((SELECT key FROM projects WHERE id = t.project_id), '-', task_no)"] : "sort_order"; const { searchQuery, sortField } = TasksControllerV2.toPaginationOptions(options, searchField); const isSubTasks = !!options.parent_task; @@ -126,20 +130,20 @@ export default class TasksControllerV2 extends TasksControllerBase { const filterByAssignee = TasksControllerV2.getFilterByAssignee(options.filterBy as string); // Returns statuses of each task as a json array if filterBy === "member" const statusesQuery = TasksControllerV2.getStatusesQuery(options.filterBy as string); - + // Custom columns data query - const customColumnsQuery = options.customColumns + const customColumnsQuery = options.customColumns ? `, (SELECT COALESCE( jsonb_object_agg( - custom_cols.key, + custom_cols.key, custom_cols.value - ), + ), '{}'::JSONB ) FROM ( - SELECT + SELECT cc.key, - CASE + CASE WHEN ccv.text_value IS NOT NULL THEN to_jsonb(ccv.text_value) WHEN ccv.number_value IS NOT NULL THEN to_jsonb(ccv.number_value) WHEN ccv.boolean_value IS NOT NULL THEN to_jsonb(ccv.boolean_value) @@ -192,6 +196,13 @@ export default class TasksControllerV2 extends TasksControllerBase { t.archived, t.description, t.sort_order, + t.progress_value, + t.manual_progress, + t.weight, + (SELECT use_manual_progress FROM projects WHERE id = t.project_id) AS project_use_manual_progress, + (SELECT use_weighted_progress FROM projects WHERE id = t.project_id) AS project_use_weighted_progress, + (SELECT use_time_progress FROM projects WHERE id = t.project_id) AS project_use_time_progress, + (SELECT get_task_complete_ratio(t.id)->>'ratio') AS complete_ratio, (SELECT phase_id FROM task_phase WHERE task_id = t.id) AS phase_id, (SELECT name @@ -315,9 +326,23 @@ export default class TasksControllerV2 extends TasksControllerBase { @HandleExceptions() public static async getList(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const startTime = performance.now(); + console.log(`[PERFORMANCE] getList method called for project ${req.params.id} - THIS METHOD IS DEPRECATED, USE getTasksV3 INSTEAD`); + + // PERFORMANCE OPTIMIZATION: Skip expensive progress calculation by default + // Progress values are already calculated and stored in the database + // Only refresh if explicitly requested via refresh_progress=true query parameter + if (req.query.refresh_progress === "true" && req.params.id) { + console.log(`[PERFORMANCE] Starting progress refresh for project ${req.params.id} (getList)`); + const progressStartTime = performance.now(); + await this.refreshProjectTaskProgressValues(req.params.id); + const progressEndTime = performance.now(); + console.log(`[PERFORMANCE] Progress refresh completed in ${(progressEndTime - progressStartTime).toFixed(2)}ms`); + } + const isSubTasks = !!req.query.parent_task; const groupBy = (req.query.group || GroupBy.STATUS) as string; - + // Add customColumns flag to query params req.query.customColumns = "true"; @@ -334,7 +359,7 @@ export default class TasksControllerV2 extends TasksControllerBase { return g; }, {}); - this.updateMapByGroup(tasks, groupBy, map); + await this.updateMapByGroup(tasks, groupBy, map); const updatedGroups = Object.keys(map).map(key => { const group = map[key]; @@ -350,15 +375,31 @@ export default class TasksControllerV2 extends TasksControllerBase { }; }); + const endTime = performance.now(); + const totalTime = endTime - startTime; + console.log(`[PERFORMANCE] getList method completed in ${totalTime.toFixed(2)}ms for project ${req.params.id} with ${tasks.length} tasks`); + + // Log warning if this deprecated method is taking too long + if (totalTime > 1000) { + console.warn(`[PERFORMANCE WARNING] DEPRECATED getList method taking ${totalTime.toFixed(2)}ms - Frontend should use getTasksV3 instead!`); + } + return res.status(200).send(new ServerResponse(true, updatedGroups)); } - public static updateMapByGroup(tasks: any[], groupBy: string, map: { [p: string]: ITaskGroup }) { + public static async updateMapByGroup(tasks: any[], groupBy: string, map: { [p: string]: ITaskGroup }) { let index = 0; const unmapped = []; + + // PERFORMANCE OPTIMIZATION: Remove expensive individual DB calls for each task + // Progress values are already calculated and included in the main query + // No need to make additional database calls here + + // Process tasks with their already-calculated progress values for (const task of tasks) { task.index = index++; TasksControllerV2.updateTaskViewModel(task); + if (groupBy === GroupBy.STATUS) { map[task.status]?.tasks.push(task); } else if (groupBy === GroupBy.PRIORITY) { @@ -394,11 +435,25 @@ export default class TasksControllerV2 extends TasksControllerBase { @HandleExceptions() public static async getTasksOnly(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const startTime = performance.now(); + console.log(`[PERFORMANCE] getTasksOnly method called for project ${req.params.id} - Consider using getTasksV3 for better performance`); + + // PERFORMANCE OPTIMIZATION: Skip expensive progress calculation by default + // Progress values are already calculated and stored in the database + // Only refresh if explicitly requested via refresh_progress=true query parameter + if (req.query.refresh_progress === "true" && req.params.id) { + console.log(`[PERFORMANCE] Starting progress refresh for project ${req.params.id} (getTasksOnly)`); + const progressStartTime = performance.now(); + await this.refreshProjectTaskProgressValues(req.params.id); + const progressEndTime = performance.now(); + console.log(`[PERFORMANCE] Progress refresh completed in ${(progressEndTime - progressStartTime).toFixed(2)}ms`); + } + const isSubTasks = !!req.query.parent_task; - + // Add customColumns flag to query params req.query.customColumns = "true"; - + const q = TasksControllerV2.getQuery(req.user?.id as string, req.query); const params = isSubTasks ? [req.params.id || null, req.query.parent_task] : [req.params.id || null]; const result = await db.query(q, params); @@ -410,11 +465,25 @@ export default class TasksControllerV2 extends TasksControllerBase { [data] = result.rows; } else { // else we return a flat list of tasks data = [...result.rows]; + + // PERFORMANCE OPTIMIZATION: Remove expensive individual DB calls for each task + // Progress values are already calculated and included in the main query via get_task_complete_ratio + // The database query already includes complete_ratio, so no need for additional calls + for (const task of data) { TasksControllerV2.updateTaskViewModel(task); } } + const endTime = performance.now(); + const totalTime = endTime - startTime; + console.log(`[PERFORMANCE] getTasksOnly method completed in ${totalTime.toFixed(2)}ms for project ${req.params.id} with ${data.length} tasks`); + + // Log warning if this method is taking too long + if (totalTime > 1000) { + console.warn(`[PERFORMANCE WARNING] getTasksOnly method taking ${totalTime.toFixed(2)}ms - Consider using getTasksV3 for better performance!`); + } + return res.status(200).send(new ServerResponse(true, data)); } @@ -443,6 +512,53 @@ export default class TasksControllerV2 extends TasksControllerBase { return res.status(200).send(new ServerResponse(true, task)); } + @HandleExceptions() + public static async resetParentTaskManualProgress(parentTaskId: string): Promise { + try { + // Check if this task has subtasks + const subTasksResult = await db.query( + "SELECT COUNT(*) as subtask_count FROM tasks WHERE parent_task_id = $1 AND archived IS FALSE", + [parentTaskId] + ); + + const subtaskCount = parseInt(subTasksResult.rows[0]?.subtask_count || "0"); + + // If it has subtasks, reset the manual_progress flag to false + if (subtaskCount > 0) { + await db.query( + "UPDATE tasks SET manual_progress = false WHERE id = $1", + [parentTaskId] + ); + console.log(`Reset manual progress for parent task ${parentTaskId} with ${subtaskCount} subtasks`); + + // Get the project settings to determine which calculation method to use + const projectResult = await db.query( + "SELECT project_id FROM tasks WHERE id = $1", + [parentTaskId] + ); + + const projectId = projectResult.rows[0]?.project_id; + + if (projectId) { + // Recalculate the parent task's progress based on its subtasks + const progressResult = await db.query( + "SELECT get_task_complete_ratio($1) AS ratio", + [parentTaskId] + ); + + const progressRatio = progressResult.rows[0]?.ratio?.ratio || 0; + + // Emit the updated progress value to all clients + // Note: We don't have socket context here, so we can't directly emit + // This will be picked up on the next client refresh + console.log(`Recalculated progress for parent task ${parentTaskId}: ${progressRatio}%`); + } + } + } catch (error) { + log_error(`Error resetting parent task manual progress: ${error}`); + } + } + @HandleExceptions() public static async convertToSubtask(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { @@ -483,6 +599,11 @@ export default class TasksControllerV2 extends TasksControllerBase { : [req.body.id, req.body.project_id, req.body.parent_task_id, req.body.to_group_id]; await db.query(q, params); + // Reset the parent task's manual progress when converting a task to a subtask + if (req.body.parent_task_id) { + await this.resetParentTaskManualProgress(req.body.parent_task_id); + } + const result = await db.query("SELECT get_single_task($1) AS task;", [req.body.id]); const [data] = result.rows; const model = TasksControllerV2.updateTaskViewModel(data.task); @@ -504,6 +625,21 @@ export default class TasksControllerV2 extends TasksControllerBase { return this.createTagList(result.rows); } + public static async getProjectSubscribers(projectId: string) { + const q = ` + SELECT u.name, u.avatar_url, ps.user_id, ps.team_member_id, ps.project_id + FROM project_subscribers ps + LEFT JOIN users u ON ps.user_id = u.id + WHERE ps.project_id = $1; + `; + const result = await db.query(q, [projectId]); + + for (const member of result.rows) + member.color_code = getColor(member.name); + + return this.createTagList(result.rows); + } + public static async checkUserAssignedToTask(taskId: string, userId: string, teamId: string) { const q = ` SELECT EXISTS( @@ -633,27 +769,27 @@ export default class TasksControllerV2 extends TasksControllerBase { // Get column information const columnQuery = ` - SELECT id, field_type - FROM cc_custom_columns + SELECT id, field_type + FROM cc_custom_columns WHERE project_id = $1 AND key = $2 `; const columnResult = await db.query(columnQuery, [project_id, column_key]); - + if (columnResult.rowCount === 0) { return res.status(404).send(new ServerResponse(false, "Custom column not found")); } - + const column = columnResult.rows[0]; const columnId = column.id; const fieldType = column.field_type; - + // Determine which value field to use based on the field_type let textValue = null; let numberValue = null; let dateValue = null; let booleanValue = null; let jsonValue = null; - + switch (fieldType) { case "number": numberValue = parseFloat(String(value)); @@ -670,58 +806,585 @@ export default class TasksControllerV2 extends TasksControllerBase { default: textValue = String(value); } - + // Check if a value already exists const existingValueQuery = ` - SELECT id - FROM cc_column_values + SELECT id + FROM cc_column_values WHERE task_id = $1 AND column_id = $2 `; const existingValueResult = await db.query(existingValueQuery, [taskId, columnId]); - + if (existingValueResult.rowCount && existingValueResult.rowCount > 0) { // Update existing value const updateQuery = ` - UPDATE cc_column_values - SET text_value = $1, - number_value = $2, - date_value = $3, - boolean_value = $4, - json_value = $5, - updated_at = NOW() + UPDATE cc_column_values + SET text_value = $1, + number_value = $2, + date_value = $3, + boolean_value = $4, + json_value = $5, + updated_at = NOW() WHERE task_id = $6 AND column_id = $7 `; await db.query(updateQuery, [ - textValue, - numberValue, - dateValue, - booleanValue, - jsonValue, - taskId, + textValue, + numberValue, + dateValue, + booleanValue, + jsonValue, + taskId, columnId ]); } else { // Insert new value const insertQuery = ` - INSERT INTO cc_column_values - (task_id, column_id, text_value, number_value, date_value, boolean_value, json_value, created_at, updated_at) + INSERT INTO cc_column_values + (task_id, column_id, text_value, number_value, date_value, boolean_value, json_value, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW()) `; await db.query(insertQuery, [ - taskId, - columnId, - textValue, - numberValue, - dateValue, - booleanValue, + taskId, + columnId, + textValue, + numberValue, + dateValue, + booleanValue, jsonValue ]); } - return res.status(200).send(new ServerResponse(true, { + return res.status(200).send(new ServerResponse(true, { task_id: taskId, column_key, value })); } + + public static async refreshProjectTaskProgressValues(projectId: string): Promise { + try { + // Run the recalculate_all_task_progress function only for tasks in this project + const query = ` + DO $$ + BEGIN + -- First, reset manual_progress flag for all tasks that have subtasks within this project + UPDATE tasks AS t + SET manual_progress = FALSE + WHERE project_id = '${projectId}' + AND 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) in this project + SELECT + id, + parent_task_id, + 0 AS level + FROM tasks + WHERE project_id = '${projectId}' + AND 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) + 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 tasks.project_id = '${projectId}' + AND (manual_progress IS FALSE OR manual_progress IS NULL); + END $$; + `; + + await db.query(query); + console.log(`Finished refreshing progress values for project ${projectId}`); + } catch (error) { + log_error("Error refreshing project task progress values", error); + } + } + + public static async updateTaskProgress(taskId: string): Promise { + try { + // Calculate the task's progress using get_task_complete_ratio + const result = await db.query("SELECT get_task_complete_ratio($1) AS info;", [taskId]); + const [data] = result.rows; + + if (data && data.info && data.info.ratio !== undefined) { + const progressValue = +((data.info.ratio || 0).toFixed()); + + // Update the task's progress_value in the database + await db.query( + "UPDATE tasks SET progress_value = $1 WHERE id = $2", + [progressValue, taskId] + ); + + console.log(`Updated progress for task ${taskId} to ${progressValue}%`); + + // If this task has a parent, update the parent's progress as well + const parentResult = await db.query( + "SELECT parent_task_id FROM tasks WHERE id = $1", + [taskId] + ); + + if (parentResult.rows.length > 0 && parentResult.rows[0].parent_task_id) { + await this.updateTaskProgress(parentResult.rows[0].parent_task_id); + } + } + } catch (error) { + log_error(`Error updating task progress: ${error}`); + } + } + + // Add this method to update progress when a task's weight is changed + public static async updateTaskWeight(taskId: string, weight: number): Promise { + try { + // Update the task's weight + await db.query( + "UPDATE tasks SET weight = $1 WHERE id = $2", + [weight, taskId] + ); + + // Get the parent task ID + const parentResult = await db.query( + "SELECT parent_task_id FROM tasks WHERE id = $1", + [taskId] + ); + + // If this task has a parent, update the parent's progress + if (parentResult.rows.length > 0 && parentResult.rows[0].parent_task_id) { + await this.updateTaskProgress(parentResult.rows[0].parent_task_id); + } + } catch (error) { + log_error(`Error updating task weight: ${error}`); + } + } + + @HandleExceptions() + public static async getTasksV3(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const startTime = performance.now(); + const isSubTasks = !!req.query.parent_task; + const groupBy = (req.query.group || GroupBy.STATUS) as string; + const archived = req.query.archived === "true"; + + // PERFORMANCE OPTIMIZATION: Skip expensive progress calculation by default + // Progress values are already calculated and stored in the database + // Only refresh if explicitly requested via refresh_progress=true query parameter + // This dramatically improves initial load performance (from ~2-5s to ~200-500ms) + const shouldRefreshProgress = req.query.refresh_progress === "true"; + + if (shouldRefreshProgress && req.params.id) { + const progressStartTime = performance.now(); + await this.refreshProjectTaskProgressValues(req.params.id); + const progressEndTime = performance.now(); + } + + const queryStartTime = performance.now(); + const q = TasksControllerV2.getQuery(req.user?.id as string, req.query); + const params = isSubTasks ? [req.params.id || null, req.query.parent_task] : [req.params.id || null]; + + const result = await db.query(q, params); + const tasks = [...result.rows]; + const queryEndTime = performance.now(); + + // Get groups metadata dynamically from database + const groupsStartTime = performance.now(); + const groups = await this.getGroups(groupBy, req.params.id); + const groupsEndTime = performance.now(); + + // Create priority value to name mapping + const priorityMap: Record = { + "0": "low", + "1": "medium", + "2": "high" + }; + + // Create status category mapping based on actual status names from database + const statusCategoryMap: Record = {}; + for (const group of groups) { + if (groupBy === GroupBy.STATUS && group.id) { + // Use the actual status name from database, convert to lowercase for consistency + statusCategoryMap[group.id] = group.name.toLowerCase().replace(/\s+/g, "_"); + } + } + + + + // Transform tasks with all necessary data preprocessing + const transformStartTime = performance.now(); + const transformedTasks = tasks.map((task, index) => { + // Update task with calculated values (lightweight version) + TasksControllerV2.updateTaskViewModel(task); + task.index = index; + + // Convert time values + const convertTimeValue = (value: any): number => { + if (typeof value === "number") return value; + if (typeof value === "string") { + const parsed = parseFloat(value); + return isNaN(parsed) ? 0 : parsed; + } + if (value && typeof value === "object") { + if ("hours" in value || "minutes" in value) { + const hours = Number(value.hours || 0); + const minutes = Number(value.minutes || 0); + return hours + (minutes / 60); + } + } + return 0; + }; + + return { + id: task.id, + task_key: task.task_key || "", + title: task.name || "", + description: task.description || "", + // Use dynamic status mapping from database + status: statusCategoryMap[task.status] || task.status, + // Pre-processed priority using mapping + priority: priorityMap[task.priority_value?.toString()] || "medium", + // Use actual phase name from database + phase: task.phase_name || "Development", + progress: typeof task.complete_ratio === "number" ? task.complete_ratio : 0, + assignees: task.assignees?.map((a: any) => a.team_member_id) || [], + assignee_names: task.assignee_names || task.names || [], + labels: task.labels?.map((l: any) => ({ + id: l.id || l.label_id, + name: l.name, + color: l.color_code || "#1890ff", + end: l.end, + names: l.names + })) || [], + dueDate: task.end_date || task.END_DATE, + startDate: task.start_date, + timeTracking: { + estimated: convertTimeValue(task.total_time), + logged: convertTimeValue(task.time_spent), + }, + customFields: {}, + custom_column_values: task.custom_column_values || {}, // Include custom column values + createdAt: task.created_at || new Date().toISOString(), + updatedAt: task.updated_at || new Date().toISOString(), + order: typeof task.sort_order === "number" ? task.sort_order : 0, + // Additional metadata for frontend + originalStatusId: task.status, + originalPriorityId: task.priority, + statusColor: task.status_color, + priorityColor: task.priority_color, + // Add subtask count + sub_tasks_count: task.sub_tasks_count || 0, + // Add indicator fields for frontend icons + comments_count: task.comments_count || 0, + has_subscribers: !!task.has_subscribers, + attachments_count: task.attachments_count || 0, + has_dependencies: !!task.has_dependencies, + schedule_id: task.schedule_id || null, + reporter: task.reporter || null, + }; + }); + const transformEndTime = performance.now(); + + // Create groups based on dynamic data from database + const groupingStartTime = performance.now(); + const groupedResponse: Record = {}; + + // Initialize groups from database data + groups.forEach(group => { + const groupKey = groupBy === GroupBy.STATUS + ? group.name.toLowerCase().replace(/\s+/g, "_") + : groupBy === GroupBy.PRIORITY + ? priorityMap[(group as any).value?.toString()] || group.name.toLowerCase() + : group.name.toLowerCase().replace(/\s+/g, "_"); + + groupedResponse[groupKey] = { + id: group.id, + title: group.name, + groupType: groupBy, + groupValue: groupKey, + collapsed: false, + tasks: [], + taskIds: [], + color: group.color_code || this.getDefaultGroupColor(groupBy, groupKey), + // Include additional metadata from database + category_id: group.category_id, + start_date: group.start_date, + end_date: group.end_date, + sort_index: (group as any).sort_index, + }; + }); + + // Distribute tasks into groups + const unmappedTasks: any[] = []; + + transformedTasks.forEach(task => { + let groupKey: string; + let taskAssigned = false; + + if (groupBy === GroupBy.STATUS) { + groupKey = task.status; + if (groupedResponse[groupKey]) { + groupedResponse[groupKey].tasks.push(task); + groupedResponse[groupKey].taskIds.push(task.id); + taskAssigned = true; + } + } else if (groupBy === GroupBy.PRIORITY) { + groupKey = task.priority; + if (groupedResponse[groupKey]) { + groupedResponse[groupKey].tasks.push(task); + groupedResponse[groupKey].taskIds.push(task.id); + taskAssigned = true; + } + } else if (groupBy === GroupBy.PHASE) { + // For phase grouping, check if task has a valid phase + if (task.phase && task.phase.trim() !== "") { + groupKey = task.phase.toLowerCase().replace(/\s+/g, "_"); + if (groupedResponse[groupKey]) { + groupedResponse[groupKey].tasks.push(task); + groupedResponse[groupKey].taskIds.push(task.id); + taskAssigned = true; + } + } + // If task doesn't have a valid phase, add to unmapped + if (!taskAssigned) { + unmappedTasks.push(task); + } + } + }); + + // Calculate progress stats for priority and phase grouping + if (groupBy === GroupBy.PRIORITY || groupBy === GroupBy.PHASE) { + Object.values(groupedResponse).forEach((group: any) => { + if (group.tasks && group.tasks.length > 0) { + const todoCount = group.tasks.filter((task: any) => { + // For tasks, we need to check their original status category + const originalTask = tasks.find(t => t.id === task.id); + return originalTask?.status_category?.is_todo; + }).length; + + const doingCount = group.tasks.filter((task: any) => { + const originalTask = tasks.find(t => t.id === task.id); + return originalTask?.status_category?.is_doing; + }).length; + + const doneCount = group.tasks.filter((task: any) => { + const originalTask = tasks.find(t => t.id === task.id); + return originalTask?.status_category?.is_done; + }).length; + + const total = group.tasks.length; + + // Calculate progress percentages + group.todo_progress = total > 0 ? +((todoCount / total) * 100).toFixed(0) : 0; + group.doing_progress = total > 0 ? +((doingCount / total) * 100).toFixed(0) : 0; + group.done_progress = total > 0 ? +((doneCount / total) * 100).toFixed(0) : 0; + } + }); + } + + // Create unmapped group if there are tasks without proper phase assignment + if (unmappedTasks.length > 0 && groupBy === GroupBy.PHASE) { + const unmappedGroup = { + id: UNMAPPED, + title: UNMAPPED, + groupType: groupBy, + groupValue: UNMAPPED.toLowerCase(), + collapsed: false, + tasks: unmappedTasks, + taskIds: unmappedTasks.map(task => task.id), + color: "#fbc84c69", // Orange color with transparency + category_id: null, + start_date: null, + end_date: null, + sort_index: 999, // Put unmapped group at the end + todo_progress: 0, + doing_progress: 0, + done_progress: 0, + }; + + // Calculate progress stats for unmapped group + if (unmappedTasks.length > 0) { + const todoCount = unmappedTasks.filter((task: any) => { + const originalTask = tasks.find(t => t.id === task.id); + return originalTask?.status_category?.is_todo; + }).length; + + const doingCount = unmappedTasks.filter((task: any) => { + const originalTask = tasks.find(t => t.id === task.id); + return originalTask?.status_category?.is_doing; + }).length; + + const doneCount = unmappedTasks.filter((task: any) => { + const originalTask = tasks.find(t => t.id === task.id); + return originalTask?.status_category?.is_done; + }).length; + + const total = unmappedTasks.length; + + unmappedGroup.todo_progress = total > 0 ? +((todoCount / total) * 100).toFixed(0) : 0; + unmappedGroup.doing_progress = total > 0 ? +((doingCount / total) * 100).toFixed(0) : 0; + unmappedGroup.done_progress = total > 0 ? +((doneCount / total) * 100).toFixed(0) : 0; + } + + groupedResponse[UNMAPPED.toLowerCase()] = unmappedGroup; + } + + // Sort tasks within each group by order + Object.values(groupedResponse).forEach((group: any) => { + group.tasks.sort((a: any, b: any) => a.order - b.order); + }); + + // Convert to array format expected by frontend, maintaining database order + const responseGroups = groups + .map(group => { + const groupKey = groupBy === GroupBy.STATUS + ? group.name.toLowerCase().replace(/\s+/g, "_") + : groupBy === GroupBy.PRIORITY + ? priorityMap[(group as any).value?.toString()] || group.name.toLowerCase() + : group.name.toLowerCase().replace(/\s+/g, "_"); + + return groupedResponse[groupKey]; + }) + .filter(group => group && (group.tasks.length > 0 || req.query.include_empty === "true")); + + // Add unmapped group to the end if it exists + if (groupedResponse[UNMAPPED.toLowerCase()]) { + responseGroups.push(groupedResponse[UNMAPPED.toLowerCase()]); + } + + const groupingEndTime = performance.now(); + + const endTime = performance.now(); + const totalTime = endTime - startTime; + + // Log warning if request is taking too long + if (totalTime > 1000) { + console.warn(`[PERFORMANCE WARNING] Slow request detected: ${totalTime.toFixed(2)}ms for project ${req.params.id} with ${transformedTasks.length} tasks`); + } + + return res.status(200).send(new ServerResponse(true, { + groups: responseGroups, + allTasks: transformedTasks, + grouping: groupBy, + totalTasks: transformedTasks.length + })); + } + + private static getDefaultGroupColor(groupBy: string, groupValue: string): string { + const colorMaps: Record> = { + [GroupBy.STATUS]: { + todo: "#f0f0f0", + doing: "#1890ff", + done: "#52c41a", + }, + [GroupBy.PRIORITY]: { + critical: "#ff4d4f", + high: "#ff7a45", + medium: "#faad14", + low: "#52c41a", + }, + [GroupBy.PHASE]: { + planning: "#722ed1", + development: "#1890ff", + testing: "#faad14", + deployment: "#52c41a", + unmapped: "#fbc84c69", + }, + }; + + return colorMaps[groupBy]?.[groupValue] || "#d9d9d9"; + } + + @HandleExceptions() + public static async refreshTaskProgress(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + try { + const startTime = performance.now(); + + if (req.params.id) { + console.log(`[PERFORMANCE] Starting background progress refresh for project ${req.params.id}`); + await this.refreshProjectTaskProgressValues(req.params.id); + + const endTime = performance.now(); + const totalTime = endTime - startTime; + console.log(`[PERFORMANCE] Background progress refresh completed in ${totalTime.toFixed(2)}ms for project ${req.params.id}`); + + return res.status(200).send(new ServerResponse(true, { + message: "Task progress values refreshed successfully", + performanceMetrics: { + refreshTime: Math.round(totalTime), + projectId: req.params.id + } + })); + } + return res.status(400).send(new ServerResponse(false, null, "Project ID is required")); + } catch (error) { + console.error("Error refreshing task progress:", error); + return res.status(500).send(new ServerResponse(false, null, "Failed to refresh task progress")); + } + } + + // Optimized method for getting task progress without blocking main UI + @HandleExceptions() + public static async getTaskProgressStatus(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + try { + if (!req.params.id) { + return res.status(400).send(new ServerResponse(false, null, "Project ID is required")); + } + + // Get basic progress stats without expensive calculations + const result = await db.query(` + SELECT + COUNT(*) as total_tasks, + COUNT(CASE WHEN EXISTS( + SELECT 1 FROM tasks_with_status_view + WHERE tasks_with_status_view.task_id = tasks.id + AND is_done IS TRUE + ) THEN 1 END) as completed_tasks, + AVG(CASE + WHEN progress_value IS NOT NULL THEN progress_value + ELSE 0 + END) as avg_progress, + MAX(updated_at) as last_updated + FROM tasks + WHERE project_id = $1 AND archived IS FALSE + `, [req.params.id]); + + const [stats] = result.rows; + + return res.status(200).send(new ServerResponse(true, { + projectId: req.params.id, + totalTasks: parseInt(stats.total_tasks) || 0, + completedTasks: parseInt(stats.completed_tasks) || 0, + avgProgress: parseFloat(stats.avg_progress) || 0, + lastUpdated: stats.last_updated, + completionPercentage: stats.total_tasks > 0 ? + Math.round((parseInt(stats.completed_tasks) / parseInt(stats.total_tasks)) * 100) : 0 + })); + } catch (error) { + console.error("Error getting task progress status:", error); + return res.status(500).send(new ServerResponse(false, null, "Failed to get task progress status")); + } + } } diff --git a/worklenz-backend/src/controllers/worklenz-controller-base.ts b/worklenz-backend/src/controllers/worklenz-controller-base.ts index 60d0c998..c494f47b 100644 --- a/worklenz-backend/src/controllers/worklenz-controller-base.ts +++ b/worklenz-backend/src/controllers/worklenz-controller-base.ts @@ -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 diff --git a/worklenz-backend/src/cron_jobs/index.ts b/worklenz-backend/src/cron_jobs/index.ts index f13ec2e8..108a76f2 100644 --- a/worklenz-backend/src/cron_jobs/index.ts +++ b/worklenz-backend/src/cron_jobs/index.ts @@ -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(); } diff --git a/worklenz-backend/src/cron_jobs/recurring-tasks.ts b/worklenz-backend/src/cron_jobs/recurring-tasks.ts index a9ae7847..2780edd5 100644 --- a/worklenz-backend/src/cron_jobs/recurring-tasks.ts +++ b/worklenz-backend/src/cron_jobs/recurring-tasks.ts @@ -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.`); diff --git a/worklenz-backend/src/passport/passport-strategies/passport-local-login.ts b/worklenz-backend/src/passport/passport-strategies/passport-local-login.ts index 7d29fae8..d71c4a36 100644 --- a/worklenz-backend/src/passport/passport-strategies/passport-local-login.ts +++ b/worklenz-backend/src/passport/passport-strategies/passport-local-login.ts @@ -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); diff --git a/worklenz-backend/src/public/locales/alb/404-page.json b/worklenz-backend/src/public/locales/alb/404-page.json new file mode 100644 index 00000000..a5e803fe --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/404-page.json @@ -0,0 +1,4 @@ +{ + "doesNotExistText": "Na vjen keq, faqja që kërkoni nuk ekziston.", + "backHomeButton": "Kthehu në Faqen Kryesore" +} diff --git a/worklenz-backend/src/public/locales/alb/account-setup.json b/worklenz-backend/src/public/locales/alb/account-setup.json new file mode 100644 index 00000000..d5f624b3 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/account-setup.json @@ -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)" +} diff --git a/worklenz-backend/src/public/locales/alb/admin-center/current-bill.json b/worklenz-backend/src/public/locales/alb/admin-center/current-bill.json new file mode 100644 index 00000000..1f76f32b --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/admin-center/current-bill.json @@ -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}}" +} diff --git a/worklenz-backend/src/public/locales/alb/admin-center/overview.json b/worklenz-backend/src/public/locales/alb/admin-center/overview.json new file mode 100644 index 00000000..296eae4c --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/admin-center/overview.json @@ -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" +} diff --git a/worklenz-backend/src/public/locales/alb/admin-center/projects.json b/worklenz-backend/src/public/locales/alb/admin-center/projects.json new file mode 100644 index 00000000..356aaec9 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/admin-center/projects.json @@ -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" +} diff --git a/worklenz-backend/src/public/locales/alb/admin-center/sidebar.json b/worklenz-backend/src/public/locales/alb/admin-center/sidebar.json new file mode 100644 index 00000000..584a9a10 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/admin-center/sidebar.json @@ -0,0 +1,8 @@ +{ + "overview": "Përmbledhje", + "users": "Përdoruesit", + "teams": "Ekipet", + "billing": "Faturimi", + "projects": "Projektet", + "adminCenter": "Qendra Administrative" +} diff --git a/worklenz-backend/src/public/locales/alb/admin-center/teams.json b/worklenz-backend/src/public/locales/alb/admin-center/teams.json new file mode 100644 index 00000000..de37bf7a --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/admin-center/teams.json @@ -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" +} diff --git a/worklenz-backend/src/public/locales/alb/admin-center/users.json b/worklenz-backend/src/public/locales/alb/admin-center/users.json new file mode 100644 index 00000000..9cfe7956 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/admin-center/users.json @@ -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" +} diff --git a/worklenz-backend/src/public/locales/alb/all-project-list.json b/worklenz-backend/src/public/locales/alb/all-project-list.json new file mode 100644 index 00000000..8079f13d --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/all-project-list.json @@ -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" +} diff --git a/worklenz-backend/src/public/locales/alb/auth/auth-common.json b/worklenz-backend/src/public/locales/alb/auth/auth-common.json new file mode 100644 index 00000000..5f687234 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/auth/auth-common.json @@ -0,0 +1,5 @@ +{ + "loggingOut": "Po dilni...", + "authenticating": "Po autentikoheni...", + "gettingThingsReady": "Po përgatiten gjërat për ju..." +} diff --git a/worklenz-backend/src/public/locales/alb/auth/forgot-password.json b/worklenz-backend/src/public/locales/alb/auth/forgot-password.json new file mode 100644 index 00000000..2ee64388 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/auth/forgot-password.json @@ -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." +} diff --git a/worklenz-backend/src/public/locales/alb/auth/login.json b/worklenz-backend/src/public/locales/alb/auth/login.json new file mode 100644 index 00000000..668b4fdc --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/auth/login.json @@ -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" + } +} diff --git a/worklenz-backend/src/public/locales/alb/auth/signup.json b/worklenz-backend/src/public/locales/alb/auth/signup.json new file mode 100644 index 00000000..1dac7a39 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/auth/signup.json @@ -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." +} diff --git a/worklenz-backend/src/public/locales/alb/auth/verify-reset-email.json b/worklenz-backend/src/public/locales/alb/auth/verify-reset-email.json new file mode 100644 index 00000000..16017318 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/auth/verify-reset-email.json @@ -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" +} diff --git a/worklenz-backend/src/public/locales/alb/common.json b/worklenz-backend/src/public/locales/alb/common.json new file mode 100644 index 00000000..5af25f69 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/common.json @@ -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" +} diff --git a/worklenz-backend/src/public/locales/alb/create-first-project-form.json b/worklenz-backend/src/public/locales/alb/create-first-project-form.json new file mode 100644 index 00000000..80c3bb86 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/create-first-project-form.json @@ -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" +} diff --git a/worklenz-backend/src/public/locales/alb/create-first-tasks.json b/worklenz-backend/src/public/locales/alb/create-first-tasks.json new file mode 100644 index 00000000..5e74d7d4 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/create-first-tasks.json @@ -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" +} diff --git a/worklenz-backend/src/public/locales/alb/home.json b/worklenz-backend/src/public/locales/alb/home.json new file mode 100644 index 00000000..58d26e0b --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/home.json @@ -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" + } +} diff --git a/worklenz-backend/src/public/locales/alb/invite-initial-team-members.json b/worklenz-backend/src/public/locales/alb/invite-initial-team-members.json new file mode 100644 index 00000000..c86da726 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/invite-initial-team-members.json @@ -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" +} diff --git a/worklenz-backend/src/public/locales/alb/kanban-board.json b/worklenz-backend/src/public/locales/alb/kanban-board.json new file mode 100644 index 00000000..def705aa --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/kanban-board.json @@ -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" +} diff --git a/worklenz-backend/src/public/locales/alb/license-expired.json b/worklenz-backend/src/public/locales/alb/license-expired.json new file mode 100644 index 00000000..94abf5ae --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/license-expired.json @@ -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..." +} diff --git a/worklenz-backend/src/public/locales/alb/navbar.json b/worklenz-backend/src/public/locales/alb/navbar.json new file mode 100644 index 00000000..88c53de4 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/navbar.json @@ -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" + } +} diff --git a/worklenz-backend/src/public/locales/alb/organization-name-form.json b/worklenz-backend/src/public/locales/alb/organization-name-form.json new file mode 100644 index 00000000..d01b09c8 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/organization-name-form.json @@ -0,0 +1,5 @@ +{ + "nameYourOrganization": "Emërtoni organizatën tuaj.", + "worklenzAccountTitle": "Zgjidhni një emër për llogarinë tuaj në Worklenz.", + "continue": "Vazhdo" +} diff --git a/worklenz-backend/src/public/locales/alb/phases-drawer.json b/worklenz-backend/src/public/locales/alb/phases-drawer.json new file mode 100644 index 00000000..cccda7d2 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/phases-drawer.json @@ -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" +} diff --git a/worklenz-backend/src/public/locales/alb/project-drawer.json b/worklenz-backend/src/public/locales/alb/project-drawer.json new file mode 100644 index 00000000..952dba7e --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/project-drawer.json @@ -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" +} diff --git a/worklenz-backend/src/public/locales/alb/project-view-files.json b/worklenz-backend/src/public/locales/alb/project-view-files.json new file mode 100644 index 00000000..1a49c36a --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/project-view-files.json @@ -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." +} diff --git a/worklenz-backend/src/public/locales/alb/project-view-insights.json b/worklenz-backend/src/public/locales/alb/project-view-insights.json new file mode 100644 index 00000000..c7714578 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/project-view-insights.json @@ -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" + } +} diff --git a/worklenz-backend/src/public/locales/alb/project-view-members.json b/worklenz-backend/src/public/locales/alb/project-view-members.json new file mode 100644 index 00000000..239b77e9 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/project-view-members.json @@ -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." +} diff --git a/worklenz-backend/src/public/locales/alb/project-view-updates.json b/worklenz-backend/src/public/locales/alb/project-view-updates.json new file mode 100644 index 00000000..15a4ec1c --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/project-view-updates.json @@ -0,0 +1,6 @@ +{ + "inputPlaceholder": "Shto një koment..", + "addButton": "Shto", + "cancelButton": "Anulo", + "deleteButton": "Fshi" +} diff --git a/worklenz-backend/src/public/locales/alb/project-view.json b/worklenz-backend/src/public/locales/alb/project-view.json new file mode 100644 index 00000000..2bc256fe --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/project-view.json @@ -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" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/alb/project-view/import-task-templates.json b/worklenz-backend/src/public/locales/alb/project-view/import-task-templates.json new file mode 100644 index 00000000..fab0381b --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/project-view/import-task-templates.json @@ -0,0 +1,11 @@ +{ + "importTaskTemplate": "Importo Shabllon Detyrash", + "templateName": "Emri i Shabllonit", + "templateDescription": "Përshkrimi i Shabllonit", + "selectedTasks": "Detyrat e Përzgjedhura", + "tasks": "Detyrat", + "templates": "Shabllonet", + "remove": "Hiq", + "cancel": "Anulo", + "import": "Importo" +} diff --git a/worklenz-backend/src/public/locales/alb/project-view/project-member-drawer.json b/worklenz-backend/src/public/locales/alb/project-view/project-member-drawer.json new file mode 100644 index 00000000..aa6637e1 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/project-view/project-member-drawer.json @@ -0,0 +1,7 @@ +{ + "title": "Anëtarët e Projektit", + "searchLabel": "Shtoni anëtarë duke shkruar emrin ose email-in e tyre", + "searchPlaceholder": "Shkruani emrin ose email-in", + "inviteAsAMember": "Fto si anëtar", + "inviteNewMemberByEmail": "Fto anëtar të ri me email" +} diff --git a/worklenz-backend/src/public/locales/alb/project-view/project-view-header.json b/worklenz-backend/src/public/locales/alb/project-view/project-view-header.json new file mode 100644 index 00000000..51d91ba1 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/project-view/project-view-header.json @@ -0,0 +1,30 @@ +{ + "importTasks": "Importo detyra", + "importTask": "Importo detyrë", + "createTask": "Krijo detyrë", + "settings": "Cilësimet", + "subscribe": "Abonohu", + "unsubscribe": "Çabonohu", + "deleteProject": "Fshi projektin", + "startDate": "Data e fillimit", + "endDate": "Data e mbarimit", + "projectSettings": "Cilësimet e projektit", + "projectSummary": "Përmbledhja e projektit", + "receiveProjectSummary": "Merrni një përmbledhje të projektit çdo mbrëmje.", + "refreshProject": "Rifresko projektin", + "saveAsTemplate": "Ruaj si model", + "invite": "Fto", + "share": "Ndaj", + "subscribeTooltip": "Abonohu tek njoftimet e projektit", + "unsubscribeTooltip": "Çabonohu nga njoftimet e projektit", + "refreshTooltip": "Rifresko të dhënat e projektit", + "settingsTooltip": "Hap cilësimet e projektit", + "saveAsTemplateTooltip": "Ruaj këtë projekt si model", + "inviteTooltip": "Fto anëtarë të ekipit në këtë projekt", + "createTaskTooltip": "Krijo një detyrë të re", + "importTaskTooltip": "Importo detyrë nga modeli", + "navigateBackTooltip": "Kthehu tek lista e projekteve", + "projectStatusTooltip": "Statusi i projektit", + "projectDatesInfo": "Informacion për kohëzgjatjen e projektit", + "projectCategoryTooltip": "Kategoria e projektit" +} diff --git a/worklenz-backend/src/public/locales/alb/project-view/save-as-template.json b/worklenz-backend/src/public/locales/alb/project-view/save-as-template.json new file mode 100644 index 00000000..63d7ace8 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/project-view/save-as-template.json @@ -0,0 +1,27 @@ +{ + "title": "Ruaj si Shabllon", + "templateName": "Emri i Shabllonit", + "includes": "Çfarë duhet të përfshihet në shabllon nga projekti?", + "includesOptions": { + "statuses": "Statuset", + "phases": "Fazat", + "labels": "Etiketat" + }, + "taskIncludes": "Çfarë duhet të përfshihet në shabllon nga detyrat?", + "taskIncludesOptions": { + "statuses": "Statuset", + "phases": "Fazat", + "labels": "Etiketat", + "name": "Emri", + "priority": "Prioriteti", + "status": "Statusi", + "phase": "Faza", + "label": "Etiketa", + "timeEstimate": "Vlerësimi i Kohës", + "description": "Përshkrimi", + "subTasks": "Nëndetyrat" + }, + "cancel": "Anulo", + "save": "Ruaj", + "templateNamePlaceholder": "Shkruani emrin e shabllonit" +} diff --git a/worklenz-backend/src/public/locales/alb/reporting-members-drawer.json b/worklenz-backend/src/public/locales/alb/reporting-members-drawer.json new file mode 100644 index 00000000..899e590e --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/reporting-members-drawer.json @@ -0,0 +1,90 @@ +{ + "exportButton": "Eksporto", + "timeLogsButton": "Regjistrimet e Kohës", + "activityLogsButton": "Regjistrimet e Aktivitetit", + "tasksButton": "Detyrat", + "searchByNameInputPlaceholder": "Kërko sipas emrit", + + "overviewTab": "Përmbledhje", + "timeLogsTab": "Regjistrimet e Kohës", + "activityLogsTab": "Regjistrimet e Aktivitetit", + "tasksTab": "Detyrat", + + "projectsText": "Projektet", + "totalTasksText": "Detyrat Gjithsej", + "assignedTasksText": "Detyrat e Caktuara", + "completedTasksText": "Detyrat e Përfunduara", + "ongoingTasksText": "Detyrat në Vazhdim", + "overdueTasksText": "Detyrat e Vonuara", + "loggedHoursText": "Orët e Regjistruara", + + "tasksText": "Detyrat", + "allText": "Të Gjitha", + + "tasksByProjectsText": "Detyrat Sipas Projekteve", + "tasksByStatusText": "Detyrat Sipas Statusit", + "tasksByPriorityText": "Detyrat Sipas Prioritetit", + + "todoText": "Për Të Bërë", + "doingText": "Duke bërë", + "doneText": "E Përfunduar", + "lowText": "I Ulët", + "mediumText": "I Mesëm", + "highText": "I Lartë", + + "billableButton": "Fakturueshme", + "billableText": "Fakturueshme", + "nonBillableText": "Jo Fakturueshme", + + "timeLogsEmptyPlaceholder": "Asnjë regjistrim kohe për të shfaqur", + "loggedText": "Regjistruar", + "forText": "për", + "inText": "në", + "updatedText": "Përditësuar", + "fromText": "Nga", + "toText": "në", + "withinText": "brenda", + + "activityLogsEmptyPlaceholder": "Asnjë regjistrim aktiviteti për të shfaqur", + + "filterByText": "Filtro sipas:", + "selectProjectPlaceholder": "Zgjidh Projektin", + + "taskColumn": "Detyra", + "nameColumn": "Emri", + "projectColumn": "Projekti", + "statusColumn": "Statusi", + "priorityColumn": "Prioriteti", + "dueDateColumn": "Afati", + "completedDateColumn": "Data e Përfundimit", + "estimatedTimeColumn": "Koha e Vlerësuar", + "loggedTimeColumn": "Koha e Regjistruar", + "overloggedTimeColumn": "Koha e Tepërt", + "daysLeftColumn": "Ditë të Mbetura/Vonuar", + "startDateColumn": "Data e Fillimit", + "endDateColumn": "Data e Përfundimit", + "actualTimeColumn": "Koha Aktuale", + "projectHealthColumn": "Gjendja e Projektit", + "categoryColumn": "Kategoria", + "projectManagerColumn": "Menaxheri i Projektit", + + "tasksStatsOverviewDrawerTitle": "Detyrat e ", + "projectsStatsOverviewDrawerTitle": "Projektet e ", + + "cancelledText": "Anuluar", + "blockedText": "E Bllokuar", + "onHoldText": "Në Pritje", + "proposedText": "E Propozuar", + "inPlanningText": "Në Planifikim", + "inProgressText": "Në Progres", + "completedText": "E Përfunduar", + "continuousText": "E Vazhdueshme", + + "daysLeftText": "ditë të mbetura", + "daysOverdueText": "ditë vonuar", + + "notSetText": "Pa Caktuar", + "needsAttentionText": "Kërkon Vëmendje", + "atRiskText": "Në Rrezik", + "goodText": "Në Rregull" +} diff --git a/worklenz-backend/src/public/locales/alb/reporting-members.json b/worklenz-backend/src/public/locales/alb/reporting-members.json new file mode 100644 index 00000000..d88f0662 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/reporting-members.json @@ -0,0 +1,35 @@ +{ + "yesterdayText": "Dje", + "lastSevenDaysText": "7 Ditët e Fundit", + "lastWeekText": "Javën e Kaluar", + "lastThirtyDaysText": "30 Ditët e Fundit", + "lastMonthText": "Muajin e Kaluar", + "lastThreeMonthsText": "3 Muajt e Fundit", + "allTimeText": "Të Gjitha", + "customRangeText": "Interval i Përshtatur", + "startDateInputPlaceholder": "Data e fillimit", + "EndDateInputPlaceholder": "Data e përfundimit", + "filterButton": "Filtro", + + "membersTitle": "Anëtarët", + "includeArchivedButton": "Përfshij Projektet e Arkivuara", + "exportButton": "Eksporto", + "excelButton": "Excel", + "searchByNameInputPlaceholder": "Kërko sipas emrit", + + "memberColumn": "Anëtari", + "tasksProgressColumn": "Progresi i Detyrave", + "tasksAssignedColumn": "Detyrat e Caktuara", + "completedTasksColumn": "Detyrat e Përfunduara", + "overdueTasksColumn": "Detyrat e Vonuara", + "ongoingTasksColumn": "Detyrat në Vazhdim", + + "tasksAssignedColumnTooltip": "Detyrat e caktuara në intervalin e zgjedhur", + "overdueTasksColumnTooltip": "Detyrat e vonuara deri në fund të intervalit të zgjedhur", + "completedTasksColumnTooltip": "Detyrat e përfunduara në intervalin e zgjedhur", + "ongoingTasksColumnTooltip": "Detyrat e filluara por jo të përfunduara ende", + + "todoText": "Për Të Bërë", + "doingText": "Duke bërë", + "doneText": "E Përfunduar" +} diff --git a/worklenz-backend/src/public/locales/alb/reporting-overview-drawer.json b/worklenz-backend/src/public/locales/alb/reporting-overview-drawer.json new file mode 100644 index 00000000..9e4d9186 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/reporting-overview-drawer.json @@ -0,0 +1,39 @@ +{ + "exportButton": "Eksporto", + "projectsButton": "Projektet", + "membersButton": "Anëtarët", + "searchByNameInputPlaceholder": "Kërko sipas emrit", + + "overviewTab": "Përmbledhje", + "projectsTab": "Projektet", + "membersTab": "Anëtarët", + + "projectsByStatusText": "Projektet Sipas Statusit", + "projectsByCategoryText": "Projektet Sipas Kategorisë", + "projectsByHealthText": "Projektet Sipas Gjendjes", + + "projectsText": "Projektet", + "allText": "Të Gjitha", + + "cancelledText": "Anuluar", + "blockedText": "E Bllokuar", + "onHoldText": "Në Pritje", + "proposedText": "E Propozuar", + "inPlanningText": "Në Planifikim", + "inProgressText": "Në Progres", + "completedText": "E Përfunduar", + "continuousText": "E Vazhdueshme", + + "notSetText": "Pa Caktuar", + "needsAttentionText": "Kërkon Vëmendje", + "atRiskText": "Në Rrezik", + "goodText": "Në Rregull", + + "nameColumn": "Emri", + "emailColumn": "Email", + "projectsColumn": "Projektet", + "tasksColumn": "Detyrat", + "overdueTasksColumn": "Detyrat e Vonuara", + "completedTasksColumn": "Detyrat e Përfunduara", + "ongoingTasksColumn": "Detyrat në Vazhdim" +} diff --git a/worklenz-backend/src/public/locales/alb/reporting-overview.json b/worklenz-backend/src/public/locales/alb/reporting-overview.json new file mode 100644 index 00000000..b8977912 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/reporting-overview.json @@ -0,0 +1,25 @@ +{ + "overviewTitle": "Përmbledhje", + "includeArchivedButton": "Përfshij Projektet e Arkivuara", + + "teamCount": "Ekip", + "teamCountPlural": "Ekipe", + "projectCount": "Projekt", + "projectCountPlural": "Projekte", + "memberCount": "Anëtar", + "memberCountPlural": "Anëtarë", + "activeProjectCount": "Projekt Aktiv", + "activeProjectCountPlural": "Projekte Aktive", + "overdueProjectCount": "Projekt i Vonuar", + "overdueProjectCountPlural": "Projekte të Vonuara", + "unassignedMemberCount": "Anëtar i Pacaktuar", + "unassignedMemberCountPlural": "Anëtarë të Pacaktuar", + "memberWithOverdueTaskCount": "Anëtar me Detyrë të Vonuar", + "memberWithOverdueTaskCountPlural": "Anëtarë me Detyra të Vonuara", + + "teamsText": "Ekipet", + + "nameColumn": "Emri", + "projectsColumn": "Projektet", + "membersColumn": "Anëtarët" +} diff --git a/worklenz-backend/src/public/locales/alb/reporting-projects-drawer.json b/worklenz-backend/src/public/locales/alb/reporting-projects-drawer.json new file mode 100644 index 00000000..fd3a380c --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/reporting-projects-drawer.json @@ -0,0 +1,59 @@ +{ + "exportButton": "Eksporto", + "membersButton": "Anëtarët", + "tasksButton": "Detyrat", + "searchByNameInputPlaceholder": "Kërko sipas emrit", + + "overviewTab": "Përmbledhje", + "membersTab": "Anëtarët", + "tasksTab": "Detyrat", + + "completedTasksText": "Detyrat e Përfunduara", + "incompleteTasksText": "Detyrat e Papërfunduara", + "overdueTasksText": "Detyrat e Vonuara", + "allocatedHoursText": "Orët e Alokuara", + "loggedHoursText": "Orët e Regjistruara", + + "tasksText": "Detyrat", + "allText": "Të Gjitha", + + "tasksByStatusText": "Detyrat Sipas Statusit", + "tasksByPriorityText": "Detyrat Sipas Prioritetit", + "tasksByDueDateText": "Detyrat Sipas Afatit", + + "todoText": "Për Të Bërë", + "doingText": "Duke bërë", + "doneText": "E Përfunduar", + "lowText": "I Ulët", + "mediumText": "I Mesëm", + "highText": "I Lartë", + "completedText": "E Përfunduar", + "upcomingText": "Në Ardhje", + "overdueText": "E Vonuar", + "noDueDateText": "Pa Afat", + + "nameColumn": "Emri", + "tasksCountColumn": "Numri i Detyrave", + "completedTasksColumn": "Detyrat e Përfunduara", + "incompleteTasksColumn": "Detyrat e Papërfunduara", + "overdueTasksColumn": "Detyrat e Vonuara", + "contributionColumn": "Kontributi", + "progressColumn": "Progresi", + "loggedTimeColumn": "Koha e Regjistruar", + "taskColumn": "Detyra", + "projectColumn": "Projekti", + "statusColumn": "Statusi", + "priorityColumn": "Prioriteti", + "phaseColumn": "Faza", + "dueDateColumn": "Afati", + "completedDateColumn": "Data e Përfundimit", + "estimatedTimeColumn": "Koha e Vlerësuar", + "overloggedTimeColumn": "Koha e Tepërt", + "completedOnColumn": "Përfunduar Më", + "daysOverdueColumn": "Ditë vonim", + + "groupByText": "Grupo Sipas:", + "statusText": "Statusi", + "priorityText": "Prioriteti", + "phaseText": "Faza" +} diff --git a/worklenz-backend/src/public/locales/alb/reporting-projects-filters.json b/worklenz-backend/src/public/locales/alb/reporting-projects-filters.json new file mode 100644 index 00000000..614d57f3 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/reporting-projects-filters.json @@ -0,0 +1,35 @@ +{ + "searchByNamePlaceholder": "Kërko sipas emrit", + "searchByCategoryPlaceholder": "Kërko sipas kategorisë", + + "statusText": "Statusi", + "healthText": "Gjendja", + "categoryText": "Kategoria", + "projectManagerText": "Menaxheri i Projektit", + "showFieldsText": "Shfaq fushat", + + "cancelledText": "Anuluar", + "blockedText": "E bllokuar", + "onHoldText": "Në pritje", + "proposedText": "E propozuar", + "inPlanningText": "Në planifikim", + "inProgressText": "Në progres", + "completedText": "E përfunduar", + "continuousText": "E vazhdueshme", + + "notSetText": "Pa caktuar", + "needsAttentionText": "Kërkon vëmendje", + "atRiskText": "Në rrezik", + "goodText": "Në rregull", + + "nameText": "Projekti", + "estimatedVsActualText": "Vlerësuar vs Aktual", + "tasksProgressText": "Progresi i detyrave", + "lastActivityText": "Aktiviteti i fundit", + "datesText": "Datat e Fillimit/Përfundimit", + "daysLeftText": "Ditë të mbetura/vonuar", + "projectHealthText": "Gjendja e projektit", + "projectUpdateText": "Përditësimi i projektit", + "clientText": "Klienti", + "teamText": "Ekipi" +} diff --git a/worklenz-backend/src/public/locales/alb/reporting-projects.json b/worklenz-backend/src/public/locales/alb/reporting-projects.json new file mode 100644 index 00000000..c10e8f7a --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/reporting-projects.json @@ -0,0 +1,52 @@ +{ + "projectCount": "Projekt", + "projectCountPlural": "Projekte", + "includeArchivedButton": "Përfshij Projektet e Arkivuara", + "exportButton": "Eksporto", + "excelButton": "Excel", + + "projectColumn": "Projekti", + "estimatedVsActualColumn": "Vlerësuar vs Aktual", + "tasksProgressColumn": "Progresi i Detyrave", + "lastActivityColumn": "Aktiviteti i Fundit", + "statusColumn": "Statusi", + "datesColumn": "Data e Fillimit/Përfundimit", + "daysLeftColumn": "Ditë të Mbetura/Vonuar", + "projectHealthColumn": "Gjendja e Projektit", + "categoryColumn": "Kategoria", + "projectUpdateColumn": "Përditësimi i Projektit", + "clientColumn": "Klienti", + "teamColumn": "Ekipi", + "projectManagerColumn": "Menaxheri i Projektit", + + "openButton": "Hap", + + "estimatedText": "Vlerësuar", + "actualText": "Aktual", + + "todoText": "Për të Bërë", + "doingText": "duke bërë", + "doneText": "E Përfunduar", + + "cancelledText": "Anuluar", + "blockedText": "E Bllokuar", + "onHoldText": "Në Pritje", + "proposedText": "E Propozuar", + "inPlanningText": "Në Planifikim", + "inProgressText": "Në Progres", + "completedText": "E Përfunduar", + "continuousText": "E Vazhdueshme", + + "daysLeftText": "ditë të mbetura", + "dayLeftText": "ditë e mbetur", + "daysOverdueText": "ditë vonuar", + + "notSetText": "Pa Caktuar", + "needsAttentionText": "Kërkon Vëmendje", + "atRiskText": "Në Rrezik", + "goodText": "Në Rregull", + + "setCategoryText": "Cakto Kategorinë", + "searchByNameInputPlaceholder": "Kërko sipas emrit", + "todayText": "Sot" +} diff --git a/worklenz-backend/src/public/locales/alb/reporting-sidebar.json b/worklenz-backend/src/public/locales/alb/reporting-sidebar.json new file mode 100644 index 00000000..a8c14e68 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/reporting-sidebar.json @@ -0,0 +1,8 @@ +{ + "overview": "Përmbledhje", + "projects": "Projektet", + "members": "Anëtarët", + "timeReports": "Raportet e Kohës", + "estimateVsActual": "Vlerësimi vs Aktual", + "currentOrganizationTooltip": "Organizata aktuale" +} diff --git a/worklenz-backend/src/public/locales/alb/schedule.json b/worklenz-backend/src/public/locales/alb/schedule.json new file mode 100644 index 00000000..a5670aaa --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/schedule.json @@ -0,0 +1,39 @@ +{ + "today": "Sot", + "week": "Javë", + "month": "Muaj", + + "settings": "Cilësimet", + "workingDays": "Ditët e punës", + "monday": "E hënë", + "tuesday": "E martë", + "wednesday": "E mërkurë", + "thursday": "E enjte", + "friday": "E premte", + "saturday": "E shtunë", + "sunday": "E diel", + "workingHours": "Orët e punës", + "hours": "Orë", + "saveButton": "Ruaj", + + "totalAllocation": "Alokimi Total", + "timeLogged": "Koha e Regjistruar", + "remainingTime": "Koha e Mbetur", + "total": "Total", + "perDay": "Në Ditë", + "tasks": "detyra", + "startDate": "Data e Fillimit", + "endDate": "Data e Përfundimit", + + "hoursPerDay": "Orë Në Ditë", + "totalHours": "Orë Totale", + "deleteButton": "Fshi", + "cancelButton": "Anulo", + + "tabTitle": "Detyra pa Data Fillimi & Përfundimi", + + "allocatedTime": "Koha e alokuar", + "totalLogged": "Total i Regjistruar", + "loggedBillable": "Regjistruar Fakturueshme", + "loggedNonBillable": "Regjistruar Jo Fakturueshme" +} diff --git a/worklenz-backend/src/public/locales/alb/settings/categories.json b/worklenz-backend/src/public/locales/alb/settings/categories.json new file mode 100644 index 00000000..62eb60d5 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/settings/categories.json @@ -0,0 +1,10 @@ +{ + "categoryColumn": "Kategoria", + "deleteConfirmationTitle": "Jeni të sigurt?", + "deleteConfirmationOk": "Po", + "deleteConfirmationCancel": "Anulo", + "associatedTaskColumn": "Projektet e Lidhura", + "searchPlaceholder": "Kërko sipas emrit", + "emptyText": "Kategoritë mund të krijohen gjatë përditësimit ose krijimit të projekteve.", + "colorChangeTooltip": "Klikoni për të ndryshuar ngjyrën" +} diff --git a/worklenz-backend/src/public/locales/alb/settings/change-password.json b/worklenz-backend/src/public/locales/alb/settings/change-password.json new file mode 100644 index 00000000..ac1500bd --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/settings/change-password.json @@ -0,0 +1,15 @@ +{ + "title": "Ndrysho Fjalëkalimin", + "currentPassword": "Fjalëkalimi Aktual", + "newPassword": "Fjalëkalimi i Ri", + "confirmPassword": "Konfirmo Fjalëkalimin", + "currentPasswordPlaceholder": "Vendosni fjalëkalimin aktual", + "newPasswordPlaceholder": "Fjalëkalimi i Ri", + "confirmPasswordPlaceholder": "Konfirmo Fjalëkalimin", + "currentPasswordRequired": "Ju lutemi vendosni fjalëkalimin aktual!", + "newPasswordRequired": "Ju lutemi vendosni fjalëkalimin e ri!", + "passwordValidationError": "Fjalëkalimi duhet të përmbajë të paktën 8 karaktere, me një shkronjë të madhe, një numër dhe një simbol.", + "passwordMismatch": "Fjalëkalimet nuk përputhen!", + "passwordRequirements": "Fjalëkalimi i ri duhet të jetë së paku 8 karaktere, me një shkronjë të madhe, një numër dhe një simbol.", + "updateButton": "Përditëso Fjalëkalimin" +} diff --git a/worklenz-backend/src/public/locales/alb/settings/clients.json b/worklenz-backend/src/public/locales/alb/settings/clients.json new file mode 100644 index 00000000..72407a5e --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/settings/clients.json @@ -0,0 +1,22 @@ +{ + "nameColumn": "Emri", + "projectColumn": "Projekti", + "noProjectsAvailable": "Nuk ka projekte të disponueshme", + "deleteConfirmationTitle": "Jeni i sigurt?", + "deleteConfirmationOk": "Po", + "deleteConfirmationCancel": "Anulo", + "searchPlaceholder": "Kërko sipas emrit", + "createClient": "Krijo Klient", + "pinTooltip": "Klikoni për ta fiksuar në menynë kryesore", + "createClientDrawerTitle": "Krijo Klient", + "updateClientDrawerTitle": "Përditëso Klientin", + "nameLabel": "Emri", + "namePlaceholder": "Emri", + "nameRequiredError": "Ju lutemi shkruani një Emër", + "createButton": "Krijo", + "updateButton": "Përditëso", + "createClientSuccessMessage": "Klienti u krijua me sukses!", + "createClientErrorMessage": "Krijimi i klientit dështoi!", + "updateClientSuccessMessage": "Klienti u përditësua me sukses!", + "updateClientErrorMessage": "Përditësimi i klientit dështoi!" +} diff --git a/worklenz-backend/src/public/locales/alb/settings/job-titles.json b/worklenz-backend/src/public/locales/alb/settings/job-titles.json new file mode 100644 index 00000000..a464716a --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/settings/job-titles.json @@ -0,0 +1,20 @@ +{ + "nameColumn": "Emri", + "deleteConfirmationTitle": "Jeni i sigurt?", + "deleteConfirmationOk": "Po", + "deleteConfirmationCancel": "Anulo", + "searchPlaceholder": "Kërko sipas emrit", + "createJobTitleButton": "Krijo Titull Pune", + "pinTooltip": "Klikoni për ta fiksuar në menynë kryesore", + "createJobTitleDrawerTitle": "Krijo Titull Pune", + "updateJobTitleDrawerTitle": "Përditëso Titullin e Punës", + "nameLabel": "Emri", + "namePlaceholder": "Emri", + "nameRequiredError": "Ju lutemi shkruani një Emër", + "createButton": "Krijo", + "updateButton": "Përditëso", + "createJobTitleSuccessMessage": "Titulli i punës u krijua me sukses!", + "createJobTitleErrorMessage": "Krijimi i titullit të punës dështoi!", + "updateJobTitleSuccessMessage": "Titulli i punës u përditësua me sukses!", + "updateJobTitleErrorMessage": "Përditësimi i titullit të punës dështoi!" +} diff --git a/worklenz-backend/src/public/locales/alb/settings/labels.json b/worklenz-backend/src/public/locales/alb/settings/labels.json new file mode 100644 index 00000000..40e6361b --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/settings/labels.json @@ -0,0 +1,11 @@ +{ + "labelColumn": "Etiketa", + "deleteConfirmationTitle": "Jeni i sigurt?", + "deleteConfirmationOk": "Po", + "deleteConfirmationCancel": "Anulo", + "associatedTaskColumn": "Numri i Detyrave të Lidhura", + "searchPlaceholder": "Kërko sipas emrit", + "emptyText": "Etiketat mund të krijohen gjatë përditësimit ose krijimit të detyrave.", + "pinTooltip": "Klikoni për ta fiksuar në menynë kryesore", + "colorChangeTooltip": "Klikoni për të ndryshuar ngjyrën" +} diff --git a/worklenz-backend/src/public/locales/alb/settings/language.json b/worklenz-backend/src/public/locales/alb/settings/language.json new file mode 100644 index 00000000..7c0d3756 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/settings/language.json @@ -0,0 +1,7 @@ +{ + "language": "Gjuha", + "language_required": "Gjuha është e detyrueshme", + "time_zone": "Zona kohore", + "time_zone_required": "Zona kohore është e detyrueshme", + "save_changes": "Ruaj Ndryshimet" +} diff --git a/worklenz-backend/src/public/locales/alb/settings/notifications.json b/worklenz-backend/src/public/locales/alb/settings/notifications.json new file mode 100644 index 00000000..4bfd55b2 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/settings/notifications.json @@ -0,0 +1,11 @@ +{ + "title": "Cilësimet e Njoftimeve", + "emailTitle": "Më dërgo njoftime me email", + "emailDescription": "Kjo përfshin caktimet e reja të detyrave", + "dailyDigestTitle": "Më dërgo një përmbledhje ditore", + "dailyDigestDescription": "Çdo mbrëmje, do të merrni një përmbledhje të aktivitetit të fundit në detyra.", + "popupTitle": "Shfaq njoftimet në kompjuterin tim kur Worklenz është i hapur", + "popupDescription": "Njoftimet e shfaqura mund të çaktivizohen nga shfletuesi juaj. Ndryshoni cilësimet e shfletuesit për t'i lejuar ato.", + "unreadItemsTitle": "Shfaq numrin e artikujve të palexuar", + "unreadItemsDescription": "Do të shihni numërimin për çdo njoftim." +} diff --git a/worklenz-backend/src/public/locales/alb/settings/profile.json b/worklenz-backend/src/public/locales/alb/settings/profile.json new file mode 100644 index 00000000..dcce50d5 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/settings/profile.json @@ -0,0 +1,14 @@ +{ + "uploadError": "Mund të ngarkoni vetëm skedarë JPG/PNG!", + "uploadSizeError": "Imazhi duhet të jetë më i vogël se 2MB!", + "upload": "Ngarko", + "nameLabel": "Emri", + "nameRequiredError": "Emri është i detyrueshëm", + "emailLabel": "Email", + "emailRequiredError": "Email-i është i detyrueshëm", + "saveChanges": "Ruaj Ndryshimet", + "profileJoinedText": "U bashkua një muaj më parë", + "profileLastUpdatedText": "Përditësuar një muaj më parë", + "avatarTooltip": "Klikoni për të ngarkuar një avatar", + "title": "Cilësimet e Profilit" +} diff --git a/worklenz-backend/src/public/locales/alb/settings/project-templates.json b/worklenz-backend/src/public/locales/alb/settings/project-templates.json new file mode 100644 index 00000000..ac0a87ef --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/settings/project-templates.json @@ -0,0 +1,8 @@ +{ + "nameColumn": "Emri", + "editToolTip": "Modifiko", + "deleteToolTip": "Fshi", + "confirmText": "Jeni i sigurt?", + "okText": "Po", + "cancelText": "Anulo" +} diff --git a/worklenz-backend/src/public/locales/alb/settings/sidebar.json b/worklenz-backend/src/public/locales/alb/settings/sidebar.json new file mode 100644 index 00000000..a2b6dd2e --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/settings/sidebar.json @@ -0,0 +1,14 @@ +{ + "profile": "Profili", + "notifications": "Njoftimet", + "clients": "Klientët", + "job-titles": "Tituj Pune", + "labels": "Etiketa", + "categories": "Kategoritë", + "project-templates": "Shabllonet e Projekteve", + "task-templates": "Shabllonet e Detyrave", + "team-members": "Anëtarët e Ekipit", + "teams": "Ekipet", + "change-password": "Ndrysho Fjalëkalimin", + "language-and-region": "Gjuha dhe Rajoni" +} diff --git a/worklenz-backend/src/public/locales/alb/settings/task-templates.json b/worklenz-backend/src/public/locales/alb/settings/task-templates.json new file mode 100644 index 00000000..b053027c --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/settings/task-templates.json @@ -0,0 +1,9 @@ +{ + "nameColumn": "Emri", + "createdColumn": "Krijuar", + "editToolTip": "Redakto", + "deleteToolTip": "Fshi", + "confirmText": "Jeni i sigurt?", + "okText": "Po", + "cancelText": "Anulo" +} diff --git a/worklenz-backend/src/public/locales/alb/settings/team-members.json b/worklenz-backend/src/public/locales/alb/settings/team-members.json new file mode 100644 index 00000000..955954dc --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/settings/team-members.json @@ -0,0 +1,47 @@ +{ + "title": "Anëtarët e Ekipit", + "nameColumn": "Emri", + "projectsColumn": "Projektet", + "emailColumn": "Email", + "teamAccessColumn": "Qasja në Ekip", + "memberCount": "Anëtar", + "membersCountPlural": "Anëtarë", + "searchPlaceholder": "Kërko anëtarë sipas emrit", + "pinTooltip": "Rifresko listën e anëtarëve", + "addMemberButton": "Shto Anëtar të Ri", + "editTooltip": "Modifiko anëtarin", + "deactivateTooltip": "Çaktivizo anëtarin", + "activateTooltip": "Aktivizo anëtarin", + "deleteTooltip": "Fshi anëtarin", + "confirmDeleteTitle": "Jeni i sigurt që doni të fshini këtë anëtar?", + "confirmActivateTitle": "Jeni i sigurt që doni të ndryshoni statusin e këtij anëtari?", + "okText": "Po, vazhdo", + "cancelText": "Jo, anulo", + "deactivatedText": "(Aktualisht i çaktivizuar)", + "pendingInvitationText": "(Ftesë në pritje)", + "addMemberDrawerTitle": "Shto Anëtar të Ri në Ekip", + "updateMemberDrawerTitle": "Përditëso Anëtarin e Ekipit", + "addMemberEmailHint": "Anëtarët do të shtohen në ekip pavarësisht nga statusi i pranimit të ftesës", + "memberEmailLabel": "Email(o)", + "memberEmailPlaceholder": "Vendos adresën email të anëtarit të ekipit", + "memberEmailRequiredError": "Ju lutemi vendosni një adresë email të vlefshme", + "jobTitleLabel": "Titulli i Punës", + "jobTitlePlaceholder": "Zgjidh ose kërko titull pune (Opsionale)", + "memberAccessLabel": "Niveli i Qasjes", + "addToTeamButton": "Shto Anëtar në Ekip", + "updateButton": "Ruaj Ndryshimet", + "resendInvitationButton": "Dërgo Përsëri Email-in e Ftesës", + "invitationSentSuccessMessage": "Ftesa për ekip u dërgua me sukses!", + "createMemberSuccessMessage": "Anëtari i ri i ekipit u shtua me sukses!", + "createMemberErrorMessage": "Dështoi shtimi i anëtarit të ri. Ju lutemi provoni përsëri.", + "updateMemberSuccessMessage": "Anëtari i ekipit u përditësua me sukses!", + "updateMemberErrorMessage": "Dështoi përditësimi i anëtarit. Ju lutemi provoni përsëri.", + "memberText": "Anëtar", + "adminText": "Administrues", + "ownerText": "Pronar i Ekipit", + "addedText": "Shtuar", + "updatedText": "Përditësuar", + "noResultFound": "Shkruani një adresë email dhe shtypni Enter...", + "jobTitlesFetchError": "Dështoi marrja e titujve të punës", + "invitationResent": "Ftesa u dërgua sërish me sukses!" +} diff --git a/worklenz-backend/src/public/locales/alb/settings/teams.json b/worklenz-backend/src/public/locales/alb/settings/teams.json new file mode 100644 index 00000000..30f87d79 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/settings/teams.json @@ -0,0 +1,16 @@ +{ + "title": "Ekipet", + "team": "Ekip", + "teams": "Ekipet", + "name": "Emri", + "created": "Krijuar", + "ownsBy": "I përket", + "edit": "Ndrysho", + "editTeam": "Ndrysho Ekipin", + "pinTooltip": "Kliko për ta fiksuar në menunë kryesore", + "editTeamName": "Ndrysho Emrin e Ekipit", + "updateName": "Përditëso Emrin", + "namePlaceholder": "Emri", + "nameRequired": "Ju lutem shkruani një Emër", + "updateFailed": "Ndryshimi i emrit të ekipit dështoi!" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/alb/task-drawer/task-drawer-info-tab.json b/worklenz-backend/src/public/locales/alb/task-drawer/task-drawer-info-tab.json new file mode 100644 index 00000000..75e1226a --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/task-drawer/task-drawer-info-tab.json @@ -0,0 +1,29 @@ +{ + "details": { + "task-key": "Çelësi i Detyrës", + "phase": "Faza", + "assignees": "Përgjegjësit", + "due-date": "Data e Përfundimit", + "time-estimation": "Vlerësimi i Kohës", + "priority": "Prioriteti", + "labels": "Etiketa", + "billable": "Fakturueshme", + "notify": "Njofto", + "when-done-notify": "Kur të përfundojë, njofto", + "start-date": "Data e Fillimit", + "end-date": "Data e Përfundimit", + "hide-start-date": "Fshih Datën e Fillimit", + "show-start-date": "Shfaq Datën e Fillimit", + "hours": "Orë", + "minutes": "Minuta" + }, + "description": { + "title": "Përshkrimi", + "placeholder": "Shtoni një përshkrim më të detajuar..." + }, + "subTasks": { + "title": "Nën-Detyrat", + "add-sub-task": "+ Shto Nën-Detyrë", + "refresh-sub-tasks": "Rifresko Nën-Detyrat" + } +} diff --git a/worklenz-backend/src/public/locales/alb/task-drawer/task-drawer.json b/worklenz-backend/src/public/locales/alb/task-drawer/task-drawer.json new file mode 100644 index 00000000..9d6c022f --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/task-drawer/task-drawer.json @@ -0,0 +1,123 @@ +{ + "taskHeader": { + "taskNamePlaceholder": "Shkruani Detyrën tuaj", + "deleteTask": "Fshi Detyrën" + }, + "taskInfoTab": { + "title": "Informacioni", + "details": { + "title": "Detajet", + "task-key": "Çelësi i Detyrës", + "phase": "Faza", + "assignees": "Të Caktuar", + "due-date": "Data e Përfundimit", + "time-estimation": "Vlerësimi i Kohës", + "priority": "Prioriteti", + "labels": "Etiketat", + "billable": "E Faturueshme", + "notify": "Njofto", + "when-done-notify": "Kur përfundon, njofto", + "start-date": "Data e Fillimit", + "end-date": "Data e Përfundimit", + "hide-start-date": "Fshih Datën e Fillimit", + "show-start-date": "Shfaq Datën e Fillimit", + "hours": "Orë", + "minutes": "Minuta", + "progressValue": "Vlera e Progresit", + "progressValueTooltip": "Vendosni përqindjen e progresit (0-100%)", + "progressValueRequired": "Ju lutemi vendosni një vlerë progresi", + "progressValueRange": "Progresi duhet të jetë midis 0 dhe 100", + "taskWeight": "Pesha e Detyrës", + "taskWeightTooltip": "Vendosni peshën e kësaj nëndetyre (përqindje)", + "taskWeightRequired": "Ju lutemi vendosni një peshë detyre", + "taskWeightRange": "Pesha duhet të jetë midis 0 dhe 100", + "recurring": "E Përsëritur" + }, + "labels": { + "labelInputPlaceholder": "Kërko ose krijo", + "labelsSelectorInputTip": "Shtyp Enter për të krijuar" + }, + "description": { + "title": "Përshkrimi", + "placeholder": "Shto një përshkrim më të detajuar..." + }, + "subTasks": { + "title": "Nëndetyrat", + "addSubTask": "Shto Nëndetyrë", + "addSubTaskInputPlaceholder": "Shkruani detyrën tuaj dhe shtypni enter", + "refreshSubTasks": "Rifresko Nëndetyrat", + "edit": "Modifiko", + "delete": "Fshi", + "confirmDeleteSubTask": "Jeni i sigurt që doni të fshini këtë nëndetyrë?", + "deleteSubTask": "Fshi Nëndetyrën" + }, + "dependencies": { + "title": "Varësitë", + "addDependency": "+ Shto varësi të re", + "blockedBy": "Bllokuar nga", + "searchTask": "Shkruani për të kërkuar detyrë", + "noTasksFound": "Nuk u gjetën detyra", + "confirmDeleteDependency": "Jeni i sigurt që doni të fshini?" + }, + "attachments": { + "title": "Bashkëngjitjet", + "chooseOrDropFileToUpload": "Zgjidhni ose hidhni skedar për të ngarkuar", + "uploading": "Duke ngarkuar..." + }, + "comments": { + "title": "Komentet", + "addComment": "+ Shto koment të ri", + "noComments": "Ende pa komente. Bëhu i pari që komenton!", + "delete": "Fshi", + "confirmDeleteComment": "Jeni i sigurt që doni të fshini këtë koment?", + "addCommentPlaceholder": "Shto një koment...", + "cancel": "Anulo", + "commentButton": "Komento", + "attachFiles": "Bashkëngjit skedarë", + "addMoreFiles": "Shto më shumë skedarë", + "selectedFiles": "Skedarët e Zgjedhur (Deri në 25MB, Maksimumi {count})", + "maxFilesError": "Mund të ngarkoni maksimum {count} skedarë", + "processFilesError": "Dështoi përpunimi i skedarëve", + "addCommentError": "Ju lutemi shtoni një koment ose bashkëngjitni skedarë", + "createdBy": "Krijuar {{time}} nga {{user}}", + "updatedTime": "Përditësuar {{time}}" + }, + "searchInputPlaceholder": "Kërko sipas emrit", + "pendingInvitation": "Ftesë në Pritje" + }, + "taskTimeLogTab": { + "title": "Regjistri i Kohës", + "addTimeLog": "Shto regjistrim të ri kohe", + "totalLogged": "Totali i Regjistruar", + "exportToExcel": "Eksporto në Excel", + "noTimeLogsFound": "Nuk u gjetën regjistra kohe", + "timeLogForm": { + "date": "Data", + "startTime": "Koha e Fillimit", + "endTime": "Koha e Përfundimit", + "workDescription": "Përshkrimi i Punës", + "descriptionPlaceholder": "Shto një përshkrim", + "logTime": "Regjistro kohën", + "updateTime": "Përditëso kohën", + "cancel": "Anulo", + "selectDateError": "Ju lutemi zgjidhni një datë", + "selectStartTimeError": "Ju lutemi zgjidhni kohën e fillimit", + "selectEndTimeError": "Ju lutemi zgjidhni kohën e përfundimit", + "endTimeAfterStartError": "Koha e përfundimit duhet të jetë pas kohës së fillimit" + } + }, + "taskActivityLogTab": { + "title": "Regjistri i Aktivitetit", + "add": "SHTO", + "remove": "HIQE", + "none": "Asnjë", + "weight": "Pesha", + "createdTask": "krijoi detyrën." + }, + "taskProgress": { + "markAsDoneTitle": "Shëno Detyrën si të Kryer?", + "confirmMarkAsDone": "Po, shëno si të kryer", + "cancelMarkAsDone": "Jo, mbaj statusin aktual", + "markAsDoneDescription": "Keni vendosur progresin në 100%. Doni të përditësoni statusin e detyrës në \"Kryer\"?" + } +} diff --git a/worklenz-backend/src/public/locales/alb/task-list-filters.json b/worklenz-backend/src/public/locales/alb/task-list-filters.json new file mode 100644 index 00000000..c3156498 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/task-list-filters.json @@ -0,0 +1,85 @@ +{ + "searchButton": "Kërko", + "resetButton": "Rivendos", + "searchInputPlaceholder": "Kërko sipas emrit", + + "sortText": "Rendit", + "statusText": "Statusi", + "phaseText": "Faza", + "memberText": "Anëtarët", + "assigneesText": "Përgjegjësit", + "priorityText": "Prioriteti", + "labelsText": "Etiketa", + "membersText": "Anëtarët", + "groupByText": "Grupo sipas", + "showArchivedText": "Shfaq të arkivuara", + "showFieldsText": "Shfaq fushat", + "keyText": "Çelësi", + "taskText": "Detyra", + "descriptionText": "Përshkrimi", + "phasesText": "Fazat", + "listText": "Listë", + "progressText": "Progresi", + "timeTrackingText": "Gjurmimi i Kohës", + "timetrackingText": "Gjurmimi i Kohës", + "estimationText": "Vlerësimi", + "startDateText": "Data e Fillimit", + "startdateText": "Data e Fillimit", + "endDateText": "Data e Përfundimit", + "dueDateText": "Afati", + "duedateText": "Afati", + "completedDateText": "Data e Përfundimit", + "completeddateText": "Data e Përfundimit", + "createdDateText": "Data e Krijimit", + "createddateText": "Data e Krijimit", + "lastUpdatedText": "Përditësuar Së Fundi", + "lastupdatedText": "Përditësuar Së Fundi", + "reporterText": "Raportuesi", + "dueTimeText": "Koha e Afatit", + "duetimeText": "Koha e Afatit", + + "lowText": "I ulët", + "mediumText": "I mesëm", + "highText": "I lartë", + + "createStatusButtonTooltip": "Cilësimet e statusit", + "configPhaseButtonTooltip": "Cilësimet e fazës", + "noLabelsFound": "Nuk u gjetën etiketa", + + "addStatusButton": "Shto Status", + "addPhaseButton": "Shto Fazë", + + "createStatus": "Krijo Status", + "name": "Emri", + "category": "Kategoria", + "selectCategory": "Zgjidh një kategori", + "pleaseEnterAName": "Ju lutemi vendosni një emër", + "pleaseSelectACategory": "Ju lutemi zgjidhni një kategori", + "create": "Krijo", + + "searchTasks": "Kërko detyrat...", + "searchPlaceholder": "Kërko...", + "fieldsText": "Fushat", + "loadingFilters": "Duke ngarkuar filtrat...", + "noOptionsFound": "Nuk u gjetën opsione", + "filtersActive": "filtra aktiv", + "filterActive": "filtër aktiv", + "clearAll": "Pastro të gjitha", + "clearing": "Duke pastruar...", + "cancel": "Anulo", + "search": "Kërko", + "groupedBy": "Grupuar sipas", + "manageStatuses": "Menaxho Statuset", + "managePhases": "Menaxho Fazat", + "dragToReorderStatuses": "Zvarrit statuset për t'i rirenditur. Çdo status mund të ketë një kategori të ndryshme.", + "enterNewStatusName": "Shkruani emrin e statusit të ri...", + "addStatus": "Shto Status", + "noStatusesFound": "Nuk u gjetën statuse. Krijoni statusin tuaj të parë më sipër.", + "deleteStatus": "Fshi Statusin", + "deleteStatusConfirm": "Jeni të sigurt që doni të fshini këtë status? Ky veprim nuk mund të zhbëhet.", + "rename": "Riemëro", + "delete": "Fshi", + "enterStatusName": "Shkruani emrin e statusit", + "selectCategory": "Zgjidh kategorinë", + "close": "Mbyll" +} diff --git a/worklenz-backend/src/public/locales/alb/task-list-table.json b/worklenz-backend/src/public/locales/alb/task-list-table.json new file mode 100644 index 00000000..7e3f83dd --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/task-list-table.json @@ -0,0 +1,136 @@ +{ + "keyColumn": "Çelësi", + "taskColumn": "Detyra", + "descriptionColumn": "Përshkrimi", + "progressColumn": "Progresi", + "membersColumn": "Anëtarët", + "assigneesColumn": "Përgjegjësit", + "labelsColumn": "Etiketa", + "phasesColumn": "Fazat", + "phaseColumn": "Faza", + "statusColumn": "Statusi", + "priorityColumn": "Prioriteti", + "timeTrackingColumn": "Gjurmimi i Kohës", + "timetrackingColumn": "Gjurmimi i Kohës", + "estimationColumn": "Vlerësimi", + "startDateColumn": "Data e Fillimit", + "startdateColumn": "Data e Fillimit", + "dueDateColumn": "Data e Afatit", + "duedateColumn": "Data e Afatit", + "completedDateColumn": "Data e Përfundimit", + "completeddateColumn": "Data e Përfundimit", + "createdDateColumn": "Data e Krijimit", + "createddateColumn": "Data e Krijimit", + "lastUpdatedColumn": "Përditësuar Së Fundi", + "lastupdatedColumn": "Përditësuar Së Fundi", + "reporterColumn": "Raportuesi", + "dueTimeColumn": "Koha e Afatit", + "todoSelectorText": "Për të Bërë", + "doingSelectorText": "Duke bërë", + "doneSelectorText": "E Përfunduar", + + "lowSelectorText": "I ulët", + "mediumSelectorText": "I mesëm", + "highSelectorText": "I lartë", + + "selectText": "Zgjidh", + "labelsSelectorInputTip": "Shtyp Enter për të krijuar!", + + "addTaskText": "Shto Detyrë", + "addSubTaskText": "+ Shto Nën-Detyrë", + "noTasksInGroup": "Nuk ka detyra në këtë grup", + "addTaskInputPlaceholder": "Shkruaj detyrën dhe shtyp Enter", + + "openButton": "Hap", + "okButton": "Në rregull", + + "noLabelsFound": "Nuk u gjetën etiketa", + "searchInputPlaceholder": "Kërko ose krijo", + "assigneeSelectorInviteButton": "Fto një anëtar të ri me email", + "labelInputPlaceholder": "Kërko ose krijo", + "searchLabelsPlaceholder": "Kërko etiketa...", + "createLabelButton": "Krijo \"{{name}}\"", + "manageLabelsPath": "Cilësimet → Etiketat", + + "pendingInvitation": "Ftesë në Pritje", + + "contextMenu": { + "assignToMe": "Cakto mua", + "moveTo": "Zhvendos në", + "unarchive": "Ç'arkivizo", + "archive": "Arkivizo", + "convertToSubTask": "Shndërro në Nën-Detyrë", + "convertToTask": "Shndërro në Detyrë", + "delete": "Fshi", + "searchByNameInputPlaceholder": "Kërko sipas emrit" + }, + "setDueDate": "Cakto datën e afatit", + "setStartDate": "Cakto datën e fillimit", + "clearDueDate": "Pastro datën e afatit", + "clearStartDate": "Pastro datën e fillimit", + "dueDatePlaceholder": "Data e afatit", + "startDatePlaceholder": "Data e fillimit", + + "emptyStates": { + "noTaskGroups": "Nuk u gjetën grupe detyrash", + "noTaskGroupsDescription": "Detyrat do të shfaqen këtu kur krijohen ose kur aplikohen filtra.", + "errorPrefix": "Gabim:", + "dragTaskFallback": "Detyrë" + }, + + "customColumns": { + "addCustomColumn": "Shto një kolonë të personalizuar", + "customColumnHeader": "Kolona e Personalizuar", + "customColumnSettings": "Cilësimet e kolonës së personalizuar", + "noCustomValue": "Asnjë vlerë", + "peopleField": "Fusha e njerëzve", + "noDate": "Asnjë datë", + "unsupportedField": "Lloj fushe i pambështetur", + + "modal": { + "addFieldTitle": "Shto fushë", + "editFieldTitle": "Redakto fushën", + "fieldTitle": "Titulli i fushës", + "fieldTitleRequired": "Titulli i fushës është i kërkuar", + "columnTitlePlaceholder": "Titulli i kolonës", + "type": "Lloji", + "deleteConfirmTitle": "Jeni i sigurt që doni të fshini këtë kolonë të personalizuar?", + "deleteConfirmDescription": "Kjo veprim nuk mund të zhbëhet. Të gjitha të dhënat e lidhura me këtë kolonë do të fshihen përgjithmonë.", + "deleteButton": "Fshi", + "cancelButton": "Anulo", + "createButton": "Krijo", + "updateButton": "Përditëso", + "createSuccessMessage": "Kolona e personalizuar u krijua me sukses", + "updateSuccessMessage": "Kolona e personalizuar u përditësua me sukses", + "deleteSuccessMessage": "Kolona e personalizuar u fshi me sukses", + "deleteErrorMessage": "Dështoi në fshirjen e kolonës së personalizuar", + "createErrorMessage": "Dështoi në krijimin e kolonës së personalizuar", + "updateErrorMessage": "Dështoi në përditësimin e kolonës së personalizuar" + }, + + "fieldTypes": { + "people": "Njerëz", + "number": "Numër", + "date": "Data", + "selection": "Zgjedhje", + "checkbox": "Kutia e kontrollit", + "labels": "Etiketat", + "key": "Çelësi", + "formula": "Formula" + } + }, + + "indicators": { + "tooltips": { + "subtasks": "{{count}} nën-detyrë", + "subtasks_plural": "{{count}} nën-detyra", + "comments": "{{count}} koment", + "comments_plural": "{{count}} komente", + "attachments": "{{count}} bashkëngjitje", + "attachments_plural": "{{count}} bashkëngjitje", + "subscribers": "Detyra ka pajtues", + "dependencies": "Detyra ka varësi", + "recurring": "Detyrë përsëritëse" + } + } +} diff --git a/worklenz-backend/src/public/locales/alb/task-management.json b/worklenz-backend/src/public/locales/alb/task-management.json new file mode 100644 index 00000000..a156ef3f --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/task-management.json @@ -0,0 +1,21 @@ +{ + "noTasksInGroup": "Nuk ka detyra në këtë grup", + "noTasksInGroupDescription": "Shtoni një detyrë për të filluar", + "addFirstTask": "Shtoni detyrën tuaj të parë", + "openTask": "Hap", + "subtask": "nën-detyrë", + "subtasks": "nën-detyra", + "comment": "koment", + "comments": "komente", + "attachment": "bashkëngjitje", + "attachments": "bashkëngjitje", + "enterSubtaskName": "Shkruani emrin e nën-detyrës...", + "add": "Shto", + "cancel": "Anulo", + "renameGroup": "Riemërto Grupin", + "renameStatus": "Riemërto Statusin", + "renamePhase": "Riemërto Fazën", + "changeCategory": "Ndrysho Kategorinë", + "clickToEditGroupName": "Kliko për të ndryshuar emrin e grupit", + "enterGroupName": "Shkruani emrin e grupit" +} diff --git a/worklenz-backend/src/public/locales/alb/task-template-drawer.json b/worklenz-backend/src/public/locales/alb/task-template-drawer.json new file mode 100644 index 00000000..034ac916 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/task-template-drawer.json @@ -0,0 +1,11 @@ +{ + "createTaskTemplate": "Krijo Shabllon Detyre", + "editTaskTemplate": "Modifiko Shabllon Detyre", + "cancelText": "Anulo", + "saveText": "Ruaj", + "templateNameText": "Emri i Shabllonit", + "selectedTasks": "Detyrat e Përzgjedhura", + "removeTask": "Hiq", + "cancelButton": "Anulo", + "saveButton": "Ruaj" +} diff --git a/worklenz-backend/src/public/locales/alb/tasks/task-table-bulk-actions.json b/worklenz-backend/src/public/locales/alb/tasks/task-table-bulk-actions.json new file mode 100644 index 00000000..45980b24 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/tasks/task-table-bulk-actions.json @@ -0,0 +1,26 @@ +{ + "taskSelected": "detyrë e zgjedhur", + "tasksSelected": "detyra të zgjedhura", + "changeStatus": "Ndrysho Statusin/ Prioritetin/ Fazat", + "changeLabel": "Ndrysho Etiketën", + "assignToMe": "Cakto mua", + "changeAssignees": "Ndrysho Përgjegjësit", + "archive": "Arkivo", + "unarchive": "Ç'arkivo", + "delete": "Fshi", + "moreOptions": "Më shumë opsione", + "deselectAll": "Zgjidhja të gjitha", + "status": "Statusi", + "priority": "Prioriteti", + "phase": "Faza", + "member": "Anëtar", + "createTaskTemplate": "Krijo Shabllon Detyre", + "apply": "Apliko", + "createLabel": "+ Krijo Etiketë", + "searchOrCreateLabel": "Kërko ose krijo etiketë...", + "hitEnterToCreate": "Shtyp Enter për të krijuar", + "labelExists": "Etiketa ekziston tashmë", + "pendingInvitation": "Ftesë në Pritje", + "noMatchingLabels": "Asnjë etiketë që përputhet", + "noLabels": "Asnjë etiketë" +} diff --git a/worklenz-backend/src/public/locales/alb/template-drawer.json b/worklenz-backend/src/public/locales/alb/template-drawer.json new file mode 100644 index 00000000..e6174c10 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/template-drawer.json @@ -0,0 +1,19 @@ +{ + "title": "Modifiko Shabllon Detyre", + "cancelText": "Anulo", + "saveText": "Ruaj", + "templateNameText": "Emri i Shabllonit", + "selectedTasks": "Detyrat e Përzgjedhura", + "removeTask": "Hiq", + "description": "Përshkrimi", + "phase": "Faza", + "statuses": "Statuset", + "priorities": "Prioritetet", + "labels": "Etiketa", + "tasks": "Detyrat", + "noTemplateSelected": "Asnjë shabllon i përzgjedhur", + "noDescription": "Pa përshkrim", + "worklenzTemplates": "Shabllonet Worklenz", + "yourTemplatesLibrary": "Biblioteka Juaj", + "searchTemplates": "Kërko Shabllone" +} diff --git a/worklenz-backend/src/public/locales/alb/templateDrawer.json b/worklenz-backend/src/public/locales/alb/templateDrawer.json new file mode 100644 index 00000000..6b2cd828 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/templateDrawer.json @@ -0,0 +1,23 @@ +{ + "bugTracking": "Gjurmimi i Gabimeve", + "construction": "Ndërtim", + "designCreative": "Dizajn & Kreativ", + "education": "Arsim", + "finance": "Financë", + "hrRecruiting": "Burime Njerëzore & Rekrutim", + "informationTechnology": "Teknologji Informacioni", + "legal": "Juridik", + "manufacturing": "Prodhim", + "marketing": "Marketing", + "nonprofit": "Jo-fitimprurës", + "personalUse": "Përdorim Personal", + "salesCRM": "Shitje & CRM", + "serviceConsulting": "Shërbime & Këshillim", + "softwareDevelopment": "Zhvillim Softueri", + "description": "Përshkrimi", + "phase": "Faza", + "statuses": "Statuset", + "priorities": "Prioritetet", + "labels": "Etiketa", + "tasks": "Detyrat" +} diff --git a/worklenz-backend/src/public/locales/alb/time-report.json b/worklenz-backend/src/public/locales/alb/time-report.json new file mode 100644 index 00000000..8a0bb69b --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/time-report.json @@ -0,0 +1,44 @@ +{ + "includeArchivedProjects": "Përfshij Projektet e Arkivuara", + "export": "Eksporto", + "timeSheet": "Fletë Kohore", + + "searchByName": "Kërko sipas emrit", + "selectAll": "Zgjidh të Gjitha", + "teams": "Ekipet", + + "searchByProject": "Kërko sipas emrit të projektit", + "projects": "Projektet", + + "searchByCategory": "Kërko sipas emrit të kategorisë", + "categories": "Kategoritë", + + "billable": "Fakturueshme", + "nonBillable": "Jo Fakturueshme", + + "total": "Total", + + "projectsTimeSheet": "Fletë Kohore e Projekteve", + + "loggedTime": "Koha e Regjistruar(orë)", + + "exportToExcel": "Eksporto në Excel", + "logged": "regjistruar", + "for": "për", + + "membersTimeSheet": "Fletë Kohore e Anëtarëve", + "member": "Anëtar", + + "estimatedVsActual": "Vlerësuar vs Aktual", + "workingDays": "Ditë Pune", + "manDays": "Ditë Njeri", + "days": "Ditë", + "estimatedDays": "Ditë të Vlerësuara", + "actualDays": "Ditë Aktuale", + + "noCategories": "Nuk u gjetën kategori", + "noCategory": "Pa Kategori", + "noProjects": "Nuk u gjetën projekte", + "noTeams": "Nuk u gjetën ekipe", + "noData": "Nuk u gjetën të dhëna" +} diff --git a/worklenz-backend/src/public/locales/alb/unauthorized.json b/worklenz-backend/src/public/locales/alb/unauthorized.json new file mode 100644 index 00000000..a731e676 --- /dev/null +++ b/worklenz-backend/src/public/locales/alb/unauthorized.json @@ -0,0 +1,5 @@ +{ + "title": "E paautorizuar!", + "subtitle": "Nuk jeni të autorizuar të hyni në këtë faqe", + "button": "Kthehu në Faqen Kryesore" +} diff --git a/worklenz-backend/src/public/locales/de/404-page.json b/worklenz-backend/src/public/locales/de/404-page.json new file mode 100644 index 00000000..641a1847 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/404-page.json @@ -0,0 +1,4 @@ +{ + "doesNotExistText": "Entschuldigung, die von Ihnen besuchte Seite existiert nicht.", + "backHomeButton": "Zurück zur Startseite" +} diff --git a/worklenz-backend/src/public/locales/de/account-setup.json b/worklenz-backend/src/public/locales/de/account-setup.json new file mode 100644 index 00000000..ddfb7b80 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/account-setup.json @@ -0,0 +1,31 @@ +{ + "continue": "Weiter", + + "setupYourAccount": "Richten Sie Ihr Worklenz-Konto ein.", + "organizationStepTitle": "Organisation benennen", + "organizationStepLabel": "Wählen Sie einen Namen für Ihr Worklenz-Konto.", + + "projectStepTitle": "Erstellen Sie Ihr erstes Projekt", + "projectStepLabel": "An welchem Projekt arbeiten Sie gerade?", + "projectStepPlaceholder": "z.B. Marketingplan", + + "tasksStepTitle": "Erstellen Sie Ihre ersten Aufgaben", + "tasksStepLabel": "Geben Sie einige Aufgaben ein, die Sie in", + "tasksStepAddAnother": "Weitere hinzufügen", + + "emailPlaceholder": "E-Mail-Adresse", + "invalidEmail": "Bitte geben Sie eine gültige E-Mail-Adresse ein", + "or": "oder", + "templateButton": "Aus Vorlage importieren", + "goBack": "Zurück", + "cancel": "Abbrechen", + "create": "Erstellen", + "templateDrawerTitle": "Aus Vorlagen auswählen", + "step3InputLabel": "Per E-Mail einladen", + "addAnother": "Weitere hinzufügen", + "skipForNow": "Jetzt überspringen", + "formTitle": "Erstellen Sie Ihre erste Aufgabe.", + "step3Title": "Laden Sie Ihr Team zur Zusammenarbeit ein", + "maxMembers": " (Sie können bis zu 5 Mitglieder einladen)", + "maxTasks": " (Sie können bis zu 5 Aufgaben erstellen)" +} diff --git a/worklenz-backend/src/public/locales/de/admin-center/current-bill.json b/worklenz-backend/src/public/locales/de/admin-center/current-bill.json new file mode 100644 index 00000000..fcf2c636 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/admin-center/current-bill.json @@ -0,0 +1,113 @@ +{ + "title": "Abrechnungen", + "currentBill": "Aktuelle Rechnung", + "configuration": "Konfiguration", + "currentPlanDetails": "Aktuelle Plan Details", + "upgradePlan": "Plan upgraden", + "cardBodyText01": "Kostenlose Testversion", + "cardBodyText02": "(Ihr Testplan läuft in 1 Monat 19 Tagen ab)", + "redeemCode": "Gutscheincode einlösen", + "accountStorage": "Kontospeicher", + "used": "Verwendet:", + "remaining": "Verbleibend:", + "charges": "Gebühren", + "tooltip": "Gebühren für den aktuellen Abrechnungszeitraum", + "description": "Beschreibung", + "billingPeriod": "Abrechnungszeitraum", + "billStatus": "Rechnungsstatus", + "perUserValue": "Pro Benutzer Wert", + "users": "Benutzer", + + "amount": "Betrag", + "invoices": "Rechnungen", + "transactionId": "Transaktions-ID", + "transactionDate": "Transaktionsdatum", + "paymentMethod": "Zahlungsmethode", + "status": "Status", + "ltdUsers": "Sie können bis zu {{ltd_users}} Benutzer hinzufügen.", + + "totalSeats": "Gesamte Plätze", + "availableSeats": "Verfügbare Plätze", + "addMoreSeats": "Weitere Plätze hinzufügen", + + "drawerTitle": "Gutscheincode einlösen", + "label": "Gutscheincode", + "drawerPlaceholder": "Geben Sie Ihren Gutscheincode ein", + "redeemSubmit": "Einreichen", + + "modalTitle": "Wählen Sie den besten Plan für Ihr Team", + "seatLabel": "Anzahl der Plätze", + "freePlan": "Kostenloser Plan", + "startup": "Startup", + "business": "Business", + "tag": "Am beliebtesten", + "enterprise": "Enterprise", + + "freeSubtitle": "kostenlos für immer", + "freeUsers": "Ideal für die persönliche Nutzung", + "freeText01": "100MB Speicher", + "freeText02": "3 Projekte", + "freeText03": "5 Teammitglieder", + + "startupSubtitle": "PAUSCHALPREIS / Monat", + "startupUsers": "Bis zu 15 Benutzer", + "startupText01": "25GB Speicher", + "startupText02": "Unbegrenzte aktive Projekte", + "startupText03": "Zeitplan", + "startupText04": "Berichterstattung", + "startupText05": "Projekte abonnieren", + + "businessSubtitle": "Benutzer / Monat", + "businessUsers": "16 - 200 Benutzer", + + "enterpriseUsers": "200 - 500+ Benutzer", + + "footerTitle": "Bitte geben Sie uns eine Kontaktnummer, unter der wir Sie erreichen können.", + "footerLabel": "Kontaktnummer", + "footerButton": "Kontaktieren Sie uns", + + "redeemCodePlaceHolder": "Geben Sie Ihren Gutscheincode ein", + "submit": "Einreichen", + + "trialPlan": "Kostenlose Testversion", + "trialExpireDate": "Gültig bis {{trial_expire_date}}", + "trialExpired": "Ihre kostenlose Testversion ist {{trial_expire_string}} abgelaufen", + "trialInProgress": "Ihre kostenlose Testversion läuft {{trial_expire_string}} ab", + + "required": "Dieses Feld ist erforderlich", + "invalidCode": "Ungültiger Code", + + "selectPlan": "Wählen Sie den besten Plan für Ihr Team", + "changeSubscriptionPlan": "Ändern Sie Ihren Abonnementplan", + "noOfSeats": "Anzahl der Plätze", + "annualPlan": "Pro - Jährlich", + "monthlyPlan": "Pro - Monatlich", + "freeForever": "Kostenlos für immer", + "bestForPersonalUse": "Ideal für die persönliche Nutzung", + "storage": "Speicher", + "projects": "Projekte", + "teamMembers": "Teammitglieder", + "unlimitedTeamMembers": "Unbegrenzte Teammitglieder", + "unlimitedActiveProjects": "Unbegrenzte aktive Projekte", + "schedule": "Zeitplan", + "reporting": "Berichterstattung", + "subscribeToProjects": "Projekte abonnieren", + "billedAnnually": "Jährlich abgerechnet", + "billedMonthly": "Monatlich abgerechnet", + + "pausePlan": "Plan pausieren", + "resumePlan": "Plan fortsetzen", + "changePlan": "Plan ändern", + "cancelPlan": "Plan kündigen", + + "perMonthPerUser": "pro Benutzer/Monat", + "viewInvoice": "Rechnung anzeigen", + "switchToFreePlan": "Wechsel zum kostenlosen Plan", + + "expirestoday": "heute", + "expirestomorrow": "morgen", + "expiredDaysAgo": "vor {{days}} Tagen", + + "continueWith": "Fortfahren mit {{plan}}", + "changeToPlan": "Wechseln zu {{plan}}" +} diff --git a/worklenz-backend/src/public/locales/de/admin-center/overview.json b/worklenz-backend/src/public/locales/de/admin-center/overview.json new file mode 100644 index 00000000..0330d788 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/admin-center/overview.json @@ -0,0 +1,8 @@ +{ + "overview": "Übersicht", + "name": "Organisationsname", + "owner": "Organisationsinhaber", + "admins": "Organisationsadministratoren", + "contactNumber": "Kontaktnummer hinzufügen", + "edit": "Bearbeiten" +} diff --git a/worklenz-backend/src/public/locales/de/admin-center/projects.json b/worklenz-backend/src/public/locales/de/admin-center/projects.json new file mode 100644 index 00000000..2d4f3534 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/admin-center/projects.json @@ -0,0 +1,12 @@ +{ + "membersCount": "Mitgliederanzahl", + "createdAt": "Erstellt am", + "projectName": "Projektname", + "teamName": "Teamname", + "refreshProjects": "Projekte aktualisieren", + "searchPlaceholder": "Nach Projektname suchen", + "deleteProject": "Sind Sie sicher, dass Sie dieses Projekt löschen möchten?", + "confirm": "Bestätigen", + "cancel": "Abbrechen", + "delete": "Projekt löschen" +} diff --git a/worklenz-backend/src/public/locales/de/admin-center/sidebar.json b/worklenz-backend/src/public/locales/de/admin-center/sidebar.json new file mode 100644 index 00000000..670595a3 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/admin-center/sidebar.json @@ -0,0 +1,8 @@ +{ + "overview": "Übersicht", + "users": "Benutzer", + "teams": "Teams", + "billing": "Abrechnung", + "projects": "Projekte", + "adminCenter": "Admin-Center" +} diff --git a/worklenz-backend/src/public/locales/de/admin-center/teams.json b/worklenz-backend/src/public/locales/de/admin-center/teams.json new file mode 100644 index 00000000..7ab2831f --- /dev/null +++ b/worklenz-backend/src/public/locales/de/admin-center/teams.json @@ -0,0 +1,33 @@ +{ + "title": "Teams", + "subtitle": "Teams", + "tooltip": "Teams aktualisieren", + "placeholder": "Nach Namen suchen", + "addTeam": "Team hinzufügen", + "team": "Team", + "membersCount": "Mitgliederanzahl", + "members": "Mitglieder", + "drawerTitle": "Neues Team erstellen", + "label": "Teamname", + "drawerPlaceholder": "Name", + "create": "Erstellen", + "delete": "Löschen", + "settings": "Einstellungen", + "popTitle": "Sind Sie sicher?", + "message": "Bitte geben Sie einen Namen ein", + "teamSettings": "Team-Einstellungen", + "teamName": "Teamname", + "teamDescription": "Teambeschreibung", + "teamMembers": "Teammitglieder", + "teamMembersCount": "Anzahl der Teammitglieder", + "teamMembersPlaceholder": "Nach Namen suchen", + "addMember": "Mitglied hinzufügen", + "add": "Hinzufügen", + "update": "Aktualisieren", + "teamNamePlaceholder": "Name des Teams", + "user": "Benutzer", + "role": "Rolle", + "owner": "Besitzer", + "admin": "Administrator", + "member": "Mitglied" +} diff --git a/worklenz-backend/src/public/locales/de/admin-center/users.json b/worklenz-backend/src/public/locales/de/admin-center/users.json new file mode 100644 index 00000000..47de9a59 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/admin-center/users.json @@ -0,0 +1,9 @@ +{ + "title": "Benutzer", + "subTitle": "Benutzer", + "placeholder": "Nach Namen suchen", + "user": "Benutzer", + "email": "E-Mail", + "lastActivity": "Letzte Aktivität", + "refresh": "Benutzer aktualisieren" +} diff --git a/worklenz-backend/src/public/locales/de/all-project-list.json b/worklenz-backend/src/public/locales/de/all-project-list.json new file mode 100644 index 00000000..89a9803d --- /dev/null +++ b/worklenz-backend/src/public/locales/de/all-project-list.json @@ -0,0 +1,34 @@ +{ + "name": "Name", + "client": "Kunde", + "category": "Kategorie", + "status": "Status", + "tasksProgress": "Aufgabenfortschritt", + "updated_at": "Zuletzt aktualisiert", + "members": "Mitglieder", + "setting": "Einstellungen", + "projects": "Projekte", + "refreshProjects": "Projekte aktualisieren", + "all": "Alle", + "favorites": "Favoriten", + "archived": "Archiviert", + "placeholder": "Nach Namen suchen", + "archive": "Archivieren", + "unarchive": "Dearchivieren", + "archiveConfirm": "Sind Sie sicher, dass Sie dieses Projekt archivieren möchten?", + "unarchiveConfirm": "Sind Sie sicher, dass Sie dieses Projekt dearchivieren möchten?", + "yes": "Ja", + "no": "Nein", + "clickToFilter": "Zum Filtern klicken nach", + "noProjects": "Keine Projekte gefunden", + "addToFavourites": "Zu Favoriten hinzufügen", + "list": "Liste", + "group": "Gruppe", + "listView": "Listenansicht", + "groupView": "Gruppenansicht", + "groupBy": { + "category": "Kategorie", + "client": "Kunde" + }, + "noPermission": "Sie haben keine Berechtigung, diese Aktion durchzuführen" +} diff --git a/worklenz-backend/src/public/locales/de/auth/auth-common.json b/worklenz-backend/src/public/locales/de/auth/auth-common.json new file mode 100644 index 00000000..dab26d10 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/auth/auth-common.json @@ -0,0 +1,5 @@ +{ + "loggingOut": "Abmelden...", + "authenticating": "Authentifizierung läuft...", + "gettingThingsReady": "Bereite alles für Sie vor..." +} diff --git a/worklenz-backend/src/public/locales/de/auth/forgot-password.json b/worklenz-backend/src/public/locales/de/auth/forgot-password.json new file mode 100644 index 00000000..a94c7463 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/auth/forgot-password.json @@ -0,0 +1,12 @@ +{ + "headerDescription": "Passwort zurücksetzen", + "emailLabel": "E-Mail", + "emailPlaceholder": "Ihre E-Mail eingeben", + "emailRequired": "Bitte geben Sie Ihre E-Mail-Adresse ein!", + "resetPasswordButton": "Passwort zurücksetzen", + "returnToLoginButton": "Zurück zum Login", + "passwordResetSuccessMessage": "Ein Link zum Zurücksetzen des Passworts wurde an Ihre E-Mail gesendet.", + "orText": "ODER", + "successTitle": "Anweisung zum Zurücksetzen gesendet!", + "successMessage": "Die Informationen zum Zurücksetzen wurden an Ihre E-Mail gesendet. Bitte überprüfen Sie Ihr E-Mail-Postfach." +} diff --git a/worklenz-backend/src/public/locales/de/auth/login.json b/worklenz-backend/src/public/locales/de/auth/login.json new file mode 100644 index 00000000..f42d0db9 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/auth/login.json @@ -0,0 +1,27 @@ +{ + "headerDescription": "Melden Sie sich an", + "emailLabel": "E-Mail", + "emailPlaceholder": "Ihre E-Mail-Adresse eingeben", + "emailRequired": "Bitte geben Sie Ihre E-Mail-Adresse ein!", + "passwordLabel": "Passwort", + "passwordPlaceholder": "Ihr Passwort eingeben", + "passwordRequired": "Bitte geben Sie Ihr Passwort ein!", + "rememberMe": "Erinnere dich an mich", + "loginButton": "Anmelden", + "signupButton": "Registrieren", + "forgotPasswordButton": "Passwort vergessen?", + "signInWithGoogleButton": "Mit Google anmelden", + "dontHaveAccountText": "Noch kein Konto?", + "orText": "ODER", + "successMessage": "Sie haben sich erfolgreich angemeldet!", + "loginError": "Anmeldung fehlgeschlagen", + "googleLoginError": "Google-Anmeldung fehlgeschlagen", + "validationMessages": { + "email": "Bitte geben Sie eine gültige E-Mail-Adresse ein", + "password": "Das Passwort muss mindestens 8 Zeichen lang sein" + }, + "errorMessages": { + "loginErrorTitle": "Anmeldung fehlgeschlagen", + "loginErrorMessage": "Bitte überprüfen Sie Ihre E-Mail-Adresse und Ihr Passwort und versuchen Sie es erneut" + } +} diff --git a/worklenz-backend/src/public/locales/de/auth/signup.json b/worklenz-backend/src/public/locales/de/auth/signup.json new file mode 100644 index 00000000..55a63a23 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/auth/signup.json @@ -0,0 +1,29 @@ +{ + "headerDescription": "Registrieren Sie sich, um loszulegen", + "nameLabel": "Vollständiger Name", + "namePlaceholder": "Ihren vollständigen Namen eingeben", + "nameRequired": "Bitte geben Sie Ihren vollständigen Namen ein!", + "nameMinCharacterRequired": "Der Name muss mindestens 4 Zeichen lang sein!", + "emailLabel": "E-Mail", + "emailPlaceholder": "Ihre E-Mail-Adresse eingeben", + "emailRequired": "Bitte geben Sie Ihre E-Mail-Adresse ein!", + "passwordLabel": "Passwort", + "passwordPlaceholder": "Ihr Passwort eingeben", + "passwordRequired": "Bitte geben Sie Ihr Passwort ein!", + "passwordMinCharacterRequired": "Das Passwort muss mindestens 8 Zeichen lang sein!", + "passwordPatternRequired": "Das Passwort erfüllt nicht die Anforderungen!", + "strongPasswordPlaceholder": "Ein stärkeres Passwort eingeben", + "passwordValidationAltText": "Das Passwort muss mindestens 8 Zeichen enthalten, mit Groß- und Kleinbuchstaben, einer Zahl und einem Sonderzeichen.", + "signupSuccessMessage": "Sie haben sich erfolgreich registriert!", + "privacyPolicyLink": "Datenschutzrichtlinie", + "termsOfUseLink": "Nutzungsbedingungen", + "bySigningUpText": "Mit der Registrierung stimmen Sie unseren", + "andText": "und", + "signupButton": "Registrieren", + "signInWithGoogleButton": "Mit Google anmelden", + "alreadyHaveAccountText": "Sie haben bereits ein Konto?", + "loginButton": "Anmelden", + "orText": "ODER", + "reCAPTCHAVerificationError": "reCAPTCHA-Verifizierungsfehler", + "reCAPTCHAVerificationErrorMessage": "Wir konnten Ihre reCAPTCHA nicht verifizieren. Bitte versuchen Sie es erneut." +} diff --git a/worklenz-backend/src/public/locales/de/auth/verify-reset-email.json b/worklenz-backend/src/public/locales/de/auth/verify-reset-email.json new file mode 100644 index 00000000..323c685f --- /dev/null +++ b/worklenz-backend/src/public/locales/de/auth/verify-reset-email.json @@ -0,0 +1,14 @@ +{ + "title": "E-Mail zurücksetzen bestätigen", + "description": "Geben Sie Ihr neues Passwort ein", + "placeholder": "Neues Passwort eingeben", + "confirmPasswordPlaceholder": "Neues Passwort bestätigen", + "passwordHint": "Mindestens 8 Zeichen, mit Groß- und Kleinbuchstaben, einer Zahl und einem Sonderzeichen.", + "resetPasswordButton": "Passwort zurücksetzen", + "orText": "Oder", + "resendResetEmail": "Zurücksetz-E-Mail erneut senden", + "passwordRequired": "Bitte geben Sie Ihr neues Passwort ein", + "returnToLoginButton": "Zurück zur Anmeldung", + "confirmPasswordRequired": "Bitte bestätigen Sie Ihr neues Passwort", + "passwordMismatch": "Die beiden Passwörter stimmen nicht überein" +} diff --git a/worklenz-backend/src/public/locales/de/common.json b/worklenz-backend/src/public/locales/de/common.json new file mode 100644 index 00000000..937ad4a9 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/common.json @@ -0,0 +1,9 @@ +{ + "login-success": "Anmeldung erfolgreich!", + "login-failed": "Anmeldung fehlgeschlagen. Bitte überprüfen Sie Ihre Anmeldedaten und versuchen Sie es erneut.", + "signup-success": "Registrierung erfolgreich! Willkommen an Bord.", + "signup-failed": "Registrierung fehlgeschlagen. Bitte füllen Sie alle erforderlichen Felder aus und versuchen Sie es erneut.", + "reconnecting": "Vom Server getrennt.", + "connection-lost": "Verbindung zum Server fehlgeschlagen. Bitte überprüfen Sie Ihre Internetverbindung.", + "connection-restored": "Erfolgreich mit dem Server verbunden" +} diff --git a/worklenz-backend/src/public/locales/de/create-first-project-form.json b/worklenz-backend/src/public/locales/de/create-first-project-form.json new file mode 100644 index 00000000..02ce495e --- /dev/null +++ b/worklenz-backend/src/public/locales/de/create-first-project-form.json @@ -0,0 +1,13 @@ +{ + "formTitle": "Erstellen Sie Ihr erstes Projekt", + "inputLabel": "An welchem Projekt arbeiten Sie gerade?", + "or": "oder", + "templateButton": "Aus Vorlage importieren", + "createFromTemplate": "Aus Vorlage erstellen", + "goBack": "Zurück", + "continue": "Weitermachen", + "cancel": "Abbrechen", + "create": "Erstellen", + "templateDrawerTitle": "Aus Vorlagen auswählen", + "createProject": "Projekt erstellen" +} diff --git a/worklenz-backend/src/public/locales/de/create-first-tasks.json b/worklenz-backend/src/public/locales/de/create-first-tasks.json new file mode 100644 index 00000000..ae7a4256 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/create-first-tasks.json @@ -0,0 +1,7 @@ +{ + "formTitle": "Erstellen Sie Ihre erste Aufgabe.", + "inputLabel": "Geben Sie einige Aufgaben ein, die Sie erledigen werden in", + "addAnother": "Einen weiteren hinzufügen", + "goBack": "Zurück", + "continue": "Weiter" +} diff --git a/worklenz-backend/src/public/locales/de/home.json b/worklenz-backend/src/public/locales/de/home.json new file mode 100644 index 00000000..cc868952 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/home.json @@ -0,0 +1,46 @@ +{ + "todoList": { + "title": "Aufgabenliste", + "refreshTasks": "Aufgaben aktualisieren", + "addTask": "+ Aufgabe hinzufügen", + "noTasks": "Keine Aufgaben", + "pressEnter": "Drücken Sie", + "toCreate": "zum Erstellen.", + "markAsDone": "Als erledigt markieren" + }, + "projects": { + "title": "Projekte", + "refreshProjects": "Projekte aktualisieren", + "noRecentProjects": "Sie sind aktuell keinem Projekt zugewiesen.", + "noFavouriteProjects": "Keine Projekte als Favoriten markiert.", + "recent": "Kürzlich", + "favourites": "Favoriten" + }, + "tasks": { + "assignedToMe": "Mir zugewiesen", + "assignedByMe": "Von mir zugewiesen", + "all": "Alle", + "today": "Heute", + "upcoming": "Bevorstehend", + "overdue": "Überfällig", + "noDueDate": "Kein Fälligkeitsdatum", + "noTasks": "Keine Aufgaben zum Anzeigen.", + "addTask": "+ Aufgabe hinzufügen", + "name": "Name", + "project": "Projekt", + "status": "Status", + "dueDate": "Fälligkeitsdatum", + "dueDatePlaceholder": "Fälligkeitsdatum festlegen", + "tomorrow": "Morgen", + "nextWeek": "Nächste Woche", + "nextMonth": "Nächster Monat", + "projectRequired": "Bitte wählen Sie ein Projekt aus", + "pressTabToSelectDueDateAndProject": "Drücken Sie Tab, um ein Fälligkeitsdatum und ein Projekt auszuwählen", + "dueOn": "Fällige Aufgaben am", + "taskRequired": "Bitte fügen Sie eine Aufgabe hinzu", + "list": "Liste", + "calendar": "Kalender", + "tasks": "Aufgaben", + "refresh": "Aktualisieren" + } +} diff --git a/worklenz-backend/src/public/locales/de/invite-initial-team-members.json b/worklenz-backend/src/public/locales/de/invite-initial-team-members.json new file mode 100644 index 00000000..e3ba64ae --- /dev/null +++ b/worklenz-backend/src/public/locales/de/invite-initial-team-members.json @@ -0,0 +1,8 @@ +{ + "formTitle": "Laden Sie Ihr Team zur Zusammenarbeit ein", + "inputLabel": "Per E-Mail einladen", + "addAnother": "Weitere hinzufügen", + "goBack": "Zurück", + "continue": "Weitermachen", + "skipForNow": "Vorerst überspringen" +} diff --git a/worklenz-backend/src/public/locales/de/kanban-board.json b/worklenz-backend/src/public/locales/de/kanban-board.json new file mode 100644 index 00000000..70e1f6ca --- /dev/null +++ b/worklenz-backend/src/public/locales/de/kanban-board.json @@ -0,0 +1,30 @@ +{ + "rename": "Umbenennen", + "delete": "Löschen", + "addTask": "Aufgabe hinzufügen", + "addSectionButton": "Abschnitt hinzufügen", + "changeCategory": "Kategorie ändern", + + "deleteTooltip": "Löschen", + "deleteConfirmationTitle": "Sind Sie sicher?", + "deleteConfirmationOk": "Ja", + "deleteConfirmationCancel": "Abbrechen", + + "dueDate": "Fälligkeitsdatum", + "cancel": "Abbrechen", + + "today": "Heute", + "tomorrow": "Morgen", + "assignToMe": "Mir zuweisen", + "archive": "Archivieren", + + "newTaskNamePlaceholder": "Aufgabenname eingeben", + "newSubtaskNamePlaceholder": "Unteraufgabenname eingeben", + "untitledSection": "Unbenannter Abschnitt", + "unmapped": "Nicht zugeordnet", + "clickToChangeDate": "Klicken Sie, um das Datum zu ändern", + "noDueDate": "Kein Fälligkeitsdatum", + "save": "Speichern", + "clear": "Löschen", + "nextWeek": "Nächste Woche" +} diff --git a/worklenz-backend/src/public/locales/de/license-expired.json b/worklenz-backend/src/public/locales/de/license-expired.json new file mode 100644 index 00000000..437ddeb2 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/license-expired.json @@ -0,0 +1,6 @@ +{ + "title": "Ihre Worklenz-Testversion ist abgelaufen!", + "subtitle": "Bitte führen Sie jetzt ein Upgrade durch.", + "button": "Jetzt upgraden", + "checking": "Überprüfen des Abonnementstatus..." +} diff --git a/worklenz-backend/src/public/locales/de/navbar.json b/worklenz-backend/src/public/locales/de/navbar.json new file mode 100644 index 00000000..c84912e4 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/navbar.json @@ -0,0 +1,31 @@ +{ + "logoAlt": "Worklenz-Logo", + "home": "Startseite", + "projects": "Projekte", + "schedule": "Zeitplan", + "reporting": "Berichterstattung", + "clients": "Kunden", + "teams": "Teams", + "labels": "Labels", + "jobTitles": "Jobtitel", + "upgradePlan": "Plan upgraden", + "upgradePlanTooltip": "Plan upgraden", + "invite": "Einladen", + "inviteTooltip": "Teammitglieder zur Teilnahme einladen", + "switchTeamTooltip": "Team wechseln", + "help": "Hilfe", + "notificationTooltip": "Benachrichtigungen anzeigen", + "profileTooltip": "Profil anzeigen", + "adminCenter": "Admin-Center", + "settings": "Einstellungen", + "logOut": "Abmelden", + "notificationsDrawer": { + "read": "Gelesene Benachrichtigungen", + "unread": "Ungelesene Benachrichtigungen", + "markAsRead": "Als gelesen markieren", + "readAndJoin": "Lesen & Beitreten", + "accept": "Annehmen", + "acceptAndJoin": "Annehmen & Beitreten", + "noNotifications": "Keine Benachrichtigungen" + } +} diff --git a/worklenz-backend/src/public/locales/de/organization-name-form.json b/worklenz-backend/src/public/locales/de/organization-name-form.json new file mode 100644 index 00000000..06d3efcf --- /dev/null +++ b/worklenz-backend/src/public/locales/de/organization-name-form.json @@ -0,0 +1,5 @@ +{ + "nameYourOrganization": "Benennen Sie Ihre Organisation.", + "worklenzAccountTitle": "Wählen Sie einen Namen für Ihr Worklenz-Konto.", + "continue": "Weiter" +} diff --git a/worklenz-backend/src/public/locales/de/phases-drawer.json b/worklenz-backend/src/public/locales/de/phases-drawer.json new file mode 100644 index 00000000..c9e41e09 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/phases-drawer.json @@ -0,0 +1,19 @@ +{ + "configurePhases": "Phasen konfigurieren", + "phaseLabel": "Phasenbezeichnung", + "enterPhaseName": "Namen für Phasenbezeichnung eingeben", + "addOption": "Option hinzufügen", + "phaseOptions": "Phasenoptionen:", + "dragToReorderPhases": "Ziehen Sie Phasen, um sie neu zu ordnen. Jede Phase kann eine andere Farbe haben.", + "enterNewPhaseName": "Neuen Phasennamen eingeben...", + "addPhase": "Phase hinzufügen", + "noPhasesFound": "Keine Phasen gefunden. Erstellen Sie Ihre erste Phase oben.", + "deletePhase": "Phase löschen", + "deletePhaseConfirm": "Sind Sie sicher, dass Sie diese Phase löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "rename": "Umbenennen", + "delete": "Löschen", + "enterPhaseName": "Phasennamen eingeben", + "selectColor": "Farbe auswählen", + "managePhases": "Phasen verwalten", + "close": "Schließen" +} diff --git a/worklenz-backend/src/public/locales/de/project-drawer.json b/worklenz-backend/src/public/locales/de/project-drawer.json new file mode 100644 index 00000000..d20f220b --- /dev/null +++ b/worklenz-backend/src/public/locales/de/project-drawer.json @@ -0,0 +1,42 @@ +{ + "createProject": "Projekt erstellen", + "editProject": "Projekt bearbeiten", + "enterCategoryName": "Namen für die Kategorie eingeben", + "hitEnterToCreate": "Enter drücken zum Erstellen!", + "enterNotes": "Notizen", + "youCanManageClientsUnderSettings": "Kunden können Sie unter Einstellungen verwalten", + "addCategory": "Kategorie zum Projekt hinzufügen", + "newCategory": "Neue Kategorie", + "notes": "Notizen", + "startDate": "Startdatum", + "endDate": "Enddatum", + "estimateWorkingDays": "Arbeitstage schätzen", + "estimateManDays": "Personentage schätzen", + "hoursPerDay": "Stunden pro Tag", + "create": "Erstellen", + "update": "Aktualisieren", + "delete": "Löschen", + "typeToSearchClients": "Kundensuche", + "projectColor": "Projektfarbe", + "pleaseEnterAName": "Bitte geben Sie einen Namen ein", + "enterProjectName": "Projektnamen eingeben", + "name": "Name", + "status": "Status", + "health": "Gesundheit", + "category": "Kategorie", + "projectManager": "Projektleiter", + "client": "Kunde", + "deleteConfirmation": "Sind Sie sicher, dass Sie löschen möchten?", + "deleteConfirmationDescription": "Dies entfernt alle zugehörigen Daten und kann nicht rückgängig gemacht werden.", + "yes": "Ja", + "no": "Nein", + "createdAt": "Erstellt am", + "updatedAt": "Aktualisiert am", + "by": "von", + "add": "Hinzufügen", + "asClient": "als Kunde", + "createClient": "Kunde erstellen", + "searchInputPlaceholder": "Nach Name oder E-Mail suchen", + "hoursPerDayValidationMessage": "Stunden pro Tag müssen zwischen 1 und 24", + "noPermission": "Keine Berechtigung" +} diff --git a/worklenz-backend/src/public/locales/de/project-view-files.json b/worklenz-backend/src/public/locales/de/project-view-files.json new file mode 100644 index 00000000..8408df16 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/project-view-files.json @@ -0,0 +1,14 @@ +{ + "nameColumn": "Name", + "attachedTaskColumn": "Zugehörige Aufgabe", + "sizeColumn": "Größe", + "uploadedByColumn": "Hochgeladen von", + "uploadedAtColumn": "Hochgeladen am", + "fileIconAlt": "Dateisymbol", + "titleDescriptionText": "Alle Anhänge zu Aufgaben in diesem Projekt werden hier angezeigt.", + "deleteConfirmationTitle": "Sind Sie sicher?", + "deleteConfirmationOk": "Ja", + "deleteConfirmationCancel": "Abbrechen", + "segmentedTooltip": "Demnächst verfügbar! Wechseln zwischen Listenansicht und Miniaturansicht.", + "emptyText": "Es gibt keine Anhänge in diesem Projekt." +} diff --git a/worklenz-backend/src/public/locales/de/project-view-insights.json b/worklenz-backend/src/public/locales/de/project-view-insights.json new file mode 100644 index 00000000..5f11df54 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/project-view-insights.json @@ -0,0 +1,41 @@ +{ + "overview": { + "title": "Übersicht", + "statusOverview": "Statusübersicht", + "priorityOverview": "Prioritätenübersicht", + "lastUpdatedTasks": "Zuletzt aktualisierte Aufgaben" + }, + "members": { + "title": "Mitglieder", + "tooltip": "Mitglieder", + "tasksByMembers": "Aufgaben nach Mitgliedern", + "tasksByMembersTooltip": "Aufgaben nach Mitgliedern", + "name": "Name", + "taskCount": "Anzahl Aufgaben", + "contribution": "Beitrag", + "completed": "Abgeschlossen", + "incomplete": "Unvollständig", + "overdue": "Überfällig", + "progress": "Fortschritt" + }, + "tasks": { + "overdueTasks": "Überfällige Aufgaben", + "overLoggedTasks": "Aufgaben mit zu viel erfasster Zeit", + "tasksCompletedEarly": "Vorzeitig abgeschlossene Aufgaben", + "tasksCompletedLate": "Verspätet abgeschlossene Aufgaben", + "overLoggedTasksTooltip": "Aufgaben, bei denen mehr Zeit erfasst wurde als geschätzt", + "overdueTasksTooltip": "Aufgaben, deren Fälligkeitsdatum überschritten wurde" + }, + "common": { + "seeAll": "Alle anzeigen", + "totalLoggedHours": "Gesamterfasste Stunden", + "totalEstimation": "Gesamtschätzung", + "completedTasks": "Abgeschlossene Aufgaben", + "incompleteTasks": "Unvollständige Aufgaben", + "overdueTasks": "Überfällige Aufgaben", + "overdueTasksTooltip": "Aufgaben, deren Fälligkeitsdatum überschritten wurde", + "totalLoggedHoursTooltip": "Zeitschätzung und erfasste Zeit für Aufgaben.", + "includeArchivedTasks": "Archivierte Aufgaben einbeziehen", + "export": "Exportieren" + } +} diff --git a/worklenz-backend/src/public/locales/de/project-view-members.json b/worklenz-backend/src/public/locales/de/project-view-members.json new file mode 100644 index 00000000..eee5d0a1 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/project-view-members.json @@ -0,0 +1,17 @@ +{ + "nameColumn": "Name", + "jobTitleColumn": "Jobtitel", + "emailColumn": "E-Mail", + "tasksColumn": "Aufgaben", + "taskProgressColumn": "Aufgabenfortschritt", + "accessColumn": "Zugriff", + "fileIconAlt": "Dateisymbol", + "deleteConfirmationTitle": "Sind Sie sicher?", + "deleteConfirmationOk": "Ja", + "deleteConfirmationCancel": "Abbrechen", + "refreshButtonTooltip": "Mitglieder aktualisieren", + "deleteButtonTooltip": "Aus Projekt entfernen", + "memberCount": "Mitglied", + "membersCountPlural": "Mitglieder", + "emptyText": "Es gibt keine Anhänge in diesem Projekt." +} diff --git a/worklenz-backend/src/public/locales/de/project-view-updates.json b/worklenz-backend/src/public/locales/de/project-view-updates.json new file mode 100644 index 00000000..d32cf352 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/project-view-updates.json @@ -0,0 +1,6 @@ +{ + "inputPlaceholder": "Kommentar hinzufügen..", + "addButton": "Hinzufügen", + "cancelButton": "Abbrechen", + "deleteButton": "Löschen" +} diff --git a/worklenz-backend/src/public/locales/de/project-view.json b/worklenz-backend/src/public/locales/de/project-view.json new file mode 100644 index 00000000..448a7249 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/project-view.json @@ -0,0 +1,14 @@ +{ + "taskList": "Aufgabenliste", + "board": "Kanban-Board", + "insights": "Insights", + "files": "Dateien", + "members": "Mitglieder", + "updates": "Aktualisierungen", + "projectView": "Projektansicht", + "loading": "Projekt wird geladen...", + "error": "Fehler beim Laden des Projekts", + "pinnedTab": "Als Standard-Registerkarte festgesetzt", + "pinTab": "Als Standard-Registerkarte festsetzen", + "unpinTab": "Standard-Registerkarte lösen" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/de/project-view/import-task-templates.json b/worklenz-backend/src/public/locales/de/project-view/import-task-templates.json new file mode 100644 index 00000000..c5af50a0 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/project-view/import-task-templates.json @@ -0,0 +1,11 @@ +{ + "importTaskTemplate": "Aufgabenvorlage importieren", + "templateName": "Vorlagenname", + "templateDescription": "Vorlagenbeschreibung", + "selectedTasks": "Ausgewählte Aufgaben", + "tasks": "Aufgaben", + "templates": "Vorlagen", + "remove": "Entfernen", + "cancel": "Abbrechen", + "import": "Importieren" +} diff --git a/worklenz-backend/src/public/locales/de/project-view/project-member-drawer.json b/worklenz-backend/src/public/locales/de/project-view/project-member-drawer.json new file mode 100644 index 00000000..cb391b2c --- /dev/null +++ b/worklenz-backend/src/public/locales/de/project-view/project-member-drawer.json @@ -0,0 +1,7 @@ +{ + "title": "Projektmitglieder", + "searchLabel": "Mitglieder hinzufügen durch Eingabe von Name oder E-Mail", + "searchPlaceholder": "Name oder E-Mail eingeben", + "inviteAsAMember": "Als Mitglied einladen", + "inviteNewMemberByEmail": "Neues Mitglied per E-Mail einladen" +} diff --git a/worklenz-backend/src/public/locales/de/project-view/project-view-header.json b/worklenz-backend/src/public/locales/de/project-view/project-view-header.json new file mode 100644 index 00000000..c52c6052 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/project-view/project-view-header.json @@ -0,0 +1,30 @@ +{ + "importTasks": "Aufgaben importieren", + "importTask": "Aufgabe importieren", + "createTask": "Aufgabe erstellen", + "settings": "Einstellungen", + "subscribe": "Abonnieren", + "unsubscribe": "Abonnement beenden", + "deleteProject": "Projekt löschen", + "startDate": "Startdatum", + "endDate": "Enddatum", + "projectSettings": "Projekteinstellungen", + "projectSummary": "Projektzusammenfassung", + "receiveProjectSummary": "Erhalten Sie jeden Abend eine Projektzusammenfassung.", + "refreshProject": "Projekt aktualisieren", + "saveAsTemplate": "Als Vorlage speichern", + "invite": "Einladen", + "share": "Teilen", + "subscribeTooltip": "Projektbenachrichtigungen abonnieren", + "unsubscribeTooltip": "Projektbenachrichtigungen beenden", + "refreshTooltip": "Projektdaten aktualisieren", + "settingsTooltip": "Projekteinstellungen öffnen", + "saveAsTemplateTooltip": "Dieses Projekt als Vorlage speichern", + "inviteTooltip": "Teammitglieder zu diesem Projekt einladen", + "createTaskTooltip": "Neue Aufgabe erstellen", + "importTaskTooltip": "Aufgabe aus Vorlage importieren", + "navigateBackTooltip": "Zurück zur Projektliste", + "projectStatusTooltip": "Projektstatus", + "projectDatesInfo": "Informationen zum Projektzeitraum", + "projectCategoryTooltip": "Projektkategorie" +} diff --git a/worklenz-backend/src/public/locales/de/project-view/save-as-template.json b/worklenz-backend/src/public/locales/de/project-view/save-as-template.json new file mode 100644 index 00000000..7b732962 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/project-view/save-as-template.json @@ -0,0 +1,27 @@ +{ + "title": "Als Vorlage speichern", + "templateName": "Vorlagenname", + "includes": "Was soll aus dem Projekt in die Vorlage aufgenommen werden?", + "includesOptions": { + "statuses": "Status", + "phases": "Phasen", + "labels": "Labels" + }, + "taskIncludes": "Was soll aus den Aufgaben in die Vorlage aufgenommen werden?", + "taskIncludesOptions": { + "statuses": "Status", + "phases": "Phasen", + "labels": "Labels", + "name": "Name", + "priority": "Priorität", + "status": "Status", + "phase": "Phase", + "label": "Label", + "timeEstimate": "Zeitschätzung", + "description": "Beschreibung", + "subTasks": "Unteraufgaben" + }, + "cancel": "Abbrechen", + "save": "Speichern", + "templateNamePlaceholder": "Vorlagennamen eingeben" +} diff --git a/worklenz-backend/src/public/locales/de/reporting-members-drawer.json b/worklenz-backend/src/public/locales/de/reporting-members-drawer.json new file mode 100644 index 00000000..807f43c7 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/reporting-members-drawer.json @@ -0,0 +1,90 @@ +{ + "exportButton": "Exportieren", + "timeLogsButton": "Zeiterfassungen", + "activityLogsButton": "Aktivitätsprotokolle", + "tasksButton": "Aufgaben", + "searchByNameInputPlaceholder": "Nach Namen suchen", + + "overviewTab": "Übersicht", + "timeLogsTab": "Zeiterfassungen", + "activityLogsTab": "Aktivitätsprotokolle", + "tasksTab": "Aufgaben", + + "projectsText": "Projekte", + "totalTasksText": "Gesamtaufgaben", + "assignedTasksText": "Zugewiesene Aufgaben", + "completedTasksText": "Abgeschlossene Aufgaben", + "ongoingTasksText": "Laufende Aufgaben", + "overdueTasksText": "Überfällige Aufgaben", + "loggedHoursText": "Erfasste Stunden", + + "tasksText": "Aufgaben", + "allText": "Alle", + + "tasksByProjectsText": "Aufgaben nach Projekten", + "tasksByStatusText": "Aufgaben nach Status", + "tasksByPriorityText": "Aufgaben nach Priorität", + + "todoText": "Zu erledigen", + "doingText": "Tun", + "doneText": "Erledigt", + "lowText": "Niedrig", + "mediumText": "Mittel", + "highText": "Hoch", + + "billableButton": "Abrechenbar", + "billableText": "Abrechenbar", + "nonBillableText": "Nicht abrechenbar", + + "timeLogsEmptyPlaceholder": "Keine Zeiterfassungen vorhanden", + "loggedText": "Erfasst", + "forText": "für", + "inText": "in", + "updatedText": "Aktualisiert", + "fromText": "Von", + "toText": "bis", + "withinText": "innerhalb", + + "activityLogsEmptyPlaceholder": "Keine Aktivitätsprotokolle vorhanden", + + "filterByText": "Filtern nach:", + "selectProjectPlaceholder": "Projekt auswählen", + + "taskColumn": "Aufgabe", + "nameColumn": "Name", + "projectColumn": "Projekt", + "statusColumn": "Status", + "priorityColumn": "Priorität", + "dueDateColumn": "Fälligkeitsdatum", + "completedDateColumn": "Abschlussdatum", + "estimatedTimeColumn": "Geschätzte Zeit", + "loggedTimeColumn": "Erfasste Zeit", + "overloggedTimeColumn": "Übererfasste Zeit", + "daysLeftColumn": "Tage übrig/überfällig", + "startDateColumn": "Startdatum", + "endDateColumn": "Enddatum", + "actualTimeColumn": "Tatsächliche Zeit", + "projectHealthColumn": "Projektstatus", + "categoryColumn": "Kategorie", + "projectManagerColumn": "Projektleiter", + + "tasksStatsOverviewDrawerTitle": "Aufgaben von", + "projectsStatsOverviewDrawerTitle": "Projekte von", + + "cancelledText": "Abgebrochen", + "blockedText": "Blockiert", + "onHoldText": "Pausiert", + "proposedText": "Vorgeschlagen", + "inPlanningText": "In Planung", + "inProgressText": "In Bearbeitung", + "completedText": "Abgeschlossen", + "continuousText": "Kontinuierlich", + + "daysLeftText": "Tage übrig", + "daysOverdueText": "Tage überfällig", + + "notSetText": "Nicht festgelegt", + "needsAttentionText": "Benötigt Aufmerksamkeit", + "atRiskText": "Gefährdet", + "goodText": "Gut" +} diff --git a/worklenz-backend/src/public/locales/de/reporting-members.json b/worklenz-backend/src/public/locales/de/reporting-members.json new file mode 100644 index 00000000..5454d2a8 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/reporting-members.json @@ -0,0 +1,35 @@ +{ + "yesterdayText": "Gestern", + "lastSevenDaysText": "Letzte 7 Tage", + "lastWeekText": "Letzte Woche", + "lastThirtyDaysText": "Letzte 30 Tage", + "lastMonthText": "Letzter Monat", + "lastThreeMonthsText": "Letzte 3 Monate", + "allTimeText": "Gesamter Zeitraum", + "customRangeText": "Benutzerdefinierter Bereich", + "startDateInputPlaceholder": "Startdatum", + "EndDateInputPlaceholder": "Enddatum", + "filterButton": "Filtern", + + "membersTitle": "Mitglieder", + "includeArchivedButton": "Archivierte Projekte einschließen", + "exportButton": "Exportieren", + "excelButton": "Excel", + "searchByNameInputPlaceholder": "Nach Namen suchen", + + "memberColumn": "Mitglied", + "tasksProgressColumn": "Aufgabenfortschritt", + "tasksAssignedColumn": "Zugewiesene Aufgaben", + "completedTasksColumn": "Abgeschlossene Aufgaben", + "overdueTasksColumn": "Überfällige Aufgaben", + "ongoingTasksColumn": "Laufende Aufgaben", + + "tasksAssignedColumnTooltip": "Im ausgewählten Zeitraum zugewiesene Aufgaben", + "overdueTasksColumnTooltip": "Zum Ende des ausgewählten Zeitraums überfällige Aufgaben", + "completedTasksColumnTooltip": "Im ausgewählten Zeitraum abgeschlossene Aufgaben", + "ongoingTasksColumnTooltip": "Begonnene, aber noch nicht abgeschlossene Aufgaben", + + "todoText": "Zu erledigen", + "doingText": "Tun", + "doneText": "Erledigt" +} diff --git a/worklenz-backend/src/public/locales/de/reporting-overview-drawer.json b/worklenz-backend/src/public/locales/de/reporting-overview-drawer.json new file mode 100644 index 00000000..6bf4678a --- /dev/null +++ b/worklenz-backend/src/public/locales/de/reporting-overview-drawer.json @@ -0,0 +1,39 @@ +{ + "exportButton": "Exportieren", + "projectsButton": "Projekte", + "membersButton": "Mitglieder", + "searchByNameInputPlaceholder": "Nach Namen suchen", + + "overviewTab": "Übersicht", + "projectsTab": "Projekte", + "membersTab": "Mitglieder", + + "projectsByStatusText": "Projekte nach Status", + "projectsByCategoryText": "Projekte nach Kategorie", + "projectsByHealthText": "Projekte nach Gesundheit", + + "projectsText": "Projekte", + "allText": "Alle", + + "cancelledText": "Abgebrochen", + "blockedText": "Blockiert", + "onHoldText": "Pausiert", + "proposedText": "Vorgeschlagen", + "inPlanningText": "In Planung", + "inProgressText": "In Bearbeitung", + "completedText": "Abgeschlossen", + "continuousText": "Kontinuierlich", + + "notSetText": "Nicht festgelegt", + "needsAttentionText": "Benötigt Aufmerksamkeit", + "atRiskText": "Gefährdet", + "goodText": "Gut", + + "nameColumn": "Name", + "emailColumn": "E-Mail", + "projectsColumn": "Projekte", + "tasksColumn": "Aufgaben", + "overdueTasksColumn": "Überfällige Aufgaben", + "completedTasksColumn": "Abgeschlossene Aufgaben", + "ongoingTasksColumn": "Laufende Aufgaben" +} diff --git a/worklenz-backend/src/public/locales/de/reporting-overview.json b/worklenz-backend/src/public/locales/de/reporting-overview.json new file mode 100644 index 00000000..411ec83a --- /dev/null +++ b/worklenz-backend/src/public/locales/de/reporting-overview.json @@ -0,0 +1,25 @@ +{ + "overviewTitle": "Übersicht", + "includeArchivedButton": "Archivierte Projekte einschließen", + + "teamCount": "Team", + "teamCountPlural": "Teams", + "projectCount": "Projekt", + "projectCountPlural": "Projekte", + "memberCount": "Mitglied", + "memberCountPlural": "Mitglieder", + "activeProjectCount": "Aktives Projekt", + "activeProjectCountPlural": "Aktive Projekte", + "overdueProjectCount": "Überfälliges Projekt", + "overdueProjectCountPlural": "Überfällige Projekte", + "unassignedMemberCount": "Nicht zugewiesenes Mitglied", + "unassignedMemberCountPlural": "Nicht zugewiesene Mitglieder", + "memberWithOverdueTaskCount": "Mitglied mit überfälliger Aufgabe", + "memberWithOverdueTaskCountPlural": "Mitglieder mit überfälligen Aufgaben", + + "teamsText": "Teams", + + "nameColumn": "Name", + "projectsColumn": "Projekte", + "membersColumn": "Mitglieder" +} diff --git a/worklenz-backend/src/public/locales/de/reporting-projects-drawer.json b/worklenz-backend/src/public/locales/de/reporting-projects-drawer.json new file mode 100644 index 00000000..3f335a6c --- /dev/null +++ b/worklenz-backend/src/public/locales/de/reporting-projects-drawer.json @@ -0,0 +1,59 @@ +{ + "exportButton": "Exportieren", + "membersButton": "Mitglieder", + "tasksButton": "Aufgaben", + "searchByNameInputPlaceholder": "Nach Namen suchen", + + "overviewTab": "Übersicht", + "membersTab": "Mitglieder", + "tasksTab": "Aufgaben", + + "completedTasksText": "Abgeschlossene Aufgaben", + "incompleteTasksText": "Unvollständige Aufgaben", + "overdueTasksText": "Überfällige Aufgaben", + "allocatedHoursText": "Zugewiesene Stunden", + "loggedHoursText": "Erfasste Stunden", + + "tasksText": "Aufgaben", + "allText": "Alle", + + "tasksByStatusText": "Aufgaben nach Status", + "tasksByPriorityText": "Aufgaben nach Priorität", + "tasksByDueDateText": "Aufgaben nach Fälligkeit", + + "todoText": "Zu erledigen", + "doingText": "Tun", + "doneText": "Erledigt", + "lowText": "Niedrig", + "mediumText": "Mittel", + "highText": "Hoch", + "completedText": "Abgeschlossen", + "upcomingText": "Bevorstehend", + "overdueText": "Überfällig", + "noDueDateText": "Kein Fälligkeitsdatum", + + "nameColumn": "Name", + "tasksCountColumn": "Anzahl Aufgaben", + "completedTasksColumn": "Abgeschlossene Aufgaben", + "incompleteTasksColumn": "Unvollständige Aufgaben", + "overdueTasksColumn": "Überfällige Aufgaben", + "contributionColumn": "Beitrag", + "progressColumn": "Fortschritt", + "loggedTimeColumn": "Erfasste Zeit", + "taskColumn": "Aufgabe", + "projectColumn": "Projekt", + "statusColumn": "Status", + "priorityColumn": "Priorität", + "phaseColumn": "Phase", + "dueDateColumn": "Fälligkeitsdatum", + "completedDateColumn": "Abschlussdatum", + "estimatedTimeColumn": "Geschätzte Zeit", + "overloggedTimeColumn": "Übererfasste Zeit", + "completedOnColumn": "Abgeschlossen am", + "daysOverdueColumn": "Tage überfällig", + + "groupByText": "Gruppieren nach:", + "statusText": "Status", + "priorityText": "Priorität", + "phaseText": "Phase" +} diff --git a/worklenz-backend/src/public/locales/de/reporting-projects-filters.json b/worklenz-backend/src/public/locales/de/reporting-projects-filters.json new file mode 100644 index 00000000..c48fa256 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/reporting-projects-filters.json @@ -0,0 +1,35 @@ +{ + "searchByNamePlaceholder": "Nach Namen suchen", + "searchByCategoryPlaceholder": "Nach Kategorie suchen", + + "statusText": "Status", + "healthText": "Gesundheit", + "categoryText": "Kategorie", + "projectManagerText": "Projektleiter", + "showFieldsText": "Felder anzeigen", + + "cancelledText": "Abgebrochen", + "blockedText": "Blockiert", + "onHoldText": "Pausiert", + "proposedText": "Vorgeschlagen", + "inPlanningText": "In Planung", + "inProgressText": "In Bearbeitung", + "completedText": "Abgeschlossen", + "continuousText": "Kontinuierlich", + + "notSetText": "Nicht festgelegt", + "needsAttentionText": "Benötigt Aufmerksamkeit", + "atRiskText": "Gefährdet", + "goodText": "Gut", + + "nameText": "Projekt", + "estimatedVsActualText": "Geplant vs. Tatsächlich", + "tasksProgressText": "Aufgabenfortschritt", + "lastActivityText": "Letzte Aktivität", + "datesText": "Start-/Enddatum", + "daysLeftText": "Tage übrig/überfällig", + "projectHealthText": "Projektstatus", + "projectUpdateText": "Projektupdate", + "clientText": "Kunde", + "teamText": "Team" +} diff --git a/worklenz-backend/src/public/locales/de/reporting-projects.json b/worklenz-backend/src/public/locales/de/reporting-projects.json new file mode 100644 index 00000000..0f63310b --- /dev/null +++ b/worklenz-backend/src/public/locales/de/reporting-projects.json @@ -0,0 +1,52 @@ +{ + "projectCount": "Projekt", + "projectCountPlural": "Projekte", + "includeArchivedButton": "Archivierte Projekte einschließen", + "exportButton": "Exportieren", + "excelButton": "Excel", + + "projectColumn": "Projekt", + "estimatedVsActualColumn": "Geschätzt vs. Tatsächlich", + "tasksProgressColumn": "Aufgabenfortschritt", + "lastActivityColumn": "Letzte Aktivität", + "statusColumn": "Status", + "datesColumn": "Start-/Enddatum", + "daysLeftColumn": "Tage übrig/überfällig", + "projectHealthColumn": "Projektzustand", + "categoryColumn": "Kategorie", + "projectUpdateColumn": "Projektupdate", + "clientColumn": "Kunde", + "teamColumn": "Team", + "projectManagerColumn": "Projektleiter", + + "openButton": "Öffnen", + + "estimatedText": "Geschätzt", + "actualText": "Tatsächlich", + + "todoText": "Zu erledigen", + "doingText": "Tun", + "doneText": "Erledigt", + + "cancelledText": "Abgebrochen", + "blockedText": "Blockiert", + "onHoldText": "Pausiert", + "proposedText": "Vorgeschlagen", + "inPlanningText": "In Planung", + "inProgressText": "In Bearbeitung", + "completedText": "Abgeschlossen", + "continuousText": "Kontinuierlich", + + "daysLeftText": "Tage übrig", + "dayLeftText": "Tag übrig", + "daysOverdueText": "Tage überfällig", + + "notSetText": "Nicht festgelegt", + "needsAttentionText": "Benötigt Aufmerksamkeit", + "atRiskText": "Gefährdet", + "goodText": "Gut", + + "setCategoryText": "Kategorie festlegen", + "searchByNameInputPlaceholder": "Nach Namen suchen", + "todayText": "Heute" +} diff --git a/worklenz-backend/src/public/locales/de/reporting-sidebar.json b/worklenz-backend/src/public/locales/de/reporting-sidebar.json new file mode 100644 index 00000000..74d6bfb9 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/reporting-sidebar.json @@ -0,0 +1,8 @@ +{ + "overview": "Übersicht", + "projects": "Projekte", + "members": "Mitglieder", + "timeReports": "Zeitberichte", + "estimateVsActual": "Schätzen vs. Tatsächlich", + "currentOrganizationTooltip": "Aktuelle Organisation" +} diff --git a/worklenz-backend/src/public/locales/de/schedule.json b/worklenz-backend/src/public/locales/de/schedule.json new file mode 100644 index 00000000..046c7bb0 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/schedule.json @@ -0,0 +1,39 @@ +{ + "today": "Heute", + "week": "Woche", + "month": "Monat", + + "settings": "Einstellungen", + "workingDays": "Arbeitstage", + "monday": "Montag", + "tuesday": "Dienstag", + "wednesday": "Mittwoch", + "thursday": "Donnerstag", + "friday": "Freitag", + "saturday": "Samstag", + "sunday": "Sonntag", + "workingHours": "Arbeitsstunden", + "hours": "Stunden", + "saveButton": "Speichern", + + "totalAllocation": "Gesamtzuteilung", + "timeLogged": "Zeiterfassung", + "remainingTime": "Verbleibende Zeit", + "total": "Gesamt", + "perDay": "Pro Tag", + "tasks": "Aufgaben", + "startDate": "Startdatum", + "endDate": "Enddatum", + + "hoursPerDay": "Stunden pro Tag", + "totalHours": "Gesamtstunden", + "deleteButton": "Löschen", + "cancelButton": "Abbrechen", + + "tabTitle": "Aufgaben ohne Start- & Enddatum", + + "allocatedTime": "Zugewiesene Zeit", + "totalLogged": "Gesamterfasst", + "loggedBillable": "Abrechenbar erfasst", + "loggedNonBillable": "Nicht abrechenbar erfasst" +} diff --git a/worklenz-backend/src/public/locales/de/settings/categories.json b/worklenz-backend/src/public/locales/de/settings/categories.json new file mode 100644 index 00000000..5694d11d --- /dev/null +++ b/worklenz-backend/src/public/locales/de/settings/categories.json @@ -0,0 +1,10 @@ +{ + "categoryColumn": "Kategorie", + "deleteConfirmationTitle": "Sind Sie sicher?", + "deleteConfirmationOk": "Ja", + "deleteConfirmationCancel": "Abbrechen", + "associatedTaskColumn": "Zugehörige Projekte", + "searchPlaceholder": "Nach Namen suchen", + "emptyText": "Kategorien können beim Aktualisieren oder Erstellen von Projekten angelegt werden.", + "colorChangeTooltip": "Zum Farbwechsel klicken" +} diff --git a/worklenz-backend/src/public/locales/de/settings/change-password.json b/worklenz-backend/src/public/locales/de/settings/change-password.json new file mode 100644 index 00000000..6b65a8cf --- /dev/null +++ b/worklenz-backend/src/public/locales/de/settings/change-password.json @@ -0,0 +1,15 @@ +{ + "title": "Passwort ändern", + "currentPassword": "Aktuelles Passwort", + "newPassword": "Neues Passwort", + "confirmPassword": "Passwort bestätigen", + "currentPasswordPlaceholder": "Aktuelles Passwort eingeben", + "newPasswordPlaceholder": "Neues Passwort", + "confirmPasswordPlaceholder": "Passwort bestätigen", + "currentPasswordRequired": "Bitte geben Sie Ihr aktuelles Passwort ein!", + "newPasswordRequired": "Bitte geben Sie Ihr neues Passwort ein!", + "passwordValidationError": "Das Passwort muss mindestens 8 Zeichen lang sein und einen Großbuchstaben, eine Zahl und ein Sonderzeichen enthalten.", + "passwordMismatch": "Die Passwörter stimmen nicht überein!", + "passwordRequirements": "Das neue Passwort muss mindestens 8 Zeichen lang sein und einen Großbuchstaben, eine Zahl und ein Sonderzeichen enthalten.", + "updateButton": "Passwort aktualisieren" +} diff --git a/worklenz-backend/src/public/locales/de/settings/clients.json b/worklenz-backend/src/public/locales/de/settings/clients.json new file mode 100644 index 00000000..d2982bdb --- /dev/null +++ b/worklenz-backend/src/public/locales/de/settings/clients.json @@ -0,0 +1,22 @@ +{ + "nameColumn": "Name", + "projectColumn": "Projekt", + "noProjectsAvailable": "Keine Projekte verfügbar", + "deleteConfirmationTitle": "Sind Sie sicher?", + "deleteConfirmationOk": "Ja", + "deleteConfirmationCancel": "Abbrechen", + "searchPlaceholder": "Nach Namen suchen", + "createClient": "Kunde anlegen", + "pinTooltip": "Zum Anheften an das Hauptmenü klicken", + "createClientDrawerTitle": "Kunde anlegen", + "updateClientDrawerTitle": "Kunde aktualisieren", + "nameLabel": "Name", + "namePlaceholder": "Name", + "nameRequiredError": "Bitte geben Sie einen Namen ein", + "createButton": "Anlegen", + "updateButton": "Aktualisieren", + "createClientSuccessMessage": "Kunde erfolgreich angelegt!", + "createClientErrorMessage": "Anlegen des Kunden fehlgeschlagen!", + "updateClientSuccessMessage": "Kunde erfolgreich aktualisiert!", + "updateClientErrorMessage": "Aktualisierung des Kunden fehlgeschlagen!" +} diff --git a/worklenz-backend/src/public/locales/de/settings/job-titles.json b/worklenz-backend/src/public/locales/de/settings/job-titles.json new file mode 100644 index 00000000..f4403ad1 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/settings/job-titles.json @@ -0,0 +1,20 @@ +{ + "nameColumn": "Name", + "deleteConfirmationTitle": "Sind Sie sicher?", + "deleteConfirmationOk": "Ja", + "deleteConfirmationCancel": "Abbrechen", + "searchPlaceholder": "Nach Namen suchen", + "createJobTitleButton": "Jobtitel erstellen", + "pinTooltip": "Zum Anheften an das Hauptmenü klicken", + "createJobTitleDrawerTitle": "Jobtitel erstellen", + "updateJobTitleDrawerTitle": "Jobtitel aktualisieren", + "nameLabel": "Name", + "namePlaceholder": "Name", + "nameRequiredError": "Bitte geben Sie einen Namen ein", + "createButton": "Erstellen", + "updateButton": "Aktualisieren", + "createJobTitleSuccessMessage": "Jobtitel erfolgreich erstellt!", + "createJobTitleErrorMessage": "Erstellung des Jobtitels fehlgeschlagen!", + "updateJobTitleSuccessMessage": "Jobtitel erfolgreich aktualisiert!", + "updateJobTitleErrorMessage": "Aktualisierung des Jobtitels fehlgeschlagen!" +} diff --git a/worklenz-backend/src/public/locales/de/settings/labels.json b/worklenz-backend/src/public/locales/de/settings/labels.json new file mode 100644 index 00000000..18b6a021 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/settings/labels.json @@ -0,0 +1,11 @@ +{ + "labelColumn": "Label", + "deleteConfirmationTitle": "Sind Sie sicher?", + "deleteConfirmationOk": "Ja", + "deleteConfirmationCancel": "Abbrechen", + "associatedTaskColumn": "Zugeordnete Aufgabenanzahl", + "searchPlaceholder": "Nach Name suchen", + "emptyText": "Labels können beim Aktualisieren oder Erstellen von Aufgaben erstellt werden.", + "pinTooltip": "Zum Anheften an das Hauptmenü klicken", + "colorChangeTooltip": "Zum Ändern der Farbe klicken" +} diff --git a/worklenz-backend/src/public/locales/de/settings/language.json b/worklenz-backend/src/public/locales/de/settings/language.json new file mode 100644 index 00000000..9e6bc27d --- /dev/null +++ b/worklenz-backend/src/public/locales/de/settings/language.json @@ -0,0 +1,7 @@ +{ + "language": "Sprache", + "language_required": "Sprache ist erforderlich", + "time_zone": "Zeitzone", + "time_zone_required": "Zeitzone ist erforderlich", + "save_changes": "Änderungen speichern" +} diff --git a/worklenz-backend/src/public/locales/de/settings/notifications.json b/worklenz-backend/src/public/locales/de/settings/notifications.json new file mode 100644 index 00000000..272542a0 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/settings/notifications.json @@ -0,0 +1,11 @@ +{ + "title": "Benachrichtigungseinstellungen", + "emailTitle": "E-Mail-Benachrichtigungen erhalten", + "emailDescription": "Dies beinhaltet neue Aufgaben-Zuweisungen", + "dailyDigestTitle": "Tägliche Übersicht erhalten", + "dailyDigestDescription": "Jeden Abend erhalten Sie eine Zusammenfassung der letzten Aktivitäten in Aufgaben.", + "popupTitle": "Popup-Benachrichtigungen auf meinem Computer anzeigen, wenn Worklenz geöffnet ist", + "popupDescription": "Popup-Benachrichtigungen können von Ihrem Browser blockiert werden. Ändern Sie Ihre Browser-Einstellungen, um diese zu erlauben.", + "unreadItemsTitle": "Anzahl ungelesener Elemente anzeigen", + "unreadItemsDescription": "Sie sehen Zähler für jede Benachrichtigung." +} diff --git a/worklenz-backend/src/public/locales/de/settings/profile.json b/worklenz-backend/src/public/locales/de/settings/profile.json new file mode 100644 index 00000000..4d7fc4cd --- /dev/null +++ b/worklenz-backend/src/public/locales/de/settings/profile.json @@ -0,0 +1,14 @@ +{ + "uploadError": "Sie können nur JPG/PNG-Dateien hochladen!", + "uploadSizeError": "Bilder müssen kleiner als 2MB sein!", + "upload": "Hochladen", + "nameLabel": "Name", + "nameRequiredError": "Name ist erforderlich", + "emailLabel": "E-Mail", + "emailRequiredError": "E-Mail ist erforderlich", + "saveChanges": "Änderungen speichern", + "profileJoinedText": "Vor einem Monat beigetreten", + "profileLastUpdatedText": "Vor einem Monat aktualisiert", + "avatarTooltip": "Klicken Sie zum Hochladen eines Avatars", + "title": "Profil-Einstellungen" +} diff --git a/worklenz-backend/src/public/locales/de/settings/project-templates.json b/worklenz-backend/src/public/locales/de/settings/project-templates.json new file mode 100644 index 00000000..6c6e23ca --- /dev/null +++ b/worklenz-backend/src/public/locales/de/settings/project-templates.json @@ -0,0 +1,8 @@ +{ + "nameColumn": "Name", + "editToolTip": "Bearbeiten", + "deleteToolTip": "Löschen", + "confirmText": "Sind Sie sicher?", + "okText": "Ja", + "cancelText": "Abbrechen" +} diff --git a/worklenz-backend/src/public/locales/de/settings/sidebar.json b/worklenz-backend/src/public/locales/de/settings/sidebar.json new file mode 100644 index 00000000..d4d8754d --- /dev/null +++ b/worklenz-backend/src/public/locales/de/settings/sidebar.json @@ -0,0 +1,14 @@ +{ + "profile": "Profil", + "notifications": "Benachrichtigungen", + "clients": "Kunden", + "job-titles": "Jobbezeichnungen", + "labels": "Labels", + "categories": "Kategorien", + "project-templates": "Projektvorlagen", + "task-templates": "Aufgabenvorlagen", + "team-members": "Teammitglieder", + "teams": "Teams", + "change-password": "Passwort ändern", + "language-and-region": "Sprache und Region" +} diff --git a/worklenz-backend/src/public/locales/de/settings/task-templates.json b/worklenz-backend/src/public/locales/de/settings/task-templates.json new file mode 100644 index 00000000..ffe93318 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/settings/task-templates.json @@ -0,0 +1,9 @@ +{ + "nameColumn": "Name", + "createdColumn": "Erstellt", + "editToolTip": "Bearbeiten", + "deleteToolTip": "Löschen", + "confirmText": "Sind Sie sicher?", + "okText": "Ja", + "cancelText": "Abbrechen" +} diff --git a/worklenz-backend/src/public/locales/de/settings/team-members.json b/worklenz-backend/src/public/locales/de/settings/team-members.json new file mode 100644 index 00000000..d223f08e --- /dev/null +++ b/worklenz-backend/src/public/locales/de/settings/team-members.json @@ -0,0 +1,47 @@ +{ + "title": "Teammitglieder", + "nameColumn": "Name", + "projectsColumn": "Projekte", + "emailColumn": "E-Mail", + "teamAccessColumn": "Team-Zugriff", + "memberCount": "Mitglied", + "membersCountPlural": "Mitglieder", + "searchPlaceholder": "Mitglieder nach Namen suchen", + "pinTooltip": "Mitgliederliste aktualisieren", + "addMemberButton": "Neues Mitglied hinzufügen", + "editTooltip": "Mitglied bearbeiten", + "deactivateTooltip": "Mitglied deaktivieren", + "activateTooltip": "Mitglied aktivieren", + "deleteTooltip": "Mitglied löschen", + "confirmDeleteTitle": "Sind Sie sicher, dass Sie dieses Mitglied löschen möchten?", + "confirmActivateTitle": "Sind Sie sicher, dass Sie den Status dieses Mitglieds ändern möchten?", + "okText": "Ja, fortfahren", + "cancelText": "Nein, abbrechen", + "deactivatedText": "(Aktuell deaktiviert)", + "pendingInvitationText": "(Einladung ausstehend)", + "addMemberDrawerTitle": "Neues Teammitglied hinzufügen", + "updateMemberDrawerTitle": "Teammitglied aktualisieren", + "addMemberEmailHint": "Mitglieder werden dem Team hinzugefügt, unabhängig vom Status der Einladungsannahme", + "memberEmailLabel": "E-Mail(s)", + "memberEmailPlaceholder": "E-Mail-Adresse des Teammitglieds eingeben", + "memberEmailRequiredError": "Bitte geben Sie eine gültige E-Mail-Adresse ein", + "jobTitleLabel": "Jobtitel", + "jobTitlePlaceholder": "Jobtitel auswählen oder suchen (optional)", + "memberAccessLabel": "Zugriffslevel", + "addToTeamButton": "Mitglied zum Team hinzufügen", + "updateButton": "Änderungen speichern", + "resendInvitationButton": "Einladungs-E-Mail erneut senden", + "invitationSentSuccessMessage": "Team-Einladung erfolgreich versendet!", + "createMemberSuccessMessage": "Neues Teammitglied erfolgreich hinzugefügt!", + "createMemberErrorMessage": "Hinzufügen des Teammitglieds fehlgeschlagen. Bitte versuchen Sie es erneut.", + "updateMemberSuccessMessage": "Teammitglied erfolgreich aktualisiert!", + "updateMemberErrorMessage": "Aktualisierung des Teammitglieds fehlgeschlagen. Bitte versuchen Sie es erneut.", + "memberText": "Mitglied", + "adminText": "Administrator", + "ownerText": "Team-Besitzer", + "addedText": "Hinzugefügt", + "updatedText": "Aktualisiert", + "noResultFound": "Geben Sie eine E-Mail-Adresse ein und drücken Sie Enter...", + "jobTitlesFetchError": "Fehler beim Abrufen der Jobtitel", + "invitationResent": "Einladung erfolgreich erneut gesendet!" +} diff --git a/worklenz-backend/src/public/locales/de/settings/teams.json b/worklenz-backend/src/public/locales/de/settings/teams.json new file mode 100644 index 00000000..bf39215d --- /dev/null +++ b/worklenz-backend/src/public/locales/de/settings/teams.json @@ -0,0 +1,16 @@ +{ + "title": "Teams", + "team": "Team", + "teams": "Teams", + "name": "Name", + "created": "Erstellt", + "ownsBy": "Gehört zu", + "edit": "Bearbeiten", + "editTeam": "Team bearbeiten", + "pinTooltip": "Klicken Sie hier, um dies im Hauptmenü zu fixieren", + "editTeamName": "Team-Name bearbeiten", + "updateName": "Name aktualisieren", + "namePlaceholder": "Name", + "nameRequired": "Bitte geben Sie einen Namen ein", + "updateFailed": "Änderung des Team-Namens fehlgeschlagen!" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/de/task-drawer/task-drawer-info-tab.json b/worklenz-backend/src/public/locales/de/task-drawer/task-drawer-info-tab.json new file mode 100644 index 00000000..aece79f0 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/task-drawer/task-drawer-info-tab.json @@ -0,0 +1,29 @@ +{ + "details": { + "task-key": "Aufgabenschlüssel", + "phase": "Phase", + "assignees": "Zugewiesene", + "due-date": "Fälligkeitsdatum", + "time-estimation": "Zeitschätzung", + "priority": "Priorität", + "labels": "Labels", + "billable": "Abrechenbar", + "notify": "Benachrichtigen", + "when-done-notify": "Bei Fertigstellung benachrichtigen", + "start-date": "Startdatum", + "end-date": "Enddatum", + "hide-start-date": "Startdatum ausblenden", + "show-start-date": "Startdatum anzeigen", + "hours": "Stunden", + "minutes": "Minuten" + }, + "description": { + "title": "Beschreibung", + "placeholder": "Fügen Sie eine detailliertere Beschreibung hinzu..." + }, + "subTasks": { + "title": "Unteraufgaben", + "add-sub-task": "+ Unteraufgabe hinzufügen", + "refresh-sub-tasks": "Unteraufgaben aktualisieren" + } +} diff --git a/worklenz-backend/src/public/locales/de/task-drawer/task-drawer.json b/worklenz-backend/src/public/locales/de/task-drawer/task-drawer.json new file mode 100644 index 00000000..62e3f881 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/task-drawer/task-drawer.json @@ -0,0 +1,123 @@ +{ + "taskHeader": { + "taskNamePlaceholder": "Geben Sie Ihre Aufgabe ein", + "deleteTask": "Aufgabe löschen" + }, + "taskInfoTab": { + "title": "Info", + "details": { + "title": "Details", + "task-key": "Aufgaben-Schlüssel", + "phase": "Phase", + "assignees": "Beauftragte", + "due-date": "Fälligkeitsdatum", + "time-estimation": "Zeitschätzung", + "priority": "Priorität", + "labels": "Labels", + "billable": "Abrechenbar", + "notify": "Benachrichtigen", + "when-done-notify": "Bei Abschluss benachrichtigen", + "start-date": "Startdatum", + "end-date": "Enddatum", + "hide-start-date": "Startdatum ausblenden", + "show-start-date": "Startdatum anzeigen", + "hours": "Stunden", + "minutes": "Minuten", + "progressValue": "Fortschrittswert", + "progressValueTooltip": "Fortschritt in Prozent einstellen (0-100%)", + "progressValueRequired": "Bitte geben Sie einen Fortschrittswert ein", + "progressValueRange": "Fortschritt muss zwischen 0 und 100 liegen", + "taskWeight": "Aufgabengewicht", + "taskWeightTooltip": "Gewicht dieser Teilaufgabe festlegen (Prozent)", + "taskWeightRequired": "Bitte geben Sie ein Aufgabengewicht ein", + "taskWeightRange": "Gewicht muss zwischen 0 und 100 liegen", + "recurring": "Wiederkehrend" + }, + "labels": { + "labelInputPlaceholder": "Suchen oder erstellen", + "labelsSelectorInputTip": "Enter drücken zum Erstellen" + }, + "description": { + "title": "Beschreibung", + "placeholder": "Detailliertere Beschreibung hinzufügen..." + }, + "subTasks": { + "title": "Teilaufgaben", + "addSubTask": "Teilaufgabe hinzufügen", + "addSubTaskInputPlaceholder": "Geben Sie Ihre Aufgabe ein und drücken Sie Enter", + "refreshSubTasks": "Teilaufgaben aktualisieren", + "edit": "Bearbeiten", + "delete": "Löschen", + "confirmDeleteSubTask": "Sind Sie sicher, dass Sie diese Teilaufgabe löschen möchten?", + "deleteSubTask": "Teilaufgabe löschen" + }, + "dependencies": { + "title": "Abhängigkeiten", + "addDependency": "+ Neue Abhängigkeit hinzufügen", + "blockedBy": "Blockiert von", + "searchTask": "Aufgabe suchen", + "noTasksFound": "Keine Aufgaben gefunden", + "confirmDeleteDependency": "Sind Sie sicher, dass Sie löschen möchten?" + }, + "attachments": { + "title": "Anhänge", + "chooseOrDropFileToUpload": "Datei zum Hochladen wählen oder ablegen", + "uploading": "Wird hochgeladen..." + }, + "comments": { + "title": "Kommentare", + "addComment": "+ Neuen Kommentar hinzufügen", + "noComments": "Noch keine Kommentare. Seien Sie der Erste!", + "delete": "Löschen", + "confirmDeleteComment": "Sind Sie sicher, dass Sie diesen Kommentar löschen möchten?", + "addCommentPlaceholder": "Kommentar hinzufügen...", + "cancel": "Abbrechen", + "commentButton": "Kommentieren", + "attachFiles": "Dateien anhängen", + "addMoreFiles": "Weitere Dateien hinzufügen", + "selectedFiles": "Ausgewählte Dateien (Bis zu 25MB, Maximum {count})", + "maxFilesError": "Sie können maximal {count} Dateien hochladen", + "processFilesError": "Fehler beim Verarbeiten der Dateien", + "addCommentError": "Bitte fügen Sie einen Kommentar hinzu oder hängen Sie Dateien an", + "createdBy": "Erstellt {{time}} von {{user}}", + "updatedTime": "Aktualisiert {{time}}" + }, + "searchInputPlaceholder": "Nach Name suchen", + "pendingInvitation": "Ausstehende Einladung" + }, + "taskTimeLogTab": { + "title": "Zeiterfassung", + "addTimeLog": "Neuen Zeiteintrag hinzufügen", + "totalLogged": "Gesamt erfasst", + "exportToExcel": "Nach Excel exportieren", + "noTimeLogsFound": "Keine Zeiteinträge gefunden", + "timeLogForm": { + "date": "Datum", + "startTime": "Startzeit", + "endTime": "Endzeit", + "workDescription": "Arbeitsbeschreibung", + "descriptionPlaceholder": "Beschreibung hinzufügen", + "logTime": "Zeit erfassen", + "updateTime": "Zeit aktualisieren", + "cancel": "Abbrechen", + "selectDateError": "Bitte wählen Sie ein Datum", + "selectStartTimeError": "Bitte wählen Sie eine Startzeit", + "selectEndTimeError": "Bitte wählen Sie eine Endzeit", + "endTimeAfterStartError": "Endzeit muss nach der Startzeit liegen" + } + }, + "taskActivityLogTab": { + "title": "Aktivitätsprotokoll", + "add": "HINZUFÜGEN", + "remove": "ENTFERNEN", + "none": "Keine", + "weight": "Gewicht", + "createdTask": "hat die Aufgabe erstellt." + }, + "taskProgress": { + "markAsDoneTitle": "Aufgabe als erledigt markieren?", + "confirmMarkAsDone": "Ja, als erledigt markieren", + "cancelMarkAsDone": "Nein, aktuellen Status beibehalten", + "markAsDoneDescription": "Sie haben den Fortschritt auf 100% gesetzt. Möchten Sie den Aufgabenstatus auf \"Erledigt\" aktualisieren?" + } +} diff --git a/worklenz-backend/src/public/locales/de/task-list-filters.json b/worklenz-backend/src/public/locales/de/task-list-filters.json new file mode 100644 index 00000000..0854c34f --- /dev/null +++ b/worklenz-backend/src/public/locales/de/task-list-filters.json @@ -0,0 +1,85 @@ +{ + "searchButton": "Suchen", + "resetButton": "Zurücksetzen", + "searchInputPlaceholder": "Nach Namen suchen", + + "sortText": "Sortieren", + "statusText": "Status", + "phaseText": "Phase", + "memberText": "Mitglieder", + "assigneesText": "Zugewiesene", + "priorityText": "Priorität", + "labelsText": "Labels", + "membersText": "Mitglieder", + "groupByText": "Gruppieren nach", + "showArchivedText": "Archivierte anzeigen", + "showFieldsText": "Felder anzeigen", + "keyText": "Schlüssel", + "taskText": "Aufgabe", + "descriptionText": "Beschreibung", + "phasesText": "Phasen", + "listText": "Liste", + "progressText": "Fortschritt", + "timeTrackingText": "Zeiterfassung", + "timetrackingText": "Zeiterfassung", + "estimationText": "Schätzung", + "startDateText": "Startdatum", + "startdateText": "Startdatum", + "endDateText": "Enddatum", + "dueDateText": "Fälligkeitsdatum", + "duedateText": "Fälligkeitsdatum", + "completedDateText": "Abschlussdatum", + "completeddateText": "Abschlussdatum", + "createdDateText": "Erstellungsdatum", + "createddateText": "Erstellungsdatum", + "lastUpdatedText": "Zuletzt aktualisiert", + "lastupdatedText": "Zuletzt aktualisiert", + "reporterText": "Melder", + "dueTimeText": "Fällige Zeit", + "duetimeText": "Fällige Zeit", + + "lowText": "Niedrig", + "mediumText": "Mittel", + "highText": "Hoch", + + "createStatusButtonTooltip": "Status-Einstellungen", + "configPhaseButtonTooltip": "Phasen-Einstellungen", + "noLabelsFound": "Keine Labels gefunden", + + "addStatusButton": "Status hinzufügen", + "addPhaseButton": "Phase hinzufügen", + + "createStatus": "Status erstellen", + "name": "Name", + "category": "Kategorie", + "selectCategory": "Kategorie auswählen", + "pleaseEnterAName": "Bitte geben Sie einen Namen ein", + "pleaseSelectACategory": "Bitte wählen Sie eine Kategorie aus", + "create": "Erstellen", + + "searchTasks": "Aufgaben suchen...", + "searchPlaceholder": "Suchen...", + "fieldsText": "Felder", + "loadingFilters": "Filter werden geladen...", + "noOptionsFound": "Keine Optionen gefunden", + "filtersActive": "Filter aktiv", + "filterActive": "Filter aktiv", + "clearAll": "Alle löschen", + "clearing": "Wird gelöscht...", + "cancel": "Stornieren", + "search": "Suchen", + "groupedBy": "Gruppiert nach", + "manageStatuses": "Status verwalten", + "managePhases": "Phasen verwalten", + "dragToReorderStatuses": "Ziehen Sie Status, um sie neu zu ordnen. Jeder Status kann eine andere Kategorie haben.", + "enterNewStatusName": "Neuen Statusnamen eingeben...", + "addStatus": "Status hinzufügen", + "noStatusesFound": "Keine Status gefunden. Erstellen Sie Ihren ersten Status oben.", + "deleteStatus": "Status löschen", + "deleteStatusConfirm": "Sind Sie sicher, dass Sie diesen Status löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "rename": "Umbenennen", + "delete": "Löschen", + "enterStatusName": "Statusnamen eingeben", + "selectCategory": "Kategorie auswählen", + "close": "Schließen" +} diff --git a/worklenz-backend/src/public/locales/de/task-list-table.json b/worklenz-backend/src/public/locales/de/task-list-table.json new file mode 100644 index 00000000..9c2ff314 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/task-list-table.json @@ -0,0 +1,136 @@ +{ + "keyColumn": "Schlüssel", + "taskColumn": "Aufgabe", + "descriptionColumn": "Beschreibung", + "progressColumn": "Fortschritt", + "membersColumn": "Mitglieder", + "assigneesColumn": "Zugewiesene", + "labelsColumn": "Labels", + "phasesColumn": "Phasen", + "phaseColumn": "Phase", + "statusColumn": "Status", + "priorityColumn": "Priorität", + "timeTrackingColumn": "Zeiterfassung", + "timetrackingColumn": "Zeiterfassung", + "estimationColumn": "Schätzung", + "startDateColumn": "Startdatum", + "startdateColumn": "Startdatum", + "dueDateColumn": "Fälligkeitsdatum", + "duedateColumn": "Fälligkeitsdatum", + "completedDateColumn": "Abschlussdatum", + "completeddateColumn": "Abschlussdatum", + "createdDateColumn": "Erstellungsdatum", + "createddateColumn": "Erstellungsdatum", + "lastUpdatedColumn": "Zuletzt aktualisiert", + "lastupdatedColumn": "Zuletzt aktualisiert", + "reporterColumn": "Melder", + "dueTimeColumn": "Fällige Zeit", + "todoSelectorText": "Zu erledigen", + "doingSelectorText": "Tun", + "doneSelectorText": "Erledigt", + + "lowSelectorText": "Niedrig", + "mediumSelectorText": "Mittel", + "highSelectorText": "Hoch", + + "selectText": "Auswählen", + "labelsSelectorInputTip": "Enter drücken zum Erstellen!", + + "addTaskText": "Aufgabe hinzufügen", + "addSubTaskText": "+ Unteraufgabe hinzufügen", + "addTaskInputPlaceholder": "Aufgabe eingeben und Enter drücken", + "noTasksInGroup": "Keine Aufgaben in dieser Gruppe", + + "openButton": "Öffnen", + "okButton": "OK", + + "noLabelsFound": "Keine Labels gefunden", + "searchInputPlaceholder": "Suchen oder erstellen", + "assigneeSelectorInviteButton": "Neues Mitglied per E-Mail einladen", + "labelInputPlaceholder": "Suchen oder erstellen", + "searchLabelsPlaceholder": "Labels suchen...", + "createLabelButton": "\"{{name}}\" erstellen", + "manageLabelsPath": "Einstellungen → Labels", + + "pendingInvitation": "Einladung ausstehend", + + "contextMenu": { + "assignToMe": "Mir zuweisen", + "moveTo": "Verschieben nach", + "unarchive": "Dearchivieren", + "archive": "Archivieren", + "convertToSubTask": "In Unteraufgabe umwandeln", + "convertToTask": "In Aufgabe umwandeln", + "delete": "Löschen", + "searchByNameInputPlaceholder": "Nach Namen suchen" + }, + "setDueDate": "Fälligkeitsdatum festlegen", + "setStartDate": "Startdatum festlegen", + "clearDueDate": "Fälligkeitsdatum löschen", + "clearStartDate": "Startdatum löschen", + "dueDatePlaceholder": "Fälligkeitsdatum", + "startDatePlaceholder": "Startdatum", + + "emptyStates": { + "noTaskGroups": "Keine Aufgabengruppen gefunden", + "noTaskGroupsDescription": "Aufgaben werden hier angezeigt, wenn sie erstellt oder Filter angewendet werden.", + "errorPrefix": "Fehler:", + "dragTaskFallback": "Aufgabe" + }, + + "customColumns": { + "addCustomColumn": "Benutzerdefinierte Spalte hinzufügen", + "customColumnHeader": "Benutzerdefinierte Spalte", + "customColumnSettings": "Einstellungen für benutzerdefinierte Spalte", + "noCustomValue": "Kein Wert", + "peopleField": "Personenfeld", + "noDate": "Kein Datum", + "unsupportedField": "Nicht unterstützter Feldtyp", + + "modal": { + "addFieldTitle": "Feld hinzufügen", + "editFieldTitle": "Feld bearbeiten", + "fieldTitle": "Feldtitel", + "fieldTitleRequired": "Feldtitel ist erforderlich", + "columnTitlePlaceholder": "Spaltentitel", + "type": "Typ", + "deleteConfirmTitle": "Sind Sie sicher, dass Sie diese benutzerdefinierte Spalte löschen möchten?", + "deleteConfirmDescription": "Diese Aktion kann nicht rückgängig gemacht werden. Alle mit dieser Spalte verbundenen Daten werden dauerhaft gelöscht.", + "deleteButton": "Löschen", + "cancelButton": "Abbrechen", + "createButton": "Erstellen", + "updateButton": "Aktualisieren", + "createSuccessMessage": "Benutzerdefinierte Spalte erfolgreich erstellt", + "updateSuccessMessage": "Benutzerdefinierte Spalte erfolgreich aktualisiert", + "deleteSuccessMessage": "Benutzerdefinierte Spalte erfolgreich gelöscht", + "deleteErrorMessage": "Fehler beim Löschen der benutzerdefinierten Spalte", + "createErrorMessage": "Fehler beim Erstellen der benutzerdefinierten Spalte", + "updateErrorMessage": "Fehler beim Aktualisieren der benutzerdefinierten Spalte" + }, + + "fieldTypes": { + "people": "Personen", + "number": "Zahl", + "date": "Datum", + "selection": "Auswahl", + "checkbox": "Kontrollkästchen", + "labels": "Etiketten", + "key": "Schlüssel", + "formula": "Formel" + } + }, + + "indicators": { + "tooltips": { + "subtasks": "{{count}} Unteraufgabe", + "subtasks_plural": "{{count}} Unteraufgaben", + "comments": "{{count}} Kommentar", + "comments_plural": "{{count}} Kommentare", + "attachments": "{{count}} Anhang", + "attachments_plural": "{{count}} Anhänge", + "subscribers": "Aufgabe hat Abonnenten", + "dependencies": "Aufgabe hat Abhängigkeiten", + "recurring": "Wiederkehrende Aufgabe" + } + } +} diff --git a/worklenz-backend/src/public/locales/de/task-management.json b/worklenz-backend/src/public/locales/de/task-management.json new file mode 100644 index 00000000..b20d94a4 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/task-management.json @@ -0,0 +1,21 @@ +{ + "noTasksInGroup": "Keine Aufgaben in dieser Gruppe", + "noTasksInGroupDescription": "Fügen Sie eine Aufgabe hinzu, um zu beginnen", + "addFirstTask": "Fügen Sie Ihre erste Aufgabe hinzu", + "openTask": "Öffnen", + "subtask": "Unteraufgabe", + "subtasks": "Unteraufgaben", + "comment": "Kommentar", + "comments": "Kommentare", + "attachment": "Anhang", + "attachments": "Anhänge", + "enterSubtaskName": "Unteraufgabenname eingeben...", + "add": "Hinzufügen", + "cancel": "Abbrechen", + "renameGroup": "Gruppe umbenennen", + "renameStatus": "Status umbenennen", + "renamePhase": "Phase umbenennen", + "changeCategory": "Kategorie ändern", + "clickToEditGroupName": "Klicken Sie, um den Gruppennamen zu bearbeiten", + "enterGroupName": "Gruppennamen eingeben" +} diff --git a/worklenz-backend/src/public/locales/de/task-template-drawer.json b/worklenz-backend/src/public/locales/de/task-template-drawer.json new file mode 100644 index 00000000..21dbe369 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/task-template-drawer.json @@ -0,0 +1,11 @@ +{ + "createTaskTemplate": "Aufgabenvorlage erstellen", + "editTaskTemplate": "Aufgabenvorlage bearbeiten", + "cancelText": "Abbrechen", + "saveText": "Speichern", + "templateNameText": "Vorlagenname", + "selectedTasks": "Ausgewählte Aufgaben", + "removeTask": "Entfernen", + "cancelButton": "Abbrechen", + "saveButton": "Speichern" +} diff --git a/worklenz-backend/src/public/locales/de/tasks/task-table-bulk-actions.json b/worklenz-backend/src/public/locales/de/tasks/task-table-bulk-actions.json new file mode 100644 index 00000000..e8b039f2 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/tasks/task-table-bulk-actions.json @@ -0,0 +1,41 @@ +{ + "taskSelected": "Aufgabe ausgewählt", + "tasksSelected": "Aufgaben ausgewählt", + "changeStatus": "Status/Priorität/Phase ändern", + "changeLabel": "Label ändern", + "assignToMe": "Mir zuweisen", + "changeAssignees": "Zuständige ändern", + "archive": "Archivieren", + "unarchive": "Dearchivieren", + "delete": "Löschen", + "moreOptions": "Weitere Optionen", + "deselectAll": "Alle abwählen", + "status": "Status", + "priority": "Priorität", + "phase": "Phase", + "member": "Mitglied", + "createTaskTemplate": "Aufgabenvorlage erstellen", + "apply": "Anwenden", + "createLabel": "+ Label erstellen", + "searchOrCreateLabel": "Label suchen oder erstellen...", + "hitEnterToCreate": "Enter drücken zum Erstellen", + "labelExists": "Label existiert bereits", + "pendingInvitation": "Einladung ausstehend", + "noMatchingLabels": "Keine passenden Labels", + "noLabels": "Keine Labels", + "CHANGE_STATUS": "Status ändern", + "CHANGE_PRIORITY": "Priorität ändern", + "CHANGE_PHASE": "Phase ändern", + "ADD_LABELS": "Labels hinzufügen", + "ASSIGN_TO_ME": "Mir zuweisen", + "ASSIGN_MEMBERS": "Mitglieder zuweisen", + "ARCHIVE": "Archivieren", + "DELETE": "Löschen", + "CANCEL": "Abbrechen", + "CLEAR_SELECTION": "Auswahl löschen", + "TASKS_SELECTED": "{{count}} Aufgabe ausgewählt", + "TASKS_SELECTED_plural": "{{count}} Aufgaben ausgewählt", + "DELETE_TASKS_CONFIRM": "{{count}} Aufgabe löschen?", + "DELETE_TASKS_CONFIRM_plural": "{{count}} Aufgaben löschen?", + "DELETE_TASKS_WARNING": "Diese Aktion kann nicht rückgängig gemacht werden." +} diff --git a/worklenz-backend/src/public/locales/de/template-drawer.json b/worklenz-backend/src/public/locales/de/template-drawer.json new file mode 100644 index 00000000..8655504d --- /dev/null +++ b/worklenz-backend/src/public/locales/de/template-drawer.json @@ -0,0 +1,19 @@ +{ + "title": "Aufgabenvorlage bearbeiten", + "cancelText": "Abbrechen", + "saveText": "Speichern", + "templateNameText": "Vorlagenname", + "selectedTasks": "Ausgewählte Aufgaben", + "removeTask": "Entfernen", + "description": "Beschreibung", + "phase": "Phase", + "statuses": "Status", + "priorities": "Prioritäten", + "labels": "Labels", + "tasks": "Aufgaben", + "noTemplateSelected": "Keine Vorlage ausgewählt", + "noDescription": "Keine Beschreibung", + "worklenzTemplates": "Worklenz-Vorlagen", + "yourTemplatesLibrary": "Ihre Bibliothek", + "searchTemplates": "Vorlagen durchsuchen" +} diff --git a/worklenz-backend/src/public/locales/de/templateDrawer.json b/worklenz-backend/src/public/locales/de/templateDrawer.json new file mode 100644 index 00000000..571ed15f --- /dev/null +++ b/worklenz-backend/src/public/locales/de/templateDrawer.json @@ -0,0 +1,23 @@ +{ + "bugTracking": "Fehlerverfolgung", + "construction": "Bauwesen", + "designCreative": "Design & Kreatives", + "education": "Bildung", + "finance": "Finanzen", + "hrRecruiting": "Personalwesen & Recruiting", + "informationTechnology": "Informationstechnologie", + "legal": "Rechtliches", + "manufacturing": "Produktion", + "marketing": "Marketing", + "nonprofit": "Gemeinnützig", + "personalUse": "Persönliche Nutzung", + "salesCRM": "Vertrieb & CRM", + "serviceConsulting": "Dienstleistungen & Beratung", + "softwareDevelopment": "Softwareentwicklung", + "description": "Beschreibung", + "phase": "Phase", + "statuses": "Status", + "priorities": "Prioritäten", + "labels": "Labels", + "tasks": "Aufgaben" +} diff --git a/worklenz-backend/src/public/locales/de/time-report.json b/worklenz-backend/src/public/locales/de/time-report.json new file mode 100644 index 00000000..efadbb8a --- /dev/null +++ b/worklenz-backend/src/public/locales/de/time-report.json @@ -0,0 +1,44 @@ +{ + "includeArchivedProjects": "Archivierte Projekte einschließen", + "export": "Exportieren", + "timeSheet": "Stundenzettel", + + "searchByName": "Nach Namen suchen", + "selectAll": "Alle auswählen", + "teams": "Teams", + + "searchByProject": "Nach Projektnamen suchen", + "projects": "Projekte", + + "searchByCategory": "Nach Kategorienamen suchen", + "categories": "Kategorien", + + "billable": "Abrechenbar", + "nonBillable": "Nicht abrechenbar", + + "total": "Gesamt", + + "projectsTimeSheet": "Projekt-Zeiterfassung", + + "loggedTime": "Erfasste Zeit (Stunden)", + + "exportToExcel": "Nach Excel exportieren", + "logged": "erfasst", + "for": "für", + + "membersTimeSheet": "Mitglieder-Zeiterfassung", + "member": "Mitglied", + + "estimatedVsActual": "Geschätzt vs. Tatsächlich", + "workingDays": "Arbeitstage", + "manDays": "Manntage", + "days": "Tage", + "estimatedDays": "Geschätzt Tage", + "actualDays": "Tatsächliche Tage", + + "noCategories": "Keine Kategorien gefunden", + "noCategory": "Keine Kategorie", + "noProjects": "Keine Projekte gefunden", + "noTeams": "Keine Teams gefunden", + "noData": "Keine Daten gefunden" +} diff --git a/worklenz-backend/src/public/locales/de/unauthorized.json b/worklenz-backend/src/public/locales/de/unauthorized.json new file mode 100644 index 00000000..4384ad80 --- /dev/null +++ b/worklenz-backend/src/public/locales/de/unauthorized.json @@ -0,0 +1,5 @@ +{ + "title": "Unbefugt!", + "subtitle": "Sie sind nicht berechtigt, auf diese Seite zuzugreifen", + "button": "Zur Startseite" +} diff --git a/worklenz-backend/src/public/locales/en/404-page.json b/worklenz-backend/src/public/locales/en/404-page.json new file mode 100644 index 00000000..a93627f1 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/404-page.json @@ -0,0 +1,4 @@ +{ + "doesNotExistText": "Sorry, the page you visited does not exist.", + "backHomeButton": "Back Home" +} diff --git a/worklenz-backend/src/public/locales/en/account-setup.json b/worklenz-backend/src/public/locales/en/account-setup.json new file mode 100644 index 00000000..5e71ca40 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/account-setup.json @@ -0,0 +1,31 @@ +{ + "continue": "Continue", + + "setupYourAccount": "Setup Your Worklenz Account.", + "organizationStepTitle": "Name Your Organization", + "organizationStepLabel": "Pick a name for your Worklenz account.", + + "projectStepTitle": "Create your first project", + "projectStepLabel": "What project are you working on right now?", + "projectStepPlaceholder": "e.g. Marketing Plan", + + "tasksStepTitle": "Create your first tasks", + "tasksStepLabel": "Type a few tasks that you are going to do in", + "tasksStepAddAnother": "Add another", + + "emailPlaceholder": "Email address", + "invalidEmail": "Please enter a valid email address", + "or": "or", + "templateButton": "Import from template", + "goBack": "Go Back", + "cancel": "Cancel", + "create": "Create", + "templateDrawerTitle": "Select from templates", + "step3InputLabel": "Invite with email", + "addAnother": "Add another", + "skipForNow": "Skip for now", + "formTitle": "Create your first task.", + "step3Title": "Invite your team to work with", + "maxMembers": " (You can invite up to 5 members)", + "maxTasks": " (You can create up to 5 tasks)" +} diff --git a/worklenz-backend/src/public/locales/en/admin-center/current-bill.json b/worklenz-backend/src/public/locales/en/admin-center/current-bill.json new file mode 100644 index 00000000..fe840789 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/admin-center/current-bill.json @@ -0,0 +1,121 @@ +{ + "title": "Billings", + "currentBill": "Current Bill", + "configuration": "Configuration", + "currentPlanDetails": "Current Plan Details", + "upgradePlan": "Upgrade Plan", + "cardBodyText01": "Free trial", + "cardBodyText02": "(Your trial plan expires in 1 month 19 days)", + "redeemCode": "Redeem Code", + "accountStorage": "Account Storage", + "used": "Used:", + "remaining": "Remaining:", + "charges": "Charges", + "tooltip": "Charges for the current billing cycle", + "description": "Description", + "billingPeriod": "Billing Period", + "billStatus": "Bill Status", + "perUserValue": "Per User Value", + "users": "Users", + + "amount": "Amount", + "invoices": "Invoices", + "transactionId": "Transaction ID", + "transactionDate": "Transaction Date", + "paymentMethod": "Payment Method", + "status": "Status", + "ltdUsers": "You can add up to {{ltd_users}} users.", + + "totalSeats": "Total seats", + "availableSeats": "Available seats", + "addMoreSeats": "Add more seats", + + "drawerTitle": "Redeem Code", + "label": "Redeem Code", + "drawerPlaceholder": "Enter your redeem code", + "redeemSubmit": "Submit", + + "modalTitle": "Select the best plan for your team", + "seatLabel": "No of seats", + "freePlan": "Free Plan", + "startup": "Startup", + "business": "Business", + "tag": "Most Popular", + "enterprise": "Enterprise", + + "freeSubtitle": "free forever", + "freeUsers": "Best for personal use", + "freeText01": "100MB storage", + "freeText02": "3 projects", + "freeText03": "5 team members", + + "startupSubtitle": "FLAT RATE / month", + "startupUsers": "Upto 15 users", + "startupText01": "25GB storage", + "startupText02": "Unlimited active projects", + "startupText03": "Schedule", + "startupText04": "Reporting", + "startupText05": "Subscribe to projects", + + "businessSubtitle": "user / month", + "businessUsers": "16 - 200 users", + + "enterpriseUsers": "200 - 500+ users", + + "footerTitle": "Please provide us with a contact number we can use to reach you.", + "footerLabel": "Contact Number", + "footerButton": "Contact us", + + "redeemCodePlaceHolder": "Enter your redeem code", + "submit": "Submit", + + "trialPlan": "Free Trial", + "trialExpireDate": "Valid until {{trial_expire_date}}", + "trialExpired": "Your free trial expired {{trial_expire_string}}", + "trialInProgress": "Your free trial expires {{trial_expire_string}}", + + "required": "This field is required", + "invalidCode": "Invalid code", + + "selectPlan": "Select the best plan for your team", + "changeSubscriptionPlan": "Change your subscription plan", + "noOfSeats": "Number of seats", + "annualPlan": "Pro - Annual", + "monthlyPlan": "Pro - Monthly", + "freeForever": "Free Forever", + "bestForPersonalUse": "Best for personal use", + "storage": "Storage", + "projects": "Projects", + "teamMembers": "Team Members", + "unlimitedTeamMembers": "Unlimited Team Members", + "unlimitedActiveProjects": "Unlimited active projects", + "schedule": "Schedule", + "reporting": "Reporting", + "subscribeToProjects": "Subscribe to projects", + "billedAnnually": "Billed Annually", + "billedMonthly": "Billed Monthly", + + "pausePlan": "Pause Plan", + "resumePlan": "Resume Plan", + "changePlan": "Change Plan", + "cancelPlan": "Cancel Plan", + + "perMonthPerUser": "per user/month", + "viewInvoice": "View Invoice", + "switchToFreePlan": "Switch to Free Plan", + + "expirestoday": "today", + "expirestomorrow": "tomorrow", + "expiredDaysAgo": "{{days}} days ago", + + "continueWith": "Continue with {{plan}}", + "changeToPlan": "Change to {{plan}}", + "creditPlan": "Credit Plan", + "customPlan": "Custom Plan", + "planValidTill": "Your plan is valid till {{date}}", + "purchaseSeatsText": "To continue, you'll need to purchase additional seats.", + "currentSeatsText": "You currently have {{seats}} seats available.", + "selectSeatsText": "Please select the number of additional seats to purchase.", + "purchase": "Purchase", + "contactSales": "Contact sales" +} diff --git a/worklenz-backend/src/public/locales/en/admin-center/overview.json b/worklenz-backend/src/public/locales/en/admin-center/overview.json new file mode 100644 index 00000000..efc42855 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/admin-center/overview.json @@ -0,0 +1,8 @@ +{ + "overview": "Overview", + "name": "Organization Name", + "owner": "Organization Owner", + "admins": "Organization Admins", + "contactNumber": "Add Contact Number", + "edit": "Edit" +} diff --git a/worklenz-backend/src/public/locales/en/admin-center/projects.json b/worklenz-backend/src/public/locales/en/admin-center/projects.json new file mode 100644 index 00000000..4e491d73 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/admin-center/projects.json @@ -0,0 +1,12 @@ +{ + "membersCount": "Members Count", + "createdAt": "Created at", + "projectName": "Project Name", + "teamName": "Team Name", + "refreshProjects": "Refresh projects", + "searchPlaceholder": "Search by project name", + "deleteProject": "Are you sure you want to delete this project?", + "confirm": "Confirm", + "cancel": "Cancel", + "delete": "Delete Project" +} diff --git a/worklenz-backend/src/public/locales/en/admin-center/sidebar.json b/worklenz-backend/src/public/locales/en/admin-center/sidebar.json new file mode 100644 index 00000000..3b03d499 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/admin-center/sidebar.json @@ -0,0 +1,8 @@ +{ + "overview": "Overview", + "users": "Users", + "teams": "Teams", + "billing": "Billing", + "projects": "Projects", + "adminCenter": "Admin Center" +} diff --git a/worklenz-backend/src/public/locales/en/admin-center/teams.json b/worklenz-backend/src/public/locales/en/admin-center/teams.json new file mode 100644 index 00000000..bf829a87 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/admin-center/teams.json @@ -0,0 +1,35 @@ +{ + "title": "Teams", + "subtitle": "teams", + "tooltip": "Refresh teams", + "placeholder": "Search by name", + "addTeam": "Add Team", + "team": "Team", + "membersCount": "Members Count", + "members": "Members", + "drawerTitle": "Create New Team", + "label": "Team Name", + "drawerPlaceholder": "Name", + "create": "Create", + "delete": "Delete", + "settings": "Settings", + "popTitle": "Are you sure?", + "message": "Please enter a Name", + "teamSettings": "Team Settings", + "teamName": "Team Name", + "teamDescription": "Team Description", + "teamMembers": "Team Members", + "teamMembersCount": "Team Members Count", + "teamMembersPlaceholder": "Search by name", + "addMember": "Add Member", + "add": "Add", + "update": "Update", + "teamNamePlaceholder": "Name of the team", + "user": "User", + "role": "Role", + "owner": "Owner", + "admin": "Admin", + "member": "Member", + "cannotChangeOwnerRole": "Owner role cannot be changed", + "pendingInvitation": "Pending invitation" +} diff --git a/worklenz-backend/src/public/locales/en/admin-center/users.json b/worklenz-backend/src/public/locales/en/admin-center/users.json new file mode 100644 index 00000000..7e462ef6 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/admin-center/users.json @@ -0,0 +1,9 @@ +{ + "title": "Users", + "subTitle": "users", + "placeholder": "Search by name", + "user": "User", + "email": "Email", + "lastActivity": "Last Activity", + "refresh": "Refresh users" +} diff --git a/worklenz-backend/src/public/locales/en/all-project-list.json b/worklenz-backend/src/public/locales/en/all-project-list.json new file mode 100644 index 00000000..ab98cb6b --- /dev/null +++ b/worklenz-backend/src/public/locales/en/all-project-list.json @@ -0,0 +1,34 @@ +{ + "name": "Name", + "client": "Client", + "category": "Category", + "status": "Status", + "tasksProgress": "Tasks Progress", + "updated_at": "Last Updated", + "members": "Members", + "setting": "Settings", + "projects": "Projects", + "refreshProjects": "Refresh projects", + "all": "All", + "favorites": "Favorites", + "archived": "Archived", + "placeholder": "Search by name", + "archive": "Archive", + "unarchive": "Unarchive", + "archiveConfirm": "Are you sure you want to archive this project?", + "unarchiveConfirm": "Are you sure you want to unarchive this project?", + "yes": "Yes", + "no": "No", + "clickToFilter": "Click to filter by", + "noProjects": "No projects found", + "addToFavourites": "Add to favourites", + "list": "List", + "group": "Group", + "listView": "List View", + "groupView": "Group View", + "groupBy": { + "category": "Category", + "client": "Client" + }, + "noPermission": "You don't have permission to perform this action" +} diff --git a/worklenz-backend/src/public/locales/en/auth/auth-common.json b/worklenz-backend/src/public/locales/en/auth/auth-common.json new file mode 100644 index 00000000..94803a12 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/auth/auth-common.json @@ -0,0 +1,5 @@ +{ + "loggingOut": "Logging out...", + "authenticating": "Authenticating...", + "gettingThingsReady": "Getting things ready for you..." +} diff --git a/worklenz-backend/src/public/locales/en/auth/forgot-password.json b/worklenz-backend/src/public/locales/en/auth/forgot-password.json new file mode 100644 index 00000000..3534c388 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/auth/forgot-password.json @@ -0,0 +1,12 @@ +{ + "headerDescription": "Reset your password", + "emailLabel": "Email", + "emailPlaceholder": "Enter your email", + "emailRequired": "Please enter your Email!", + "resetPasswordButton": "Reset Password", + "returnToLoginButton": "Return to Login", + "passwordResetSuccessMessage": "A password reset link has been sent to your email.", + "orText": "OR", + "successTitle": "Reset instruction sent!", + "successMessage": "Reset information has been sent to your email. Please check your email." +} diff --git a/worklenz-backend/src/public/locales/en/auth/login.json b/worklenz-backend/src/public/locales/en/auth/login.json new file mode 100644 index 00000000..b77e7fba --- /dev/null +++ b/worklenz-backend/src/public/locales/en/auth/login.json @@ -0,0 +1,27 @@ +{ + "headerDescription": "Login to your account", + "emailLabel": "Email", + "emailPlaceholder": "Enter your email", + "emailRequired": "Please enter your Email!", + "passwordLabel": "Password", + "passwordPlaceholder": "Enter your password", + "passwordRequired": "Please enter your Password!", + "rememberMe": "Remember me", + "loginButton": "Log in", + "signupButton": "Sign up", + "forgotPasswordButton": "Forgot password?", + "signInWithGoogleButton": "Sign in with Google", + "dontHaveAccountText": "Don’t have an account?", + "orText": "OR", + "successMessage": "You have successfully logged in!", + "loginError": "Login failed", + "googleLoginError": "Google login failed", + "validationMessages": { + "email": "Please enter a valid email address", + "password": "Password must be at least 8 characters long" + }, + "errorMessages": { + "loginErrorTitle": "Login failed", + "loginErrorMessage": "Please check your email and password and try again" + } +} diff --git a/worklenz-backend/src/public/locales/en/auth/signup.json b/worklenz-backend/src/public/locales/en/auth/signup.json new file mode 100644 index 00000000..af4611ba --- /dev/null +++ b/worklenz-backend/src/public/locales/en/auth/signup.json @@ -0,0 +1,29 @@ +{ + "headerDescription": "Sign up to get started", + "nameLabel": "Full Name", + "namePlaceholder": "Enter your full name", + "nameRequired": "Please enter your full name!", + "nameMinCharacterRequired": "Full name must be at least 4 characters!", + "emailLabel": "Email", + "emailPlaceholder": "Enter your email", + "emailRequired": "Please enter your Email!", + "passwordLabel": "Password", + "passwordPlaceholder": "Enter your password", + "passwordRequired": "Please enter your Password!", + "passwordMinCharacterRequired": "Password must be at least 8 characters!", + "passwordPatternRequired": "Password does not meet the requirements!", + "strongPasswordPlaceholder": "Enter a stronger password", + "passwordValidationAltText": "Password must include at least 8 characters with upper and lower case letters, a number, and a symbol.", + "signupSuccessMessage": "You have successfully signed up!", + "privacyPolicyLink": "Privacy Policy", + "termsOfUseLink": "Terms of Use", + "bySigningUpText": "By signing up, you agree to our", + "andText": "and", + "signupButton": "Sign up", + "signInWithGoogleButton": "Sign in with Google", + "alreadyHaveAccountText": "Already have an account?", + "loginButton": "Login", + "orText": "OR", + "reCAPTCHAVerificationError": "reCAPTCHA Verification Error", + "reCAPTCHAVerificationErrorMessage": "We were unable to verify your reCAPTCHA. Please try again." +} diff --git a/worklenz-backend/src/public/locales/en/auth/verify-reset-email.json b/worklenz-backend/src/public/locales/en/auth/verify-reset-email.json new file mode 100644 index 00000000..e685e193 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/auth/verify-reset-email.json @@ -0,0 +1,14 @@ +{ + "title": "Verify Reset Email", + "description": "Enter your new password", + "placeholder": "Enter your new password", + "confirmPasswordPlaceholder": "Confirm your new password", + "passwordHint": "Minimum of 8 characters, with upper and lowercase and a number and a symbol.", + "resetPasswordButton": "Reset password", + "orText": "Or", + "resendResetEmail": "Resend reset email", + "passwordRequired": "Please enter your new password", + "returnToLoginButton": "Return to Login", + "confirmPasswordRequired": "Please confirm your new password", + "passwordMismatch": "The two passwords do not match" +} diff --git a/worklenz-backend/src/public/locales/en/common.json b/worklenz-backend/src/public/locales/en/common.json new file mode 100644 index 00000000..815560be --- /dev/null +++ b/worklenz-backend/src/public/locales/en/common.json @@ -0,0 +1,9 @@ +{ + "login-success": "Login successful!", + "login-failed": "Login failed. Please check your credentials and try again.", + "signup-success": "Signup successful! Welcome aboard.", + "signup-failed": "Signup failed. Please ensure all required fields are filled and try again.", + "reconnecting": "Disconnected from server.", + "connection-lost": "Failed to connect to server. Please check your internet connection.", + "connection-restored": "Connected to server successfully" +} diff --git a/worklenz-backend/src/public/locales/en/create-first-project-form.json b/worklenz-backend/src/public/locales/en/create-first-project-form.json new file mode 100644 index 00000000..337f4a10 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/create-first-project-form.json @@ -0,0 +1,13 @@ +{ + "formTitle": "Create your first project", + "inputLabel": "What project are you working on right now?", + "or": "or", + "templateButton": "Import from template", + "createFromTemplate": "Create from template", + "goBack": "Go Back", + "continue": "Continue", + "cancel": "Cancel", + "create": "Create", + "templateDrawerTitle": "Select from templates", + "createProject": "Create Project" +} diff --git a/worklenz-backend/src/public/locales/en/create-first-tasks.json b/worklenz-backend/src/public/locales/en/create-first-tasks.json new file mode 100644 index 00000000..1447f355 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/create-first-tasks.json @@ -0,0 +1,7 @@ +{ + "formTitle": "Create your first task.", + "inputLable": "Type a few tasks that you are going to do in", + "addAnother": "Add another", + "goBack": "Go back", + "continue": "Continue" +} diff --git a/worklenz-backend/src/public/locales/en/home.json b/worklenz-backend/src/public/locales/en/home.json new file mode 100644 index 00000000..ccf40936 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/home.json @@ -0,0 +1,46 @@ +{ + "todoList": { + "title": "To do list", + "refreshTasks": "Refresh tasks", + "addTask": "+ Add Task", + "noTasks": "No tasks", + "pressEnter": "Press", + "toCreate": "to create.", + "markAsDone": "Mark as done" + }, + "projects": { + "title": "Projects", + "refreshProjects": "Refresh projects", + "noRecentProjects": "You are currently not assigned to any project.", + "noFavouriteProjects": "No projects have been marked as favorites.", + "recent": "Recent", + "favourites": "Favourites" + }, + "tasks": { + "assignedToMe": "Assigned to me", + "assignedByMe": "Assigned by me", + "all": "All", + "today": "Today", + "upcoming": "Upcoming", + "overdue": "Overdue", + "noDueDate": "No due date", + "noTasks": "No tasks to show.", + "addTask": "+ Add task", + "name": "Name", + "project": "Project", + "status": "Status", + "dueDate": "Due Date", + "dueDatePlaceholder": "Set Due Date", + "tomorrow": "Tomorrow", + "nextWeek": "Next Week", + "nextMonth": "Next Month", + "projectRequired": "Please select a project", + "pressTabToSelectDueDateAndProject": "Press Tab to select a due date and a project", + "dueOn": "Tasks due on", + "taskRequired": "Please add a task", + "list": "List", + "calendar": "Calendar", + "tasks": "Tasks", + "refresh": "Refresh" + } +} diff --git a/worklenz-backend/src/public/locales/en/invite-initial-team-members.json b/worklenz-backend/src/public/locales/en/invite-initial-team-members.json new file mode 100644 index 00000000..09f23e87 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/invite-initial-team-members.json @@ -0,0 +1,8 @@ +{ + "formTitle": "Invite your team to work with", + "inputLable": "Invite with email", + "addAnother": "Add another", + "goBack": "Go back", + "continue": "Continue", + "skipForNow": "Skip for now" +} diff --git a/worklenz-backend/src/public/locales/en/kanban-board.json b/worklenz-backend/src/public/locales/en/kanban-board.json new file mode 100644 index 00000000..e295a6c6 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/kanban-board.json @@ -0,0 +1,33 @@ +{ + "rename": "Rename", + "delete": "Delete", + "addTask": "Add Task", + "addSectionButton": "Add Section", + "changeCategory": "Change category", + + "deleteTooltip": "Delete", + "deleteConfirmationTitle": "Are you sure?", + "deleteConfirmationOk": "Yes", + "deleteConfirmationCancel": "Cancel", + + "dueDate": "Due date", + "cancel": "Cancel", + + "today": "Today", + "tomorrow": "Tomorrow", + "assignToMe": "Assign to me", + "archive": "Archive", + + "newTaskNamePlaceholder": "Write a task Name", + "newSubtaskNamePlaceholder": "Write a subtask Name", + "untitledSection": "Untitled section", + "unmapped": "Unmapped", + "clickToChangeDate": "Click to change date", + "noDueDate": "No due date", + "save": "Save", + "clear": "Clear", + "nextWeek": "Next week", + "noSubtasks": "No subtasks", + "showSubtasks": "Show subtasks", + "hideSubtasks": "Hide subtasks" +} diff --git a/worklenz-backend/src/public/locales/en/license-expired.json b/worklenz-backend/src/public/locales/en/license-expired.json new file mode 100644 index 00000000..e4556064 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/license-expired.json @@ -0,0 +1,6 @@ +{ + "title": "Your Worklenz trial has expired!", + "subtitle": "Please upgrade now.", + "button": "Upgrade now", + "checking": "Checking subscription status..." +} diff --git a/worklenz-backend/src/public/locales/en/navbar.json b/worklenz-backend/src/public/locales/en/navbar.json new file mode 100644 index 00000000..e7e22cb3 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/navbar.json @@ -0,0 +1,31 @@ +{ + "logoAlt": "Worklenz Logo", + "home": "Home", + "projects": "Projects", + "schedule": "Schedule", + "reporting": "Reporting", + "clients": "Clients", + "teams": "Teams", + "labels": "Labels", + "jobTitles": "Job Titles", + "upgradePlan": "Upgrade Plan", + "upgradePlanTooltip": "Upgrade Plan", + "invite": "Invite", + "inviteTooltip": "Invite team members to join", + "switchTeamTooltip": "Switch team", + "help": "Help", + "notificationTooltip": "View notifications", + "profileTooltip": "View profile", + "adminCenter": "Admin Center", + "settings": "Settings", + "logOut": "Log Out", + "notificationsDrawer": { + "read": "Read notifications", + "unread": "Unread notifications", + "markAsRead": "Mark as read", + "readAndJoin": "Read & Join", + "accept": "Accept", + "acceptAndJoin": "Accept & Join", + "noNotifications": "No notifications" + } +} diff --git a/worklenz-backend/src/public/locales/en/organization-name-form.json b/worklenz-backend/src/public/locales/en/organization-name-form.json new file mode 100644 index 00000000..26ffa973 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/organization-name-form.json @@ -0,0 +1,5 @@ +{ + "nameYourOrganization": "Name your organization.", + "worklenzAccountTitle": "Pick a name for your Worklenz account.", + "continue": "Continue" +} diff --git a/worklenz-backend/src/public/locales/en/phases-drawer.json b/worklenz-backend/src/public/locales/en/phases-drawer.json new file mode 100644 index 00000000..10ad78a4 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/phases-drawer.json @@ -0,0 +1,19 @@ +{ + "configurePhases": "Configure Phases", + "phaseLabel": "Phase Label", + "enterPhaseName": "Enter a name for phase label", + "addOption": "Add Option", + "phaseOptions": "Phase Options:", + "dragToReorderPhases": "Drag phases to reorder them. Each phase can have a different color.", + "enterNewPhaseName": "Enter new phase name...", + "addPhase": "Add Phase", + "noPhasesFound": "No phases found. Create your first phase above.", + "deletePhase": "Delete Phase", + "deletePhaseConfirm": "Are you sure you want to delete this phase? This action cannot be undone.", + "rename": "Rename", + "delete": "Delete", + "enterPhaseName": "Enter phase name", + "selectColor": "Select color", + "managePhases": "Manage Phases", + "close": "Close" +} diff --git a/worklenz-backend/src/public/locales/en/project-drawer.json b/worklenz-backend/src/public/locales/en/project-drawer.json new file mode 100644 index 00000000..be553a01 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/project-drawer.json @@ -0,0 +1,52 @@ +{ + "createProject": "Create Project", + "editProject": "Edit Project", + "enterCategoryName": "Enter a name for the category", + "hitEnterToCreate": "Hit enter to create!", + "enterNotes": "Notes", + "youCanManageClientsUnderSettings": "You can manage clients under Settings", + "addCategory": "Add a category to the project", + "newCategory": "New Category", + "notes": "Notes", + "startDate": "Start Date", + "endDate": "End Date", + "estimateWorkingDays": "Estimate working days", + "estimateManDays": "Estimate man days", + "hoursPerDay": "Hours per day", + "create": "Create", + "update": "Update", + "delete": "Delete", + "typeToSearchClients": "Type to search clients", + "projectColor": "Project Color", + "pleaseEnterAName": "Please enter a name", + "enterProjectName": "Enter project name", + "name": "Name", + "status": "Status", + "health": "Health", + "category": "Category", + "projectManager": "Project Manager", + "client": "Client", + "deleteConfirmation": "Are you sure you want to delete?", + "deleteConfirmationDescription": "This will remove all associated data and cannot be undone.", + "yes": "Yes", + "no": "No", + "createdAt": "Created", + "updatedAt": "Updated", + "by": "by", + "add": "Add", + "asClient": "as client", + "createClient": "Create client", + "searchInputPlaceholder": "Search by name or email", + "hoursPerDayValidationMessage": "Hours per day must be a number between 1 and 24", + "workingDaysValidationMessage": "Working days must be a positive number", + "manDaysValidationMessage": "Man days must be a positive number", + "noPermission": "No permission", + "progressSettings": "Progress Settings", + "manualProgress": "Manual Progress", + "manualProgressTooltip": "Allow manual progress updates for tasks without subtasks", + "weightedProgress": "Weighted Progress", + "weightedProgressTooltip": "Calculate progress based on subtask weights", + "timeProgress": "Time-based Progress", + "timeProgressTooltip": "Calculate progress based on estimated time", + "enterProjectKey": "Enter project key" +} diff --git a/worklenz-backend/src/public/locales/en/project-view-files.json b/worklenz-backend/src/public/locales/en/project-view-files.json new file mode 100644 index 00000000..12672620 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/project-view-files.json @@ -0,0 +1,14 @@ +{ + "nameColumn": "Name", + "attachedTaskColumn": "Attached Task", + "sizeColumn": "Size", + "uploadedByColumn": "Uploaded By", + "uploadedAtColumn": "Uploaded At", + "fileIconAlt": "File icon", + "titleDescriptionText": "All attachments to tasks in this project will appear here.", + "deleteConfirmationTitle": "Are you sure?", + "deleteConfirmationOk": "Yes", + "deleteConfirmationCancel": "Cancel", + "segmentedTooltip": "Coming soon! Switch between list view and thumbnail view.", + "emptyText": "There are no attachments in the project." +} diff --git a/worklenz-backend/src/public/locales/en/project-view-insights.json b/worklenz-backend/src/public/locales/en/project-view-insights.json new file mode 100644 index 00000000..1b174a85 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/project-view-insights.json @@ -0,0 +1,41 @@ +{ + "overview": { + "title": "Overview", + "statusOverview": "Status Overview", + "priorityOverview": "Priority Overview", + "lastUpdatedTasks": "Last Updated Tasks" + }, + "members": { + "title": "Members", + "tooltip": "Members", + "tasksByMembers": "Tasks by members", + "tasksByMembersTooltip": "Tasks by members", + "name": "Name", + "taskCount": "Task Count", + "contribution": "Contribution", + "completed": "Completed", + "incomplete": "Incomplete", + "overdue": "Overdue", + "progress": "Progress" + }, + "tasks": { + "overdueTasks": "Overdue Tasks", + "overLoggedTasks": "Over logged Tasks", + "tasksCompletedEarly": "Tasks completed early", + "tasksCompletedLate": "Tasks completed late", + "overLoggedTasksTooltip": "Tasks that has time logged past their estimated time", + "overdueTasksTooltip": "Tasks that are past their due date" + }, + "common": { + "seeAll": "See all", + "totalLoggedHours": "Total logged hours", + "totalEstimation": "Total estimation", + "completedTasks": "Completed tasks", + "incompleteTasks": "Incomplete tasks", + "overdueTasks": "Overdue tasks", + "overdueTasksTooltip": "Tasks that are past their due date", + "totalLoggedHoursTooltip": "Task estimation and logged time for tasks.", + "includeArchivedTasks": "Include Archived Tasks", + "export": "Export" + } +} diff --git a/worklenz-backend/src/public/locales/en/project-view-members.json b/worklenz-backend/src/public/locales/en/project-view-members.json new file mode 100644 index 00000000..6ed8ddf0 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/project-view-members.json @@ -0,0 +1,17 @@ +{ + "nameColumn": "Name", + "jobTitleColumn": "Job Title", + "emailColumn": "Email", + "tasksColumn": "Tasks", + "taskProgressColumn": "Task Progress", + "accessColumn": "Access", + "fileIconAlt": "File icon", + "deleteConfirmationTitle": "Are you sure?", + "deleteConfirmationOk": "Yes", + "deleteConfirmationCancel": "Cancel", + "refreshButtonTooltip": "Refresh members", + "deleteButtonTooltip": "Remove from project", + "memberCount": "Member", + "membersCountPlural": "Members", + "emptyText": "There are no attachments in the project." +} diff --git a/worklenz-backend/src/public/locales/en/project-view-updates.json b/worklenz-backend/src/public/locales/en/project-view-updates.json new file mode 100644 index 00000000..d7140ad8 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/project-view-updates.json @@ -0,0 +1,6 @@ +{ + "inputPlaceholder": "Add a comment..", + "addButton": "Add", + "cancelButton": "Cancel", + "deleteButton": "Delete" +} diff --git a/worklenz-backend/src/public/locales/en/project-view.json b/worklenz-backend/src/public/locales/en/project-view.json new file mode 100644 index 00000000..82ab21f2 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/project-view.json @@ -0,0 +1,14 @@ +{ + "taskList": "Task List", + "board": "Board", + "insights": "Insights", + "files": "Files", + "members": "Members", + "updates": "Updates", + "projectView": "Project View", + "loading": "Loading project...", + "error": "Error loading project", + "pinnedTab": "Pinned as default tab", + "pinTab": "Pin as default tab", + "unpinTab": "Unpin default tab" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/en/project-view/import-task-templates.json b/worklenz-backend/src/public/locales/en/project-view/import-task-templates.json new file mode 100644 index 00000000..d732aa08 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/project-view/import-task-templates.json @@ -0,0 +1,11 @@ +{ + "importTaskTemplate": "Import Task Template", + "templateName": "Template Name", + "templateDescription": "Template Description", + "selectedTasks": "Selected Tasks", + "tasks": "Tasks", + "templates": "Templates", + "remove": "Remove", + "cancel": "Cancel", + "import": "Import" +} diff --git a/worklenz-backend/src/public/locales/en/project-view/project-member-drawer.json b/worklenz-backend/src/public/locales/en/project-view/project-member-drawer.json new file mode 100644 index 00000000..ad2d60c8 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/project-view/project-member-drawer.json @@ -0,0 +1,7 @@ +{ + "title": "Project Members", + "searchLabel": "Add members by adding their name or email", + "searchPlaceholder": "Type name or email", + "inviteAsAMember": "Invite as a member", + "inviteNewMemberByEmail": "Invite new member by email" +} diff --git a/worklenz-backend/src/public/locales/en/project-view/project-view-header.json b/worklenz-backend/src/public/locales/en/project-view/project-view-header.json new file mode 100644 index 00000000..1bbb6c15 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/project-view/project-view-header.json @@ -0,0 +1,30 @@ +{ + "importTasks": "Import tasks", + "importTask": "Import task", + "createTask": "Create task", + "settings": "Settings", + "subscribe": "Subscribe", + "unsubscribe": "Unsubscribe", + "deleteProject": "Delete project", + "startDate": "Start date", + "endDate": "End date", + "projectSettings": "Project settings", + "projectSummary": "Project summary", + "receiveProjectSummary": "Receive a project summary every evening.", + "refreshProject": "Refresh project", + "saveAsTemplate": "Save as template", + "invite": "Invite", + "share": "Share", + "subscribeTooltip": "Subscribe to project notifications", + "unsubscribeTooltip": "Unsubscribe from project notifications", + "refreshTooltip": "Refresh project data", + "settingsTooltip": "Open project settings", + "saveAsTemplateTooltip": "Save this project as a template", + "inviteTooltip": "Invite team members to this project", + "createTaskTooltip": "Create a new task", + "importTaskTooltip": "Import task from template", + "navigateBackTooltip": "Go back to projects list", + "projectStatusTooltip": "Project status", + "projectDatesInfo": "Project timeline information", + "projectCategoryTooltip": "Project category" +} diff --git a/worklenz-backend/src/public/locales/en/project-view/save-as-template.json b/worklenz-backend/src/public/locales/en/project-view/save-as-template.json new file mode 100644 index 00000000..2b3e7564 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/project-view/save-as-template.json @@ -0,0 +1,27 @@ +{ + "title": "Save as Template", + "templateName": "Template Name", + "includes": "What should be included in the template from the project ?", + "includesOptions": { + "statuses": "Statuses", + "phases": "Phases", + "labels": "Labels" + }, + "taskIncludes": "What should be included in the template from the tasks ?", + "taskIncludesOptions": { + "statuses": "Statuses", + "phases": "Phases", + "labels": "Labels", + "name": "Name", + "priority": "Priority", + "status": "Status", + "phase": "Phase", + "label": "Label", + "timeEstimate": "Time Estimate", + "description": "Description", + "subTasks": "Sub Tasks" + }, + "cancel": "Cancel", + "save": "Save", + "templateNamePlaceholder": "Enter template name" +} diff --git a/worklenz-backend/src/public/locales/en/reporting-members-drawer.json b/worklenz-backend/src/public/locales/en/reporting-members-drawer.json new file mode 100644 index 00000000..cca01177 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/reporting-members-drawer.json @@ -0,0 +1,90 @@ +{ + "exportButton": "Export", + "timeLogsButton": "TimeLogs", + "activityLogsButton": "Activity Logs", + "tasksButton": "Tasks", + "searchByNameInputPlaceholder": "Search by name", + + "overviewTab": "Overview", + "timeLogsTab": "Time Logs", + "activityLogsTab": "Activity Logs", + "tasksTab": "Tasks", + + "projectsText": "Projects", + "totalTasksText": "Total Tasks", + "assignedTasksText": "Assigned Tasks", + "completedTasksText": "Completed Tasks", + "ongoingTasksText": "Ongoing Tasks", + "overdueTasksText": "Overdue Tasks", + "loggedHoursText": "Logged Hours", + + "tasksText": "Tasks", + "allText": "All", + + "tasksByProjectsText": "Tasks By Projects", + "tasksByStatusText": "Tasks By Status", + "tasksByPriorityText": "Tasks By Priority", + + "todoText": "To Do", + "doingText": "Doing", + "doneText": "Done", + "lowText": "Low", + "mediumText": "Medium", + "highText": "High", + + "billableButton": "Billable", + "billableText": "Billable", + "nonBillableText": "Non Billable", + + "timeLogsEmptyPlaceholder": "No time logs to show", + "loggedText": "Logged", + "forText": "for", + "inText": "in", + "updatedText": "Updated", + "fromText": "From", + "toText": "to", + "withinText": "within", + + "activityLogsEmptyPlaceholder": "No activity logs to show", + + "filterByText": "Filter by:", + "selectProjectPlaceholder": "Select Project", + + "taskColumn": "Task", + "nameColumn": "Name", + "projectColumn": "Project", + "statusColumn": "Status", + "priorityColumn": "Priority", + "dueDateColumn": "Due Date", + "completedDateColumn": "Completed Date", + "estimatedTimeColumn": "Estimated Time", + "loggedTimeColumn": "Logged Time", + "overloggedTimeColumn": "Overlogged Time", + "daysLeftColumn": "Days Left/Overdue", + "startDateColumn": "Start Date", + "endDateColumn": "End Date", + "actualTimeColumn": "Actual Time", + "projectHealthColumn": "Project Health", + "categoryColumn": "Category", + "projectManagerColumn": "Project Manager", + + "tasksStatsOverviewDrawerTitle": "'s Tasks", + "projectsStatsOverviewDrawerTitle": "'s Projects", + + "cancelledText": "Cancelled", + "blockedText": "Blocked", + "onHoldText": "On Hold", + "proposedText": "Proposed", + "inPlanningText": "In Planning", + "inProgressText": "In Progress", + "completedText": "Completed", + "continuousText": "Continuous", + + "daysLeftText": "days left", + "daysOverdueText": "days overdue", + + "notSetText": "NotSet", + "needsAttentionText": "Needs Attention", + "atRiskText": "At Risk", + "goodText": "Good" +} diff --git a/worklenz-backend/src/public/locales/en/reporting-members.json b/worklenz-backend/src/public/locales/en/reporting-members.json new file mode 100644 index 00000000..a8035dcd --- /dev/null +++ b/worklenz-backend/src/public/locales/en/reporting-members.json @@ -0,0 +1,35 @@ +{ + "yesterdayText": "Yesterday", + "lastSevenDaysText": "Last 7 Days", + "lastWeekText": "Last Week", + "lastThirtyDaysText": "Last 30 Days", + "lastMonthText": "Last Month", + "lastThreeMonthsText": "Last 3 Months", + "allTimeText": "All Time", + "customRangeText": "Custom range", + "startDateInputPlaceholder": "Start date", + "EndDateInputPlaceholder": "End date", + "filterButton": "Filter", + + "membersTitle": "Members", + "includeArchivedButton": "Include Archived Projects", + "exportButton": "Export", + "excelButton": "Excel", + "searchByNameInputPlaceholder": "Search by name", + + "memberColumn": "Member", + "tasksProgressColumn": "Tasks Progress", + "tasksAssignedColumn": "Tasks Assigned", + "completedTasksColumn": "Completed Tasks", + "overdueTasksColumn": "Overdue Tasks", + "ongoingTasksColumn": "Ongoing Tasks", + + "tasksAssignedColumnTooltip": "Tasks assigned on selected date range", + "overdueTasksColumnTooltip": "Tasks overdue for end of the selected date range", + "completedTasksColumnTooltip": "Tasks completed on selected date range", + "ongoingTasksColumnTooltip": "Started tasks not completed yet", + + "todoText": "To Do", + "doingText": "Doing", + "doneText": "Done" +} diff --git a/worklenz-backend/src/public/locales/en/reporting-overview-drawer.json b/worklenz-backend/src/public/locales/en/reporting-overview-drawer.json new file mode 100644 index 00000000..84fab1b8 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/reporting-overview-drawer.json @@ -0,0 +1,39 @@ +{ + "exportButton": "Export", + "projectsButton": "Projects", + "membersButton": "Members", + "searchByNameInputPlaceholder": "Search by name", + + "overviewTab": "Overview", + "projectsTab": "Projects", + "membersTab": "Members", + + "projectsByStatusText": "Projects By Status", + "projectsByCategoryText": "Projects By Category", + "projectsByHealthText": "Projects By Health", + + "projectsText": "Projects", + "allText": "All", + + "cancelledText": "Cancelled", + "blockedText": "Blocked", + "onHoldText": "On Hold", + "proposedText": "Proposed", + "inPlanningText": "In Planning", + "inProgressText": "In Progress", + "completedText": "Completed", + "continuousText": "Continuous", + + "notSetText": "Not Set", + "needsAttentionText": "Needs Attention", + "atRiskText": "At Risk", + "goodText": "Good", + + "nameColumn": "Name", + "emailColumn": "Email", + "projectsColumn": "Projects", + "tasksColumn": "Tasks", + "overdueTasksColumn": "Overdue Tasks", + "completedTasksColumn": "Completed Tasks", + "ongoingTasksColumn": "Ongoing Tasks" +} diff --git a/worklenz-backend/src/public/locales/en/reporting-overview.json b/worklenz-backend/src/public/locales/en/reporting-overview.json new file mode 100644 index 00000000..73faffd3 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/reporting-overview.json @@ -0,0 +1,25 @@ +{ + "overviewTitle": "Overview", + "includeArchivedButton": "Include Archived Projects", + + "teamCount": "Team", + "teamCountPlural": "Teams", + "projectCount": "Project", + "projectCountPlural": "Projects", + "memberCount": "Member", + "memberCountPlural": "Members", + "activeProjectCount": "Active Project", + "activeProjectCountPlural": "Active Projects", + "overdueProjectCount": "Overdue Project", + "overdueProjectCountPlural": "Overdue Projects", + "unassignedMemberCount": "Unassigned Member", + "unassignedMemberCountPlural": "Unassigned Members", + "memberWithOverdueTaskCount": "Member With Overdue Task", + "memberWithOverdueTaskCountPlural": "Member With Overdue Tasks", + + "teamsText": "Teams", + + "nameColumn": "Name", + "projectsColumn": "Projects", + "membersColumn": "Members" +} diff --git a/worklenz-backend/src/public/locales/en/reporting-projects-drawer.json b/worklenz-backend/src/public/locales/en/reporting-projects-drawer.json new file mode 100644 index 00000000..243bb411 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/reporting-projects-drawer.json @@ -0,0 +1,59 @@ +{ + "exportButton": "Export", + "membersButton": "Members", + "tasksButton": "Tasks", + "searchByNameInputPlaceholder": "Search by name", + + "overviewTab": "Overview", + "membersTab": "Members", + "tasksTab": "Tasks", + + "completedTasksText": "Completed Tasks", + "incompleteTasksText": "Incomplete Tasks", + "overdueTasksText": "Overdue Tasks", + "allocatedHoursText": "Allocated Hours", + "loggedHoursText": "Logged Hours", + + "tasksText": "Tasks", + "allText": "All", + + "tasksByStatusText": "Tasks By Status", + "tasksByPriorityText": "Tasks By Priority", + "tasksByDueDateText": "Tasks By Due Date", + + "todoText": "To Do", + "doingText": "Doing", + "doneText": "Done", + "lowText": "Low", + "mediumText": "Medium", + "highText": "High", + "completedText": "Completed", + "upcomingText": "Upcoming", + "overdueText": "Overdue", + "noDueDateText": "No Due Date", + + "nameColumn": "Name", + "tasksCountColumn": "Tasks Count", + "completedTasksColumn": "Completed Tasks", + "incompleteTasksColumn": "Incomplete Tasks", + "overdueTasksColumn": "Overdue Tasks", + "contributionColumn": "Contribution", + "progressColumn": "Progress", + "loggedTimeColumn": "Logged Time", + "taskColumn": "Task", + "projectColumn": "Project", + "statusColumn": "Status", + "priorityColumn": "Priority", + "phaseColumn": "Phase", + "dueDateColumn": "Due Date", + "completedDateColumn": "Completed Date", + "estimatedTimeColumn": "Estimated Time", + "overloggedTimeColumn": "Overlogged Time", + "completedOnColumn": "Completed On", + "daysOverdueColumn": "Days overdue", + + "groupByText": "Group By:", + "statusText": "Status", + "priorityText": "Priority", + "phaseText": "Phase" +} diff --git a/worklenz-backend/src/public/locales/en/reporting-projects-filters.json b/worklenz-backend/src/public/locales/en/reporting-projects-filters.json new file mode 100644 index 00000000..7d9afccd --- /dev/null +++ b/worklenz-backend/src/public/locales/en/reporting-projects-filters.json @@ -0,0 +1,35 @@ +{ + "searchByNamePlaceholder": "Search by name", + "searchByCategoryPlaceholder": "Search by category", + + "statusText": "Status", + "healthText": "Health", + "categoryText": "Category", + "projectManagerText": "Project Manager", + "showFieldsText": "Show fields", + + "cancelledText": "Cancelled", + "blockedText": "Blocked", + "onHoldText": "On Hold", + "proposedText": "Proposed", + "inPlanningText": "In Planning", + "inProgressText": "In Progress", + "completedText": "Completed", + "continuousText": "Continuous", + + "notSetText": "NotSet", + "needsAttentionText": "Needs Attention", + "atRiskText": "At Risk", + "goodText": "Good", + + "nameText": "Project", + "estimatedVsActualText": "Estimated Vs Actual", + "tasksProgressText": "Tasks Progress", + "lastActivityText": "Last Activity", + "datesText": "Start/End Dates", + "daysLeftText": "Days Left/Overdue", + "projectHealthText": "Project Health", + "projectUpdateText": "Project Update", + "clientText": "Client", + "teamText": "Team" +} diff --git a/worklenz-backend/src/public/locales/en/reporting-projects.json b/worklenz-backend/src/public/locales/en/reporting-projects.json new file mode 100644 index 00000000..8dd472c4 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/reporting-projects.json @@ -0,0 +1,52 @@ +{ + "projectCount": "Project", + "projectCountPlural": "Projects", + "includeArchivedButton": "Include Archived Projects", + "exportButton": "Export", + "excelButton": "Excel", + + "projectColumn": "Project", + "estimatedVsActualColumn": "Estimated Vs Actual", + "tasksProgressColumn": "Tasks Progress", + "lastActivityColumn": "Last Activity", + "statusColumn": "Status", + "datesColumn": "Start/End Dates", + "daysLeftColumn": "Days Left/Overdue", + "projectHealthColumn": "Project Health", + "categoryColumn": "Category", + "projectUpdateColumn": "Project Update", + "clientColumn": "Client", + "teamColumn": "Team", + "projectManagerColumn": "Project Manager", + + "openButton": "Open", + + "estimatedText": "Estimated", + "actualText": "Actual", + + "todoText": "To Do", + "doingText": "Doing", + "doneText": "Done", + + "cancelledText": "Cancelled", + "blockedText": "Blocked", + "onHoldText": "On Hold", + "proposedText": "Proposed", + "inPlanningText": "In Planning", + "inProgressText": "In Progress", + "completedText": "Completed", + "continuousText": "Continuous", + + "daysLeftText": "days left", + "dayLeftText": "day left", + "daysOverdueText": "days overdue", + + "notSetText": "Not Set", + "needsAttentionText": "Needs Attention", + "atRiskText": "At Risk", + "goodText": "Good", + + "setCategoryText": "Set Category", + "searchByNameInputPlaceholder": "Search by name", + "todayText": "Today" +} diff --git a/worklenz-backend/src/public/locales/en/reporting-sidebar.json b/worklenz-backend/src/public/locales/en/reporting-sidebar.json new file mode 100644 index 00000000..8e82224d --- /dev/null +++ b/worklenz-backend/src/public/locales/en/reporting-sidebar.json @@ -0,0 +1,8 @@ +{ + "overview": "Overview", + "projects": "Projects", + "members": "Members", + "timeReports": "Time Reports", + "estimateVsActual": "Estimate Vs Actual", + "currentOrganizationTooltip": "Current organization" +} diff --git a/worklenz-backend/src/public/locales/en/schedule.json b/worklenz-backend/src/public/locales/en/schedule.json new file mode 100644 index 00000000..9e30c04b --- /dev/null +++ b/worklenz-backend/src/public/locales/en/schedule.json @@ -0,0 +1,39 @@ +{ + "today": "Today", + "week": "Week", + "month": "Month", + + "settings": "Settings", + "workingDays": "Working days", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "workingHours": "Working hours", + "hours": "hours", + "saveButton": "Save", + + "totalAllocation": "Total Allocation", + "timeLogged": "Time Logged", + "remainingTime": "Remaining Time", + "total": "Total", + "perDay": "Per Day", + "tasks": "tasks", + "startDate": "Start Date", + "endDate": "End Date", + + "hoursPerDay": "Hours Per Day", + "totalHours": "Total Hours", + "deleteButton": "Delete", + "cancelButton": "Cancel", + + "tabTitle": "Task without Start & End dates", + + "allocatedTime": "Allocated time", + "totalLogged": "Total Logged", + "loggedBillable": "Logged Billable", + "loggedNonBillable": "Logged Non Billable" +} diff --git a/worklenz-backend/src/public/locales/en/settings/appearance.json b/worklenz-backend/src/public/locales/en/settings/appearance.json new file mode 100644 index 00000000..9ce8de64 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/settings/appearance.json @@ -0,0 +1,5 @@ +{ + "title": "Appearance", + "darkMode": "Dark Mode", + "darkModeDescription": "Switch between light and dark mode to customize your viewing experience." +} diff --git a/worklenz-backend/src/public/locales/en/settings/categories.json b/worklenz-backend/src/public/locales/en/settings/categories.json new file mode 100644 index 00000000..716cb5c3 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/settings/categories.json @@ -0,0 +1,10 @@ +{ + "categoryColumn": "Category", + "deleteConfirmationTitle": "Are you sure?", + "deleteConfirmationOk": "Yes", + "deleteConfirmationCancel": "Cancel", + "associatedTaskColumn": "Associated Projects", + "searchPlaceholder": "Search by name", + "emptyText": "Categories can be created while updating or creating projects.", + "colorChangeTooltip": "Click to change color" +} diff --git a/worklenz-backend/src/public/locales/en/settings/change-password.json b/worklenz-backend/src/public/locales/en/settings/change-password.json new file mode 100644 index 00000000..ad39088b --- /dev/null +++ b/worklenz-backend/src/public/locales/en/settings/change-password.json @@ -0,0 +1,15 @@ +{ + "title": "Change Password", + "currentPassword": "Current Password", + "newPassword": "New Password", + "confirmPassword": "Confirm Password", + "currentPasswordPlaceholder": "Enter your current password", + "newPasswordPlaceholder": "New Password", + "confirmPasswordPlaceholder": "Confirm Password", + "currentPasswordRequired": "Please input your current password!", + "newPasswordRequired": "Please input your new password!", + "passwordValidationError": "Password must be at least 8 characters with an uppercase letter, a number, and a symbol.", + "passwordMismatch": "Passwords do not match!", + "passwordRequirements": "New password should be a minimum of 8 characters, with an uppercase letter, a number, and a symbol.", + "updateButton": "Update Password" +} diff --git a/worklenz-backend/src/public/locales/en/settings/clients.json b/worklenz-backend/src/public/locales/en/settings/clients.json new file mode 100644 index 00000000..b7fa4dac --- /dev/null +++ b/worklenz-backend/src/public/locales/en/settings/clients.json @@ -0,0 +1,22 @@ +{ + "nameColumn": "Name", + "projectColumn": "Project", + "noProjectsAvailable": "No projects available", + "deleteConfirmationTitle": "Are you sure?", + "deleteConfirmationOk": "Yes", + "deleteConfirmationCancel": "Cancel", + "searchPlaceholder": "Search by name", + "createClient": "Create Client", + "pinTooltip": "Click to pin this into the main menu", + "createClientDrawerTitle": "Create Client", + "updateClientDrawerTitle": "Update Client", + "nameLabel": "Name", + "namePlaceholder": "Name", + "nameRequiredError": "Please enter a Name", + "createButton": "Create", + "updateButton": "Update", + "createClientSuccessMessage": "Create client success!", + "createClientErrorMessage": "Create client failed!", + "updateClientSuccessMessage": "Update client success!", + "updateClientErrorMessage": "Update client failed!" +} diff --git a/worklenz-backend/src/public/locales/en/settings/job-titles.json b/worklenz-backend/src/public/locales/en/settings/job-titles.json new file mode 100644 index 00000000..9ec54f98 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/settings/job-titles.json @@ -0,0 +1,20 @@ +{ + "nameColumn": "Name", + "deleteConfirmationTitle": "Are you sure?", + "deleteConfirmationOk": "Yes", + "deleteConfirmationCancel": "Cancel", + "searchPlaceholder": "Search by name", + "createJobTitleButton": "Create Job Title", + "pinTooltip": "Click to pin this into the main menu", + "createJobTitleDrawerTitle": "Create Job Title", + "updateJobTitleDrawerTitle": "Update Job Title", + "nameLabel": "Name", + "namePlaceholder": "Name", + "nameRequiredError": "Please enter a Name", + "createButton": "Create", + "updateButton": "Update", + "createJobTitleSuccessMessage": "Create job title success!", + "createJobTitleErrorMessage": "Create job title failed!", + "updateJobTitleSuccessMessage": "Update job title success!", + "updateJobTitleErrorMessage": "Update job title failed!" +} diff --git a/worklenz-backend/src/public/locales/en/settings/labels.json b/worklenz-backend/src/public/locales/en/settings/labels.json new file mode 100644 index 00000000..5c3d2479 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/settings/labels.json @@ -0,0 +1,11 @@ +{ + "labelColumn": "Label", + "deleteConfirmationTitle": "Are you sure?", + "deleteConfirmationOk": "Yes", + "deleteConfirmationCancel": "Cancel", + "associatedTaskColumn": "Associated Task Count", + "searchPlaceholder": "Search by name", + "emptyText": "Labels can be created while updating or creating tasks.", + "pinTooltip": "Click to pin this into the main menu", + "colorChangeTooltip": "Click to change color" +} diff --git a/worklenz-backend/src/public/locales/en/settings/language.json b/worklenz-backend/src/public/locales/en/settings/language.json new file mode 100644 index 00000000..331cb7df --- /dev/null +++ b/worklenz-backend/src/public/locales/en/settings/language.json @@ -0,0 +1,7 @@ +{ + "language": "Language", + "language_required": "Language is required", + "time_zone": "Time zone", + "time_zone_required": "Time zone is required", + "save_changes": "Save Changes" +} diff --git a/worklenz-backend/src/public/locales/en/settings/notifications.json b/worklenz-backend/src/public/locales/en/settings/notifications.json new file mode 100644 index 00000000..7cc1eb47 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/settings/notifications.json @@ -0,0 +1,11 @@ +{ + "title": "Notifications Settings", + "emailTitle": "Send me email notifications", + "emailDescription": "This includes new task assignments", + "dailyDigestTitle": "Send me a daily digest", + "dailyDigestDescription": "Every evening, you will receive a summary of recent activity in tasks.", + "popupTitle": "Pop up notifications on my computer when Worklenz is open", + "popupDescription": "Pop up notifications can be disabled by your browser. Change your browser settings to allow them.", + "unreadItemsTitle": "Show the number of unread items", + "unreadItemsDescription": "You'll see counts for each notification." +} diff --git a/worklenz-backend/src/public/locales/en/settings/profile.json b/worklenz-backend/src/public/locales/en/settings/profile.json new file mode 100644 index 00000000..43ce2f41 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/settings/profile.json @@ -0,0 +1,14 @@ +{ + "uploadError": "You can only upload JPG/PNG file!", + "uploadSizeError": "Image must be smaller than 2MB!", + "upload": "Upload", + "nameLabel": "Name", + "nameRequiredError": "Name is required", + "emailLabel": "Email", + "emailRequiredError": "Email is required", + "saveChanges": "Save Changes", + "profileJoinedText": "Joined a month ago", + "profileLastUpdatedText": "Last updated a month ago", + "avatarTooltip": "Click to upload an avatar", + "title": "Profile Settings" +} diff --git a/worklenz-backend/src/public/locales/en/settings/project-templates.json b/worklenz-backend/src/public/locales/en/settings/project-templates.json new file mode 100644 index 00000000..802e017b --- /dev/null +++ b/worklenz-backend/src/public/locales/en/settings/project-templates.json @@ -0,0 +1,8 @@ +{ + "nameColumn": "Name", + "editToolTip": "Edit", + "deleteToolTip": "Delete", + "confirmText": "Are you sure?", + "okText": "Yes", + "cancelText": "Cancel" +} diff --git a/worklenz-backend/src/public/locales/en/settings/sidebar.json b/worklenz-backend/src/public/locales/en/settings/sidebar.json new file mode 100644 index 00000000..d0b64829 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/settings/sidebar.json @@ -0,0 +1,15 @@ +{ + "profile": "Profile", + "notifications": "Notifications", + "clients": "Clients", + "job-titles": "Job Titles", + "labels": "Labels", + "categories": "Categories", + "project-templates": "Project Templates", + "task-templates": "Task Templates", + "team-members": "Team Members", + "teams": "Teams", + "change-password": "Change Password", + "language-and-region": "Language and Region", + "appearance": "Appearance" +} diff --git a/worklenz-backend/src/public/locales/en/settings/task-templates.json b/worklenz-backend/src/public/locales/en/settings/task-templates.json new file mode 100644 index 00000000..b40bed2d --- /dev/null +++ b/worklenz-backend/src/public/locales/en/settings/task-templates.json @@ -0,0 +1,9 @@ +{ + "nameColumn": "Name", + "createdColumn": "Created", + "editToolTip": "Edit", + "deleteToolTip": "Delete", + "confirmText": "Are you sure?", + "okText": "Yes", + "cancelText": "Cancel" +} diff --git a/worklenz-backend/src/public/locales/en/settings/team-members.json b/worklenz-backend/src/public/locales/en/settings/team-members.json new file mode 100644 index 00000000..36918b90 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/settings/team-members.json @@ -0,0 +1,47 @@ +{ + "title": "Team Members", + "nameColumn": "Name", + "projectsColumn": "Projects", + "emailColumn": "Email", + "teamAccessColumn": "Team Access", + "memberCount": "Member", + "membersCountPlural": "Members", + "searchPlaceholder": "Search members by name", + "pinTooltip": "Refresh member list", + "addMemberButton": "Add New Member", + "editTooltip": "Edit member", + "deactivateTooltip": "Deactivate member", + "activateTooltip": "Activate member", + "deleteTooltip": "Delete member", + "confirmDeleteTitle": "Are you sure you want to delete this member?", + "confirmActivateTitle": "Are you sure you want to change this member's status?", + "okText": "Yes, proceed", + "cancelText": "No, cancel", + "deactivatedText": "(Currently deactivated)", + "pendingInvitationText": "(Invitation pending)", + "addMemberDrawerTitle": "Add New Team Member", + "updateMemberDrawerTitle": "Update Team Member", + "addMemberEmailHint": "Members will be added to the team regardless of invitation acceptance status", + "memberEmailLabel": "Email(s)", + "memberEmailPlaceholder": "Enter team member email address", + "memberEmailRequiredError": "Please enter a valid email address", + "jobTitleLabel": "Job Title", + "jobTitlePlaceholder": "Select or search job title (Optional)", + "memberAccessLabel": "Access Level", + "addToTeamButton": "Add Member to Team", + "updateButton": "Save Changes", + "resendInvitationButton": "Resend Invitation Email", + "invitationSentSuccessMessage": "Team invitation sent successfully!", + "createMemberSuccessMessage": "New team member added successfully!", + "createMemberErrorMessage": "Failed to add team member. Please try again.", + "updateMemberSuccessMessage": "Team member updated successfully!", + "updateMemberErrorMessage": "Failed to update team member. Please try again.", + "memberText": "Member", + "adminText": "Admin", + "ownerText": "Team Owner", + "addedText": "Added", + "updatedText": "Updated", + "noResultFound": "Type an email address and hit enter...", + "jobTitlesFetchError": "Failed to fetch job titles", + "invitationResent": "Invitation resent successfully!" +} diff --git a/worklenz-backend/src/public/locales/en/settings/teams.json b/worklenz-backend/src/public/locales/en/settings/teams.json new file mode 100644 index 00000000..57a1df51 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/settings/teams.json @@ -0,0 +1,16 @@ +{ + "title": "Teams", + "team": "Team", + "teams": "Teams", + "name": "Name", + "created": "Created", + "ownsBy": "Owns By", + "edit": "Edit", + "editTeam": "Edit Team", + "pinTooltip": "Click to pin this into the main menu", + "editTeamName": "Edit Team Name", + "updateName": "Update Name", + "namePlaceholder": "Name", + "nameRequired": "Please enter a Name", + "updateFailed": "Team name change failed!" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/en/task-drawer/task-drawer-info-tab.json b/worklenz-backend/src/public/locales/en/task-drawer/task-drawer-info-tab.json new file mode 100644 index 00000000..f88ecde9 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/task-drawer/task-drawer-info-tab.json @@ -0,0 +1,30 @@ +{ + "details": { + "task-key": "Task Key", + "phase": "Phase", + "assignees": "Assignees", + "due-date": "Due Date", + "time-estimation": "Time Estimation", + "priority": "Priority", + "labels": "Labels", + "billable": "Billable", + "notify": "Notify", + "when-done-notify": "When done, notify", + "start-date": "Start Date", + "end-date": "End Date", + "hide-start-date": "Hide Start Date", + "show-start-date": "Show Start Date", + "hours": "Hours", + "minutes": "Minutes", + "recurring": "Recurring" + }, + "description": { + "title": "Description", + "placeholder": "Add a more detailed description..." + }, + "subTasks": { + "title": "Sub Tasks", + "add-sub-task": "Add Sub Task", + "refresh-sub-tasks": "Refresh Sub Tasks" + } +} diff --git a/worklenz-backend/src/public/locales/en/task-drawer/task-drawer-recurring-config.json b/worklenz-backend/src/public/locales/en/task-drawer/task-drawer-recurring-config.json new file mode 100644 index 00000000..1d22e41b --- /dev/null +++ b/worklenz-backend/src/public/locales/en/task-drawer/task-drawer-recurring-config.json @@ -0,0 +1,34 @@ +{ + "recurring": "Recurring", + "recurringTaskConfiguration": "Recurring task configuration", + "repeats": "Repeats", + "daily": "Daily", + "weekly": "Weekly", + "everyXDays": "Every X Days", + "everyXWeeks": "Every X Weeks", + "everyXMonths": "Every X Months", + "monthly": "Monthly", + "selectDaysOfWeek": "Select Days of the Week", + "mon": "Mon", + "tue": "Tue", + "wed": "Wed", + "thu": "Thu", + "fri": "Fri", + "sat": "Sat", + "sun": "Sun", + "monthlyRepeatType": "Monthly repeat type", + "onSpecificDate": "On a specific date", + "onSpecificDay": "On a specific day", + "dateOfMonth": "Date of the month", + "weekOfMonth": "Week of the month", + "dayOfWeek": "Day of the week", + "first": "First", + "second": "Second", + "third": "Third", + "fourth": "Fourth", + "last": "Last", + "intervalDays": "Interval (days)", + "intervalWeeks": "Interval (weeks)", + "intervalMonths": "Interval (months)", + "saveChanges": "Save Changes" +} diff --git a/worklenz-backend/src/public/locales/en/task-drawer/task-drawer.json b/worklenz-backend/src/public/locales/en/task-drawer/task-drawer.json new file mode 100644 index 00000000..b5147324 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/task-drawer/task-drawer.json @@ -0,0 +1,123 @@ +{ + "taskHeader": { + "taskNamePlaceholder": "Type your Task", + "deleteTask": "Delete Task" + }, + "taskInfoTab": { + "title": "Info", + "details": { + "title": "Details", + "task-key": "Task Key", + "phase": "Phase", + "assignees": "Assignees", + "due-date": "Due Date", + "time-estimation": "Time Estimation", + "priority": "Priority", + "labels": "Labels", + "billable": "Billable", + "notify": "Notify", + "when-done-notify": "When done, notify", + "start-date": "Start Date", + "end-date": "End Date", + "hide-start-date": "Hide Start Date", + "show-start-date": "Show Start Date", + "hours": "Hours", + "minutes": "Minutes", + "progressValue": "Progress Value", + "progressValueTooltip": "Set the progress percentage (0-100%)", + "progressValueRequired": "Please enter a progress value", + "progressValueRange": "Progress must be between 0 and 100", + "taskWeight": "Task Weight", + "taskWeightTooltip": "Set the weight of this subtask (percentage)", + "taskWeightRequired": "Please enter a task weight", + "taskWeightRange": "Weight must be between 0 and 100", + "recurring": "Recurring" + }, + "labels": { + "labelInputPlaceholder": "Search or create", + "labelsSelectorInputTip": "Hit Enter to create" + }, + "description": { + "title": "Description", + "placeholder": "Add a more detailed description..." + }, + "subTasks": { + "title": "Sub Tasks", + "addSubTask": "Add Sub Task", + "addSubTaskInputPlaceholder": "Type your task and hit enter", + "refreshSubTasks": "Refresh Sub Tasks", + "edit": "Edit", + "delete": "Delete", + "confirmDeleteSubTask": "Are you sure you want to delete this subtask?", + "deleteSubTask": "Delete Sub Task" + }, + "dependencies": { + "title": "Dependencies", + "addDependency": "+ Add new dependency", + "blockedBy": "Blocked By", + "searchTask": "Type to search task", + "noTasksFound": "No tasks found", + "confirmDeleteDependency": "Are you sure you want to delete?" + }, + "attachments": { + "title": "Attachments", + "chooseOrDropFileToUpload": "Choose or drop file to upload", + "uploading": "Uploading..." + }, + "comments": { + "title": "Comments", + "addComment": "+ Add new comment", + "noComments": "No comments yet. Be the first to comment!", + "delete": "Delete", + "confirmDeleteComment": "Are you sure you want to delete this comment?", + "addCommentPlaceholder": "Add a comment...", + "cancel": "Cancel", + "commentButton": "Comment", + "attachFiles": "Attach files", + "addMoreFiles": "Add more files", + "selectedFiles": "Selected Files (Up to 25MB, Maximum of {count})", + "maxFilesError": "You can only upload a maximum of {count} files", + "processFilesError": "Failed to process files", + "addCommentError": "Please add a comment or attach files", + "createdBy": "Created {{time}} by {{user}}", + "updatedTime": "Updated {{time}}" + }, + "searchInputPlaceholder": "Search by name", + "pendingInvitation": "Pending Invitation" + }, + "taskTimeLogTab": { + "title": "Time Log", + "addTimeLog": "Add new time log", + "totalLogged": "Total Logged", + "exportToExcel": "Export to Excel", + "noTimeLogsFound": "No time logs found", + "timeLogForm": { + "date": "Date", + "startTime": "Start Time", + "endTime": "End Time", + "workDescription": "Work Description", + "descriptionPlaceholder": "Add a description", + "logTime": "Log time", + "updateTime": "Update time", + "cancel": "Cancel", + "selectDateError": "Please select a date", + "selectStartTimeError": "Please select start time", + "selectEndTimeError": "Please select end time", + "endTimeAfterStartError": "End time must be after start time" + } + }, + "taskActivityLogTab": { + "title": "Activity Log", + "add": "ADD", + "remove": "REMOVE", + "none": "None", + "weight": "Weight", + "createdTask": "created the task." + }, + "taskProgress": { + "markAsDoneTitle": "Mark Task as Done?", + "confirmMarkAsDone": "Yes, mark as done", + "cancelMarkAsDone": "No, keep current status", + "markAsDoneDescription": "You've set the progress to 100%. Would you like to update the task status to \"Done\"?" + } +} diff --git a/worklenz-backend/src/public/locales/en/task-list-filters.json b/worklenz-backend/src/public/locales/en/task-list-filters.json new file mode 100644 index 00000000..a38356c6 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/task-list-filters.json @@ -0,0 +1,85 @@ +{ + "searchButton": "Search", + "resetButton": "Reset", + "searchInputPlaceholder": "Search by name", + + "sortText": "Sort", + "statusText": "Status", + "phaseText": "Phase", + "memberText": "Members", + "assigneesText": "Assignees", + "priorityText": "Priority", + "labelsText": "Labels", + "membersText": "Members", + "groupByText": "Group by", + "showArchivedText": "Show archived", + "showFieldsText": "Show fields", + "keyText": "Key", + "taskText": "Task", + "descriptionText": "Description", + "phasesText": "Phases", + "listText": "List", + "progressText": "Progress", + "timeTrackingText": "Time Tracking", + "timetrackingText": "Time Tracking", + "estimationText": "Estimation", + "startDateText": "Start Date", + "startdateText": "Start Date", + "endDateText": "End Date", + "dueDateText": "Due Date", + "duedateText": "Due Date", + "completedDateText": "Completed Date", + "completeddateText": "Completed Date", + "createdDateText": "Created Date", + "createddateText": "Created Date", + "lastUpdatedText": "Last Updated", + "lastupdatedText": "Last Updated", + "reporterText": "Reporter", + "dueTimeText": "Due Time", + "duetimeText": "Due Time", + + "lowText": "Low", + "mediumText": "Medium", + "highText": "High", + + "createStatusButtonTooltip": "Status settings", + "configPhaseButtonTooltip": "Phase settings", + "noLabelsFound": "No labels found", + + "addStatusButton": "Add Status", + "addPhaseButton": "Add Phase", + + "createStatus": "Create Status", + "name": "Name", + "category": "Category", + "selectCategory": "Select a category", + "pleaseEnterAName": "Please enter a name", + "pleaseSelectACategory": "Please select a category", + "create": "Create", + + "searchTasks": "Search tasks...", + "searchPlaceholder": "Search...", + "fieldsText": "Fields", + "loadingFilters": "Loading filters...", + "noOptionsFound": "No options found", + "filtersActive": "filters active", + "filterActive": "filter active", + "clearAll": "Clear all", + "clearing": "Clearing...", + "cancel": "Cancel", + "search": "Search", + "groupedBy": "Grouped by", + "manageStatuses": "Manage Statuses", + "managePhases": "Manage Phases", + "dragToReorderStatuses": "Drag statuses to reorder them. Each status can have a different category.", + "enterNewStatusName": "Enter new status name...", + "addStatus": "Add Status", + "noStatusesFound": "No statuses found. Create your first status above.", + "deleteStatus": "Delete Status", + "deleteStatusConfirm": "Are you sure you want to delete this status? This action cannot be undone.", + "rename": "Rename", + "delete": "Delete", + "enterStatusName": "Enter status name", + "selectCategory": "Select category", + "close": "Close" +} diff --git a/worklenz-backend/src/public/locales/en/task-list-table.json b/worklenz-backend/src/public/locales/en/task-list-table.json new file mode 100644 index 00000000..5c03f203 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/task-list-table.json @@ -0,0 +1,136 @@ +{ + "keyColumn": "Key", + "taskColumn": "Task", + "descriptionColumn": "Description", + "progressColumn": "Progress", + "membersColumn": "Members", + "assigneesColumn": "Assignees", + "labelsColumn": "Labels", + "phasesColumn": "Phases", + "phaseColumn": "Phase", + "statusColumn": "Status", + "priorityColumn": "Priority", + "timeTrackingColumn": "Time Tracking", + "timetrackingColumn": "Time Tracking", + "estimationColumn": "Estimation", + "startDateColumn": "Start Date", + "startdateColumn": "Start Date", + "dueDateColumn": "Due Date", + "duedateColumn": "Due Date", + "completedDateColumn": "Completed Date", + "completeddateColumn": "Completed Date", + "createdDateColumn": "Created Date", + "createddateColumn": "Created Date", + "lastUpdatedColumn": "Last Updated", + "lastupdatedColumn": "Last Updated", + "reporterColumn": "Reporter", + "dueTimeColumn": "Due Time", + "todoSelectorText": "To Do", + "doingSelectorText": "Doing", + "doneSelectorText": "Done", + + "lowSelectorText": "Low", + "mediumSelectorText": "Medium", + "highSelectorText": "High", + + "selectText": "Select", + "labelsSelectorInputTip": "Hit enter to create!", + + "addTaskText": "Add Task", + "addSubTaskText": "Add Sub Task", + "addTaskInputPlaceholder": "Type your task and hit enter", + "noTasksInGroup": "No tasks in this group", + + "openButton": "Open", + "okButton": "Ok", + + "noLabelsFound": "No labels found", + "searchInputPlaceholder": "Search or create", + "assigneeSelectorInviteButton": "Invite a new member by email", + "labelInputPlaceholder": "Search or create", + "searchLabelsPlaceholder": "Search labels...", + "createLabelButton": "Create \"{{name}}\"", + "manageLabelsPath": "Settings → Labels", + + "pendingInvitation": "Pending Invitation", + + "contextMenu": { + "assignToMe": "Assign to me", + "moveTo": "Move to", + "unarchive": "Unarchive", + "archive": "Archive", + "convertToSubTask": "Convert to Sub task", + "convertToTask": "Convert to Task", + "delete": "Delete", + "searchByNameInputPlaceholder": "Search by name" + }, + "setDueDate": "Set due date", + "setStartDate": "Set start date", + "clearDueDate": "Clear due date", + "clearStartDate": "Clear start date", + "dueDatePlaceholder": "Due Date", + "startDatePlaceholder": "Start Date", + + "emptyStates": { + "noTaskGroups": "No task groups found", + "noTaskGroupsDescription": "Tasks will appear here when they are created or when filters are applied.", + "errorPrefix": "Error:", + "dragTaskFallback": "Task" + }, + + "customColumns": { + "addCustomColumn": "Add a custom column", + "customColumnHeader": "Custom Column", + "customColumnSettings": "Custom column settings", + "noCustomValue": "No value", + "peopleField": "People field", + "noDate": "No date", + "unsupportedField": "Unsupported field type", + + "modal": { + "addFieldTitle": "Add field", + "editFieldTitle": "Edit field", + "fieldTitle": "Field title", + "fieldTitleRequired": "Field title is required", + "columnTitlePlaceholder": "Column title", + "type": "Type", + "deleteConfirmTitle": "Are you sure you want to delete this custom column?", + "deleteConfirmDescription": "This action cannot be undone. All data associated with this column will be permanently deleted.", + "deleteButton": "Delete", + "cancelButton": "Cancel", + "createButton": "Create", + "updateButton": "Update", + "createSuccessMessage": "Custom column created successfully", + "updateSuccessMessage": "Custom column updated successfully", + "deleteSuccessMessage": "Custom column deleted successfully", + "deleteErrorMessage": "Failed to delete custom column", + "createErrorMessage": "Failed to create custom column", + "updateErrorMessage": "Failed to update custom column" + }, + + "fieldTypes": { + "people": "People", + "number": "Number", + "date": "Date", + "selection": "Selection", + "checkbox": "Checkbox", + "labels": "Labels", + "key": "Key", + "formula": "Formula" + } + }, + + "indicators": { + "tooltips": { + "subtasks": "{{count}} subtask", + "subtasks_plural": "{{count}} subtasks", + "comments": "{{count}} comment", + "comments_plural": "{{count}} comments", + "attachments": "{{count}} attachment", + "attachments_plural": "{{count}} attachments", + "subscribers": "Task has subscribers", + "dependencies": "Task has dependencies", + "recurring": "Recurring task" + } + } +} diff --git a/worklenz-backend/src/public/locales/en/task-management.json b/worklenz-backend/src/public/locales/en/task-management.json new file mode 100644 index 00000000..2d21c746 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/task-management.json @@ -0,0 +1,35 @@ +{ + "noTasksInGroup": "No tasks in this group", + "noTasksInGroupDescription": "Add a task to get started", + "addFirstTask": "Add your first task", + "openTask": "Open", + "subtask": "subtask", + "subtasks": "subtasks", + "comment": "comment", + "comments": "comments", + "attachment": "attachment", + "attachments": "attachments", + "enterSubtaskName": "Enter subtask name...", + "add": "Add", + "cancel": "Cancel", + "renameGroup": "Rename Group", + "renameStatus": "Rename Status", + "renamePhase": "Rename Phase", + "changeCategory": "Change Category", + "clickToEditGroupName": "Click to edit group name", + "enterGroupName": "Enter group name", + + "indicators": { + "tooltips": { + "subtasks": "{{count}} subtask", + "subtasks_plural": "{{count}} subtasks", + "comments": "{{count}} comment", + "comments_plural": "{{count}} comments", + "attachments": "{{count}} attachment", + "attachments_plural": "{{count}} attachments", + "subscribers": "Task has subscribers", + "dependencies": "Task has dependencies", + "recurring": "Recurring task" + } + } +} diff --git a/worklenz-backend/src/public/locales/en/task-template-drawer.json b/worklenz-backend/src/public/locales/en/task-template-drawer.json new file mode 100644 index 00000000..9bc59126 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/task-template-drawer.json @@ -0,0 +1,12 @@ +{ + "createTaskTemplate": "Create Task Template", + "editTaskTemplate": "Edit Task Template", + "cancelText": "Cancel", + "saveText": "Save", + "templateNameText": "Template Name", + "templateNameRequired": "Template name is required", + "selectedTasks": "Selected Tasks", + "removeTask": "Remove", + "cancelButton": "Cancel", + "saveButton": "Save" +} diff --git a/worklenz-backend/src/public/locales/en/tasks/task-table-bulk-actions.json b/worklenz-backend/src/public/locales/en/tasks/task-table-bulk-actions.json new file mode 100644 index 00000000..42fcc024 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/tasks/task-table-bulk-actions.json @@ -0,0 +1,41 @@ +{ + "taskSelected": "task selected", + "tasksSelected": "tasks selected", + "changeStatus": "Change Status/ Prioriy/ Phases", + "changeLabel": "Change Label", + "assignToMe": "Assign to me", + "changeAssignees": "Change Assignees", + "archive": "Archive", + "unarchive": "Unarchive", + "delete": "Delete", + "moreOptions": "More options", + "deselectAll": "Deselect all", + "status": "Status", + "priority": "Priority", + "phase": "Phase", + "member": "Member", + "createTaskTemplate": "Create Task Template", + "apply": "Apply", + "createLabel": "+ Create Label", + "searchOrCreateLabel": "Search or create label...", + "hitEnterToCreate": "Press Enter to create", + "labelExists": "Label already exists", + "pendingInvitation": "Pending Invitation", + "noMatchingLabels": "No matching labels", + "noLabels": "No labels", + "CHANGE_STATUS": "Change Status", + "CHANGE_PRIORITY": "Change Priority", + "CHANGE_PHASE": "Change Phase", + "ADD_LABELS": "Add Labels", + "ASSIGN_TO_ME": "Assign to Me", + "ASSIGN_MEMBERS": "Assign Members", + "ARCHIVE": "Archive", + "DELETE": "Delete", + "CANCEL": "Cancel", + "CLEAR_SELECTION": "Clear Selection", + "TASKS_SELECTED": "{{count}} task selected", + "TASKS_SELECTED_plural": "{{count}} tasks selected", + "DELETE_TASKS_CONFIRM": "Delete {{count}} task?", + "DELETE_TASKS_CONFIRM_plural": "Delete {{count}} tasks?", + "DELETE_TASKS_WARNING": "This action cannot be undone." +} diff --git a/worklenz-backend/src/public/locales/en/template-drawer.json b/worklenz-backend/src/public/locales/en/template-drawer.json new file mode 100644 index 00000000..55364835 --- /dev/null +++ b/worklenz-backend/src/public/locales/en/template-drawer.json @@ -0,0 +1,19 @@ +{ + "title": "Edit Task Template", + "cancelText": "Cancel", + "saveText": "Save", + "templateNameText": "Template Name", + "selectedTasks": "Selected Tasks", + "removeTask": "Remove", + "description": "Description", + "phase": "Phase", + "statuses": "Statuses", + "priorities": "Priorities", + "labels": "Labels", + "tasks": "Tasks", + "noTemplateSelected": "No template selected", + "noDescription": "No description", + "worklenzTemplates": "Worklenz Templates", + "yourTemplatesLibrary": "Your Library", + "searchTemplates": "Search Templates" +} diff --git a/worklenz-backend/src/public/locales/en/templateDrawer.json b/worklenz-backend/src/public/locales/en/templateDrawer.json new file mode 100644 index 00000000..70bf2b0c --- /dev/null +++ b/worklenz-backend/src/public/locales/en/templateDrawer.json @@ -0,0 +1,23 @@ +{ + "bugTracking": "Bug Tracking", + "construction": "Construction", + "designCreative": "Design & Creative", + "education": "Education", + "finance": "Finance", + "hrRecruiting": "HR & Recruiting", + "informationTechnology": "Information Technology", + "legal": "Legal", + "manufacturing": "Manufacturing", + "marketing": "Marketing", + "nonprofit": "Nonprofit", + "personalUse": "Personal use", + "salesCRM": "Sales & CRM", + "serviceConsulting": "Service & Consulting", + "softwareDevelopment": "Software Development", + "description": "Description", + "phase": "Phase", + "statuses": "Statuses", + "priorities": "Priorities", + "labels": "Labels", + "tasks": "Tasks" +} diff --git a/worklenz-backend/src/public/locales/en/time-report.json b/worklenz-backend/src/public/locales/en/time-report.json new file mode 100644 index 00000000..00aa3c7f --- /dev/null +++ b/worklenz-backend/src/public/locales/en/time-report.json @@ -0,0 +1,57 @@ +{ + "includeArchivedProjects": "Include Archived Projects", + "export": "Export", + "timeSheet": "Time Sheet", + + "searchByName": "Search by name", + "selectAll": "Select All", + "teams": "Teams", + + "searchByProject": "Search by project name", + "projects": "Projects", + + "searchByCategory": "Search by category name", + "categories": "Categories", + + "billable": "Billable", + "nonBillable": "Non Billable", + + "total": "Total", + + "projectsTimeSheet": "Projects Time Sheet", + + "loggedTime": "Logged Time(hours)", + + "exportToExcel": "Export to Excel", + "logged": "logged", + "for": "for", + + "membersTimeSheet": "Members Time Sheet", + "member": "Member", + + "estimatedVsActual": "Estimated vs Actual", + "workingDays": "Working Days", + "manDays": "Man Days", + "days": "Days", + "estimatedDays": "Estimated Days", + "actualDays": "Actual Days", + + "noCategories": "No categories found", + "noCategory": "No Category", + "noProjects": "No projects found", + "noTeams": "No teams found", + "noData": "No data found", + + "groupBy": "Group by", + "groupByCategory": "Category", + "groupByTeam": "Team", + "groupByStatus": "Status", + "groupByNone": "None", + "clearSearch": "Clear search", + "selectedProjects": "Selected Projects", + "projectsSelected": "projects selected", + "showSelected": "Show Selected Only", + "expandAll": "Expand All", + "collapseAll": "Collapse All", + "ungrouped": "Ungrouped" +} diff --git a/worklenz-backend/src/public/locales/en/unauthorized.json b/worklenz-backend/src/public/locales/en/unauthorized.json new file mode 100644 index 00000000..5233250a --- /dev/null +++ b/worklenz-backend/src/public/locales/en/unauthorized.json @@ -0,0 +1,5 @@ +{ + "title": "Unauthorized!", + "subtitle": "You are not authorized to access this page", + "button": "Go to Home" +} diff --git a/worklenz-backend/src/public/locales/es/404-page.json b/worklenz-backend/src/public/locales/es/404-page.json new file mode 100644 index 00000000..9413ae9e --- /dev/null +++ b/worklenz-backend/src/public/locales/es/404-page.json @@ -0,0 +1,4 @@ +{ + "doesNotExistText": "Lo sentimos, la página que visitaste no existe.", + "backHomeButton": "Volver al inicio" +} diff --git a/worklenz-backend/src/public/locales/es/account-setup.json b/worklenz-backend/src/public/locales/es/account-setup.json new file mode 100644 index 00000000..3f7b013e --- /dev/null +++ b/worklenz-backend/src/public/locales/es/account-setup.json @@ -0,0 +1,32 @@ +{ + "continue": "Continuar", + + "setupYourAccount": "Configura tu cuenta.", + "organizationStepTitle": "Nombra tu organización", + "organizationStepLabel": "Elige un nombre para tu cuenta de Worklenz.", + + "projectStepTitle": "Crea tu primer proyecto", + "projectStepLabel": "¿En qué proyecto estás trabajando ahora?", + "projectStepPlaceholder": "e.g. Plan de Marketing", + + "step2Title": "Crea tus primeras tareas", + "step2InputLabel": "Escribe algunas tareas que vas a hacer en", + "step2AddAnother": "Agregar otro", + + "emailPlaceholder": "Dirección de correo electrónico", + "invalidEmail": "Por favor, introduce una dirección de correo electrónico válida", + "or": "o", + "templateButton": "Importar desde plantilla", + "goBack": "Volver", + "cancel": "Cancelar", + "create": "Crear", + "templateDrawerTitle": "Seleccionar de plantillas", + "step3InputLabel": "Invitar por correo electrónico", + "addAnother": "Agregar otro", + "skipForNow": "Omitir por ahora", + "formTitle": "Crea tu primera tarea.", + "step3Title": "Invita a tu equipo a trabajar", + + "maxMembers": " (Puedes invitar hasta 5 miembros)", + "maxTasks": " (Puedes crear hasta 5 tareas)" +} diff --git a/worklenz-backend/src/public/locales/es/admin-center/current-bill.json b/worklenz-backend/src/public/locales/es/admin-center/current-bill.json new file mode 100644 index 00000000..52a4bdbb --- /dev/null +++ b/worklenz-backend/src/public/locales/es/admin-center/current-bill.json @@ -0,0 +1,113 @@ +{ + "title": "Facturación", + "currentBill": "Factura Actual", + "configuration": "Configuración", + "currentPlanDetails": "Detalles del Plan Actual", + "upgradePlan": "Actualizar Plan", + "cardBodyText01": "Prueba gratuita", + "cardBodyText02": "(Tu plan de prueba expira en 1 mes 19 días)", + "redeemCode": "Canjear Código", + "accountStorage": "Almacenamiento de la Cuenta", + "used": "Usado:", + "remaining": "Restante:", + "charges": "Cargos", + "tooltip": "Cargos para el ciclo de facturación actual", + "description": "Descripción", + "billingPeriod": "Periodo de Facturación", + "billStatus": "Estado de la Factura", + "perUserValue": "Valor por Usuario", + "users": "Usuarios", + "amount": "Cantidad", + "invoices": "Facturas", + "transactionId": "ID de Transacción", + "transactionDate": "Fecha de Transacción", + "paymentMethod": "Método de Pago", + "status": "Estado", + "ltdUsers": "Puedes agregar hasta {{ltd_users}} usuarios.", + + "drawerTitle": "Canjear Código", + "label": "Canjear Código", + "drawerPlaceholder": "Ingrese su código de canje", + "redeemSubmit": "Enviar", + + "modalTitle": "Seleccione el mejor plan para su equipo", + "seatLabel": "Número de asientos", + "freePlan": "Plan Gratuito", + "startup": "Startup", + "business": "Negocio", + "tag": "Más Popular", + "enterprise": "Empresa", + + "freeSubtitle": "gratis para siempre", + "freeUsers": "Mejor para uso personal", + "freeText01": "100MB de almacenamiento", + "freeText02": "3 proyectos", + "freeText03": "5 miembros del equipo", + + "startupSubtitle": "TARIFA PLANa / mes", + "startupUsers": "Hasta 15 usuarios", + "startupText01": "25GB de almacenamiento", + "startupText02": "Proyectos activos ilimitados", + "startupText03": "Programación", + "startupText04": "Informes", + "startupText05": "Suscribirse a proyectos", + + "businessSubtitle": "usuario / mes", + "businessUsers": "16 - 200 usuarios", + + "enterpriseUsers": "200 - 500+ usuarios", + + "footerTitle": "Por favor, proporciónenos un número de teléfono que podamos usar para contactarte.", + "footerLabel": "Número de Teléfono", + "footerButton": "Contactarnos", + + "redeemCodePlaceHolder": "Ingrese su código de canje", + "submit": "Enviar", + + "trialPlan": "Plan de Prueba", + "trialExpireDate": "Válido hasta {{trial_expire_date}}", + "trialExpired": "Su prueba gratuita expiró {{trial_expire_string}}", + "trialInProgress": "Su prueba gratuita expira {{trial_expire_string}}", + + "required": "Este campo es requerido", + "invalidCode": "Código inválido", + + "selectPlan": "Seleccione el mejor plan para su equipo", + "changeSubscriptionPlan": "Cambie su plan de suscripción", + "noOfSeats": "Número de asientos", + "annualPlan": "Pro - Anual", + "monthlyPlan": "Pro - Mensual", + "freeForever": "Gratis para siempre", + "bestForPersonalUse": "Mejor para uso personal", + "storage": "Almacenamiento", + "projects": "Proyectos", + "teamMembers": "Miembros del equipo", + "unlimitedTeamMembers": "Miembros del equipo ilimitados", + "unlimitedActiveProjects": "Proyectos activos ilimitados", + "schedule": "Programación", + "reporting": "Informes", + "subscribeToProjects": "Suscribirse a proyectos", + "billedAnnually": "Facturado Anualmente", + "billedMonthly": "Facturado Mensualmente", + + "pausePlan": "Pausar Plan", + "resumePlan": "Reanudar Plan", + "changePlan": "Cambiar Plan", + "cancelPlan": "Cancelar Plan", + + "perMonthPerUser": "por usuario / mes", + "viewInvoice": "Ver Factura", + "switchToFreePlan": "Cambiar a Plan Gratuito", + + "expirestoday": "hoy", + "expirestomorrow": "mañana", + "expiredDaysAgo": "hace {{days}} días", + "creditPlan": "Plan de Crédito", + "customPlan": "Plan Personalizado", + "planValidTill": "Su plan es válido hasta {{date}}", + "purchaseSeatsText": "Para continuar, deberá comprar asientos adicionales.", + "currentSeatsText": "Actualmente tiene {{seats}} asientos disponibles.", + "selectSeatsText": "Seleccione el número de asientos adicionales a comprar.", + "purchase": "Comprar", + "contactSales": "Contactar ventas" +} diff --git a/worklenz-backend/src/public/locales/es/admin-center/overview.json b/worklenz-backend/src/public/locales/es/admin-center/overview.json new file mode 100644 index 00000000..f88dbdf6 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/admin-center/overview.json @@ -0,0 +1,8 @@ +{ + "overview": "Resumen", + "name": "Nombre de la Organización", + "owner": "Propietario de la Organización", + "admins": "Administradores de la Organización", + "contactNumber": "Agregar Número de Contacto", + "edit": "Editar" +} diff --git a/worklenz-backend/src/public/locales/es/admin-center/projects.json b/worklenz-backend/src/public/locales/es/admin-center/projects.json new file mode 100644 index 00000000..ab28374f --- /dev/null +++ b/worklenz-backend/src/public/locales/es/admin-center/projects.json @@ -0,0 +1,12 @@ +{ + "membersCount": "Cantidad de miembros", + "createdAt": "Creado en", + "projectName": "Nombre del proyecto", + "teamName": "Nombre del equipo", + "refreshProjects": "Refrescar proyectos", + "searchPlaceholder": "Buscar por nombre de proyecto", + "deleteProject": "¿Estás seguro de que deseas eliminar este proyecto?", + "confirm": "Confirmar", + "cancel": "Cancelar", + "delete": "Eliminar proyecto" +} diff --git a/worklenz-backend/src/public/locales/es/admin-center/sidebar.json b/worklenz-backend/src/public/locales/es/admin-center/sidebar.json new file mode 100644 index 00000000..7626302c --- /dev/null +++ b/worklenz-backend/src/public/locales/es/admin-center/sidebar.json @@ -0,0 +1,8 @@ +{ + "overview": "Resumen", + "users": "Usuarios", + "teams": "Equipos", + "billing": "Facturación", + "projects": "Proyectos", + "adminCenter": "Centro de Administración" +} diff --git a/worklenz-backend/src/public/locales/es/admin-center/teams.json b/worklenz-backend/src/public/locales/es/admin-center/teams.json new file mode 100644 index 00000000..13453656 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/admin-center/teams.json @@ -0,0 +1,35 @@ +{ + "title": "Equipos", + "subtitle": "equipos", + "tooltip": "Actualizar equipos", + "placeholder": "Buscar por nombre", + "addTeam": "Agregar Equipo", + "team": "Equipo", + "membersCount": "Cantidad de Miembros", + "members": "Miembros", + "drawerTitle": "Crear Nuevo Equipo", + "label": "Nombre del Equipo", + "drawerPlaceholder": "Nombre", + "create": "Crear", + "delete": "Eliminar", + "settings": "Configuración", + "popTitle": "¿Está seguro?", + "message": "Por favor ingrese un nombre", + "teamSettings": "Configuración del Equipo", + "teamName": "Nombre del Equipo", + "teamDescription": "Descripción del Equipo", + "teamMembers": "Miembros del Equipo", + "teamMembersCount": "Cantidad de Miembros del Equipo", + "teamMembersPlaceholder": "Buscar por nombre", + "addMember": "Agregar Miembro", + "add": "Agregar", + "update": "Actualizar", + "teamNamePlaceholder": "Nombre del Equipo", + "user": "Usuario", + "role": "Rol", + "owner": "Propietario", + "admin": "Administrador", + "member": "Miembro", + "cannotChangeOwnerRole": "El rol de Propietario no puede ser cambiado", + "pendingInvitation": "Invitación pendiente" +} diff --git a/worklenz-backend/src/public/locales/es/admin-center/users.json b/worklenz-backend/src/public/locales/es/admin-center/users.json new file mode 100644 index 00000000..05626c3b --- /dev/null +++ b/worklenz-backend/src/public/locales/es/admin-center/users.json @@ -0,0 +1,9 @@ +{ + "title": "Usuarios", + "subTitle": "usuarios", + "placeholder": "Buscar por nombre", + "user": "Usuario", + "email": "Correo electrónico", + "lastActivity": "Última actividad", + "refresh": "Actualizar usuarios" +} diff --git a/worklenz-backend/src/public/locales/es/all-project-list.json b/worklenz-backend/src/public/locales/es/all-project-list.json new file mode 100644 index 00000000..4a72d9c7 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/all-project-list.json @@ -0,0 +1,34 @@ +{ + "name": "Nombre", + "client": "Cliente", + "category": "Categoría", + "status": "Estado", + "tasksProgress": "Progreso de Tareas", + "updated_at": "Última Actualización", + "members": "Miembros", + "setting": "Configuración", + "projects": "Proyectos", + "refreshProjects": "Actualizar proyectos", + "all": "Todos", + "favorites": "Favoritos", + "archived": "Archivados", + "placeholder": "Buscar por nombre", + "archive": "Archivar", + "unarchive": "Desarchivar", + "archiveConfirm": "¿Está seguro de que desea archivar este proyecto?", + "unarchiveConfirm": "¿Está seguro de que desea desarchivar este proyecto?", + "yes": "Sí", + "no": "No", + "clickToFilter": "Haga clic para filtrar por", + "noProjects": "No se encontraron proyectos", + "addToFavourites": "Agregar a favoritos", + "list": "Lista", + "group": "Grupo", + "listView": "Vista de Lista", + "groupView": "Vista de Grupo", + "groupBy": { + "category": "Categoría", + "client": "Cliente" + }, + "noPermission": "No tienes permiso para realizar esta acción" +} diff --git a/worklenz-backend/src/public/locales/es/auth/auth-common.json b/worklenz-backend/src/public/locales/es/auth/auth-common.json new file mode 100644 index 00000000..6539ec51 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/auth/auth-common.json @@ -0,0 +1,5 @@ +{ + "loggingOut": "Cerrando sesión...", + "authenticating": "Autenticando...", + "gettingThingsReady": "Preparando todo para ti..." +} diff --git a/worklenz-backend/src/public/locales/es/auth/forgot-password.json b/worklenz-backend/src/public/locales/es/auth/forgot-password.json new file mode 100644 index 00000000..5ba75336 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/auth/forgot-password.json @@ -0,0 +1,12 @@ +{ + "headerDescription": "Restablecer tu contraseña", + "emailLabel": "Correo electrónico", + "emailPlaceholder": "Ingresa tu correo electrónico", + "emailRequired": "¡Por favor ingresa tu correo electrónico!", + "resetPasswordButton": "Restablecer Contraseña", + "returnToLoginButton": "Volver al Inicio de Sesión", + "passwordResetSuccessMessage": "Se ha enviado un enlace para restablecer la contraseña a tu correo electrónico.", + "orText": "O", + "successTitle": "¡Instrucciones de restablecimiento enviadas!", + "successMessage": "La información de restablecimiento se ha enviado a tu correo electrónico. Por favor, verifica tu correo." +} diff --git a/worklenz-backend/src/public/locales/es/auth/login.json b/worklenz-backend/src/public/locales/es/auth/login.json new file mode 100644 index 00000000..8c1697f8 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/auth/login.json @@ -0,0 +1,27 @@ +{ + "headerDescription": "Inicia sesión en tu cuenta", + "emailLabel": "Correo electrónico", + "emailPlaceholder": "Ingresa tu correo electrónico", + "emailRequired": "¡Por favor ingresa tu correo electrónico!", + "passwordLabel": "Contraseña", + "passwordPlaceholder": "Ingresa tu contraseña", + "passwordRequired": "¡Por favor ingresa tu contraseña!", + "rememberMe": "Recordarme", + "loginButton": "Iniciar sesión", + "signupButton": "Registrarse", + "forgotPasswordButton": "¿Olvidaste tu contraseña?", + "signInWithGoogleButton": "Iniciar sesión con Google", + "successMessage": "¡Has iniciado sesión exitosamente!", + "dontHaveAccountText": "¿No tienes una cuenta?", + "orText": "O", + "loginError": "Iniciar sesión falló", + "googleLoginError": "Iniciar sesión con Google falló", + "validationMessages": { + "password": "La contraseña debe tener al menos 8 caracteres", + "email": "Por favor ingresa una dirección de correo electrónico válida" + }, + "errorMessages": { + "loginErrorTitle": "Iniciar sesión falló", + "loginErrorMessage": "Por favor verifica tu correo electrónico y contraseña y vuelve a intentarlo" + } +} diff --git a/worklenz-backend/src/public/locales/es/auth/signup.json b/worklenz-backend/src/public/locales/es/auth/signup.json new file mode 100644 index 00000000..465ff287 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/auth/signup.json @@ -0,0 +1,29 @@ +{ + "headerDescription": "Regístrate para comenzar", + "nameLabel": "Nombre completo", + "namePlaceholder": "Ingresa tu nombre completo", + "nameRequired": "¡Por favor ingresa tu nombre completo!", + "nameMinCharacterRequired": "¡El nombre completo debe tener al menos 4 caracteres!", + "emailLabel": "Correo electrónico", + "emailPlaceholder": "Ingresa tu correo electrónico", + "emailRequired": "¡Por favor ingresa tu correo electrónico!", + "passwordLabel": "Contraseña", + "passwordPlaceholder": "Ingresa tu contraseña", + "passwordRequired": "¡Por favor ingresa tu contraseña!", + "passwordMinCharacterRequired": "¡La contraseña debe tener al menos 8 caracteres!", + "passwordPatternRequired": "¡La contraseña no cumple con los requisitos!", + "strongPasswordPlaceholder": "Ingresa una contraseña más segura", + "passwordValidationAltText": "La contraseña debe incluir al menos 8 caracteres con letras mayúsculas y minúsculas, un número y un símbolo.", + "signupSuccessMessage": "¡Te has registrado exitosamente!", + "privacyPolicyLink": "Política de Privacidad", + "termsOfUseLink": "Términos de Uso", + "bySigningUpText": "Al registrarte, aceptas nuestra", + "andText": "y", + "signupButton": "Registrarse", + "signInWithGoogleButton": "Iniciar sesión con Google", + "alreadyHaveAccountText": "¿Ya tienes una cuenta?", + "loginButton": "Iniciar sesión", + "orText": "O", + "reCAPTCHAVerificationError": "Error de verificación de reCAPTCHA", + "reCAPTCHAVerificationErrorMessage": "No pudimos verificar tu reCAPTCHA. Por favor, inténtalo de nuevo." +} diff --git a/worklenz-backend/src/public/locales/es/auth/verify-reset-email.json b/worklenz-backend/src/public/locales/es/auth/verify-reset-email.json new file mode 100644 index 00000000..1058bc1c --- /dev/null +++ b/worklenz-backend/src/public/locales/es/auth/verify-reset-email.json @@ -0,0 +1,14 @@ +{ + "title": "Verificar correo de restablecimiento", + "description": "Ingresa tu nueva contraseña", + "placeholder": "Ingresa tu nueva contraseña", + "confirmPasswordPlaceholder": "Confirma tu nueva contraseña", + "passwordHint": "Mínimo 8 caracteres, con mayúsculas y minúsculas, un número y un símbolo.", + "resetPasswordButton": "Restablecer contraseña", + "orText": "O", + "resendResetEmail": "Reenviar correo de restablecimiento", + "passwordRequired": "Por favor ingresa tu nueva contraseña", + "returnToLoginButton": "Volver al inicio de sesión", + "confirmPasswordRequired": "Por favor confirma tu nueva contraseña", + "passwordMismatch": "Las contraseñas no coinciden" +} diff --git a/worklenz-backend/src/public/locales/es/common.json b/worklenz-backend/src/public/locales/es/common.json new file mode 100644 index 00000000..583e8670 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/common.json @@ -0,0 +1,9 @@ +{ + "login-success": "¡Inicio de sesión exitoso!", + "login-failed": "Error al iniciar sesión. Por favor verifica tus credenciales e intenta nuevamente.", + "signup-success": "¡Registro exitoso! Bienvenido a bordo.", + "signup-failed": "Error al registrarse. Por favor asegúrate de llenar todos los campos requeridos e intenta nuevamente.", + "reconnecting": "Reconectando al servidor...", + "connection-lost": "Conexión perdida. Intentando reconectarse...", + "connection-restored": "Conexión restaurada. Reconectando al servidor..." +} diff --git a/worklenz-backend/src/public/locales/es/create-first-project-form.json b/worklenz-backend/src/public/locales/es/create-first-project-form.json new file mode 100644 index 00000000..4382cda5 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/create-first-project-form.json @@ -0,0 +1,13 @@ +{ + "formTitle": "Crea tu primer proyecto", + "inputLabel": "¿En qué proyecto estás trabajando ahora?", + "or": "o", + "templateButton": "Importar desde plantilla", + "createFromTemplate": "Crear desde plantilla", + "goBack": "Volver", + "continue": "Continuar", + "cancel": "Cancelar", + "create": "Crear", + "templateDrawerTitle": "Seleccionar de plantillas", + "createProject": "Crear proyecto" +} diff --git a/worklenz-backend/src/public/locales/es/create-first-tasks.json b/worklenz-backend/src/public/locales/es/create-first-tasks.json new file mode 100644 index 00000000..adca7366 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/create-first-tasks.json @@ -0,0 +1,7 @@ +{ + "formTitle": "Crea tu primera tarea.", + "inputLable": "Escribe algunas tareas que vas a hacer en", + "addAnother": "Agregar otra", + "goBack": "Volver", + "continue": "Continuar" +} diff --git a/worklenz-backend/src/public/locales/es/home.json b/worklenz-backend/src/public/locales/es/home.json new file mode 100644 index 00000000..cfd238f9 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/home.json @@ -0,0 +1,45 @@ +{ + "todoList": { + "title": "Lista de tareas", + "refreshTasks": "Actualizar tareas", + "addTask": "+ Agregar tarea", + "noTasks": "Sin tareas", + "pressEnter": "Presiona", + "toCreate": "para crear.", + "markAsDone": "Marcar como hecho" + }, + "projects": { + "title": "Proyectos", + "refreshProjects": "Actualizar proyectos", + "noRecentProjects": "Actualmente no estás asignado a ningún proyecto.", + "noFavouriteProjects": "No hay proyectos marcados como favoritos.", + "recent": "Recientes", + "favourites": "Favoritos" + }, + "tasks": { + "assignedToMe": "Asignadas a mí", + "assignedByMe": "Asignadas por mí", + "all": "Todas", + "today": "Hoy", + "upcoming": "Próximas", + "overdue": "Vencidas", + "noDueDate": "Sin fecha de vencimiento", + "noTasks": "No hay tareas para mostrar.", + "addTask": "+ Agregar tarea", + "name": "Nombre", + "project": "Proyecto", + "status": "Estado", + "dueDate": "Fecha de vencimiento", + "dueDatePlaceholder": "Establecer fecha de vencimiento", + "tomorrow": "Mañana", + "nextWeek": "La semana que viene", + "nextMonth": "El próximo mes", + "projectRequired": "Por favor selecciona un proyecto", + "dueOn": "Tareas vencidas el", + "taskRequired": "Por favor agrega una tarea", + "list": "Lista", + "calendar": "Calendario", + "tasks": "Tareas", + "refresh": "Actualizar" + } +} diff --git a/worklenz-backend/src/public/locales/es/invite-initial-team-members.json b/worklenz-backend/src/public/locales/es/invite-initial-team-members.json new file mode 100644 index 00000000..87fb006c --- /dev/null +++ b/worklenz-backend/src/public/locales/es/invite-initial-team-members.json @@ -0,0 +1,8 @@ +{ + "formTitle": "Invita a tu equipo a trabajar", + "inputLable": "Invitar por correo electrónico", + "addAnother": "Agregar otro", + "goBack": "Volver", + "continue": "Continuar", + "skipForNow": "Omitir por ahora" +} diff --git a/worklenz-backend/src/public/locales/es/kanban-board.json b/worklenz-backend/src/public/locales/es/kanban-board.json new file mode 100644 index 00000000..6e8d5975 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/kanban-board.json @@ -0,0 +1,30 @@ +{ + "rename": "Renombrar", + "delete": "Eliminar", + "addTask": "Agregar tarea", + "addSectionButton": "Agregar sección", + "changeCategory": "Cambiar categoría", + + "deleteTooltip": "Eliminar", + "deleteConfirmationTitle": "¿Estás seguro?", + "deleteConfirmationOk": "Sí", + "deleteConfirmationCancel": "Cancelar", + + "dueDate": "Fecha de vencimiento", + "cancel": "Cancelar", + + "today": "Hoy", + "tomorrow": "Mañana", + "assignToMe": "Asignarme", + "archive": "Archivar", + + "newTaskNamePlaceholder": "Escribe un nombre de tarea", + "newSubtaskNamePlaceholder": "Escribe un nombre de subtarea", + "untitledSection": "Sección sin título", + "unmapped": "Sin asignar", + "clickToChangeDate": "Haz clic para cambiar la fecha", + "noDueDate": "Sin fecha de vencimiento", + "save": "Guardar", + "clear": "Limpiar", + "nextWeek": "Próxima semana" +} diff --git a/worklenz-backend/src/public/locales/es/license-expired.json b/worklenz-backend/src/public/locales/es/license-expired.json new file mode 100644 index 00000000..3cd0de2d --- /dev/null +++ b/worklenz-backend/src/public/locales/es/license-expired.json @@ -0,0 +1,6 @@ +{ + "title": "¡Tu prueba de Worklenz ha expirado!", + "subtitle": "Por favor actualiza ahora.", + "button": "Actualizar ahora", + "checking": "Verificando estado de la suscripción..." +} diff --git a/worklenz-backend/src/public/locales/es/navbar.json b/worklenz-backend/src/public/locales/es/navbar.json new file mode 100644 index 00000000..97c79d50 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/navbar.json @@ -0,0 +1,31 @@ +{ + "logoAlt": "Logo de Worklenz", + "home": "Inicio", + "projects": "Proyectos", + "schedule": "Calendario", + "reporting": "Informes", + "clients": "Clientes", + "teams": "Equipos", + "labels": "Etiquetas", + "jobTitles": "Cargos", + "upgradePlan": "Actualizar Plan", + "upgradePlanTooltip": "Actualizar Plan", + "invite": "Invitar", + "inviteTooltip": "Invitar miembros al equipo", + "switchTeamTooltip": "Cambiar equipo", + "help": "Ayuda", + "notificationTooltip": "Ver notificaciones", + "profileTooltip": "Ver perfil", + "adminCenter": "Centro de administración", + "settings": "Configuración", + "logOut": "Cerrar sesión", + "notificationsDrawer": { + "read": "Notificaciones leídas", + "unread": "Notificaciones no leídas", + "markAsRead": "Marcar como leído", + "readAndJoin": "Leer y unirse", + "accept": "Aceptar", + "acceptAndJoin": "Aceptar y unirse", + "noNotifications": "Sin notificaciones" + } +} diff --git a/worklenz-backend/src/public/locales/es/organization-name-form.json b/worklenz-backend/src/public/locales/es/organization-name-form.json new file mode 100644 index 00000000..efd60ed7 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/organization-name-form.json @@ -0,0 +1,5 @@ +{ + "nameYourOrganization": "Nombra tu organización.", + "worklenzAccountTitle": "Elige un nombre para tu cuenta de Worklenz.", + "continue": "Continuar" +} diff --git a/worklenz-backend/src/public/locales/es/phases-drawer.json b/worklenz-backend/src/public/locales/es/phases-drawer.json new file mode 100644 index 00000000..e961b068 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/phases-drawer.json @@ -0,0 +1,19 @@ +{ + "configurePhases": "Configurar fases", + "phaseLabel": "Etiqueta de fase", + "enterPhaseName": "Ingrese un nombre para la etiqueta de fase", + "addOption": "Agregar opción", + "phaseOptions": "Opciones de fase:", + "dragToReorderPhases": "Arrastra las fases para reordenarlas. Cada fase puede tener un color diferente.", + "enterNewPhaseName": "Introducir nuevo nombre de fase...", + "addPhase": "Añadir Fase", + "noPhasesFound": "No se encontraron fases. Crea tu primera fase arriba.", + "deletePhase": "Eliminar Fase", + "deletePhaseConfirm": "¿Estás seguro de que quieres eliminar esta fase? Esta acción no se puede deshacer.", + "rename": "Renombrar", + "delete": "Eliminar", + "enterPhaseName": "Introducir nombre de la fase", + "selectColor": "Seleccionar color", + "managePhases": "Gestionar Fases", + "close": "Cerrar" +} diff --git a/worklenz-backend/src/public/locales/es/project-drawer.json b/worklenz-backend/src/public/locales/es/project-drawer.json new file mode 100644 index 00000000..447ad4f1 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/project-drawer.json @@ -0,0 +1,52 @@ +{ + "createProject": "Crear Proyecto", + "editProject": "Editar Proyecto", + "enterCategoryName": "Ingrese un nombre para la categoría", + "hitEnterToCreate": "¡Presiona enter para crear!", + "enterNotes": "Notas", + "youCanManageClientsUnderSettings": "Puedes gestionar clientes en Configuración", + "addCategory": "Agregar una categoría al proyecto", + "newCategory": "Nueva Categoría", + "notes": "Notas", + "startDate": "Fecha de Inicio", + "endDate": "Fecha de Finalización", + "estimateWorkingDays": "Estimar días de trabajo", + "estimateManDays": "Estimar días de trabajo", + "hoursPerDay": "Horas por día", + "create": "Crear", + "update": "Actualizar", + "delete": "Eliminar", + "typeToSearchClients": "Escribe para buscar clientes", + "projectColor": "Color del Proyecto", + "pleaseEnterAName": "Por favor ingresa un nombre", + "enterProjectName": "Ingresa el nombre del proyecto", + "name": "Nombre", + "status": "Estado", + "health": "Salud", + "category": "Categoría", + "projectManager": "Gerente de Proyecto", + "client": "Cliente", + "deleteConfirmation": "¿Estás seguro de que quieres eliminar?", + "deleteConfirmationDescription": "Esto eliminará todos los datos asociados y no se puede deshacer.", + "yes": "Sí", + "no": "No", + "createdAt": "Creado", + "updatedAt": "Actualizado", + "by": "por", + "add": "Agregar", + "asClient": "como cliente", + "createClient": "Crear cliente", + "searchInputPlaceholder": "Busca por nombre o email", + "hoursPerDayValidationMessage": "Las horas por día deben ser un número entre 1 y 24", + "workingDaysValidationMessage": "Los días de trabajo deben ser un número positivo", + "manDaysValidationMessage": "Los días hombre deben ser un número positivo", + "noPermission": "Sin permiso", + "progressSettings": "Configuración de Progreso", + "manualProgress": "Progreso Manual", + "manualProgressTooltip": "Permitir actualizaciones manuales de progreso para tareas sin subtareas", + "weightedProgress": "Progreso Ponderado", + "weightedProgressTooltip": "Calcular el progreso basado en los pesos de las subtareas", + "timeProgress": "Progreso Basado en Tiempo", + "timeProgressTooltip": "Calcular el progreso basado en el tiempo estimado", + "enterProjectKey": "Ingresa la clave del proyecto" +} diff --git a/worklenz-backend/src/public/locales/es/project-view-files.json b/worklenz-backend/src/public/locales/es/project-view-files.json new file mode 100644 index 00000000..13071a2a --- /dev/null +++ b/worklenz-backend/src/public/locales/es/project-view-files.json @@ -0,0 +1,14 @@ +{ + "nameColumn": "Nombre", + "attachedTaskColumn": "Tarea Adjunta", + "sizeColumn": "Tamaño", + "uploadedByColumn": "Subido Por", + "uploadedAtColumn": "Subido El", + "fileIconAlt": "Icono de archivo", + "titleDescriptionText": "Todos los archivos adjuntos a las tareas en este proyecto aparecerán aquí.", + "deleteConfirmationTitle": "¿Estás seguro?", + "deleteConfirmationOk": "Sí", + "deleteConfirmationCancel": "Cancelar", + "segmentedTooltip": "¡Próximamente! Cambiar entre vista de lista y vista de miniaturas.", + "emptyText": "No hay archivos adjuntos en el proyecto." +} diff --git a/worklenz-backend/src/public/locales/es/project-view-insights.json b/worklenz-backend/src/public/locales/es/project-view-insights.json new file mode 100644 index 00000000..bd60b58e --- /dev/null +++ b/worklenz-backend/src/public/locales/es/project-view-insights.json @@ -0,0 +1,41 @@ +{ + "overview": { + "title": "Resumen", + "statusOverview": "Resumen de Estado", + "priorityOverview": "Resumen de Prioridad", + "lastUpdatedTasks": "Últimas Tareas Actualizadas" + }, + "members": { + "title": "Miembros", + "tooltip": "Miembros", + "tasksByMembers": "Tareas por miembros", + "tasksByMembersTooltip": "Tareas por miembros", + "name": "Nombre", + "taskCount": "Cantidad de Tareas", + "contribution": "Contribución", + "completed": "Completadas", + "incomplete": "Incompletas", + "overdue": "Atrasadas", + "progress": "Progreso" + }, + "tasks": { + "overdueTasks": "Tareas Atrasadas", + "overLoggedTasks": "Tareas con Exceso de Tiempo", + "tasksCompletedEarly": "Tareas completadas antes de tiempo", + "tasksCompletedLate": "Tareas completadas tarde", + "overLoggedTasksTooltip": "Tareas que tienen tiempo registrado más allá de su tiempo estimado", + "overdueTasksTooltip": "Tareas que están más allá de su fecha límite" + }, + "common": { + "seeAll": "Ver todo", + "totalLoggedHours": "Total de horas registradas", + "totalEstimation": "Estimación total", + "completedTasks": "Tareas completadas", + "incompleteTasks": "Tareas incompletas", + "overdueTasks": "Tareas atrasadas", + "overdueTasksTooltip": "Tareas que están más allá de su fecha límite", + "totalLoggedHoursTooltip": "Estimación de tareas y tiempo registrado para las tareas.", + "includeArchivedTasks": "Incluir Tareas Archivadas", + "export": "Exportar" + } +} diff --git a/worklenz-backend/src/public/locales/es/project-view-members.json b/worklenz-backend/src/public/locales/es/project-view-members.json new file mode 100644 index 00000000..95a8d943 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/project-view-members.json @@ -0,0 +1,17 @@ +{ + "nameColumn": "Nombre", + "jobTitleColumn": "Cargo", + "emailColumn": "Correo", + "tasksColumn": "Tareas", + "taskProgressColumn": "Progreso de Tareas", + "accessColumn": "Acceso", + "fileIconAlt": "Icono de archivo", + "deleteConfirmationTitle": "¿Estás seguro?", + "deleteConfirmationOk": "Sí", + "deleteConfirmationCancel": "Cancelar", + "refreshButtonTooltip": "Actualizar miembros", + "deleteButtonTooltip": "Eliminar del proyecto", + "memberCount": "Miembro", + "membersCountPlural": "Miembros", + "emptyText": "No hay archivos adjuntos en el proyecto." +} diff --git a/worklenz-backend/src/public/locales/es/project-view-updates.json b/worklenz-backend/src/public/locales/es/project-view-updates.json new file mode 100644 index 00000000..d565fcfc --- /dev/null +++ b/worklenz-backend/src/public/locales/es/project-view-updates.json @@ -0,0 +1,6 @@ +{ + "inputPlaceholder": "Agregar un comentario..", + "addButton": "Agregar", + "cancelButton": "Cancelar", + "deleteButton": "Eliminar" +} diff --git a/worklenz-backend/src/public/locales/es/project-view.json b/worklenz-backend/src/public/locales/es/project-view.json new file mode 100644 index 00000000..a4c12d9f --- /dev/null +++ b/worklenz-backend/src/public/locales/es/project-view.json @@ -0,0 +1,14 @@ +{ + "taskList": "Lista de Tareas", + "board": "Tablero Kanban", + "insights": "Análisis", + "files": "Archivos", + "members": "Miembros", + "updates": "Actualizaciones", + "projectView": "Vista del Proyecto", + "loading": "Cargando proyecto...", + "error": "Error al cargar el proyecto", + "pinnedTab": "Fijado como pestaña predeterminada", + "pinTab": "Fijar como pestaña predeterminada", + "unpinTab": "Desfijar pestaña predeterminada" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/es/project-view/import-task-templates.json b/worklenz-backend/src/public/locales/es/project-view/import-task-templates.json new file mode 100644 index 00000000..7be1539b --- /dev/null +++ b/worklenz-backend/src/public/locales/es/project-view/import-task-templates.json @@ -0,0 +1,11 @@ +{ + "importTaskTemplate": "Importar plantilla de tarea", + "templateName": "Nombre de la plantilla", + "templateDescription": "Descripción de la plantilla", + "selectedTasks": "Tareas seleccionadas", + "tasks": "Tareas", + "templates": "Plantillas", + "remove": "Eliminar", + "cancel": "Cancelar", + "import": "Importar" +} diff --git a/worklenz-backend/src/public/locales/es/project-view/project-member-drawer.json b/worklenz-backend/src/public/locales/es/project-view/project-member-drawer.json new file mode 100644 index 00000000..ab7570fd --- /dev/null +++ b/worklenz-backend/src/public/locales/es/project-view/project-member-drawer.json @@ -0,0 +1,7 @@ +{ + "title": "Miembros del Proyecto", + "searchLabel": "Agregar miembros ingresando su nombre o correo electrónico", + "searchPlaceholder": "Escriba nombre o correo electrónico", + "inviteAsAMember": "Invitar como miembro", + "inviteNewMemberByEmail": "Invitar nuevo miembro por correo electrónico" +} diff --git a/worklenz-backend/src/public/locales/es/project-view/project-view-header.json b/worklenz-backend/src/public/locales/es/project-view/project-view-header.json new file mode 100644 index 00000000..0215b89c --- /dev/null +++ b/worklenz-backend/src/public/locales/es/project-view/project-view-header.json @@ -0,0 +1,30 @@ +{ + "importTasks": "Importar tareas", + "importTask": "Importar tarea", + "createTask": "Crear tarea", + "settings": "Configuración", + "subscribe": "Suscribirse", + "unsubscribe": "Cancelar suscripción", + "deleteProject": "Eliminar proyecto", + "startDate": "Fecha de inicio", + "endDate": "Fecha de finalización", + "projectSettings": "Configuración del proyecto", + "projectSummary": "Resumen del proyecto", + "receiveProjectSummary": "Recibe un resumen del proyecto cada noche.", + "refreshProject": "Actualizar proyecto", + "saveAsTemplate": "Guardar como plantilla", + "invite": "Invitar", + "share": "Compartir", + "subscribeTooltip": "Suscribirse a notificaciones del proyecto", + "unsubscribeTooltip": "Cancelar suscripción a notificaciones del proyecto", + "refreshTooltip": "Actualizar datos del proyecto", + "settingsTooltip": "Abrir configuración del proyecto", + "saveAsTemplateTooltip": "Guardar este proyecto como plantilla", + "inviteTooltip": "Invitar miembros del equipo a este proyecto", + "createTaskTooltip": "Crear una nueva tarea", + "importTaskTooltip": "Importar tarea desde plantilla", + "navigateBackTooltip": "Volver a la lista de proyectos", + "projectStatusTooltip": "Estado del proyecto", + "projectDatesInfo": "Información de cronograma del proyecto", + "projectCategoryTooltip": "Categoría del proyecto" +} diff --git a/worklenz-backend/src/public/locales/es/project-view/save-as-template.json b/worklenz-backend/src/public/locales/es/project-view/save-as-template.json new file mode 100644 index 00000000..4d7e9354 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/project-view/save-as-template.json @@ -0,0 +1,27 @@ +{ + "title": "Guardar como Plantilla", + "templateName": "Nombre de la Plantilla", + "includes": "¿Qué se debe incluir en la plantilla del proyecto?", + "includesOptions": { + "statuses": "Estados", + "phases": "Fases", + "labels": "Etiquetas" + }, + "taskIncludes": "¿Qué se debe incluir en la plantilla de las tareas?", + "taskIncludesOptions": { + "statuses": "Estados", + "phases": "Fases", + "labels": "Etiquetas", + "name": "Nombre", + "priority": "Prioridad", + "status": "Estado", + "phase": "Fase", + "label": "Etiqueta", + "timeEstimate": "Estimación de Tiempo", + "description": "Descripción", + "subTasks": "Sub Tasks" + }, + "cancel": "Cancel", + "save": "Save", + "templateNamePlaceholder": "Enter template name" +} diff --git a/worklenz-backend/src/public/locales/es/reporting-members-drawer.json b/worklenz-backend/src/public/locales/es/reporting-members-drawer.json new file mode 100644 index 00000000..fb8ecf20 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/reporting-members-drawer.json @@ -0,0 +1,90 @@ +{ + "exportButton": "Exportar", + "timeLogsButton": "Registros de Tiempo", + "activityLogsButton": "Registros de Actividad", + "tasksButton": "Tareas", + "searchByNameInputPlaceholder": "Buscar por nombre", + + "overviewTab": "Resumen", + "timeLogsTab": "Registros de Tiempo", + "activityLogsTab": "Registros de Actividad", + "tasksTab": "Tareas", + + "projectsText": "Proyectos", + "totalTasksText": "Total de Tareas", + "assignedTasksText": "Tareas Asignadas", + "completedTasksText": "Tareas Completadas", + "ongoingTasksText": "Tareas en Curso", + "overdueTasksText": "Tareas Atrasadas", + "loggedHoursText": "Horas Registradas", + + "tasksText": "Tareas", + "allText": "Todo", + + "tasksByProjectsText": "Tareas por Proyectos", + "tasksByStatusText": "Tareas por Estado", + "tasksByPriorityText": "Tareas por Prioridad", + + "todoText": "Por Hacer", + "doingText": "Haciendo", + "doneText": "Hecho", + "lowText": "Baja", + "mediumText": "Media", + "highText": "Alta", + + "billableButton": "Facturable", + "billableText": "Facturable", + "nonBillableText": "No Facturable", + + "timeLogsEmptyPlaceholder": "No hay registros de tiempo para mostrar", + "loggedText": "Registrado", + "forText": "para", + "inText": "en", + "updatedText": "Actualizado", + "fromText": "Desde", + "toText": "hasta", + "withinText": "dentro de", + + "activityLogsEmptyPlaceholder": "No hay registros de actividad para mostrar", + + "filterByText": "Filtrar por:", + "selectProjectPlaceholder": "Seleccionar Proyecto", + + "taskColumn": "Tarea", + "nameColumn": "Nombre", + "projectColumn": "Proyecto", + "statusColumn": "Estado", + "priorityColumn": "Prioridad", + "dueDateColumn": "Fecha de Vencimiento", + "completedDateColumn": "Fecha de Finalización", + "estimatedTimeColumn": "Tiempo Estimado", + "loggedTimeColumn": "Tiempo Registrado", + "overloggedTimeColumn": "Tiempo Excedido", + "daysLeftColumn": "Días Restantes/Atrasados", + "startDateColumn": "Fecha de Inicio", + "endDateColumn": "Fecha de Fin", + "actualTimeColumn": "Tiempo Real", + "projectHealthColumn": "Salud del Proyecto", + "categoryColumn": "Categoría", + "projectManagerColumn": "Gerente de Proyecto", + + "tasksStatsOverviewDrawerTitle": "Tareas de", + "projectsStatsOverviewDrawerTitle": "Proyectos de", + + "cancelledText": "Cancelado", + "blockedText": "Bloqueado", + "onHoldText": "En Espera", + "proposedText": "Propuesto", + "inPlanningText": "En Planificación", + "inProgressText": "En Progreso", + "completedText": "Completado", + "continuousText": "Continuo", + + "daysLeftText": "días restantes", + "daysOverdueText": "días de retraso", + + "notSetText": "No Establecido", + "needsAttentionText": "Necesita Atención", + "atRiskText": "En Riesgo", + "goodText": "Bien" +} diff --git a/worklenz-backend/src/public/locales/es/reporting-members.json b/worklenz-backend/src/public/locales/es/reporting-members.json new file mode 100644 index 00000000..d87cafb8 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/reporting-members.json @@ -0,0 +1,35 @@ +{ + "yesterdayText": "Ayer", + "lastSevenDaysText": "Últimos 7 Días", + "lastWeekText": "Última Semana", + "lastThirtyDaysText": "Últimos 30 Días", + "lastMonthText": "Último Mes", + "lastThreeMonthsText": "Últimos 3 Meses", + "allTimeText": "Todo el Tiempo", + "customRangeText": "Rango personalizado", + "startDateInputPlaceholder": "Fecha de inicio", + "EndDateInputPlaceholder": "Fecha final", + "filterButton": "Filtrar", + + "membersTitle": "Miembros", + "includeArchivedButton": "Incluir Proyectos Archivados", + "exportButton": "Exportar", + "excelButton": "Excel", + "searchByNameInputPlaceholder": "Buscar por nombre", + + "memberColumn": "Miembro", + "tasksProgressColumn": "Progreso de Tareas", + "tasksAssignedColumn": "Tareas Asignadas", + "completedTasksColumn": "Tareas Completadas", + "overdueTasksColumn": "Tareas Atrasadas", + "ongoingTasksColumn": "Tareas en Curso", + + "tasksAssignedColumnTooltip": "Tareas asignadas en el rango de fechas seleccionado", + "overdueTasksColumnTooltip": "Tareas atrasadas al final del rango de fechas seleccionado", + "completedTasksColumnTooltip": "Tareas completadas en el rango de fechas seleccionado", + "ongoingTasksColumnTooltip": "Tareas iniciadas aún no completadas", + + "todoText": "Por Hacer", + "doingText": "Haciendo", + "doneText": "Hecho" +} diff --git a/worklenz-backend/src/public/locales/es/reporting-overview-drawer.json b/worklenz-backend/src/public/locales/es/reporting-overview-drawer.json new file mode 100644 index 00000000..fce8e554 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/reporting-overview-drawer.json @@ -0,0 +1,39 @@ +{ + "exportButton": "Exportar", + "projectsButton": "Proyectos", + "membersButton": "Miembros", + "searchByNameInputPlaceholder": "Buscar por nombre", + + "overviewTab": "Resumen", + "projectsTab": "Proyectos", + "membersTab": "Miembros", + + "projectsByStatusText": "Proyectos por Estado", + "projectsByCategoryText": "Proyectos por Categoría", + "projectsByHealthText": "Proyectos por Salud", + + "projectsText": "Proyectos", + "allText": "Todo", + + "cancelledText": "Cancelado", + "blockedText": "Bloqueado", + "onHoldText": "En Espera", + "proposedText": "Propuesto", + "inPlanningText": "En Planificación", + "inProgressText": "En Progreso", + "completedText": "Completado", + "continuousText": "Continuo", + + "notSetText": "No Establecido", + "needsAttentionText": "Necesita Atención", + "atRiskText": "En Riesgo", + "goodText": "Bien", + + "nameColumn": "Nombre", + "emailColumn": "Correo", + "projectsColumn": "Proyectos", + "tasksColumn": "Tareas", + "overdueTasksColumn": "Tareas Atrasadas", + "completedTasksColumn": "Tareas Completadas", + "ongoingTasksColumn": "Tareas en Curso" +} diff --git a/worklenz-backend/src/public/locales/es/reporting-overview.json b/worklenz-backend/src/public/locales/es/reporting-overview.json new file mode 100644 index 00000000..3f18fbed --- /dev/null +++ b/worklenz-backend/src/public/locales/es/reporting-overview.json @@ -0,0 +1,25 @@ +{ + "overviewTitle": "Resumen", + "includeArchivedButton": "Incluir Proyectos Archivados", + + "teamCount": "Equipo", + "teamCountPlural": "Equipos", + "projectCount": "Proyecto", + "projectCountPlural": "Proyectos", + "memberCount": "Miembro", + "memberCountPlural": "Miembros", + "activeProjectCount": "Proyecto Activo", + "activeProjectCountPlural": "Proyectos Activos", + "overdueProjectCount": "Proyecto Atrasado", + "overdueProjectCountPlural": "Proyectos Atrasados", + "unassignedMemberCount": "Miembro Sin Asignar", + "unassignedMemberCountPlural": "Miembros Sin Asignar", + "memberWithOverdueTaskCount": "Miembro Con Tarea Atrasada", + "memberWithOverdueTaskCountPlural": "Miembros Con Tareas Atrasadas", + + "teamsText": "Equipos", + + "nameColumn": "Nombre", + "projectsColumn": "Proyectos", + "membersColumn": "Miembros" +} diff --git a/worklenz-backend/src/public/locales/es/reporting-projects-drawer.json b/worklenz-backend/src/public/locales/es/reporting-projects-drawer.json new file mode 100644 index 00000000..1e056a29 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/reporting-projects-drawer.json @@ -0,0 +1,59 @@ +{ + "exportButton": "Exportar", + "membersButton": "Miembros", + "tasksButton": "Tareas", + "searchByNameInputPlaceholder": "Buscar por nombre", + + "overviewTab": "Resumen", + "membersTab": "Miembros", + "tasksTab": "Tareas", + + "completedTasksText": "Tareas Completadas", + "incompleteTasksText": "Tareas Incompletas", + "overdueTasksText": "Tareas Atrasadas", + "allocatedHoursText": "Horas Asignadas", + "loggedHoursText": "Horas Registradas", + + "tasksText": "Tareas", + "allText": "Todos", + + "tasksByStatusText": "Tareas por Estado", + "tasksByPriorityText": "Tareas por Prioridad", + "tasksByDueDateText": "Tareas por Fecha de Vencimiento", + + "todoText": "Por Hacer", + "doingText": "En Proceso", + "doneText": "Hecho", + "lowText": "Baja", + "mediumText": "Media", + "highText": "Alta", + "completedText": "Completado", + "upcomingText": "Próximo", + "overdueText": "Atrasado", + "noDueDateText": "Sin Fecha de Vencimiento", + + "nameColumn": "Nombre", + "tasksCountColumn": "Cantidad de Tareas", + "completedTasksColumn": "Tareas Completadas", + "incompleteTasksColumn": "Tareas Incompletas", + "overdueTasksColumn": "Tareas Atrasadas", + "contributionColumn": "Contribución", + "progressColumn": "Progreso", + "loggedTimeColumn": "Tiempo Registrado", + "taskColumn": "Tarea", + "projectColumn": "Proyecto", + "statusColumn": "Estado", + "priorityColumn": "Prioridad", + "phaseColumn": "Fase", + "dueDateColumn": "Fecha de Vencimiento", + "completedDateColumn": "Fecha de Finalización", + "estimatedTimeColumn": "Tiempo Estimado", + "overloggedTimeColumn": "Tiempo Excedido", + "completedOnColumn": "Completado El", + "daysOverdueColumn": "Días de Retraso", + + "groupByText": "Agrupar Por:", + "statusText": "Estado", + "priorityText": "Prioridad", + "phaseText": "Fase" +} diff --git a/worklenz-backend/src/public/locales/es/reporting-projects-filters.json b/worklenz-backend/src/public/locales/es/reporting-projects-filters.json new file mode 100644 index 00000000..1a4df4af --- /dev/null +++ b/worklenz-backend/src/public/locales/es/reporting-projects-filters.json @@ -0,0 +1,35 @@ +{ + "searchByNamePlaceholder": "Buscar por nombre", + "searchByCategoryPlaceholder": "Buscar por categoría", + + "statusText": "Estado", + "healthText": "Salud", + "categoryText": "Categoría", + "projectManagerText": "Gerente de Proyecto", + "showFieldsText": "Mostrar campos", + + "cancelledText": "Cancelado", + "blockedText": "Bloqueado", + "onHoldText": "En Espera", + "proposedText": "Propuesto", + "inPlanningText": "En Planificación", + "inProgressText": "En Progreso", + "completedText": "Completado", + "continuousText": "Continuo", + + "notSetText": "No Establecido", + "needsAttentionText": "Necesita Atención", + "atRiskText": "En Riesgo", + "goodText": "Bien", + + "nameText": "Proyecto", + "estimatedVsActualText": "Estimado vs Real", + "tasksProgressText": "Progreso de Tareas", + "lastActivityText": "Última Actividad", + "datesText": "Fechas de Inicio/Fin", + "daysLeftText": "Días Restantes/Atrasados", + "projectHealthText": "Salud del Proyecto", + "projectUpdateText": "Actualización del Proyecto", + "clientText": "Cliente", + "teamText": "Equipo" +} diff --git a/worklenz-backend/src/public/locales/es/reporting-projects.json b/worklenz-backend/src/public/locales/es/reporting-projects.json new file mode 100644 index 00000000..fbd9283f --- /dev/null +++ b/worklenz-backend/src/public/locales/es/reporting-projects.json @@ -0,0 +1,52 @@ +{ + "projectCount": "Proyecto", + "projectCountPlural": "Proyectos", + "includeArchivedButton": "Incluir Proyectos Archivados", + "exportButton": "Exportar", + "excelButton": "Excel", + + "projectColumn": "Proyecto", + "estimatedVsActualColumn": "Estimado vs Real", + "tasksProgressColumn": "Progreso de Tareas", + "lastActivityColumn": "Última Actividad", + "statusColumn": "Estado", + "datesColumn": "Fechas Inicio/Fin", + "daysLeftColumn": "Días Restantes/Atrasados", + "projectHealthColumn": "Salud del Proyecto", + "categoryColumn": "Categoría", + "projectUpdateColumn": "Actualización del Proyecto", + "clientColumn": "Cliente", + "teamColumn": "Equipo", + "projectManagerColumn": "Gerente de Proyecto", + + "openButton": "Abrir", + + "estimatedText": "Estimado", + "actualText": "Real", + + "todoText": "Por Hacer", + "doingText": "En Proceso", + "doneText": "Terminado", + + "cancelledText": "Cancelado", + "blockedText": "Bloqueado", + "onHoldText": "En Espera", + "proposedText": "Propuesto", + "inPlanningText": "En Planificación", + "inProgressText": "En Progreso", + "completedText": "Completado", + "continuousText": "Continuo", + + "daysLeftText": "días restantes", + "dayLeftText": "día restante", + "daysOverdueText": "días de retraso", + + "notSetText": "No Establecido", + "needsAttentionText": "Necesita Atención", + "atRiskText": "En Riesgo", + "goodText": "Bien", + + "setCategoryText": "Establecer Categoría", + "searchByNameInputPlaceholder": "Buscar por nombre", + "todayText": "Hoy" +} diff --git a/worklenz-backend/src/public/locales/es/reporting-sidebar.json b/worklenz-backend/src/public/locales/es/reporting-sidebar.json new file mode 100644 index 00000000..d5e89788 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/reporting-sidebar.json @@ -0,0 +1,8 @@ +{ + "overviewText": "Resumen", + "projectsText": "Proyectos", + "membersText": "Miembros", + "timeReportsText": "Informes de Tiempo", + "estimateVsActualText": "Estimado vs Real", + "currentOrganizationTooltip": "Organización actual" +} diff --git a/worklenz-backend/src/public/locales/es/schedule.json b/worklenz-backend/src/public/locales/es/schedule.json new file mode 100644 index 00000000..5b24c1f4 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/schedule.json @@ -0,0 +1,39 @@ +{ + "today": "Hoy", + "week": "Semana", + "month": "Mes", + + "settings": "Configuración", + "workingDays": "Días laborables", + "monday": "Lunes", + "tuesday": "Martes", + "wednesday": "Miércoles", + "thursday": "Jueves", + "friday": "Viernes", + "saturday": "Sábado", + "sunday": "Domingo", + "workingHours": "Horas laborables", + "hours": "horas", + "saveButton": "Guardar", + + "totalAllocation": "Asignación Total", + "timeLogged": "Tiempo Registrado", + "remainingTime": "Tiempo Restante", + "total": "Total", + "perDay": "Por Día", + "tasks": "tareas", + "startDate": "Fecha de Inicio", + "endDate": "Fecha de Fin", + + "hoursPerDay": "Horas Por Día", + "totalHours": "Horas Totales", + "deleteButton": "Eliminar", + "cancelButton": "Cancelar", + + "tabTitle": "Tarea sin Fechas de Inicio y Fin", + + "allocatedTime": "Tiempo Asignado", + "totalLogged": "Total Registrado", + "loggedBillable": "Registrado Facturable", + "loggedNonBillable": "Registrado No Facturable" +} diff --git a/worklenz-backend/src/public/locales/es/settings/appearance.json b/worklenz-backend/src/public/locales/es/settings/appearance.json new file mode 100644 index 00000000..d6b196da --- /dev/null +++ b/worklenz-backend/src/public/locales/es/settings/appearance.json @@ -0,0 +1,5 @@ +{ + "title": "Apariencia", + "darkMode": "Modo Oscuro", + "darkModeDescription": "Cambia entre el modo claro y oscuro para personalizar tu experiencia visual." +} diff --git a/worklenz-backend/src/public/locales/es/settings/categories.json b/worklenz-backend/src/public/locales/es/settings/categories.json new file mode 100644 index 00000000..417e17dd --- /dev/null +++ b/worklenz-backend/src/public/locales/es/settings/categories.json @@ -0,0 +1,10 @@ +{ + "categoryColumn": "Category", + "deleteConfirmationTitle": "Are you sure?", + "deleteConfirmationOk": "Yes", + "deleteConfirmationCancel": "Cancel", + "associatedTaskColumn": "Associated Task", + "searchPlaceholder": "Search by name", + "emptyText": "Categories can be created while updating or creating projects.", + "colorChangeTooltip": "Click to change color" +} diff --git a/worklenz-backend/src/public/locales/es/settings/change-password.json b/worklenz-backend/src/public/locales/es/settings/change-password.json new file mode 100644 index 00000000..e52b9aef --- /dev/null +++ b/worklenz-backend/src/public/locales/es/settings/change-password.json @@ -0,0 +1,15 @@ +{ + "title": "Cambiar Contraseña", + "currentPassword": "Contraseña Actual", + "newPassword": "Nueva Contraseña", + "confirmPassword": "Confirmar Contraseña", + "currentPasswordPlaceholder": "Introduce tu contraseña actual", + "newPasswordPlaceholder": "Nueva Contraseña", + "confirmPasswordPlaceholder": "Confirmar Contraseña", + "currentPasswordRequired": "¡Por favor, introduce tu contraseña actual!", + "newPasswordRequired": "¡Por favor, introduce tu nueva contraseña!", + "passwordValidationError": "La contraseña debe tener al menos 8 caracteres con una letra mayúscula, un número y un símbolo.", + "passwordMismatch": "¡Las contraseñas no coinciden!", + "passwordRequirements": "La nueva contraseña debe tener un mínimo de 8 caracteres, con una letra mayúscula, un número y un símbolo.", + "updateButton": "Actualizar Contraseña" +} diff --git a/worklenz-backend/src/public/locales/es/settings/clients.json b/worklenz-backend/src/public/locales/es/settings/clients.json new file mode 100644 index 00000000..ca206be1 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/settings/clients.json @@ -0,0 +1,22 @@ +{ + "nameColumn": "Nombre", + "projectColumn": "Proyecto", + "noProjectsAvailable": "No hay proyectos disponibles", + "deleteConfirmationTitle": "¿Estás seguro?", + "deleteConfirmationOk": "Sí", + "deleteConfirmationCancel": "Cancelar", + "searchPlaceholder": "Buscar por nombre", + "createClient": "Crear Cliente", + "pinTooltip": "Haz clic para fijar esto en el menú principal", + "createClientDrawerTitle": "Crear Cliente", + "updateClientDrawerTitle": "Actualizar Cliente", + "nameLabel": "Nombre", + "namePlaceholder": "Nombre", + "nameRequiredError": "Por favor ingresa un nombre", + "createButton": "Crear", + "updateButton": "Actualizar", + "createClientSuccessMessage": "¡Cliente creado exitosamente!", + "createClientErrorMessage": "¡Error al crear el cliente!", + "updateClientSuccessMessage": "¡Cliente actualizado exitosamente!", + "updateClientErrorMessage": "¡Error al actualizar el cliente!" +} diff --git a/worklenz-backend/src/public/locales/es/settings/job-titles.json b/worklenz-backend/src/public/locales/es/settings/job-titles.json new file mode 100644 index 00000000..1b892d72 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/settings/job-titles.json @@ -0,0 +1,20 @@ +{ + "nameColumn": "Nombre", + "deleteConfirmationTitle": "¿Estás seguro?", + "deleteConfirmationOk": "Sí", + "deleteConfirmationCancel": "Cancelar", + "searchPlaceholder": "Buscar por nombre", + "createJobTitleButton": "Crear Cargo", + "pinTooltip": "Haz clic para fijar esto en el menú principal", + "createJobTitleDrawerTitle": "Crear Cargo", + "updateJobTitleDrawerTitle": "Actualizar Cargo", + "nameLabel": "Nombre", + "namePlaceholder": "Nombre", + "nameRequiredError": "Por favor ingresa un nombre", + "createButton": "Crear", + "updateButton": "Actualizar", + "createJobTitleSuccessMessage": "¡Cargo creado exitosamente!", + "createJobTitleErrorMessage": "¡Error al crear el cargo!", + "updateJobTitleSuccessMessage": "¡Cargo actualizado exitosamente!", + "updateJobTitleErrorMessage": "¡Error al actualizar el cargo!" +} diff --git a/worklenz-backend/src/public/locales/es/settings/labels.json b/worklenz-backend/src/public/locales/es/settings/labels.json new file mode 100644 index 00000000..22cd9532 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/settings/labels.json @@ -0,0 +1,11 @@ +{ + "labelColumn": "Etiqueta", + "deleteConfirmationTitle": "¿Estás seguro?", + "deleteConfirmationOk": "Sí", + "deleteConfirmationCancel": "Cancelar", + "associatedTaskColumn": "Cantidad de Tareas Asociadas", + "searchPlaceholder": "Buscar por nombre", + "emptyText": "Las etiquetas se pueden crear al actualizar o crear tareas.", + "pinTooltip": "Haz clic para fijar esto en el menú principal", + "colorChangeTooltip": "Haz clic para cambiar el color" +} diff --git a/worklenz-backend/src/public/locales/es/settings/language.json b/worklenz-backend/src/public/locales/es/settings/language.json new file mode 100644 index 00000000..e07fd933 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/settings/language.json @@ -0,0 +1,7 @@ +{ + "language": "Idioma", + "language_required": "El idioma es requerido", + "time_zone": "Zona horaria", + "time_zone_required": "La zona horaria es requerida", + "save_changes": "Guardar cambios" +} diff --git a/worklenz-backend/src/public/locales/es/settings/notifications.json b/worklenz-backend/src/public/locales/es/settings/notifications.json new file mode 100644 index 00000000..c7a5af22 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/settings/notifications.json @@ -0,0 +1,10 @@ +{ + "emailTitle": "Enviarme notificaciones por correo electrónico", + "emailDescription": "Esto incluye nuevas asignaciones de tareas", + "dailyDigestTitle": "Enviarme un resumen diario", + "dailyDigestDescription": "Cada tarde, recibirás un resumen de la actividad reciente en las tareas.", + "popupTitle": "Mostrar notificaciones emergentes en mi computadora cuando Worklenz esté abierto", + "popupDescription": "Las notificaciones emergentes pueden ser desactivadas por tu navegador. Cambia la configuración de tu navegador para permitirlas.", + "unreadItemsTitle": "Mostrar el número de elementos no leídos", + "unreadItemsDescription": "Verás contadores para cada notificación." +} diff --git a/worklenz-backend/src/public/locales/es/settings/profile.json b/worklenz-backend/src/public/locales/es/settings/profile.json new file mode 100644 index 00000000..1a1698c8 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/settings/profile.json @@ -0,0 +1,14 @@ +{ + "uploadError": "¡Solo puedes subir archivos JPG/PNG!", + "uploadSizeError": "¡La imagen debe ser menor de 2MB!", + "upload": "Subir", + "nameLabel": "Nombre", + "nameRequiredError": "El nombre es obligatorio", + "emailLabel": "Correo electrónico", + "emailRequiredError": "El correo electrónico es obligatorio", + "saveChanges": "Guardar cambios", + "profileJoinedText": "Se unió hace un mes", + "profileLastUpdatedText": "Última actualización hace un mes", + "avatarTooltip": "Haz clic para subir un avatar", + "title": "Configuración del Perfil" +} diff --git a/worklenz-backend/src/public/locales/es/settings/project-templates.json b/worklenz-backend/src/public/locales/es/settings/project-templates.json new file mode 100644 index 00000000..045f2240 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/settings/project-templates.json @@ -0,0 +1,8 @@ +{ + "nameColumn": "Nombre", + "editToolTip": "Editar", + "deleteToolTip": "Eliminar", + "confirmText": "¿Estás seguro?", + "okText": "Sí", + "cancelText": "Cancelar" +} diff --git a/worklenz-backend/src/public/locales/es/settings/sidebar.json b/worklenz-backend/src/public/locales/es/settings/sidebar.json new file mode 100644 index 00000000..3793e77f --- /dev/null +++ b/worklenz-backend/src/public/locales/es/settings/sidebar.json @@ -0,0 +1,15 @@ +{ + "profile": "Perfil", + "notifications": "Notificaciones", + "clients": "Clientes", + "job-titles": "Títulos de trabajo", + "labels": "Etiquetas", + "categories": "Categorías", + "project-templates": "Plantillas de proyectos", + "task-templates": "Plantillas de tareas", + "team-members": "Miembros del equipo", + "teams": "Equipos", + "change-password": "Cambiar contraseña", + "language-and-region": "Idioma y región", + "appearance": "Apariencia" +} diff --git a/worklenz-backend/src/public/locales/es/settings/task-templates.json b/worklenz-backend/src/public/locales/es/settings/task-templates.json new file mode 100644 index 00000000..fbdc3c81 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/settings/task-templates.json @@ -0,0 +1,9 @@ +{ + "nameColumn": "Nombre", + "createdColumn": "Creado", + "editToolTip": "Editar", + "deleteToolTip": "Eliminar", + "confirmText": "¿Estás seguro?", + "okText": "Sí", + "cancelText": "Cancelar" +} diff --git a/worklenz-backend/src/public/locales/es/settings/team-members.json b/worklenz-backend/src/public/locales/es/settings/team-members.json new file mode 100644 index 00000000..1000bf98 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/settings/team-members.json @@ -0,0 +1,47 @@ +{ + "title": "Miembros del Equipo", + "nameColumn": "Nombre", + "projectsColumn": "Proyectos", + "emailColumn": "Correo electrónico", + "teamAccessColumn": "Acceso al equipo", + "memberCount": "Miembro", + "membersCountPlural": "Miembros", + "searchPlaceholder": "Buscar miembros por nombre", + "pinTooltip": "Actualizar lista de miembros", + "addMemberButton": "Agregar nuevo miembro", + "editTooltip": "Editar miembro", + "deactivateTooltip": "Desactivar miembro", + "activateTooltip": "Activar miembro", + "deleteTooltip": "Eliminar miembro", + "confirmDeleteTitle": "¿Está seguro de que desea eliminar este miembro?", + "confirmActivateTitle": "¿Está seguro de que desea cambiar el estado de este miembro?", + "okText": "Sí, continuar", + "cancelText": "No, cancelar", + "deactivatedText": "(Actualmente desactivado)", + "pendingInvitationText": "(Invitación pendiente)", + "addMemberDrawerTitle": "Agregar nuevo miembro del equipo", + "updateMemberDrawerTitle": "Actualizar miembro del equipo", + "addMemberEmailHint": "Los miembros se agregarán al equipo independientemente del estado de aceptación de la invitación", + "memberEmailLabel": "Dirección(es) de correo electrónico", + "memberEmailPlaceholder": "Ingrese la dirección de correo electrónico del miembro del equipo", + "memberEmailRequiredError": "Por favor, ingrese una dirección de correo electrónico válida", + "jobTitleLabel": "Cargo", + "jobTitlePlaceholder": "Seleccione o busque cargo (Opcional)", + "memberAccessLabel": "Nivel de acceso", + "addToTeamButton": "Agregar miembro al equipo", + "updateButton": "Guardar cambios", + "resendInvitationButton": "Reenviar correo de invitación", + "invitationSentSuccessMessage": "¡Invitación al equipo enviada exitosamente!", + "createMemberSuccessMessage": "¡Nuevo miembro del equipo agregado exitosamente!", + "createMemberErrorMessage": "Error al agregar miembro del equipo. Por favor, intente nuevamente.", + "updateMemberSuccessMessage": "¡Miembro del equipo actualizado exitosamente!", + "updateMemberErrorMessage": "Error al actualizar miembro del equipo. Por favor, intente nuevamente.", + "memberText": "Miembro del equipo", + "adminText": "Administrador", + "ownerText": "Propietario del equipo", + "addedText": "Agregado", + "updatedText": "Actualizado", + "noResultFound": "Escriba una dirección de correo electrónico y presione enter...", + "jobTitlesFetchError": "Error al obtener los cargos", + "invitationResent": "¡Invitación reenviada exitosamente!" +} diff --git a/worklenz-backend/src/public/locales/es/settings/teams.json b/worklenz-backend/src/public/locales/es/settings/teams.json new file mode 100644 index 00000000..808c1b78 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/settings/teams.json @@ -0,0 +1,16 @@ +{ + "title": "Equipos", + "team": "Equipo", + "teams": "Equipos", + "name": "Nombre", + "created": "Creado", + "ownsBy": "Pertenece a", + "edit": "Editar", + "editTeam": "Editar Equipo", + "pinTooltip": "Haz clic para fijar esto en el menú principal", + "editTeamName": "Editar Nombre del Equipo", + "updateName": "Actualizar Nombre", + "namePlaceholder": "Nombre", + "nameRequired": "Por favor ingresa un Nombre", + "updateFailed": "¡Falló el cambio de nombre del equipo!" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/es/task-drawer/task-drawer-info-tab.json b/worklenz-backend/src/public/locales/es/task-drawer/task-drawer-info-tab.json new file mode 100644 index 00000000..02b3038a --- /dev/null +++ b/worklenz-backend/src/public/locales/es/task-drawer/task-drawer-info-tab.json @@ -0,0 +1,30 @@ +{ + "details": { + "task-key": "Clave de tarea", + "phase": "Fase", + "assignees": "Asignados", + "due-date": "Fecha de vencimiento", + "time-estimation": "Estimación de tiempo", + "priority": "Prioridad", + "labels": "Etiquetas", + "billable": "Facturable", + "notify": "Notificar", + "when-done-notify": "Al terminar, notificar", + "start-date": "Fecha de inicio", + "end-date": "Fecha de finalización", + "hide-start-date": "Ocultar fecha de inicio", + "show-start-date": "Mostrar fecha de inicio", + "hours": "Horas", + "minutes": "Minutos", + "recurring": "Recurrente" + }, + "description": { + "title": "Descripción", + "placeholder": "Añadir una descripción más detallada..." + }, + "subTasks": { + "title": "Subtareas", + "add-sub-task": "+ Añadir subtarea", + "refresh-sub-tasks": "Actualizar subtareas" + } +} diff --git a/worklenz-backend/src/public/locales/es/task-drawer/task-drawer-recurring-config.json b/worklenz-backend/src/public/locales/es/task-drawer/task-drawer-recurring-config.json new file mode 100644 index 00000000..c1ef9e83 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/task-drawer/task-drawer-recurring-config.json @@ -0,0 +1,34 @@ +{ + "recurring": "Recurrente", + "recurringTaskConfiguration": "Configuración de tarea recurrente", + "repeats": "Repeticiones", + "daily": "Diario", + "weekly": "Semanal", + "everyXDays": "Cada X días", + "everyXWeeks": "Cada X semanas", + "everyXMonths": "Cada X meses", + "monthly": "Mensual", + "selectDaysOfWeek": "Seleccionar días de la semana", + "mon": "Lun", + "tue": "Mar", + "wed": "Mié", + "thu": "Jue", + "fri": "Vie", + "sat": "Sáb", + "sun": "Dom", + "monthlyRepeatType": "Tipo de repetición mensual", + "onSpecificDate": "En una fecha específica", + "onSpecificDay": "En un día específico", + "dateOfMonth": "Fecha del mes", + "weekOfMonth": "Semana del mes", + "dayOfWeek": "Día de la semana", + "first": "Primero", + "second": "Segundo", + "third": "Tercero", + "fourth": "Cuarto", + "last": "Último", + "intervalDays": "Intervalo (días)", + "intervalWeeks": "Intervalo (semanas)", + "intervalMonths": "Intervalo (meses)", + "saveChanges": "Guardar cambios" +} diff --git a/worklenz-backend/src/public/locales/es/task-drawer/task-drawer.json b/worklenz-backend/src/public/locales/es/task-drawer/task-drawer.json new file mode 100644 index 00000000..8e438716 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/task-drawer/task-drawer.json @@ -0,0 +1,123 @@ +{ + "taskHeader": { + "taskNamePlaceholder": "Escriba su Tarea", + "deleteTask": "Eliminar Tarea" + }, + "taskInfoTab": { + "title": "Información", + "details": { + "title": "Detalles", + "task-key": "Clave de Tarea", + "phase": "Fase", + "assignees": "Asignados", + "due-date": "Fecha de Vencimiento", + "time-estimation": "Estimación de Tiempo", + "priority": "Prioridad", + "labels": "Etiquetas", + "billable": "Facturable", + "notify": "Notificar", + "when-done-notify": "Al terminar, notificar", + "start-date": "Fecha de Inicio", + "end-date": "Fecha de Fin", + "hide-start-date": "Ocultar Fecha de Inicio", + "show-start-date": "Mostrar Fecha de Inicio", + "hours": "Horas", + "minutes": "Minutos", + "progressValue": "Valor de Progreso", + "progressValueTooltip": "Establecer el porcentaje de progreso (0-100%)", + "progressValueRequired": "Por favor, introduzca un valor de progreso", + "progressValueRange": "El progreso debe estar entre 0 y 100", + "taskWeight": "Peso de la Tarea", + "taskWeightTooltip": "Establecer el peso de esta subtarea (porcentaje)", + "taskWeightRequired": "Por favor, introduzca un peso de tarea", + "taskWeightRange": "El peso debe estar entre 0 y 100", + "recurring": "Recurrente" + }, + "labels": { + "labelInputPlaceholder": "Buscar o crear", + "labelsSelectorInputTip": "Presiona Enter para crear" + }, + "description": { + "title": "Descripción", + "placeholder": "Añadir una descripción más detallada..." + }, + "subTasks": { + "title": "Sub Tareas", + "addSubTask": "Agregar Sub Tarea", + "addSubTaskInputPlaceholder": "Escriba su tarea y presione enter", + "refreshSubTasks": "Actualizar Sub Tareas", + "edit": "Editar", + "delete": "Eliminar", + "confirmDeleteSubTask": "¿Está seguro de que desea eliminar esta subtarea?", + "deleteSubTask": "Eliminar Sub Tarea" + }, + "dependencies": { + "title": "Dependencias", + "addDependency": "+ Agregar nueva dependencia", + "blockedBy": "Bloqueado por", + "searchTask": "Escribir para buscar tarea", + "noTasksFound": "No se encontraron tareas", + "confirmDeleteDependency": "¿Está seguro de que desea eliminar?" + }, + "attachments": { + "title": "Adjuntos", + "chooseOrDropFileToUpload": "Elija o arrastre un archivo para subir", + "uploading": "Subiendo..." + }, + "comments": { + "title": "Comentarios", + "addComment": "+ Agregar nuevo comentario", + "noComments": "Aún no hay comentarios. ¡Sé el primero en comentar!", + "delete": "Eliminar", + "confirmDeleteComment": "¿Está seguro de que desea eliminar este comentario?", + "addCommentPlaceholder": "Agregar un comentario...", + "cancel": "Cancelar", + "commentButton": "Comentar", + "attachFiles": "Adjuntar archivos", + "addMoreFiles": "Agregar más archivos", + "selectedFiles": "Archivos Seleccionados (Hasta 25MB, Máximo {count})", + "maxFilesError": "Solo puede subir un máximo de {count} archivos", + "processFilesError": "Error al procesar archivos", + "addCommentError": "Por favor agregue un comentario o adjunte archivos", + "createdBy": "Creado {{time}} por {{user}}", + "updatedTime": "Actualizado {{time}}" + }, + "searchInputPlaceholder": "Buscar por nombre", + "pendingInvitation": "Invitación Pendiente" + }, + "taskTimeLogTab": { + "title": "Registro de Tiempo", + "addTimeLog": "Añadir nuevo registro de tiempo", + "totalLogged": "Total Registrado", + "exportToExcel": "Exportar a Excel", + "noTimeLogsFound": "No se encontraron registros de tiempo", + "timeLogForm": { + "date": "Fecha", + "startTime": "Hora de Inicio", + "endTime": "Hora de Fin", + "workDescription": "Descripción del Trabajo", + "descriptionPlaceholder": "Agregar una descripción", + "logTime": "Registrar tiempo", + "updateTime": "Actualizar tiempo", + "cancel": "Cancelar", + "selectDateError": "Por favor seleccione una fecha", + "selectStartTimeError": "Por favor seleccione la hora de inicio", + "selectEndTimeError": "Por favor seleccione la hora de fin", + "endTimeAfterStartError": "La hora de fin debe ser posterior a la hora de inicio" + } + }, + "taskActivityLogTab": { + "title": "Registro de Actividad", + "add": "AGREGAR", + "remove": "QUITAR", + "none": "Ninguno", + "weight": "Peso", + "createdTask": "creó la tarea." + }, + "taskProgress": { + "markAsDoneTitle": "¿Marcar Tarea como Completada?", + "confirmMarkAsDone": "Sí, marcar como completada", + "cancelMarkAsDone": "No, mantener estado actual", + "markAsDoneDescription": "Ha establecido el progreso al 100%. ¿Le gustaría actualizar el estado de la tarea a \"Completada\"?" + } +} diff --git a/worklenz-backend/src/public/locales/es/task-list-filters.json b/worklenz-backend/src/public/locales/es/task-list-filters.json new file mode 100644 index 00000000..465368f0 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/task-list-filters.json @@ -0,0 +1,81 @@ +{ + "searchButton": "Buscar", + "resetButton": "Restablecer", + "searchInputPlaceholder": "Buscar por nombre", + + "sortText": "Ordenar", + "statusText": "Estado", + "phaseText": "Fase", + "priorityText": "Prioridad", + "labelsText": "Etiquetas", + "membersText": "Miembros", + "groupByText": "Agrupar por", + "showArchivedText": "Mostrar archivados", + "showFieldsText": "Mostrar campos", + "keyText": "Clave", + "taskText": "Tarea", + "descriptionText": "Descripción", + "phasesText": "Fases", + "progressText": "Progreso", + "timeTrackingText": "Seguimiento de tiempo", + "estimationText": "Estimación", + "startDateText": "Fecha de inicio", + "endDateText": "Fecha de fin", + "dueDateText": "Fecha de vencimiento", + "completedDateText": "Fecha de finalización", + "createdDateText": "Fecha de creación", + "lastUpdatedText": "Última actualización", + "reporterText": "Reportero", + "dueTimeText": "Hora de vencimiento", + "lowText": "Baja", + "mediumText": "Media", + "highText": "Alta", + "assigneesText": "Asignados", + "timetrackingText": "Seguimiento de tiempo", + "startdateText": "Fecha de inicio", + "duedateText": "Fecha de vencimiento", + "completeddateText": "Fecha de finalización", + "createddateText": "Fecha de creación", + "lastupdatedText": "Última actualización", + "duetimeText": "Hora de vencimiento", + "createStatusButtonTooltip": "Configuración de estados", + "configPhaseButtonTooltip": "Configuración de fases", + "noLabelsFound": "No se encontraron etiquetas", + + "addStatusButton": "Agregar estado", + "addPhaseButton": "Agregar fase", + + "createStatus": "Crear estado", + "name": "Nombre", + "category": "Categoría", + "selectCategory": "Seleccionar una categoría", + "pleaseEnterAName": "Por favor, ingrese un nombre", + "pleaseSelectACategory": "Por favor, seleccione una categoría", + "create": "Crear", + + "searchTasks": "Buscar tareas...", + "searchPlaceholder": "Buscar...", + "fieldsText": "Campos", + "loadingFilters": "Cargando filtros...", + "noOptionsFound": "No se encontraron opciones", + "filtersActive": "filtros activos", + "filterActive": "filtro activo", + "clearAll": "Limpiar todo", + "clearing": "Limpiando...", + "cancel": "Cancelar", + "search": "Buscar", + "groupedBy": "Agrupado por", + "manageStatuses": "Gestionar Estados", + "managePhases": "Gestionar Fases", + "dragToReorderStatuses": "Arrastra los estados para reordenarlos. Cada estado puede tener una categoría diferente.", + "enterNewStatusName": "Introducir nuevo nombre de estado...", + "addStatus": "Añadir Estado", + "noStatusesFound": "No se encontraron estados. Crea tu primer estado arriba.", + "deleteStatus": "Eliminar Estado", + "deleteStatusConfirm": "¿Estás seguro de que quieres eliminar este estado? Esta acción no se puede deshacer.", + "rename": "Renombrar", + "delete": "Eliminar", + "enterStatusName": "Introducir nombre del estado", + "selectCategory": "Seleccionar categoría", + "close": "Cerrar" +} diff --git a/worklenz-backend/src/public/locales/es/task-list-table.json b/worklenz-backend/src/public/locales/es/task-list-table.json new file mode 100644 index 00000000..0648c2ff --- /dev/null +++ b/worklenz-backend/src/public/locales/es/task-list-table.json @@ -0,0 +1,136 @@ +{ + "keyColumn": "Clave", + "taskColumn": "Tarea", + "descriptionColumn": "Descripción", + "progressColumn": "Progreso", + "membersColumn": "Miembros", + "assigneesColumn": "Asignados", + "labelsColumn": "Etiquetas", + "phasesColumn": "Fases", + "phaseColumn": "Fase", + "statusColumn": "Estado", + "priorityColumn": "Prioridad", + "timeTrackingColumn": "Seguimiento de tiempo", + "timetrackingColumn": "Seguimiento de tiempo", + "estimationColumn": "Estimación", + "startDateColumn": "Fecha de inicio", + "startdateColumn": "Fecha de inicio", + "dueDateColumn": "Fecha de vencimiento", + "duedateColumn": "Fecha de vencimiento", + "completedDateColumn": "Fecha de completado", + "completeddateColumn": "Fecha de completado", + "createdDateColumn": "Fecha de creación", + "createddateColumn": "Fecha de creación", + "lastUpdatedColumn": "Última actualización", + "lastupdatedColumn": "Última actualización", + "reporterColumn": "Reportador", + "dueTimeColumn": "Hora de vencimiento", + "todoSelectorText": "Por hacer", + "doingSelectorText": "En progreso", + "doneSelectorText": "Completado", + + "lowSelectorText": "Baja", + "mediumSelectorText": "Media", + "highSelectorText": "Alta", + + "selectText": "Seleccionar", + "labelsSelectorInputTip": "¡Presiona enter para crear!", + + "addTaskText": "Agregar tarea", + "addSubTaskText": "Agregar subtarea", + "noTasksInGroup": "No hay tareas en este grupo", + "addTaskInputPlaceholder": "Escribe tu tarea y presiona enter", + + "openButton": "Abrir", + "okButton": "Aceptar", + + "noLabelsFound": "No se encontraron etiquetas", + "searchInputPlaceholder": "Buscar o crear", + "assigneeSelectorInviteButton": "Invitar a un nuevo miembro por correo", + "labelInputPlaceholder": "Buscar o crear", + "searchLabelsPlaceholder": "Buscar etiquetas...", + "createLabelButton": "Crear \"{{name}}\"", + "manageLabelsPath": "Configuración → Etiquetas", + + "pendingInvitation": "Invitación pendiente", + + "contextMenu": { + "assignToMe": "Asignar a mí", + "moveTo": "Mover a", + "unarchive": "Desarchivar", + "archive": "Archivar", + "convertToSubTask": "Convertir en subtarea", + "convertToTask": "Convertir en tarea", + "delete": "Eliminar", + "searchByNameInputPlaceholder": "Buscar por nombre" + }, + "setDueDate": "Establecer fecha de vencimiento", + "setStartDate": "Establecer fecha de inicio", + "clearDueDate": "Limpiar fecha de vencimiento", + "clearStartDate": "Limpiar fecha de inicio", + "dueDatePlaceholder": "Fecha de vencimiento", + "startDatePlaceholder": "Fecha de inicio", + + "emptyStates": { + "noTaskGroups": "No se encontraron grupos de tareas", + "noTaskGroupsDescription": "Las tareas aparecerán aquí cuando se creen o cuando se apliquen filtros.", + "errorPrefix": "Error:", + "dragTaskFallback": "Tarea" + }, + + "customColumns": { + "addCustomColumn": "Agregar una columna personalizada", + "customColumnHeader": "Columna Personalizada", + "customColumnSettings": "Configuración de columna personalizada", + "noCustomValue": "Sin valor", + "peopleField": "Campo de personas", + "noDate": "Sin fecha", + "unsupportedField": "Tipo de campo no compatible", + + "modal": { + "addFieldTitle": "Agregar campo", + "editFieldTitle": "Editar campo", + "fieldTitle": "Título del campo", + "fieldTitleRequired": "El título del campo es obligatorio", + "columnTitlePlaceholder": "Título de la columna", + "type": "Tipo", + "deleteConfirmTitle": "¿Está seguro de que desea eliminar esta columna personalizada?", + "deleteConfirmDescription": "Esta acción no se puede deshacer. Todos los datos asociados con esta columna se eliminarán permanentemente.", + "deleteButton": "Eliminar", + "cancelButton": "Cancelar", + "createButton": "Crear", + "updateButton": "Actualizar", + "createSuccessMessage": "Columna personalizada creada exitosamente", + "updateSuccessMessage": "Columna personalizada actualizada exitosamente", + "deleteSuccessMessage": "Columna personalizada eliminada exitosamente", + "deleteErrorMessage": "Error al eliminar la columna personalizada", + "createErrorMessage": "Error al crear la columna personalizada", + "updateErrorMessage": "Error al actualizar la columna personalizada" + }, + + "fieldTypes": { + "people": "Personas", + "number": "Número", + "date": "Fecha", + "selection": "Selección", + "checkbox": "Casilla de verificación", + "labels": "Etiquetas", + "key": "Clave", + "formula": "Fórmula" + } + }, + + "indicators": { + "tooltips": { + "subtasks": "{{count}} subtarea", + "subtasks_plural": "{{count}} subtareas", + "comments": "{{count}} comentario", + "comments_plural": "{{count}} comentarios", + "attachments": "{{count}} archivo adjunto", + "attachments_plural": "{{count}} archivos adjuntos", + "subscribers": "La tarea tiene suscriptores", + "dependencies": "La tarea tiene dependencias", + "recurring": "Tarea recurrente" + } + } +} diff --git a/worklenz-backend/src/public/locales/es/task-management.json b/worklenz-backend/src/public/locales/es/task-management.json new file mode 100644 index 00000000..1c80304c --- /dev/null +++ b/worklenz-backend/src/public/locales/es/task-management.json @@ -0,0 +1,21 @@ +{ + "noTasksInGroup": "No hay tareas en este grupo", + "noTasksInGroupDescription": "Añade una tarea para comenzar", + "addFirstTask": "Añade tu primera tarea", + "openTask": "Abrir", + "subtask": "subtarea", + "subtasks": "subtareas", + "comment": "comentario", + "comments": "comentarios", + "attachment": "adjunto", + "attachments": "adjuntos", + "enterSubtaskName": "Ingresa el nombre de la subtarea...", + "add": "Añadir", + "cancel": "Cancelar", + "renameGroup": "Renombrar Grupo", + "renameStatus": "Renombrar Estado", + "renamePhase": "Renombrar Fase", + "changeCategory": "Cambiar Categoría", + "clickToEditGroupName": "Haz clic para editar el nombre del grupo", + "enterGroupName": "Ingresa el nombre del grupo" +} diff --git a/worklenz-backend/src/public/locales/es/task-template-drawer.json b/worklenz-backend/src/public/locales/es/task-template-drawer.json new file mode 100644 index 00000000..a3bfc45b --- /dev/null +++ b/worklenz-backend/src/public/locales/es/task-template-drawer.json @@ -0,0 +1,11 @@ +{ + "createTaskTemplate": "Crear Plantilla de Tarea", + "editTaskTemplate": "Editar Plantilla de Tarea", + "cancelText": "Cancelar", + "saveText": "Guardar", + "templateNameText": "Nombre de la Plantilla", + "selectedTasks": "Tareas Seleccionadas", + "removeTask": "Eliminar", + "cancelButton": "Cancelar", + "saveButton": "Guardar" +} diff --git a/worklenz-backend/src/public/locales/es/tasks/task-table-bulk-actions.json b/worklenz-backend/src/public/locales/es/tasks/task-table-bulk-actions.json new file mode 100644 index 00000000..0f98b1a5 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/tasks/task-table-bulk-actions.json @@ -0,0 +1,41 @@ +{ + "taskSelected": "Tarea seleccionada", + "tasksSelected": "Tareas seleccionadas", + "changeStatus": "Cambiar estado/ prioridad/ fases", + "changeLabel": "Cambiar etiqueta", + "assignToMe": "Asignar a mí", + "changeAssignees": "Cambiar asignados", + "archive": "Archivar", + "unarchive": "Desarchivar", + "delete": "Eliminar", + "moreOptions": "Más opciones", + "deselectAll": "Deseleccionar todo", + "status": "Estado", + "priority": "Prioridad", + "phase": "Fase", + "member": "Miembro", + "createTaskTemplate": "Crear plantilla de tarea", + "apply": "Aplicar", + "createLabel": "+ Crear etiqueta", + "searchOrCreateLabel": "Buscar o crear etiqueta...", + "hitEnterToCreate": "Presione Enter para crear", + "labelExists": "La etiqueta ya existe", + "pendingInvitation": "Invitación Pendiente", + "noMatchingLabels": "No hay etiquetas coincidentes", + "noLabels": "Sin etiquetas", + "CHANGE_STATUS": "Cambiar Estado", + "CHANGE_PRIORITY": "Cambiar Prioridad", + "CHANGE_PHASE": "Cambiar Fase", + "ADD_LABELS": "Agregar Etiquetas", + "ASSIGN_TO_ME": "Asignar a Mí", + "ASSIGN_MEMBERS": "Asignar Miembros", + "ARCHIVE": "Archivar", + "DELETE": "Eliminar", + "CANCEL": "Cancelar", + "CLEAR_SELECTION": "Limpiar Selección", + "TASKS_SELECTED": "{{count}} tarea seleccionada", + "TASKS_SELECTED_plural": "{{count}} tareas seleccionadas", + "DELETE_TASKS_CONFIRM": "¿Eliminar {{count}} tarea?", + "DELETE_TASKS_CONFIRM_plural": "¿Eliminar {{count}} tareas?", + "DELETE_TASKS_WARNING": "Esta acción no se puede deshacer." +} diff --git a/worklenz-backend/src/public/locales/es/template-drawer.json b/worklenz-backend/src/public/locales/es/template-drawer.json new file mode 100644 index 00000000..7c6c7f3d --- /dev/null +++ b/worklenz-backend/src/public/locales/es/template-drawer.json @@ -0,0 +1,19 @@ +{ + "title": "Editar Plantilla de Tarea", + "cancelText": "Cancelar", + "saveText": "Guardar", + "templateNameText": "Nombre de la Plantilla", + "selectedTasks": "Tareas Seleccionadas", + "removeTask": "Eliminar", + "description": "Descripción", + "phase": "Fase", + "statuses": "Estados", + "priorities": "Prioridades", + "labels": "Etiquetas", + "tasks": "Tareas", + "noTemplateSelected": "No hay plantilla seleccionada", + "noDescription": "Sin descripción", + "worklenzTemplates": "Plantillas de Worklenz", + "yourTemplatesLibrary": "Tu Biblioteca", + "searchTemplates": "Buscar Plantillas" +} diff --git a/worklenz-backend/src/public/locales/es/templateDrawer.json b/worklenz-backend/src/public/locales/es/templateDrawer.json new file mode 100644 index 00000000..8028f0cd --- /dev/null +++ b/worklenz-backend/src/public/locales/es/templateDrawer.json @@ -0,0 +1,23 @@ +{ + "bugTracking": "Seguimiento de Errores", + "construction": "Construcción", + "designCreative": "Diseño y Creatividad", + "education": "Educación", + "finance": "Finanzas", + "hrRecruiting": "RRHH y Reclutamiento", + "informationTechnology": "Tecnología de la Información", + "legal": "Legal", + "manufacturing": "Fabricación", + "marketing": "Marketing", + "nonprofit": "Sin fines de lucro", + "personalUse": "Uso personal", + "salesCRM": "Ventas y CRM", + "serviceConsulting": "Servicios y Consultoría", + "softwareDevelopment": "Desarrollo de Software", + "description": "Descripción", + "phase": "Fase", + "statuses": "Estados", + "priorities": "Prioridades", + "labels": "Etiquetas", + "tasks": "Tareas" +} diff --git a/worklenz-backend/src/public/locales/es/time-report.json b/worklenz-backend/src/public/locales/es/time-report.json new file mode 100644 index 00000000..2646520f --- /dev/null +++ b/worklenz-backend/src/public/locales/es/time-report.json @@ -0,0 +1,57 @@ +{ + "includeArchivedProjects": "Incluir Proyectos Archivados", + "export": "Exportar", + "timeSheet": "Hoja de Tiempo", + + "searchByName": "Buscar por nombre", + "selectAll": "Seleccionar Todo", + "teams": "Equipos", + + "searchByProject": "Buscar por nombre del proyecto", + "projects": "Proyectos", + + "searchByCategory": "Buscar por nombre de categoría", + "categories": "Categorías", + + "billable": "Facturable", + "nonBillable": "No Facturable", + + "total": "Total", + + "projectsTimeSheet": "Hoja de Tiempo de Proyectos", + + "loggedTime": "Tiempo Registrado(horas)", + + "exportToExcel": "Exportar a Excel", + "logged": "registrado", + "for": "para", + + "membersTimeSheet": "Hoja de Tiempo de Miembros", + "member": "Miembro", + + "estimatedVsActual": "Estimado vs Real", + "workingDays": "Días Laborables", + "manDays": "Días Hombre", + "days": "Días", + "estimatedDays": "Días Estimados", + "actualDays": "Días Reales", + + "noCategories": "No se encontraron categorías", + "noCategory": "Sin Categoría", + "noProjects": "No se encontraron proyectos", + "noTeams": "No se encontraron equipos", + "noData": "No se encontraron datos", + + "groupBy": "Agrupar por", + "groupByCategory": "Categoría", + "groupByTeam": "Equipo", + "groupByStatus": "Estado", + "groupByNone": "Ninguno", + "clearSearch": "Limpiar búsqueda", + "selectedProjects": "Proyectos Seleccionados", + "projectsSelected": "proyectos seleccionados", + "showSelected": "Mostrar Solo Seleccionados", + "expandAll": "Expandir Todo", + "collapseAll": "Contraer Todo", + "ungrouped": "Sin Agrupar" +} diff --git a/worklenz-backend/src/public/locales/es/unauthorized.json b/worklenz-backend/src/public/locales/es/unauthorized.json new file mode 100644 index 00000000..e28ce8f4 --- /dev/null +++ b/worklenz-backend/src/public/locales/es/unauthorized.json @@ -0,0 +1,5 @@ +{ + "title": "¡No autorizado!", + "subtitle": "No tienes permisos para acceder a esta página", + "button": "Ir a Inicio" +} diff --git a/worklenz-backend/src/public/locales/pt/404-page.json b/worklenz-backend/src/public/locales/pt/404-page.json new file mode 100644 index 00000000..638d30b3 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/404-page.json @@ -0,0 +1,4 @@ +{ + "doesNotExistText": "Desculpe, a página que você visitou não existe.", + "backHomeButton": "Voltar ao Início" +} diff --git a/worklenz-backend/src/public/locales/pt/account-setup.json b/worklenz-backend/src/public/locales/pt/account-setup.json new file mode 100644 index 00000000..1d8a8cba --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/account-setup.json @@ -0,0 +1,32 @@ +{ + "continue": "Continuar", + + "setupYourAccount": "Configure sua conta.", + "organizationStepTitle": "Nomeie sua organização", + "organizationStepLabel": "Escolha um nome para sua conta Worklenz.", + + "projectStepTitle": "Crie seu primeiro projeto", + "projectStepLabel": "Em qual projeto você está trabalhando agora?", + "projectStepPlaceholder": "ex. Plano de Marketing", + + "step2Title": "Crie suas primeiras tarefas", + "step2InputLabel": "Digite algumas tarefas que você vai fazer em", + "step2AddAnother": "Adicionar outro", + + "emailPlaceholder": "Endereço de e-mail", + "invalidEmail": "Por favor, insira um endereço de e-mail válido", + "or": "ou", + "templateButton": "Importar do modelo", + "goBack": "Voltar", + "cancel": "Cancelar", + "create": "Criar", + "templateDrawerTitle": "Selecionar dos modelos", + "step3InputLabel": "Convidar por email", + "addAnother": "Adicionar outro", + "skipForNow": "Pular por enquanto", + "formTitle": "Crie sua primeira tarefa.", + "step3Title": "Convide sua equipe para trabalhar", + + "maxMembers": " (Você pode convidar até 5 membros)", + "maxTasks": " (Você pode criar até 5 tarefas)" +} diff --git a/worklenz-backend/src/public/locales/pt/admin-center/current-bill.json b/worklenz-backend/src/public/locales/pt/admin-center/current-bill.json new file mode 100644 index 00000000..2e4b41d7 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/admin-center/current-bill.json @@ -0,0 +1,113 @@ +{ + "title": "Cobranças", + "currentBill": "Fatura Atual", + "configuration": "Configuração", + "currentPlanDetails": "Detalhes do Plano Atual", + "upgradePlan": "Atualizar Plano", + "cardBodyText01": "Teste gratuito", + "cardBodyText02": "(Seu plano de teste expira em 1 mês e 19 dias)", + "redeemCode": "Resgatar Código", + "accountStorage": "Armazenamento da Conta", + "used": "Usado:", + "remaining": "Restante:", + "charges": "Cobranças", + "tooltip": "Cobranças para o ciclo de faturamento atual", + "description": "Descrição", + "billingPeriod": "Período de Faturamento", + "billStatus": "Status da Fatura", + "perUserValue": "Valor por Usuário", + "users": "Usuários", + "amount": "Valor", + "invoices": "Faturas", + "transactionId": "ID da Transação", + "transactionDate": "Data da Transação", + "paymentMethod": "Método de Pagamento", + "status": "Status", + "ltdUsers": "Puedes agregar hasta {{ltd_users}} usuarios.", + + "drawerTitle": "Resgatar Código", + "label": "Resgatar Código", + "drawerPlaceholder": "Digite seu código de resgate", + "redeemSubmit": "Enviar", + + "modalTitle": "Selecione o melhor plano para sua equipe", + "seatLabel": "Número de assentos", + "freePlan": "Plano Gratuito", + "startup": "Startup", + "business": "Empresarial", + "tag": "Mais Popular", + "enterprise": "Enterprise", + + "freeSubtitle": "gratuito para sempre", + "freeUsers": "Melhor para uso pessoal", + "freeText01": "100MB de armazenamento", + "freeText02": "3 projetos", + "freeText03": "5 membros na equipe", + + "startupSubtitle": "TAXA FIXA / mês", + "startupUsers": "Até 15 usuários", + "startupText01": "25GB de armazenamento", + "startupText02": "Projetos ativos ilimitados", + "startupText03": "Agendamento", + "startupText04": "Relatórios", + "startupText05": "Inscrever-se em projetos", + + "businessSubtitle": "usuário / mês", + "businessUsers": "16 - 200 usuários", + + "enterpriseUsers": "200 - 500+ usuários", + + "footerTitle": "Por favor, forneça um número de contato para que possamos entrar em contato com você.", + "footerLabel": "Número de Contato", + "footerButton": "Contate-nos", + + "redeemCodePlaceHolder": "Digite seu código de resgate", + "submit": "Enviar", + + "trialPlan": "Plano de Teste", + "trialExpireDate": "Válido até {{trial_expire_date}}", + "trialExpired": "Sua prova gratuita expirou {{trial_expire_string}}", + "trialInProgress": "Sua prova gratuita expira {{trial_expire_string}}", + + "required": "Este campo é obrigatório", + "invalidCode": "Código inválido", + + "selectPlan": "Selecione o melhor plano para sua equipe", + "changeSubscriptionPlan": "Mude seu plano de assinatura", + "noOfSeats": "Número de assentos", + "annualPlan": "Pro - Anual", + "monthlyPlan": "Pro - Mensal", + "freeForever": "Gratis para sempre", + "bestForPersonalUse": "Melhor para uso pessoal", + "storage": "Armazenamento", + "projects": "Projetos", + "teamMembers": "Membros da equipe", + "unlimitedTeamMembers": "Membros da equipe ilimitados", + "unlimitedActiveProjects": "Projetos ativos ilimitados", + "schedule": "Agendamento", + "reporting": "Relatórios", + "subscribeToProjects": "Inscreva-se em projetos", + "billedAnnually": "Faturado Anualmente", + "billedMonthly": "Faturado Mensalmente", + + "pausePlan": "Pausar Plano", + "resumePlan": "Reanudar Plano", + "changePlan": "Mudar Plano", + "cancelPlan": "Cancelar Plano", + + "perMonthPerUser": "por usuário / mês", + "viewInvoice": "Ver Fatura", + "switchToFreePlan": "Mudar para Plano Gratuito", + + "expirestoday": "hoje", + "expirestomorrow": "amanhã", + "expiredDaysAgo": "há {{days}} dias", + "creditPlan": "Plano de Crédito", + "customPlan": "Plano Personalizado", + "planValidTill": "Seu plano é válido até {{date}}", + "purchaseSeatsText": "Para continuar, você precisará comprar assentos adicionais.", + "currentSeatsText": "Atualmente você tem {{seats}} assentos disponíveis.", + "selectSeatsText": "Selecione o número de assentos adicionais para comprar.", + "purchase": "Comprar", + "contactSales": "Fale com vendas" +} diff --git a/worklenz-backend/src/public/locales/pt/admin-center/overview.json b/worklenz-backend/src/public/locales/pt/admin-center/overview.json new file mode 100644 index 00000000..7cce8587 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/admin-center/overview.json @@ -0,0 +1,8 @@ +{ + "overview": "Visão Geral", + "name": "Nome da Organização", + "owner": "Proprietário da Organização", + "admins": "Administradores da Organização", + "contactNumber": "Adicione o Número de Contato", + "edit": "Editar" +} diff --git a/worklenz-backend/src/public/locales/pt/admin-center/projects.json b/worklenz-backend/src/public/locales/pt/admin-center/projects.json new file mode 100644 index 00000000..02fdc0bb --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/admin-center/projects.json @@ -0,0 +1,12 @@ +{ + "membersCount": "Contagem de Membros", + "createdAt": "Criado em", + "projectName": "Nome do Projeto", + "teamName": "Nome do Time", + "refreshProjects": "Atualizar Projetos", + "searchPlaceholder": "Pesquisar por nome do projeto", + "deleteProject": "Tem a certeza de que deseja deletar este projeto?", + "confirm": "Confirmar", + "cancel": "Cancelar", + "delete": "Deletar Projeto" +} diff --git a/worklenz-backend/src/public/locales/pt/admin-center/sidebar.json b/worklenz-backend/src/public/locales/pt/admin-center/sidebar.json new file mode 100644 index 00000000..253b77e4 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/admin-center/sidebar.json @@ -0,0 +1,8 @@ +{ + "overview": "Visão Geral", + "users": "Usuários", + "teams": "Equipes", + "billing": "Faturamento", + "projects": "Projetos", + "adminCenter": "Central Administrativa" +} diff --git a/worklenz-backend/src/public/locales/pt/admin-center/teams.json b/worklenz-backend/src/public/locales/pt/admin-center/teams.json new file mode 100644 index 00000000..6a71b491 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/admin-center/teams.json @@ -0,0 +1,35 @@ +{ + "title": "Equipes", + "subtitle": "equipes", + "tooltip": "Atualizar equipes", + "placeholder": "Pesquisar por nome", + "addTeam": "Adicionar Equipe", + "team": "Equipe", + "membersCount": "Contagem de Membros", + "members": "Membros", + "drawerTitle": "Criar Nova Equipe", + "label": "Nome da Equipe", + "drawerPlaceholder": "Nome", + "create": "Criar", + "delete": "Deletar", + "settings": "Configurações", + "popTitle": "Tem a certeza?", + "message": "Por favor, insira um Nome", + "teamSettings": "Configurações da Equipe", + "teamName": "Nome da Equipe", + "teamDescription": "Descrição da Equipe", + "teamMembers": "Membros da Equipe", + "teamMembersCount": "Quantidade de Membros da Equipe", + "teamMembersPlaceholder": "Buscar por nome", + "addMember": "Adicionar Membro", + "add": "Adicionar", + "update": "Atualizar", + "teamNamePlaceholder": "Nome da Equipe", + "user": "Usuário", + "role": "Rol", + "owner": "Propietario", + "admin": "Administrador", + "member": "Miembro", + "cannotChangeOwnerRole": "A função de Proprietário não pode ser alterada", + "pendingInvitation": "Convite pendente" +} diff --git a/worklenz-backend/src/public/locales/pt/admin-center/users.json b/worklenz-backend/src/public/locales/pt/admin-center/users.json new file mode 100644 index 00000000..9826c548 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/admin-center/users.json @@ -0,0 +1,9 @@ +{ + "title": "Usuários", + "subTitle": "usuários", + "placeholder": "Pesquisar por nome", + "user": "Usuário", + "email": "Email", + "lastActivity": "Última Atividade", + "refresh": "Atualizar usuários" +} diff --git a/worklenz-backend/src/public/locales/pt/all-project-list.json b/worklenz-backend/src/public/locales/pt/all-project-list.json new file mode 100644 index 00000000..482132eb --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/all-project-list.json @@ -0,0 +1,34 @@ +{ + "name": "Nome", + "client": "Cliente", + "category": "Categoria", + "status": "Status", + "tasksProgress": "Progresso das Tarefas", + "updated_at": "Última Atualização", + "members": "Membros", + "setting": "Configurações", + "projects": "Projetos", + "refreshProjects": "Atualizar projetos", + "all": "Todos", + "favorites": "Favoritos", + "archived": "Arquivados", + "placeholder": "Pesquisar por nome", + "archive": "Arquivar", + "unarchive": "Desarquivar", + "archiveConfirm": "Tem certeza de que deseja arquivar este projeto?", + "unarchiveConfirm": "Tem certeza de que deseja desarquivar este projeto?", + "yes": "Sim", + "no": "Não", + "clickToFilter": "Clique para filtrar por", + "noProjects": "Nenhum projeto encontrado", + "addToFavourites": "Adicionar aos favoritos", + "list": "Lista", + "group": "Grupo", + "listView": "Visualização em Lista", + "groupView": "Visualização em Grupo", + "groupBy": { + "category": "Categoria", + "client": "Cliente" + }, + "noPermission": "Você não tem permissão para realizar esta ação" +} diff --git a/worklenz-backend/src/public/locales/pt/auth/auth-common.json b/worklenz-backend/src/public/locales/pt/auth/auth-common.json new file mode 100644 index 00000000..e828bddf --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/auth/auth-common.json @@ -0,0 +1,5 @@ +{ + "loggingOut": "Deslogando...", + "authenticating": "Autenticando...", + "gettingThingsReady": "Preparando coisas para você..." +} diff --git a/worklenz-backend/src/public/locales/pt/auth/forgot-password.json b/worklenz-backend/src/public/locales/pt/auth/forgot-password.json new file mode 100644 index 00000000..5e9c89e0 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/auth/forgot-password.json @@ -0,0 +1,12 @@ +{ + "headerDescription": "Redefina sua senha", + "emailLabel": "Email", + "emailPlaceholder": "Digite seu email", + "emailRequired": "Por favor, digite seu email!", + "resetPasswordButton": "Redefinir Senha", + "returnToLoginButton": "Voltar para Login", + "passwordResetSuccessMessage": "Um link de redefinição de senha foi enviado para seu email.", + "orText": "OU", + "successTitle": "Instruções de redefinição enviadas!", + "successMessage": "A informação de redefinição foi enviada para seu email. Por favor, verifique seu email." +} diff --git a/worklenz-backend/src/public/locales/pt/auth/login.json b/worklenz-backend/src/public/locales/pt/auth/login.json new file mode 100644 index 00000000..2ce79115 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/auth/login.json @@ -0,0 +1,27 @@ +{ + "headerDescription": "Faça login na sua conta", + "emailLabel": "Email", + "emailPlaceholder": "Digite seu email", + "emailRequired": "Por favor, digite seu email!", + "passwordLabel": "Senha", + "passwordPlaceholder": "Digite sua senha", + "passwordRequired": "Por favor, digite sua Senha!", + "rememberMe": "Lembre de mim", + "loginButton": "Entrar", + "signupButton": "Inscrever-se", + "forgotPasswordButton": "Esqueceu sua senha?", + "signInWithGoogleButton": "Entrar com Google", + "successMessage": "Você entrou com sucesso!", + "dontHaveAccountText": "Não tem uma conta?", + "orText": "OU", + "loginError": "Login falhou", + "googleLoginError": "Login com Google falhou", + "validationMessages": { + "password": "A senha deve ter pelo menos 8 caracteres", + "email": "Por favor, insira um endereço de e-mail válido" + }, + "errorMessages": { + "loginErrorTitle": "Login falhou", + "loginErrorMessage": "Por favor, verifique seu e-mail e senha e tente novamente" + } +} diff --git a/worklenz-backend/src/public/locales/pt/auth/signup.json b/worklenz-backend/src/public/locales/pt/auth/signup.json new file mode 100644 index 00000000..cd994d4a --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/auth/signup.json @@ -0,0 +1,29 @@ +{ + "headerDescription": "Inscreva-se para começar", + "nameLabel": "Nome Completo", + "namePlaceholder": "Insira seu nome completo", + "nameRequired": "Por favor, insira seu nome completo!", + "nameMinCharacterRequired": "Nome completo deve ter pelo menos 4 caracteres!", + "emailLabel": "Email", + "emailPlaceholder": "Insira seu email", + "emailRequired": "Por favor, insira seu Email!", + "passwordLabel": "Senha", + "passwordPlaceholder": "Insira sua senha", + "passwordRequired": "Por favor, insira sua Senha!", + "passwordMinCharacterRequired": "Senha deve ter pelo menos 8 caracteres!", + "passwordPatternRequired": "Senha não atende aos requisitos!", + "strongPasswordPlaceholder": "Insira uma senha mais forte", + "passwordValidationAltText": "Senha deve incluir pelo menos 8 caracteres com letras maiúsculas e minúsculas, um número e um símbolo.", + "signupSuccessMessage": "Você se inscreveu com sucesso!", + "privacyPolicyLink": "Política de Privacidade", + "termsOfUseLink": "Termos de Uso", + "bySigningUpText": "Ao se inscrever, você concorda com nossos", + "andText": "e", + "signupButton": "Inscrever-se", + "signInWithGoogleButton": "Entrar com Google", + "alreadyHaveAccountText": "Já tem uma conta?", + "loginButton": "Entrar", + "orText": "OU", + "reCAPTCHAVerificationError": "Erro de verificação do reCAPTCHA", + "reCAPTCHAVerificationErrorMessage": "Não pudemos verificar seu reCAPTCHA. Por favor, tente novamente." +} diff --git a/worklenz-backend/src/public/locales/pt/auth/verify-reset-email.json b/worklenz-backend/src/public/locales/pt/auth/verify-reset-email.json new file mode 100644 index 00000000..189a6d51 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/auth/verify-reset-email.json @@ -0,0 +1,14 @@ +{ + "title": "Verificar E-mail de Redefinição", + "description": "Digite sua nova senha", + "placeholder": "Digite sua nova senha", + "confirmPasswordPlaceholder": "Confirme sua nova senha", + "passwordHint": "Mínimo de 8 caracteres, com maiúsculas e minúsculas, um número e um símbolo.", + "resetPasswordButton": "Redefinir senha", + "orText": "Ou", + "resendResetEmail": "Reenviar e-mail de redefinição", + "passwordRequired": "Por favor, digite sua nova senha", + "returnToLoginButton": "Voltar ao Login", + "confirmPasswordRequired": "Por favor, confirme sua nova senha", + "passwordMismatch": "As senhas não coincidem" +} diff --git a/worklenz-backend/src/public/locales/pt/common.json b/worklenz-backend/src/public/locales/pt/common.json new file mode 100644 index 00000000..ce540a28 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/common.json @@ -0,0 +1,9 @@ +{ + "login-success": "Login realizado com sucesso!", + "login-failed": "Falha no login. Por favor, verifique suas credenciais e tente novamente.", + "signup-success": "Cadastro realizado com sucesso! Bem-vindo a bordo.", + "signup-failed": "Falha no cadastro. Por favor, certifique-se de que todos os campos obrigatórios estão preenchidos e tente novamente.", + "reconnecting": "Reconectando ao servidor...", + "connection-lost": "Conexão perdida. Tentando reconectar...", + "connection-restored": "Conexão restaurada. Reconectando ao servidor..." +} diff --git a/worklenz-backend/src/public/locales/pt/create-first-project-form.json b/worklenz-backend/src/public/locales/pt/create-first-project-form.json new file mode 100644 index 00000000..ec3ec300 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/create-first-project-form.json @@ -0,0 +1,13 @@ +{ + "formTitle": "Crie seu primeiro projeto", + "inputLabel": "Em qual projeto você está trabalhando agora?", + "or": "ou", + "templateButton": "Importar do modelo", + "createFromTemplate": "Criar do modelo", + "goBack": "Voltar", + "continue": "Continuar", + "cancel": "Cancelar", + "create": "Criar", + "templateDrawerTitle": "Selecione um modelo", + "createProject": "Criar projeto" +} diff --git a/worklenz-backend/src/public/locales/pt/create-first-tasks.json b/worklenz-backend/src/public/locales/pt/create-first-tasks.json new file mode 100644 index 00000000..06c1ae87 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/create-first-tasks.json @@ -0,0 +1,7 @@ +{ + "formTitle": "Crie sua primeira tarefa.", + "inputLable": "Digite algumas tarefas que você vai fazer em", + "addAnother": "Adicionar outro", + "goBack": "Voltar", + "continue": "Continuar" +} diff --git a/worklenz-backend/src/public/locales/pt/home.json b/worklenz-backend/src/public/locales/pt/home.json new file mode 100644 index 00000000..b19ece5f --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/home.json @@ -0,0 +1,45 @@ +{ + "todoList": { + "title": "Lista de tarefas", + "refreshTasks": "Atualizar tarefas", + "addTask": "+ Adicionar tarefa", + "noTasks": "Nenhuma tarefa", + "pressEnter": "Pressione", + "toCreate": "para criar.", + "markAsDone": "Marcar como feito" + }, + "projects": { + "title": "Projetos", + "refreshProjects": "Atualizar projetos", + "noRecentProjects": "Você não está atribuído a nenhum projeto.", + "noFavouriteProjects": "Nenhum projeto foi marcado como favorito.", + "recent": "Recentes", + "favourites": "Favoritos" + }, + "tasks": { + "assignedToMe": "Atribuído a mim", + "assignedByMe": "Atribuído por mim", + "all": "Todas", + "today": "Hoje", + "upcoming": "Próximas", + "overdue": "Vencidas", + "noDueDate": "Sem data de vencimento", + "noTasks": "Nenhuma tarefa para mostrar.", + "addTask": "+ Adicionar tarefa", + "name": "Nome", + "project": "Projeto", + "status": "Status", + "dueDate": "Data de vencimento", + "dueDatePlaceholder": "Definir data de vencimento", + "tomorrow": "Amanhã", + "nextWeek": "Semana que vem", + "nextMonth": "Próximo mês", + "projectRequired": "Por favor selecione um projeto", + "dueOn": "Tarefas vencidas em", + "taskRequired": "Por favor adicione uma tarefa", + "list": "Lista", + "calendar": "Calendário", + "tasks": "Tarefas", + "refresh": "Atualizar" + } +} diff --git a/worklenz-backend/src/public/locales/pt/invite-initial-team-members.json b/worklenz-backend/src/public/locales/pt/invite-initial-team-members.json new file mode 100644 index 00000000..39808ab2 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/invite-initial-team-members.json @@ -0,0 +1,8 @@ +{ + "formTitle": "Convide sua equipe para trabalhar com", + "inputLable": "Convidar com email", + "addAnother": "Adicionar outro", + "goBack": "Voltar", + "continue": "Continuar", + "skipForNow": "Pular por enquanto" +} diff --git a/worklenz-backend/src/public/locales/pt/kanban-board.json b/worklenz-backend/src/public/locales/pt/kanban-board.json new file mode 100644 index 00000000..a2034daa --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/kanban-board.json @@ -0,0 +1,30 @@ +{ + "rename": "Renomear", + "delete": "Excluir", + "addTask": "Adicionar Tarefa", + "addSectionButton": "Adicionar Seção", + "changeCategory": "Alterar categoria", + + "deleteTooltip": "Excluir", + "deleteConfirmationTitle": "Tem certeza?", + "deleteConfirmationOk": "Sim", + "deleteConfirmationCancel": "Cancelar", + + "dueDate": "Data de vencimento", + "cancel": "Cancelar", + + "today": "Hoje", + "tomorrow": "Amanhã", + "assignToMe": "Atribuir a mim", + "archive": "Arquivar", + + "newTaskNamePlaceholder": "Escreva um nome de tarefa", + "newSubtaskNamePlaceholder": "Escreva um nome de subtarefa", + "untitledSection": "Seção sem título", + "unmapped": "Não mapeado", + "clickToChangeDate": "Clique para alterar a data", + "noDueDate": "Sem data de vencimento", + "save": "Salvar", + "clear": "Limpar", + "nextWeek": "Próxima semana" +} diff --git a/worklenz-backend/src/public/locales/pt/license-expired.json b/worklenz-backend/src/public/locales/pt/license-expired.json new file mode 100644 index 00000000..aa7ae88b --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/license-expired.json @@ -0,0 +1,6 @@ +{ + "title": "Seu teste do Worklenz expirou!", + "subtitle": "Por favor, atualize agora.", + "button": "Atualizar agora", + "checking": "Verificando status da assinatura..." +} diff --git a/worklenz-backend/src/public/locales/pt/navbar.json b/worklenz-backend/src/public/locales/pt/navbar.json new file mode 100644 index 00000000..be0f3a63 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/navbar.json @@ -0,0 +1,31 @@ +{ + "logoAlt": "Logotipo Worklenz", + "home": "Início", + "projects": "Projetos", + "schedule": "Agendamento", + "reporting": "Relatórios", + "clients": "Clientes", + "teams": "Equipes", + "labels": "Rótulos", + "jobTitles": "Títulos de Emprego", + "upgradePlan": "Plano de Upgrade", + "upgradePlanTooltip": "Plano de Upgrade", + "invite": "Convidar", + "inviteTooltip": "Convidar membros da equipe a se juntar", + "switchTeamTooltip": "Trocar equipe", + "help": "Ajuda", + "notificationTooltip": "Ver notificações", + "profileTooltip": "Ver perfil", + "adminCenter": "Centro de administração", + "settings": "Configurações", + "logOut": "Sair", + "notificationsDrawer": { + "read": "Notificações lidas", + "unread": "Notificações não lidas", + "markAsRead": "Marcar como lido", + "readAndJoin": "Ler e participar", + "accept": "Aceitar", + "acceptAndJoin": "Aceitar e participar", + "noNotifications": "Sem notificações" + } +} diff --git a/worklenz-backend/src/public/locales/pt/organization-name-form.json b/worklenz-backend/src/public/locales/pt/organization-name-form.json new file mode 100644 index 00000000..c165b8cb --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/organization-name-form.json @@ -0,0 +1,5 @@ +{ + "nameYourOrganization": "Nomeie sua organização.", + "worklenzAccountTitle": "Escolha um nome para sua conta Worklenz.", + "continue": "Continuar" +} diff --git a/worklenz-backend/src/public/locales/pt/phases-drawer.json b/worklenz-backend/src/public/locales/pt/phases-drawer.json new file mode 100644 index 00000000..080b13df --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/phases-drawer.json @@ -0,0 +1,19 @@ +{ + "configurePhases": "Configurar fases", + "phaseLabel": "Etiqueta de fase", + "enterPhaseName": "Digite um nome para o rótulo da fase", + "addOption": "Adicionar Opção", + "phaseOptions": "Opções de Fase:", + "dragToReorderPhases": "Arraste as fases para reordená-las. Cada fase pode ter uma cor diferente.", + "enterNewPhaseName": "Digite o novo nome da fase...", + "addPhase": "Adicionar Fase", + "noPhasesFound": "Nenhuma fase encontrada. Crie sua primeira fase acima.", + "deletePhase": "Excluir Fase", + "deletePhaseConfirm": "Tem certeza de que deseja excluir esta fase? Esta ação não pode ser desfeita.", + "rename": "Renomear", + "delete": "Excluir", + "enterPhaseName": "Digite o nome da fase", + "selectColor": "Selecionar cor", + "managePhases": "Gerenciar Fases", + "close": "Fechar" +} diff --git a/worklenz-backend/src/public/locales/pt/project-drawer.json b/worklenz-backend/src/public/locales/pt/project-drawer.json new file mode 100644 index 00000000..92e11964 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/project-drawer.json @@ -0,0 +1,52 @@ +{ + "createProject": "Criar Projeto", + "editProject": "Editar Projeto", + "enterCategoryName": "Insira um nome para a categoria", + "hitEnterToCreate": "Pressione enter para criar!", + "enterNotes": "Notas", + "youCanManageClientsUnderSettings": "Você pode gerenciar clientes em Configurações", + "addCategory": "Adicione uma categoria ao projeto", + "newCategory": "Nova Categoria", + "notes": "Notas", + "startDate": "Data de Início", + "endDate": "Data de Fim", + "estimateWorkingDays": "Estime os dias de trabalho", + "estimateManDays": "Estime os dias de trabalho", + "hoursPerDay": "Horas por dia", + "create": "Criar", + "update": "Atualizar", + "delete": "Excluir", + "typeToSearchClients": "Digite para buscar clientes", + "projectColor": "Cor do Projeto", + "pleaseEnterAName": "Por favor, insira um nome", + "enterProjectName": "Insira o nome do projeto", + "name": "Nome", + "status": "Estado", + "health": "Saúde", + "category": "Categoria", + "projectManager": "Gerente de Projeto", + "client": "Cliente", + "deleteConfirmation": "Tem a certeza de que deseja excluir?", + "deleteConfirmationDescription": "Isso removerá todos os dados associados e não pode ser desfeito.", + "yes": "Sim", + "no": "Não", + "createdAt": "Criado", + "updatedAt": "Atualizado", + "by": "por", + "add": "Adicionar", + "asClient": "como cliente", + "createClient": "Criar cliente", + "searchInputPlaceholder": "Pesquise por nome ou email", + "hoursPerDayValidationMessage": "As horas por dia devem ser um número entre 1 e 24", + "workingDaysValidationMessage": "Os dias de trabalho devem ser um número positivo", + "manDaysValidationMessage": "Os dias de homem devem ser um número positivo", + "noPermission": "Sem permissão", + "progressSettings": "Configurações de Progresso", + "manualProgress": "Progresso Manual", + "manualProgressTooltip": "Permitir atualizações manuais de progresso para tarefas sem subtarefas", + "weightedProgress": "Progresso Ponderado", + "weightedProgressTooltip": "Calcular o progresso com base nos pesos das subtarefas", + "timeProgress": "Progresso Baseado em Tempo", + "timeProgressTooltip": "Calcular o progresso com base no tempo estimado", + "enterProjectKey": "Insira a chave do projeto" +} diff --git a/worklenz-backend/src/public/locales/pt/project-view-files.json b/worklenz-backend/src/public/locales/pt/project-view-files.json new file mode 100644 index 00000000..61f1cb59 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/project-view-files.json @@ -0,0 +1,14 @@ +{ + "nameColumn": "Nome", + "attachedTaskColumn": "Tarefa Anexada", + "sizeColumn": "Tamanho", + "uploadedByColumn": "Enviado Por", + "uploadedAtColumn": "Enviado Em", + "fileIconAlt": "Ícone do Arquivo", + "titleDescriptionText": "Todos os anexos das tarefas neste projeto aparecerão aqui.", + "deleteConfirmationTitle": "Tem certeza?", + "deleteConfirmationOk": "Sim", + "deleteConfirmationCancel": "Cancelar", + "segmentedTooltip": "Em breve! Alterne entre a visualização em lista e a visualização em miniatura.", + "emptyText": "Não há anexos no projeto." +} diff --git a/worklenz-backend/src/public/locales/pt/project-view-insights.json b/worklenz-backend/src/public/locales/pt/project-view-insights.json new file mode 100644 index 00000000..2ad6ee92 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/project-view-insights.json @@ -0,0 +1,41 @@ +{ + "overview": { + "title": "Visão Geral", + "statusOverview": "Visão Geral do Status", + "priorityOverview": "Visão Geral da Prioridade", + "lastUpdatedTasks": "Últimas Tarefas Atualizadas" + }, + "members": { + "title": "Membros", + "tooltip": "Membros", + "tasksByMembers": "Tarefas por membros", + "tasksByMembersTooltip": "Tarefas por membros", + "name": "Nome", + "taskCount": "Contagem de Tarefas", + "contribution": "Contribuição", + "completed": "Concluído", + "incomplete": "Incompleto", + "overdue": "Atrasado", + "progress": "Progresso" + }, + "tasks": { + "overdueTasks": "Tarefas Atrasadas", + "overLoggedTasks": "Tarefas com excesso de tempo registrado", + "tasksCompletedEarly": "Tarefas concluídas cedo", + "tasksCompletedLate": "Tarefas concluídas tarde", + "overLoggedTasksTooltip": "Tarefas que têm tempo registrado além do tempo estimado", + "overdueTasksTooltip": "Tarefas que estão atrasadas" + }, + "common": { + "seeAll": "Ver tudo", + "totalLoggedHours": "Total de horas registradas", + "totalEstimation": "Total de estimativa", + "completedTasks": "Tarefas concluídas", + "incompleteTasks": "Tarefas incompletas", + "overdueTasks": "Tarefas atrasadas", + "overdueTasksTooltip": "Tarefas que estão atrasadas", + "totalLoggedHoursTooltip": "Estimativa de tarefas e tempo registrado.", + "includeArchivedTasks": "Incluir Tarefas Arquivadas", + "export": "Exportar" + } +} diff --git a/worklenz-backend/src/public/locales/pt/project-view-members.json b/worklenz-backend/src/public/locales/pt/project-view-members.json new file mode 100644 index 00000000..72524807 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/project-view-members.json @@ -0,0 +1,17 @@ +{ + "nameColumn": "Nome", + "jobTitleColumn": "Título do Cargo", + "emailColumn": "Email", + "tasksColumn": "Tarefas", + "taskProgressColumn": "Progresso da Tarefa", + "accessColumn": "Acesso", + "fileIconAlt": "Ícone do Arquivo", + "deleteConfirmationTitle": "Tem a certeza?", + "deleteConfirmationOk": "Sim", + "deleteConfirmationCancel": "Cancelar", + "refreshButtonTooltip": "Atualizar membros", + "deleteButtonTooltip": "Remover do projeto", + "memberCount": "Membro", + "membersCountPlural": "Membros", + "emptyText": "Não há anexos no projeto." +} diff --git a/worklenz-backend/src/public/locales/pt/project-view-updates.json b/worklenz-backend/src/public/locales/pt/project-view-updates.json new file mode 100644 index 00000000..93a48950 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/project-view-updates.json @@ -0,0 +1,6 @@ +{ + "inputPlaceholder": "Adicione um comentário..", + "addButton": "Adicionar", + "cancelButton": "Cancelar", + "deleteButton": "Deletar" +} diff --git a/worklenz-backend/src/public/locales/pt/project-view.json b/worklenz-backend/src/public/locales/pt/project-view.json new file mode 100644 index 00000000..c58337da --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/project-view.json @@ -0,0 +1,14 @@ +{ + "taskList": "Lista de Tarefas", + "board": "Quadro Kanban", + "insights": "Insights", + "files": "Arquivos", + "members": "Membros", + "updates": "Atualizações", + "projectView": "Visualização do Projeto", + "loading": "Carregando projeto...", + "error": "Erro ao carregar projeto", + "pinnedTab": "Fixada como aba padrão", + "pinTab": "Fixar como aba padrão", + "unpinTab": "Desfixar aba padrão" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/pt/project-view/import-task-templates.json b/worklenz-backend/src/public/locales/pt/project-view/import-task-templates.json new file mode 100644 index 00000000..81a64607 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/project-view/import-task-templates.json @@ -0,0 +1,11 @@ +{ + "importTaskTemplate": "Importar modelo de tarefa", + "templateName": "Nome do modelo", + "templateDescription": "Descrição do modelo", + "selectedTasks": "Tarefas selecionadas", + "tasks": "Tarefas", + "templates": "Modelos", + "remove": "Remover", + "cancel": "Cancelar", + "import": "Importar" +} diff --git a/worklenz-backend/src/public/locales/pt/project-view/project-member-drawer.json b/worklenz-backend/src/public/locales/pt/project-view/project-member-drawer.json new file mode 100644 index 00000000..0afe3d87 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/project-view/project-member-drawer.json @@ -0,0 +1,7 @@ +{ + "title": "Membros do Projeto", + "searchLabel": "Adicionar membros inserindo nome ou e-mail", + "searchPlaceholder": "Digite nome ou e-mail", + "inviteAsAMember": "Convidar como membro", + "inviteNewMemberByEmail": "Convidar novo membro por e-mail" +} diff --git a/worklenz-backend/src/public/locales/pt/project-view/project-view-header.json b/worklenz-backend/src/public/locales/pt/project-view/project-view-header.json new file mode 100644 index 00000000..4649b768 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/project-view/project-view-header.json @@ -0,0 +1,30 @@ +{ + "importTasks": "Importar tarefas", + "importTask": "Importar tarefa", + "createTask": "Criar tarefa", + "settings": "Configurações", + "subscribe": "Inscrever-se", + "unsubscribe": "Cancelar inscrição", + "deleteProject": "Excluir projeto", + "startDate": "Data de início", + "endDate": "Data de término", + "projectSettings": "Configurações do projeto", + "projectSummary": "Resumo do projeto", + "receiveProjectSummary": "Receba um resumo do projeto todas as noites.", + "refreshProject": "Atualizar projeto", + "saveAsTemplate": "Salvar como modelo", + "invite": "Convidar", + "share": "Compartilhar", + "subscribeTooltip": "Inscrever-se nas notificações do projeto", + "unsubscribeTooltip": "Cancelar inscrição nas notificações do projeto", + "refreshTooltip": "Atualizar dados do projeto", + "settingsTooltip": "Abrir configurações do projeto", + "saveAsTemplateTooltip": "Salvar este projeto como modelo", + "inviteTooltip": "Convidar membros da equipe para este projeto", + "createTaskTooltip": "Criar uma nova tarefa", + "importTaskTooltip": "Importar tarefa de modelo", + "navigateBackTooltip": "Voltar para lista de projetos", + "projectStatusTooltip": "Status do projeto", + "projectDatesInfo": "Informações do cronograma do projeto", + "projectCategoryTooltip": "Categoria do projeto" +} diff --git a/worklenz-backend/src/public/locales/pt/project-view/save-as-template.json b/worklenz-backend/src/public/locales/pt/project-view/save-as-template.json new file mode 100644 index 00000000..c67eb20e --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/project-view/save-as-template.json @@ -0,0 +1,27 @@ +{ + "title": "Salvar como Modelo", + "templateName": "Nome do Modelo", + "includes": "O que deve ser incluído no modelo do projeto?", + "includesOptions": { + "statuses": "Status", + "phases": "Fases", + "labels": "Etiquetas" + }, + "taskIncludes": "O que deve ser incluído no modelo das tarefas?", + "taskIncludesOptions": { + "statuses": "Status", + "phases": "Fases", + "labels": "Etiquetas", + "name": "Nome", + "priority": "Prioridade", + "status": "Status", + "phase": "Fase", + "label": "Etiqueta", + "timeEstimate": "Estimativa de Tempo", + "description": "Descrição", + "subTasks": "Subtarefas" + }, + "cancel": "Cancelar", + "save": "Salvar", + "templateNamePlaceholder": "Digite o nome do modelo" +} diff --git a/worklenz-backend/src/public/locales/pt/reporting-members-drawer.json b/worklenz-backend/src/public/locales/pt/reporting-members-drawer.json new file mode 100644 index 00000000..49d0008b --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/reporting-members-drawer.json @@ -0,0 +1,90 @@ +{ + "exportButton": "Exportar", + "timeLogsButton": "Registros de Tempo", + "activityLogsButton": "Registros de Atividade", + "tasksButton": "Tarefas", + "searchByNameInputPlaceholder": "Pesquisar por nome", + + "overviewTab": "Visão Geral", + "timeLogsTab": "Registros de Tempo", + "activityLogsTab": "Registros de Atividade", + "tasksTab": "Tarefas", + + "projectsText": "Projetos", + "totalTasksText": "Total de Tarefas", + "assignedTasksText": "Tarefas Atribuídas", + "completedTasksText": "Tarefas Concluídas", + "ongoingTasksText": "Tarefas em Andamento", + "overdueTasksText": "Tarefas Atrasadas", + "loggedHoursText": "Horas Registradas", + + "tasksText": "Tarefas", + "allText": "Todas", + + "tasksByProjectsText": "Tarefas Por Projetos", + "tasksByStatusText": "Tarefas Por Status", + "tasksByPriorityText": "Tarefas Por Prioridade", + + "todoText": "A Fazer", + "doingText": "Fazendo", + "doneText": "Feita", + "lowText": "Baixa", + "mediumText": "Média", + "highText": "Alta", + + "billableButton": "Cobrável", + "billableText": "Cobrável", + "nonBillableText": "Não Cobrável", + + "timeLogsEmptyPlaceholder": "Nenhum registro de tempo para mostrar", + "loggedText": "Registrado", + "forText": "para", + "inText": "em", + "updatedText": "Atualizado", + "fromText": "De", + "toText": "até", + "withinText": "dentro de", + + "activityLogsEmptyPlaceholder": "Nenhum registro de atividade para mostrar", + + "filterByText": "Filtrar por:", + "selectProjectPlaceholder": "Selecione o Projeto", + + "taskColumn": "Tarefa", + "nameColumn": "Nome", + "projectColumn": "Projeto", + "statusColumn": "Status", + "priorityColumn": "Prioridade", + "dueDateColumn": "Data de Vencimento", + "completedDateColumn": "Data de Conclusão", + "estimatedTimeColumn": "Tempo Estimado", + "loggedTimeColumn": "Tempo Registrado", + "overloggedTimeColumn": "Tempo Excedido", + "daysLeftColumn": "Dias Restantes/Atrasados", + "startDateColumn": "Data de Início", + "endDateColumn": "Data de Fim", + "actualTimeColumn": "Tempo Real", + "projectHealthColumn": "Saúde do Projeto", + "categoryColumn": "Categoria", + "projectManagerColumn": "Gerente do Projeto", + + "tasksStatsOverviewDrawerTitle": "Tarefas de", + "projectsStatsOverviewDrawerTitle": "Projetos de", + + "cancelledText": "Cancelada", + "blockedText": "Bloqueada", + "onHoldText": "Em Espera", + "proposedText": "Proposta", + "inPlanningText": "Em Planejamento", + "inProgressText": "Em Progresso", + "completedText": "Concluída", + "continuousText": "Contínua", + + "daysLeftText": "dias restantes", + "daysOverdueText": "dias atrasados", + + "notSetText": "Não Definido", + "needsAttentionText": "Precisa de Atenção", + "atRiskText": "Em Risco", + "goodText": "Bom" +} diff --git a/worklenz-backend/src/public/locales/pt/reporting-members.json b/worklenz-backend/src/public/locales/pt/reporting-members.json new file mode 100644 index 00000000..a8035dcd --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/reporting-members.json @@ -0,0 +1,35 @@ +{ + "yesterdayText": "Yesterday", + "lastSevenDaysText": "Last 7 Days", + "lastWeekText": "Last Week", + "lastThirtyDaysText": "Last 30 Days", + "lastMonthText": "Last Month", + "lastThreeMonthsText": "Last 3 Months", + "allTimeText": "All Time", + "customRangeText": "Custom range", + "startDateInputPlaceholder": "Start date", + "EndDateInputPlaceholder": "End date", + "filterButton": "Filter", + + "membersTitle": "Members", + "includeArchivedButton": "Include Archived Projects", + "exportButton": "Export", + "excelButton": "Excel", + "searchByNameInputPlaceholder": "Search by name", + + "memberColumn": "Member", + "tasksProgressColumn": "Tasks Progress", + "tasksAssignedColumn": "Tasks Assigned", + "completedTasksColumn": "Completed Tasks", + "overdueTasksColumn": "Overdue Tasks", + "ongoingTasksColumn": "Ongoing Tasks", + + "tasksAssignedColumnTooltip": "Tasks assigned on selected date range", + "overdueTasksColumnTooltip": "Tasks overdue for end of the selected date range", + "completedTasksColumnTooltip": "Tasks completed on selected date range", + "ongoingTasksColumnTooltip": "Started tasks not completed yet", + + "todoText": "To Do", + "doingText": "Doing", + "doneText": "Done" +} diff --git a/worklenz-backend/src/public/locales/pt/reporting-overview-drawer.json b/worklenz-backend/src/public/locales/pt/reporting-overview-drawer.json new file mode 100644 index 00000000..af8b06ee --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/reporting-overview-drawer.json @@ -0,0 +1,39 @@ +{ + "exportButton": "Exportar", + "projectsButton": "Projetos", + "membersButton": "Membros", + "searchByNameInputPlaceholder": "Pesquisar por nome", + + "overviewTab": "Visão Geral", + "projectsTab": "Projetos", + "membersTab": "Membros", + + "projectsByStatusText": "Projetos Por Status", + "projectsByCategoryText": "Projetos Por Categoria", + "projectsByHealthText": "Projetos Por Saúde", + + "projectsText": "Projetos", + "allText": "Todos", + + "cancelledText": "Cancelado", + "blockedText": "Bloqueado", + "onHoldText": "Em Espera", + "proposedText": "Proposto", + "inPlanningText": "Em Planejamento", + "inProgressText": "Em Andamento", + "completedText": "Concluído", + "continuousText": "Contínuo", + + "notSetText": "Não Definido", + "needsAttentionText": "Necessita de Atenção", + "atRiskText": "Em Risco", + "goodText": "Bom", + + "nameColumn": "Nome", + "emailColumn": "Email", + "projectsColumn": "Projetos", + "tasksColumn": "Tarefas", + "overdueTasksColumn": "Tarefas Atrasadas", + "completedTasksColumn": "Tarefas Concluídas", + "ongoingTasksColumn": "Tarefas em Andamento" +} diff --git a/worklenz-backend/src/public/locales/pt/reporting-overview.json b/worklenz-backend/src/public/locales/pt/reporting-overview.json new file mode 100644 index 00000000..01681d1a --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/reporting-overview.json @@ -0,0 +1,25 @@ +{ + "overviewTitle": "Visão Geral", + "includeArchivedButton": "Incluir Projetos Arquivados", + + "teamCount": "Equipe", + "teamCountPlural": "Equipes", + "projectCount": "Projeto", + "projectCountPlural": "Projetos", + "memberCount": "Membro", + "memberCountPlural": "Membros", + "activeProjectCount": "Projeto Ativo", + "activeProjectCountPlural": "Projetos Ativos", + "overdueProjectCount": "Projeto Atrasado", + "overdueProjectCountPlural": "Projetos Atrasados", + "unassignedMemberCount": "Membro Não Atribuído", + "unassignedMemberCountPlural": "Membros Não Atribuídos", + "memberWithOverdueTaskCount": "Membro Com Tarefa Atrasada", + "memberWithOverdueTaskCountPlural": "Membros Com Tarefas Atrasadas", + + "teamsText": "Equipes", + + "nameColumn": "Nome", + "projectsColumn": "Projetos", + "membersColumn": "Membros" +} diff --git a/worklenz-backend/src/public/locales/pt/reporting-projects-drawer.json b/worklenz-backend/src/public/locales/pt/reporting-projects-drawer.json new file mode 100644 index 00000000..14bcfaca --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/reporting-projects-drawer.json @@ -0,0 +1,59 @@ +{ + "exportButton": "Exportar", + "membersButton": "Membros", + "tasksButton": "Tarefas", + "searchByNameInputPlaceholder": "Pesquisar por nome", + + "overviewTab": "Visão Geral", + "membersTab": "Membros", + "tasksTab": "Tarefas", + + "completedTasksText": "Tarefas Concluídas", + "incompleteTasksText": "Tarefas Incompletas", + "overdueTasksText": "Tarefas Atrasadas", + "allocatedHoursText": "Horas Alocadas", + "loggedHoursText": "Horas Registradas", + + "tasksText": "Tarefas", + "allText": "Todas", + + "tasksByStatusText": "Tarefas Por Status", + "tasksByPriorityText": "Tarefas Por Prioridade", + "tasksByDueDateText": "Tarefas Por Data de Vencimento", + + "todoText": "A Fazer", + "doingText": "Fazendo", + "doneText": "Feita", + "lowText": "Baixa", + "mediumText": "Média", + "highText": "Alta", + "completedText": "Concluída", + "upcomingText": "Próxima", + "overdueText": "Atrasada", + "noDueDateText": "Sem Data de Vencimento", + + "nameColumn": "Nome", + "tasksCountColumn": "Contagem de Tarefas", + "completedTasksColumn": "Tarefas Concluídas", + "incompleteTasksColumn": "Tarefas Incompletas", + "overdueTasksColumn": "Tarefas Atrasadas", + "contributionColumn": "Contribuição", + "progressColumn": "Progresso", + "loggedTimeColumn": "Tempo Registrado", + "taskColumn": "Tarefa", + "projectColumn": "Projeto", + "statusColumn": "Status", + "priorityColumn": "Prioridade", + "phaseColumn": "Fase", + "dueDateColumn": "Data de Vencimento", + "completedDateColumn": "Data de Conclusão", + "estimatedTimeColumn": "Tempo Estimado", + "overloggedTimeColumn": "Tempo Excedido", + "completedOnColumn": "Concluído Em", + "daysOverdueColumn": "Dias Atrasados", + + "groupByText": "Agrupar Por:", + "statusText": "Status", + "priorityText": "Prioridade", + "phaseText": "Fase" +} diff --git a/worklenz-backend/src/public/locales/pt/reporting-projects-filters.json b/worklenz-backend/src/public/locales/pt/reporting-projects-filters.json new file mode 100644 index 00000000..5d47d282 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/reporting-projects-filters.json @@ -0,0 +1,35 @@ +{ + "searchByNamePlaceholder": "Pesquisar por nome", + "searchByCategoryPlaceholder": "Pesquisar por categoria", + + "statusText": "Status", + "healthText": "Saúde", + "categoryText": "Categoria", + "projectManagerText": "Gerente de Projeto", + "showFieldsText": "Mostrar campos", + + "cancelledText": "Cancelado", + "blockedText": "Bloqueado", + "onHoldText": "Em Espera", + "proposedText": "Proposto", + "inPlanningText": "Em Planejamento", + "inProgressText": "Em Andamento", + "completedText": "Concluído", + "continuousText": "Contínuo", + + "notSetText": "Não Definido", + "needsAttentionText": "Precisa de Atenção", + "atRiskText": "Em Risco", + "goodText": "Bom", + + "nameText": "Projeto", + "estimatedVsActualText": "Estimado Vs Real", + "tasksProgressText": "Progresso das Tarefas", + "lastActivityText": "Última Atividade", + "datesText": "Datas de Início/Fim", + "daysLeftText": "Dias Restantes/Atrasados", + "projectHealthText": "Saúde do Projeto", + "projectUpdateText": "Atualização do Projeto", + "clientText": "Cliente", + "teamText": "Equipe" +} diff --git a/worklenz-backend/src/public/locales/pt/reporting-projects.json b/worklenz-backend/src/public/locales/pt/reporting-projects.json new file mode 100644 index 00000000..c5035b54 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/reporting-projects.json @@ -0,0 +1,52 @@ +{ + "projectCount": "Projeto", + "projectCountPlural": "Projetos", + "includeArchivedButton": "Incluir Projetos Arquivados", + "exportButton": "Exportar", + "excelButton": "Excel", + + "projectColumn": "Projeto", + "estimatedVsActualColumn": "Estimado Vs Real", + "tasksProgressColumn": "Progresso das Tarefas", + "lastActivityColumn": "Última Atividade", + "statusColumn": "Status", + "datesColumn": "Datas de Início/Fim", + "daysLeftColumn": "Dias Restantes/Atrasados", + "projectHealthColumn": "Saúde do Projeto", + "categoryColumn": "Categoria", + "projectUpdateColumn": "Atualização do Projeto", + "clientColumn": "Cliente", + "teamColumn": "Equipe", + "projectManagerColumn": "Gerente de Projeto", + + "openButton": "Abrir", + + "estimatedText": "Estimado", + "actualText": "Real", + + "todoText": "A Fazer", + "doingText": "Fazendo", + "doneText": "Feito", + + "cancelledText": "Cancelado", + "blockedText": "Bloqueado", + "onHoldText": "Em Espera", + "proposedText": "Proposto", + "inPlanningText": "Em Planejamento", + "inProgressText": "Em Andamento", + "completedText": "Concluído", + "continuousText": "Contínuo", + + "daysLeftText": "dias restantes", + "dayLeftText": "dia restante", + "daysOverdueText": "dias atrasados", + + "notSetText": "Não Definido", + "needsAttentionText": "Precisa de Atenção", + "atRiskText": "Em Risco", + "goodText": "Bom", + + "setCategoryText": "Definir Categoria", + "searchByNameInputPlaceholder": "Pesquisar por nome", + "todayText": "Hoje" +} diff --git a/worklenz-backend/src/public/locales/pt/reporting-sidebar.json b/worklenz-backend/src/public/locales/pt/reporting-sidebar.json new file mode 100644 index 00000000..e09940f3 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/reporting-sidebar.json @@ -0,0 +1,8 @@ +{ + "overviewText": "Visão Geral", + "projectsText": "Projetos", + "membersText": "Membros", + "timeReportsText": "Relatórios de Tempo", + "estimateVsActualText": "Estimado Vs Real", + "currentOrganizationTooltip": "Organização Atual" +} diff --git a/worklenz-backend/src/public/locales/pt/schedule.json b/worklenz-backend/src/public/locales/pt/schedule.json new file mode 100644 index 00000000..c2d9fed6 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/schedule.json @@ -0,0 +1,39 @@ +{ + "today": "Hoje", + "week": "Semana", + "month": "Mês", + + "settings": "Configurações", + "workingDays": "Dias de Trabalho", + "monday": "Segunda-feira", + "tuesday": "Terça-feira", + "wednesday": "Quarta-feira", + "thursday": "Quinta-feira", + "friday": "Sexta-feira", + "saturday": "Sábado", + "sunday": "Domingo", + "workingHours": "Horas de Trabalho", + "hours": "horas", + "saveButton": "Salvar", + + "totalAllocation": "Alocação Total", + "timeLogged": "Tempo Registrado", + "remainingTime": "Tempo Restante", + "total": "Total", + "perDay": "Por Dia", + "tasks": "tarefas", + "startDate": "Data de Início", + "endDate": "Data de Fim", + + "hoursPerDay": "Horas Por Dia", + "totalHours": "Horas Totais", + "deleteButton": "Excluir", + "cancelButton": "Cancelar", + + "tabTitle": "Tarefa sem Data de Início & Fim", + + "allocatedTime": "Tempo Alocado", + "totalLogged": "Total Registrado", + "loggedBillable": "Registrado Faturável", + "loggedNonBillable": "Registrado Não Faturável" +} diff --git a/worklenz-backend/src/public/locales/pt/settings/appearance.json b/worklenz-backend/src/public/locales/pt/settings/appearance.json new file mode 100644 index 00000000..13e5a1e6 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/settings/appearance.json @@ -0,0 +1,5 @@ +{ + "title": "Aparência", + "darkMode": "Modo Escuro", + "darkModeDescription": "Alterne entre o modo claro e escuro para personalizar sua experiência de visualização." +} diff --git a/worklenz-backend/src/public/locales/pt/settings/categories.json b/worklenz-backend/src/public/locales/pt/settings/categories.json new file mode 100644 index 00000000..9972d2a9 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/settings/categories.json @@ -0,0 +1,10 @@ +{ + "categoryColumn": "Categoria", + "deleteConfirmationTitle": "Tem a certeza?", + "deleteConfirmationOk": "Sim", + "deleteConfirmationCancel": "Cancelar", + "associatedTaskColumn": "Tarefa Associada", + "searchPlaceholder": "Pesquisar por nome", + "emptyText": "As categorias podem ser criadas ao atualizar ou criar projetos.", + "colorChangeTooltip": "Clique para mudar a cor" +} diff --git a/worklenz-backend/src/public/locales/pt/settings/change-password.json b/worklenz-backend/src/public/locales/pt/settings/change-password.json new file mode 100644 index 00000000..07b993dd --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/settings/change-password.json @@ -0,0 +1,15 @@ +{ + "title": "Alterar Senha", + "currentPassword": "Senha Atual", + "newPassword": "Nova Senha", + "confirmPassword": "Confirmar Senha", + "currentPasswordPlaceholder": "Digite sua senha atual", + "newPasswordPlaceholder": "Nova Senha", + "confirmPasswordPlaceholder": "Confirmar Senha", + "currentPasswordRequired": "Por favor, digite sua senha atual!", + "newPasswordRequired": "Por favor, digite sua nova senha!", + "passwordValidationError": "A senha deve ter pelo menos 8 caracteres com uma letra maiúscula, um número e um símbolo.", + "passwordMismatch": "As senhas não coincidem!", + "passwordRequirements": "A nova senha deve ter no mínimo 8 caracteres, com uma letra maiúscula, um número e um símbolo.", + "updateButton": "Atualizar Senha" +} diff --git a/worklenz-backend/src/public/locales/pt/settings/clients.json b/worklenz-backend/src/public/locales/pt/settings/clients.json new file mode 100644 index 00000000..932a7f5e --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/settings/clients.json @@ -0,0 +1,22 @@ +{ + "nameColumn": "Nome", + "projectColumn": "Projeto", + "noProjectsAvailable": "Nenhum projeto disponível", + "deleteConfirmationTitle": "Tem a certeza?", + "deleteConfirmationOk": "Sim", + "deleteConfirmationCancel": "Cancelar", + "searchPlaceholder": "Pesquisar por nome", + "createClient": "Criar Cliente", + "pinTooltip": "Clique para fixar isso no menu principal", + "createClientDrawerTitle": "Criar Cliente", + "updateClientDrawerTitle": "Atualizar Cliente", + "nameLabel": "Nome", + "namePlaceholder": "Nome", + "nameRequiredError": "Por favor, insira um Nome", + "createButton": "Criar", + "updateButton": "Atualizar", + "createClientSuccessMessage": "Criar cliente sucesso!", + "createClientErrorMessage": "Criar cliente falhou!", + "updateClientSuccessMessage": "Atualizar cliente sucesso!", + "updateClientErrorMessage": "Atualizar cliente falhou!" +} diff --git a/worklenz-backend/src/public/locales/pt/settings/job-titles.json b/worklenz-backend/src/public/locales/pt/settings/job-titles.json new file mode 100644 index 00000000..379ddc03 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/settings/job-titles.json @@ -0,0 +1,20 @@ +{ + "nameColumn": "Nome", + "deleteConfirmationTitle": "Tem a certeza?", + "deleteConfirmationOk": "Sim", + "deleteConfirmationCancel": "Cancelar", + "searchPlaceholder": "Pesquisar por nome", + "createJobTitleButton": "Criar Título de Emprego", + "pinTooltip": "Clique para fixar isso no menu principal", + "createJobTitleDrawerTitle": "Criar Título de Emprego", + "updateJobTitleDrawerTitle": "Atualizar Título de Emprego", + "nameLabel": "Nome", + "namePlaceholder": "Nome", + "nameRequiredError": "Por favor, insira um Nome", + "createButton": "Criar", + "updateButton": "Atualizar", + "createJobTitleSuccessMessage": "Criar título de emprego com sucesso!", + "createJobTitleErrorMessage": "Falha ao criar título de emprego!", + "updateJobTitleSuccessMessage": "Atualizar título de emprego com sucesso!", + "updateJobTitleErrorMessage": "Falha ao atualizar título de emprego!" +} diff --git a/worklenz-backend/src/public/locales/pt/settings/labels.json b/worklenz-backend/src/public/locales/pt/settings/labels.json new file mode 100644 index 00000000..737dccef --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/settings/labels.json @@ -0,0 +1,11 @@ +{ + "labelColumn": "Rótulo", + "deleteConfirmationTitle": "Tem a certeza?", + "deleteConfirmationOk": "Sim", + "deleteConfirmationCancel": "Cancelar", + "associatedTaskColumn": "Contagem de Tarefas Associadas", + "searchPlaceholder": "Pesquisar por nome", + "emptyText": "Os rótulos podem ser criados ao atualizar ou criar tarefas.", + "pinTooltip": "Clique para fixar isso no menu principal", + "colorChangeTooltip": "Clique para mudar a cor" +} diff --git a/worklenz-backend/src/public/locales/pt/settings/language.json b/worklenz-backend/src/public/locales/pt/settings/language.json new file mode 100644 index 00000000..f4494ff3 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/settings/language.json @@ -0,0 +1,7 @@ +{ + "language": "Idioma", + "language_required": "O idioma é obrigatório", + "time_zone": "Fuso horário", + "time_zone_required": "O fuso horário é obrigatório", + "save_changes": "Salvar alterações" +} diff --git a/worklenz-backend/src/public/locales/pt/settings/notifications.json b/worklenz-backend/src/public/locales/pt/settings/notifications.json new file mode 100644 index 00000000..5a61cdf0 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/settings/notifications.json @@ -0,0 +1,10 @@ +{ + "emailTitle": "Envie-me notificações por email", + "emailDescription": "Isso inclui novas atribuições de tarefas", + "dailyDigestTitle": "Envie-me um resumo diário", + "dailyDigestDescription": "Toda noite, você receberá um resumo da atividade recente nas tarefas.", + "popupTitle": "Notificações pop-up no meu computador quando o Worklenz está aberto", + "popupDescription": "As notificações pop-up podem ser desativadas pelo seu navegador. Altere as configurações do seu navegador para permiti-las.", + "unreadItemsTitle": "Mostrar o número de itens não lidos", + "unreadItemsDescription": "Você verá contagens para cada notificação." +} diff --git a/worklenz-backend/src/public/locales/pt/settings/profile.json b/worklenz-backend/src/public/locales/pt/settings/profile.json new file mode 100644 index 00000000..3a4a8447 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/settings/profile.json @@ -0,0 +1,14 @@ +{ + "uploadError": "Você só pode fazer upload de arquivos JPG/PNG!", + "uploadSizeError": "A imagem deve ser menor que 2MB!", + "upload": "Carregar", + "nameLabel": "Nome", + "nameRequiredError": "Nome é obrigatório", + "emailLabel": "Email", + "emailRequiredError": "Email é obrigatório", + "saveChanges": "Salvar Alterações", + "profileJoinedText": "Entrou há um mês", + "profileLastUpdatedText": "Última atualização há um mês", + "avatarTooltip": "Clique para carregar um avatar", + "title": "Configurações do Perfil" +} diff --git a/worklenz-backend/src/public/locales/pt/settings/project-templates.json b/worklenz-backend/src/public/locales/pt/settings/project-templates.json new file mode 100644 index 00000000..55546630 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/settings/project-templates.json @@ -0,0 +1,8 @@ +{ + "nameColumn": "Nome", + "editToolTip": "Editar", + "deleteToolTip": "Excluir", + "confirmText": "Tem a certeza?", + "okText": "Sim", + "cancelText": "Cancelar" +} diff --git a/worklenz-backend/src/public/locales/pt/settings/sidebar.json b/worklenz-backend/src/public/locales/pt/settings/sidebar.json new file mode 100644 index 00000000..0cb663f1 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/settings/sidebar.json @@ -0,0 +1,15 @@ +{ + "profile": "Perfil", + "notifications": "Notificações", + "clients": "Clientes", + "job-titles": "Títulos de Emprego", + "labels": "Rótulos", + "categories": "Categorias", + "project-templates": "Modelos de Projeto", + "task-templates": "Modelos de Tarefa", + "team-members": "Membros da Equipe", + "teams": "Equipes", + "change-password": "Alterar Senha", + "language-and-region": "Idioma e Região", + "appearance": "Aparência" +} diff --git a/worklenz-backend/src/public/locales/pt/settings/task-templates.json b/worklenz-backend/src/public/locales/pt/settings/task-templates.json new file mode 100644 index 00000000..fb501000 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/settings/task-templates.json @@ -0,0 +1,9 @@ +{ + "nameColumn": "Nome", + "createdColumn": "Criado", + "editToolTip": "Editar", + "deleteToolTip": "Excluir", + "confirmText": "Tem a certeza?", + "okText": "Sim", + "cancelText": "Cancelar" +} diff --git a/worklenz-backend/src/public/locales/pt/settings/team-members.json b/worklenz-backend/src/public/locales/pt/settings/team-members.json new file mode 100644 index 00000000..9ace1764 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/settings/team-members.json @@ -0,0 +1,47 @@ +{ + "title": "Membros da Equipe", + "nameColumn": "Nome", + "projectsColumn": "Projetos", + "emailColumn": "Email", + "teamAccessColumn": "Acesso à Equipe", + "memberCount": "Membro", + "membersCountPlural": "Membros", + "searchPlaceholder": "Pesquisar membros pelo nome", + "pinTooltip": "Atualizar lista de membros", + "addMemberButton": "Adicionar Novo Membro", + "editTooltip": "Editar membro", + "deactivateTooltip": "Desativar membro", + "activateTooltip": "Ativar membro", + "deleteTooltip": "Deletar membro", + "confirmDeleteTitle": "Tem a certeza de que deseja deletar este membro?", + "confirmActivateTitle": "Tem a certeza de que deseja alterar o status deste membro?", + "okText": "Sim, proceder", + "cancelText": "Não, cancelar", + "deactivatedText": "(Atualmente desativado)", + "pendingInvitationText": "(Convite pendente)", + "addMemberDrawerTitle": "Adicionar Novo Membro da Equipe", + "updateMemberDrawerTitle": "Atualizar Membro da Equipe", + "addMemberEmailHint": "Os membros serão adicionados à equipe independentemente do status de aceitação do convite", + "memberEmailLabel": "Endereço(s) de Email", + "memberEmailPlaceholder": "Insira o endereço de email do membro da equipe", + "memberEmailRequiredError": "Por favor, insira um email válido", + "jobTitleLabel": "Título do Emprego", + "jobTitlePlaceholder": "Selecione ou pesquise o título do emprego (Opcional)", + "memberAccessLabel": "Nível de Acesso", + "addToTeamButton": "Adicionar Membro à Equipe", + "updateButton": "Salvar Alterações", + "resendInvitationButton": "Redirecionar Email de Convite", + "invitationSentSuccessMessage": "Convite para a equipe enviado com sucesso!", + "createMemberSuccessMessage": "Novo membro da equipe adicionado com sucesso!", + "createMemberErrorMessage": "Falha ao adicionar membro da equipe. Por favor, tente novamente.", + "updateMemberSuccessMessage": "Membro da equipe atualizado com sucesso!", + "updateMemberErrorMessage": "Falha ao atualizar membro da equipe. Por favor, tente novamente.", + "memberText": "Membro da Equipe", + "adminText": "Administrador", + "ownerText": "Dono da Equipe", + "addedText": "Adicionado", + "updatedText": "Atualizado", + "noResultFound": "Digite um endereço de email e pressione enter...", + "jobTitlesFetchError": "Falha ao buscar cargos", + "invitationResent": "Convite reenviado com sucesso!" +} diff --git a/worklenz-backend/src/public/locales/pt/settings/teams.json b/worklenz-backend/src/public/locales/pt/settings/teams.json new file mode 100644 index 00000000..e460318f --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/settings/teams.json @@ -0,0 +1,16 @@ +{ + "title": "Equipes", + "team": "Equipe", + "teams": "Equipes", + "name": "Nome", + "created": "Criado", + "ownsBy": "Pertence a", + "edit": "Editar", + "editTeam": "Editar Equipe", + "pinTooltip": "Clique para fixar isso no menu principal", + "editTeamName": "Editar Nome da Equipe", + "updateName": "Atualizar Nome", + "namePlaceholder": "Nome", + "nameRequired": "Por favor digite um Nome", + "updateFailed": "Falha na alteração do nome da equipe!" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/pt/task-drawer/task-drawer-info-tab.json b/worklenz-backend/src/public/locales/pt/task-drawer/task-drawer-info-tab.json new file mode 100644 index 00000000..cf26b1a3 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/task-drawer/task-drawer-info-tab.json @@ -0,0 +1,30 @@ +{ + "details": { + "task-key": "Chave da tarefa", + "phase": "Fase", + "assignees": "Responsáveis", + "due-date": "Data de vencimento", + "time-estimation": "Estimativa de tempo", + "priority": "Prioridade", + "labels": "Etiquetas", + "billable": "Faturável", + "notify": "Notificar", + "when-done-notify": "Quando concluída, notificar", + "start-date": "Data de início", + "end-date": "Data de término", + "hide-start-date": "Ocultar data de início", + "show-start-date": "Mostrar data de início", + "hours": "Horas", + "minutes": "Minutos", + "recurring": "Recorrente" + }, + "description": { + "title": "Descrição", + "placeholder": "Adicionar uma descrição mais detalhada..." + }, + "subTasks": { + "title": "Subtarefas", + "add-sub-task": "+ Adicionar subtarefa", + "refresh-sub-tasks": "Atualizar subtarefas" + } +} diff --git a/worklenz-backend/src/public/locales/pt/task-drawer/task-drawer-recurring-config.json b/worklenz-backend/src/public/locales/pt/task-drawer/task-drawer-recurring-config.json new file mode 100644 index 00000000..5592d897 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/task-drawer/task-drawer-recurring-config.json @@ -0,0 +1,34 @@ +{ + "recurring": "Recorrente", + "recurringTaskConfiguration": "Configuração de tarefa recorrente", + "repeats": "Repete", + "daily": "Diário", + "weekly": "Semanal", + "everyXDays": "A cada X dias", + "everyXWeeks": "A cada X semanas", + "everyXMonths": "A cada X meses", + "monthly": "Mensal", + "selectDaysOfWeek": "Selecionar dias da semana", + "mon": "Seg", + "tue": "Ter", + "wed": "Qua", + "thu": "Qui", + "fri": "Sex", + "sat": "Sáb", + "sun": "Dom", + "monthlyRepeatType": "Tipo de repetição mensal", + "onSpecificDate": "Em uma data específica", + "onSpecificDay": "Em um dia específico", + "dateOfMonth": "Data do mês", + "weekOfMonth": "Semana do mês", + "dayOfWeek": "Dia da semana", + "first": "Primeira", + "second": "Segunda", + "third": "Terceira", + "fourth": "Quarta", + "last": "Última", + "intervalDays": "Intervalo (dias)", + "intervalWeeks": "Intervalo (semanas)", + "intervalMonths": "Intervalo (meses)", + "saveChanges": "Salvar alterações" +} diff --git a/worklenz-backend/src/public/locales/pt/task-drawer/task-drawer.json b/worklenz-backend/src/public/locales/pt/task-drawer/task-drawer.json new file mode 100644 index 00000000..c24e943e --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/task-drawer/task-drawer.json @@ -0,0 +1,123 @@ +{ + "taskHeader": { + "taskNamePlaceholder": "Digite sua Tarefa", + "deleteTask": "Deletar Tarefa" + }, + "taskInfoTab": { + "title": "Informações", + "details": { + "title": "Detalhes", + "task-key": "Chave da Tarefa", + "phase": "Fase", + "assignees": "Responsáveis", + "due-date": "Data de Vencimento", + "time-estimation": "Estimativa de Tempo", + "priority": "Prioridade", + "labels": "Etiquetas", + "billable": "Faturável", + "notify": "Notificar", + "when-done-notify": "Quando concluído, notificar", + "start-date": "Data de Início", + "end-date": "Data de Fim", + "hide-start-date": "Ocultar Data de Início", + "show-start-date": "Mostrar Data de Início", + "hours": "Horas", + "minutes": "Minutos", + "progressValue": "Valor do Progresso", + "progressValueTooltip": "Definir a porcentagem de progresso (0-100%)", + "progressValueRequired": "Por favor, insira um valor de progresso", + "progressValueRange": "O progresso deve estar entre 0 e 100", + "taskWeight": "Peso da Tarefa", + "taskWeightTooltip": "Definir o peso desta subtarefa (porcentagem)", + "taskWeightRequired": "Por favor, insira um peso da tarefa", + "taskWeightRange": "O peso deve estar entre 0 e 100", + "recurring": "Recorrente" + }, + "labels": { + "labelInputPlaceholder": "Pesquisar ou criar", + "labelsSelectorInputTip": "Pressione Enter para criar" + }, + "description": { + "title": "Descrição", + "placeholder": "Adicionar uma descrição mais detalhada..." + }, + "subTasks": { + "title": "Sub Tarefas", + "addSubTask": "Adicionar Sub Tarefa", + "addSubTaskInputPlaceholder": "Digite sua tarefa e pressione enter", + "refreshSubTasks": "Atualizar Sub Tarefas", + "edit": "Editar", + "delete": "Deletar", + "confirmDeleteSubTask": "Tem certeza de que deseja deletar esta subtarefa?", + "deleteSubTask": "Deletar Sub Tarefa" + }, + "dependencies": { + "title": "Dependências", + "addDependency": "+ Adicionar nova dependência", + "blockedBy": "Bloqueado por", + "searchTask": "Digite para pesquisar tarefa", + "noTasksFound": "Nenhuma tarefa encontrada", + "confirmDeleteDependency": "Tem certeza de que deseja deletar?" + }, + "attachments": { + "title": "Anexos", + "chooseOrDropFileToUpload": "Escolha ou arraste um arquivo para upload", + "uploading": "Enviando..." + }, + "comments": { + "title": "Comentários", + "addComment": "+ Adicionar novo comentário", + "noComments": "Ainda não há comentários. Seja o primeiro a comentar!", + "delete": "Deletar", + "confirmDeleteComment": "Tem certeza de que deseja deletar este comentário?", + "addCommentPlaceholder": "Adicionar um comentário...", + "cancel": "Cancelar", + "commentButton": "Comentar", + "attachFiles": "Anexar arquivos", + "addMoreFiles": "Adicionar mais arquivos", + "selectedFiles": "Arquivos Selecionados (Até 25MB, Máximo {count})", + "maxFilesError": "Você pode fazer upload de no máximo {count} arquivos", + "processFilesError": "Falha ao processar arquivos", + "addCommentError": "Por favor adicione um comentário ou anexe arquivos", + "createdBy": "Criado {{time}} por {{user}}", + "updatedTime": "Atualizado {{time}}" + }, + "searchInputPlaceholder": "Pesquisar por nome", + "pendingInvitation": "Convite Pendente" + }, + "taskTimeLogTab": { + "title": "Registro de Tempo", + "addTimeLog": "Adicionar novo registro de tempo", + "totalLogged": "Total Registrado", + "exportToExcel": "Exportar para Excel", + "noTimeLogsFound": "Nenhum registro de tempo encontrado", + "timeLogForm": { + "date": "Data", + "startTime": "Hora de Início", + "endTime": "Hora de Fim", + "workDescription": "Descrição do Trabalho", + "descriptionPlaceholder": "Adicionar uma descrição", + "logTime": "Registrar tempo", + "updateTime": "Atualizar tempo", + "cancel": "Cancelar", + "selectDateError": "Por favor selecione uma data", + "selectStartTimeError": "Por favor selecione a hora de início", + "selectEndTimeError": "Por favor selecione a hora de fim", + "endTimeAfterStartError": "A hora de fim deve ser posterior à hora de início" + } + }, + "taskActivityLogTab": { + "title": "Registro de Atividade", + "add": "ADICIONAR", + "remove": "REMOVER", + "none": "Nenhum", + "weight": "Peso", + "createdTask": "criou a tarefa." + }, + "taskProgress": { + "markAsDoneTitle": "Marcar Tarefa como Concluída?", + "confirmMarkAsDone": "Sim, marcar como concluída", + "cancelMarkAsDone": "Não, manter status atual", + "markAsDoneDescription": "Você definiu o progresso para 100%. Gostaria de atualizar o status da tarefa para \"Concluída\"?" + } +} diff --git a/worklenz-backend/src/public/locales/pt/task-list-filters.json b/worklenz-backend/src/public/locales/pt/task-list-filters.json new file mode 100644 index 00000000..21e8806b --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/task-list-filters.json @@ -0,0 +1,82 @@ +{ + "searchButton": "Pesquisar", + "resetButton": "Redefinir", + "searchInputPlaceholder": "Pesquisar por nome", + + "sortText": "Ordenar", + "statusText": "Status", + "phaseText": "Fase", + "priorityText": "Prioridade", + "labelsText": "Rótulos", + "membersText": "Membros", + "groupByText": "Agrupar por", + "showArchivedText": "Mostrar arquivados", + "showFieldsText": "Mostrar campos", + "keyText": "Chave", + "taskText": "Tarefa", + "descriptionText": "Descrição", + "phasesText": "Fases", + "progressText": "Progresso", + "timeTrackingText": "Rastreamento de Tempo", + "estimationText": "Estimativa", + "startDateText": "Data de Início", + "endDateText": "Data de Fim", + "dueDateText": "Data de Vencimento", + "completedDateText": "Data de Conclusão", + "createdDateText": "Data de Criação", + "lastUpdatedText": "Última Atualização", + "reporterText": "Relator", + "dueTimeText": "Hora de Vencimento", + "assigneesText": "Atribuições", + "timetrackingText": "Rastreamento de Tempo", + "startdateText": "Data de Início", + "duedateText": "Data de Vencimento", + "completeddateText": "Data de Conclusão", + "createddateText": "Data de Criação", + "lastupdatedText": "Última Atualização", + + "lowText": "Baixa", + "mediumText": "Média", + "highText": "Alta", + + "createStatusButtonTooltip": "Configurações de Status", + "configPhaseButtonTooltip": "Configurações de Fase", + "noLabelsFound": "Nenhum rótulo encontrado", + + "addStatusButton": "Adicionar Status", + "addPhaseButton": "Adicionar Fase", + + "createStatus": "Criar Status", + "name": "Nome", + "category": "Categoria", + "selectCategory": "Selecionar uma categoria", + "pleaseEnterAName": "Por favor, insira um nome", + "pleaseSelectACategory": "Por favor, selecione uma categoria", + "create": "Criar", + + "searchTasks": "Pesquisar tarefas...", + "searchPlaceholder": "Pesquisar...", + "fieldsText": "Campos", + "loadingFilters": "Carregando filtros...", + "noOptionsFound": "Nenhuma opção encontrada", + "filtersActive": "filtros ativos", + "filterActive": "filtro ativo", + "clearAll": "Limpar tudo", + "clearing": "Limpando...", + "cancel": "Cancelar", + "search": "Pesquisar", + "groupedBy": "Agrupado por", + "manageStatuses": "Gerenciar Status", + "managePhases": "Gerenciar Fases", + "dragToReorderStatuses": "Arraste os status para reordená-los. Cada status pode ter uma categoria diferente.", + "enterNewStatusName": "Digite o novo nome do status...", + "addStatus": "Adicionar Status", + "noStatusesFound": "Nenhum status encontrado. Crie seu primeiro status acima.", + "deleteStatus": "Excluir Status", + "deleteStatusConfirm": "Tem certeza de que deseja excluir este status? Esta ação não pode ser desfeita.", + "rename": "Renomear", + "delete": "Excluir", + "enterStatusName": "Digite o nome do status", + "selectCategory": "Selecionar categoria", + "close": "Fechar" +} diff --git a/worklenz-backend/src/public/locales/pt/task-list-table.json b/worklenz-backend/src/public/locales/pt/task-list-table.json new file mode 100644 index 00000000..f53d834f --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/task-list-table.json @@ -0,0 +1,136 @@ +{ + "keyColumn": "Chave", + "taskColumn": "Tarefa", + "descriptionColumn": "Descrição", + "progressColumn": "Progresso", + "membersColumn": "Membros", + "assigneesColumn": "Atribuídos", + "labelsColumn": "Etiquetas", + "phasesColumn": "Fases", + "phaseColumn": "Fase", + "statusColumn": "Status", + "priorityColumn": "Prioridade", + "timeTrackingColumn": "Acompanhamento de Tempo", + "timetrackingColumn": "Acompanhamento de Tempo", + "estimationColumn": "Estimativa", + "startDateColumn": "Data de Início", + "startdateColumn": "Data de Início", + "dueDateColumn": "Data de Vencimento", + "duedateColumn": "Data de Vencimento", + "completedDateColumn": "Data de Conclusão", + "completeddateColumn": "Data de Conclusão", + "createdDateColumn": "Data de Criação", + "createddateColumn": "Data de Criação", + "lastUpdatedColumn": "Última Atualização", + "lastupdatedColumn": "Última Atualização", + "reporterColumn": "Reportador", + "dueTimeColumn": "Hora de Vencimento", + "todoSelectorText": "A Fazer", + "doingSelectorText": "Fazendo", + "doneSelectorText": "Feito", + + "lowSelectorText": "Baixo", + "mediumSelectorText": "Médio", + "highSelectorText": "Alto", + + "selectText": "Selecionar", + "labelsSelectorInputTip": "Pressione enter para criar!", + + "addTaskText": "Adicionar Tarefa", + "addSubTaskText": "+ Adicionar Subtarefa", + "noTasksInGroup": "Nenhuma tarefa neste grupo", + "addTaskInputPlaceholder": "Digite sua tarefa e pressione enter", + + "openButton": "Abrir", + "okButton": "Ok", + + "noLabelsFound": "Nenhuma etiqueta encontrada", + "searchInputPlaceholder": "Buscar ou criar", + "assigneeSelectorInviteButton": "Convide um novo membro por e-mail", + "labelInputPlaceholder": "Buscar ou criar", + "searchLabelsPlaceholder": "Buscar etiquetas...", + "createLabelButton": "Criar \"{{name}}\"", + "manageLabelsPath": "Configurações → Etiquetas", + + "pendingInvitation": "Convite Pendente", + + "contextMenu": { + "assignToMe": "Atribuir a mim", + "moveTo": "Mover para", + "unarchive": "Desarquivar", + "archive": "Arquivar", + "convertToSubTask": "Converter em Subtarefa", + "convertToTask": "Converter em Tarefa", + "delete": "Excluir", + "searchByNameInputPlaceholder": "Buscar por nome" + }, + "setDueDate": "Definir data de vencimento", + "setStartDate": "Definir data de início", + "clearDueDate": "Limpar data de vencimento", + "clearStartDate": "Limpar data de início", + "dueDatePlaceholder": "Data de vencimento", + "startDatePlaceholder": "Data de início", + + "emptyStates": { + "noTaskGroups": "Nenhum grupo de tarefas encontrado", + "noTaskGroupsDescription": "As tarefas aparecerão aqui quando forem criadas ou quando filtros forem aplicados.", + "errorPrefix": "Erro:", + "dragTaskFallback": "Tarefa" + }, + + "customColumns": { + "addCustomColumn": "Adicionar uma coluna personalizada", + "customColumnHeader": "Coluna Personalizada", + "customColumnSettings": "Configurações da coluna personalizada", + "noCustomValue": "Sem valor", + "peopleField": "Campo de pessoas", + "noDate": "Sem data", + "unsupportedField": "Tipo de campo não suportado", + + "modal": { + "addFieldTitle": "Adicionar campo", + "editFieldTitle": "Editar campo", + "fieldTitle": "Título do campo", + "fieldTitleRequired": "O título do campo é obrigatório", + "columnTitlePlaceholder": "Título da coluna", + "type": "Tipo", + "deleteConfirmTitle": "Tem certeza de que deseja excluir esta coluna personalizada?", + "deleteConfirmDescription": "Esta ação não pode ser desfeita. Todos os dados associados a esta coluna serão excluídos permanentemente.", + "deleteButton": "Excluir", + "cancelButton": "Cancelar", + "createButton": "Criar", + "updateButton": "Atualizar", + "createSuccessMessage": "Coluna personalizada criada com sucesso", + "updateSuccessMessage": "Coluna personalizada atualizada com sucesso", + "deleteSuccessMessage": "Coluna personalizada excluída com sucesso", + "deleteErrorMessage": "Falha ao excluir a coluna personalizada", + "createErrorMessage": "Falha ao criar a coluna personalizada", + "updateErrorMessage": "Falha ao atualizar a coluna personalizada" + }, + + "fieldTypes": { + "people": "Pessoas", + "number": "Número", + "date": "Data", + "selection": "Seleção", + "checkbox": "Caixa de seleção", + "labels": "Etiquetas", + "key": "Chave", + "formula": "Fórmula" + } + }, + + "indicators": { + "tooltips": { + "subtasks": "{{count}} subtarefa", + "subtasks_plural": "{{count}} subtarefas", + "comments": "{{count}} comentário", + "comments_plural": "{{count}} comentários", + "attachments": "{{count}} anexo", + "attachments_plural": "{{count}} anexos", + "subscribers": "A tarefa tem assinantes", + "dependencies": "A tarefa tem dependências", + "recurring": "Tarefa recorrente" + } + } +} diff --git a/worklenz-backend/src/public/locales/pt/task-management.json b/worklenz-backend/src/public/locales/pt/task-management.json new file mode 100644 index 00000000..946b3162 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/task-management.json @@ -0,0 +1,21 @@ +{ + "noTasksInGroup": "Nenhuma tarefa neste grupo", + "noTasksInGroupDescription": "Adicione uma tarefa para começar", + "addFirstTask": "Adicione sua primeira tarefa", + "openTask": "Abrir", + "subtask": "subtarefa", + "subtasks": "subtarefas", + "comment": "comentário", + "comments": "comentários", + "attachment": "anexo", + "attachments": "anexos", + "enterSubtaskName": "Digite o nome da subtarefa...", + "add": "Adicionar", + "cancel": "Cancelar", + "renameGroup": "Renomear Grupo", + "renameStatus": "Renomear Status", + "renamePhase": "Renomear Fase", + "changeCategory": "Alterar Categoria", + "clickToEditGroupName": "Clique para editar o nome do grupo", + "enterGroupName": "Digite o nome do grupo" +} diff --git a/worklenz-backend/src/public/locales/pt/task-template-drawer.json b/worklenz-backend/src/public/locales/pt/task-template-drawer.json new file mode 100644 index 00000000..f1358349 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/task-template-drawer.json @@ -0,0 +1,11 @@ +{ + "createTaskTemplate": "Criar Template de Tarefa", + "editTaskTemplate": "Editar Template de Tarefa", + "cancelText": "Cancelar", + "saveText": "Salvar", + "templateNameText": "Nome do Template", + "selectedTasks": "Tarefas Selecionadas", + "removeTask": "Remover", + "cancelButton": "Cancelar", + "saveButton": "Salvar" +} diff --git a/worklenz-backend/src/public/locales/pt/tasks/task-table-bulk-actions.json b/worklenz-backend/src/public/locales/pt/tasks/task-table-bulk-actions.json new file mode 100644 index 00000000..f4a3a10e --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/tasks/task-table-bulk-actions.json @@ -0,0 +1,41 @@ +{ + "taskSelected": "Tarefa selecionada", + "tasksSelected": "Tarefas selecionadas", + "changeStatus": "Alterar Status/ Prioridade/ Fases", + "changeLabel": "Alterar Etiqueta", + "assignToMe": "Atribuir a mim", + "changeAssignees": "Alterar Assignados", + "archive": "Arquivar", + "unarchive": "Desarquivar", + "delete": "Deletar", + "moreOptions": "Mais opções", + "deselectAll": "Desmarcar todas", + "status": "Status", + "priority": "Prioridade", + "phase": "Fase", + "member": "Membro", + "createTaskTemplate": "Criar Modelo de Tarefa", + "apply": "Aplicar", + "createLabel": "+ Criar etiqueta", + "searchOrCreateLabel": "Pesquisar ou criar etiqueta...", + "hitEnterToCreate": "Pressione Enter para criar", + "labelExists": "A etiqueta já existe", + "pendingInvitation": "Convite Pendente", + "noMatchingLabels": "Nenhuma etiqueta correspondente", + "noLabels": "Sem etiquetas", + "CHANGE_STATUS": "Alterar Status", + "CHANGE_PRIORITY": "Alterar Prioridade", + "CHANGE_PHASE": "Alterar Fase", + "ADD_LABELS": "Adicionar Etiquetas", + "ASSIGN_TO_ME": "Atribuir a Mim", + "ASSIGN_MEMBERS": "Atribuir Membros", + "ARCHIVE": "Arquivar", + "DELETE": "Deletar", + "CANCEL": "Cancelar", + "CLEAR_SELECTION": "Limpar Seleção", + "TASKS_SELECTED": "{{count}} tarefa selecionada", + "TASKS_SELECTED_plural": "{{count}} tarefas selecionadas", + "DELETE_TASKS_CONFIRM": "Deletar {{count}} tarefa?", + "DELETE_TASKS_CONFIRM_plural": "Deletar {{count}} tarefas?", + "DELETE_TASKS_WARNING": "Esta ação não pode ser desfeita." +} diff --git a/worklenz-backend/src/public/locales/pt/template-drawer.json b/worklenz-backend/src/public/locales/pt/template-drawer.json new file mode 100644 index 00000000..cb79d2bf --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/template-drawer.json @@ -0,0 +1,19 @@ +{ + "title": "Editar Template de Tarefa", + "cancelText": "Cancelar", + "saveText": "Salvar", + "templateNameText": "Nome do Template", + "selectedTasks": "Tarefas Selecionadas", + "removeTask": "Remover", + "description": "Descrição", + "phase": "Fase", + "statuses": "Status", + "priorities": "Prioridades", + "labels": "Rótulos", + "tasks": "Tarefas", + "noTemplateSelected": "Nenhum template selecionado", + "noDescription": "Sem descrição", + "worklenzTemplates": "Templates de Worklenz", + "yourTemplatesLibrary": "Sua Biblioteca", + "searchTemplates": "Pesquisar Templates" +} diff --git a/worklenz-backend/src/public/locales/pt/templateDrawer.json b/worklenz-backend/src/public/locales/pt/templateDrawer.json new file mode 100644 index 00000000..c4d970c6 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/templateDrawer.json @@ -0,0 +1,23 @@ +{ + "bugTracking": "Rastreamento de Bugs", + "construction": "Construção", + "designCreative": "Design e Criatividade", + "education": "Educação", + "finance": "Finanças", + "hrRecruiting": "RH e Recrutamento", + "informationTechnology": "Tecnologia da Informação", + "legal": "Jurídico", + "manufacturing": "Manufatura", + "marketing": "Marketing", + "nonprofit": "Sem Fins Lucrativos", + "personalUse": "Uso Pessoal", + "salesCRM": "Vendas e CRM", + "serviceConsulting": "Serviço e Consultoria", + "softwareDevelopment": "Desenvolvimento de Software", + "description": "Descrição", + "phase": "Fase", + "statuses": "Status", + "priorities": "Prioridades", + "labels": "Rótulos", + "tasks": "Tarefas" +} diff --git a/worklenz-backend/src/public/locales/pt/time-report.json b/worklenz-backend/src/public/locales/pt/time-report.json new file mode 100644 index 00000000..b40546e9 --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/time-report.json @@ -0,0 +1,57 @@ +{ + "includeArchivedProjects": "Incluir Projetos Arquivados", + "export": "Exportar", + "timeSheet": "Folha de Tempo", + + "searchByName": "Pesquisar por nome", + "selectAll": "Selecionar Tudo", + "teams": "Equipes", + + "searchByProject": "Pesquisar por nome do projeto", + "projects": "Projetos", + + "searchByCategory": "Pesquisar por nome da categoria", + "categories": "Categorias", + + "billable": "Faturável", + "nonBillable": "Não Faturável", + + "total": "Total", + + "projectsTimeSheet": "Folha de Tempo de Projetos", + + "loggedTime": "Tempo Registrado(horas)", + + "exportToExcel": "Exportar para Excel", + "logged": "registrado", + "for": "para", + + "membersTimeSheet": "Folha de Tempo de Membros", + "member": "Membro", + + "estimatedVsActual": "Estimado vs Real", + "workingDays": "Dias Úteis", + "manDays": "Dias Homem", + "days": "Dias", + "estimatedDays": "Dias Estimados", + "actualDays": "Dias Reais", + + "noCategories": "Nenhuma categoria encontrada", + "noCategory": "Sem Categoria", + "noProjects": "Nenhum projeto encontrado", + "noTeams": "Nenhuma equipe encontrada", + "noData": "Nenhum dado encontrado", + + "groupBy": "Agrupar por", + "groupByCategory": "Categoria", + "groupByTeam": "Equipe", + "groupByStatus": "Status", + "groupByNone": "Nenhum", + "clearSearch": "Limpar pesquisa", + "selectedProjects": "Projetos Selecionados", + "projectsSelected": "projetos selecionados", + "showSelected": "Mostrar Apenas Selecionados", + "expandAll": "Expandir Tudo", + "collapseAll": "Recolher Tudo", + "ungrouped": "Não Agrupado" +} diff --git a/worklenz-backend/src/public/locales/pt/unauthorized.json b/worklenz-backend/src/public/locales/pt/unauthorized.json new file mode 100644 index 00000000..e67e0ffd --- /dev/null +++ b/worklenz-backend/src/public/locales/pt/unauthorized.json @@ -0,0 +1,5 @@ +{ + "title": "¡Não autorizado!", + "subtitle": "Você não tem permissão para acessar esta página", + "button": "Ir para Início" +} diff --git a/worklenz-backend/src/public/locales/zh/404-page.json b/worklenz-backend/src/public/locales/zh/404-page.json new file mode 100644 index 00000000..24a74b3e --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/404-page.json @@ -0,0 +1,4 @@ +{ + "doesNotExistText": "抱歉,您访问的页面不存在。", + "backHomeButton": "返回首页" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/account-setup.json b/worklenz-backend/src/public/locales/zh/account-setup.json new file mode 100644 index 00000000..51cac1eb --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/account-setup.json @@ -0,0 +1,27 @@ +{ + "continue": "继续", + "setupYourAccount": "设置您的Worklenz账户。", + "organizationStepTitle": "命名您的组织", + "organizationStepLabel": "为您的Worklenz账户选择一个名称。", + "projectStepTitle": "创建您的第一个项目", + "projectStepLabel": "您现在正在做什么项目?", + "projectStepPlaceholder": "例如:营销计划", + "tasksStepTitle": "创建您的第一个任务", + "tasksStepLabel": "输入您将在其中完成的几个任务", + "tasksStepAddAnother": "添加另一个", + "emailPlaceholder": "电子邮件地址", + "invalidEmail": "请输入有效的电子邮件地址", + "or": "或", + "templateButton": "从模板导入", + "goBack": "返回", + "cancel": "取消", + "create": "创建", + "templateDrawerTitle": "从模板中选择", + "step3InputLabel": "通过电子邮件邀请", + "addAnother": "添加另一个", + "skipForNow": "暂时跳过", + "formTitle": "创建您的第一个任务。", + "step3Title": "邀请您的团队一起工作", + "maxMembers": "(您最多可以邀请5名成员)", + "maxTasks": "(您最多可以创建5个任务)" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/admin-center/current-bill.json b/worklenz-backend/src/public/locales/zh/admin-center/current-bill.json new file mode 100644 index 00000000..e18e8761 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/admin-center/current-bill.json @@ -0,0 +1,96 @@ +{ + "title": "账单", + "currentBill": "当前账单", + "configuration": "配置", + "currentPlanDetails": "当前计划详情", + "upgradePlan": "升级计划", + "cardBodyText01": "免费试用", + "cardBodyText02": "(您的试用计划将在1个月19天后到期)", + "redeemCode": "兑换码", + "accountStorage": "账户存储", + "used": "已用:", + "remaining": "剩余:", + "charges": "费用", + "tooltip": "当前账单周期的费用", + "description": "描述", + "billingPeriod": "账单周期", + "billStatus": "账单状态", + "perUserValue": "每用户费用", + "users": "用户", + "amount": "金额", + "invoices": "发票", + "transactionId": "交易ID", + "transactionDate": "交易日期", + "paymentMethod": "支付方式", + "status": "状态", + "ltdUsers": "您最多可以添加{{ltd_users}}名用户。", + "totalSeats": "总席位", + "availableSeats": "可用席位", + "addMoreSeats": "添加更多席位", + "drawerTitle": "兑换码", + "label": "兑换码", + "drawerPlaceholder": "输入您的兑换码", + "redeemSubmit": "提交", + "modalTitle": "为您的团队选择最佳计划", + "seatLabel": "席位数量", + "freePlan": "免费计划", + "startup": "初创", + "business": "商业", + "tag": "最受欢迎", + "enterprise": "企业", + "freeSubtitle": "永远免费", + "freeUsers": "最适合个人使用", + "freeText01": "100MB存储", + "freeText02": "3个项目", + "freeText03": "5名团队成员", + "startupSubtitle": "固定费率/月", + "startupUsers": "最多15名用户", + "startupText01": "25GB存储", + "startupText02": "无限活跃项目", + "startupText03": "日程", + "startupText04": "报告", + "startupText05": "订阅项目", + "businessSubtitle": "每用户/月", + "businessUsers": "16 - 200名用户", + "enterpriseUsers": "200 - 500+名用户", + "footerTitle": "请提供一个我们可以联系您的电话号码。", + "footerLabel": "联系电话", + "footerButton": "联系我们", + "redeemCodePlaceHolder": "输入您的兑换码", + "submit": "提交", + "trialPlan": "免费试用", + "trialExpireDate": "有效期至{{trial_expire_date}}", + "trialExpired": "您的免费试用已于{{trial_expire_string}}到期", + "trialInProgress": "您的免费试用将在{{trial_expire_string}}到期", + "required": "此字段为必填项", + "invalidCode": "无效的代码", + "selectPlan": "为您的团队选择最佳计划", + "changeSubscriptionPlan": "更改您的订阅计划", + "noOfSeats": "席位数量", + "annualPlan": "专业 - 年度", + "monthlyPlan": "专业 - 月度", + "freeForever": "永远免费", + "bestForPersonalUse": "最适合个人使用", + "storage": "存储", + "projects": "项目", + "teamMembers": "团队成员", + "unlimitedTeamMembers": "无限团队成员", + "unlimitedActiveProjects": "无限活跃项目", + "schedule": "日程", + "reporting": "报告", + "subscribeToProjects": "订阅项目", + "billedAnnually": "按年计费", + "billedMonthly": "按月计费", + "pausePlan": "暂停计划", + "resumePlan": "恢复计划", + "changePlan": "更改计划", + "cancelPlan": "取消计划", + "perMonthPerUser": "每用户/月", + "viewInvoice": "查看发票", + "switchToFreePlan": "切换到免费计划", + "expirestoday": "今天", + "expirestomorrow": "明天", + "expiredDaysAgo": "{{days}}天前", + "continueWith": "继续使用{{plan}}", + "changeToPlan": "更改为{{plan}}" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/admin-center/overview.json b/worklenz-backend/src/public/locales/zh/admin-center/overview.json new file mode 100644 index 00000000..9c70093f --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/admin-center/overview.json @@ -0,0 +1,8 @@ +{ + "overview": "概览", + "name": "组织名称", + "owner": "组织所有者", + "admins": "组织管理员", + "contactNumber": "添加联系电话", + "edit": "编辑" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/admin-center/projects.json b/worklenz-backend/src/public/locales/zh/admin-center/projects.json new file mode 100644 index 00000000..ca2eded2 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/admin-center/projects.json @@ -0,0 +1,12 @@ +{ + "membersCount": "成员数量", + "createdAt": "创建于", + "projectName": "项目名称", + "teamName": "团队名称", + "refreshProjects": "刷新项目", + "searchPlaceholder": "按项目名称搜索", + "deleteProject": "您确定要删除此项目吗?", + "confirm": "确认", + "cancel": "取消", + "delete": "删除项目" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/admin-center/sidebar.json b/worklenz-backend/src/public/locales/zh/admin-center/sidebar.json new file mode 100644 index 00000000..ab8808c3 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/admin-center/sidebar.json @@ -0,0 +1,8 @@ +{ + "overview": "概览", + "users": "用户", + "teams": "团队", + "billing": "账单", + "projects": "项目", + "adminCenter": "管理中心" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/admin-center/teams.json b/worklenz-backend/src/public/locales/zh/admin-center/teams.json new file mode 100644 index 00000000..4244d848 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/admin-center/teams.json @@ -0,0 +1,33 @@ +{ + "title": "团队", + "subtitle": "团队", + "tooltip": "刷新团队", + "placeholder": "按名称搜索", + "addTeam": "添加团队", + "team": "团队", + "membersCount": "成员数量", + "members": "成员", + "drawerTitle": "创建新团队", + "label": "团队名称", + "drawerPlaceholder": "名称", + "create": "创建", + "delete": "删除", + "settings": "设置", + "popTitle": "您确定吗?", + "message": "请输入名称", + "teamSettings": "团队设置", + "teamName": "团队名称", + "teamDescription": "团队描述", + "teamMembers": "团队成员", + "teamMembersCount": "团队成员数量", + "teamMembersPlaceholder": "按名称搜索", + "addMember": "添加成员", + "add": "添加", + "update": "更新", + "teamNamePlaceholder": "团队名称", + "user": "用户", + "role": "角色", + "owner": "所有者", + "admin": "管理员", + "member": "成员" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/admin-center/users.json b/worklenz-backend/src/public/locales/zh/admin-center/users.json new file mode 100644 index 00000000..83800c09 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/admin-center/users.json @@ -0,0 +1,9 @@ +{ + "title": "用户", + "subTitle": "用户", + "placeholder": "按名称搜索", + "user": "用户", + "email": "电子邮件", + "lastActivity": "最后活动", + "refresh": "刷新用户" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/all-project-list.json b/worklenz-backend/src/public/locales/zh/all-project-list.json new file mode 100644 index 00000000..a6c72c06 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/all-project-list.json @@ -0,0 +1,34 @@ +{ + "name": "名称", + "client": "客户", + "category": "类别", + "status": "状态", + "tasksProgress": "任务进度", + "updated_at": "最后更新", + "members": "成员", + "setting": "设置", + "projects": "项目", + "refreshProjects": "刷新项目", + "all": "全部", + "favorites": "收藏", + "archived": "已归档", + "placeholder": "按名称搜索", + "archive": "归档", + "unarchive": "取消归档", + "archiveConfirm": "您确定要归档此项目吗?", + "unarchiveConfirm": "您确定要取消归档此项目吗?", + "yes": "是", + "no": "否", + "clickToFilter": "点击筛选", + "noProjects": "未找到项目", + "addToFavourites": "添加到收藏", + "list": "列表", + "group": "分组", + "listView": "列表视图", + "groupView": "分组视图", + "groupBy": { + "category": "类别", + "client": "客户" + }, + "noPermission": "您没有权限执行此操作" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/auth/auth-common.json b/worklenz-backend/src/public/locales/zh/auth/auth-common.json new file mode 100644 index 00000000..df57a70d --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/auth/auth-common.json @@ -0,0 +1,5 @@ +{ + "loggingOut": "正在登出...", + "authenticating": "正在认证...", + "gettingThingsReady": "正在为您准备..." +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/auth/forgot-password.json b/worklenz-backend/src/public/locales/zh/auth/forgot-password.json new file mode 100644 index 00000000..de1529a4 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/auth/forgot-password.json @@ -0,0 +1,12 @@ +{ + "headerDescription": "重置您的密码", + "emailLabel": "电子邮件", + "emailPlaceholder": "输入您的电子邮件", + "emailRequired": "请输入您的电子邮件!", + "resetPasswordButton": "重置密码", + "returnToLoginButton": "返回登录", + "passwordResetSuccessMessage": "密码重置链接已发送到您的电子邮件。", + "orText": "或", + "successTitle": "重置指令已发送!", + "successMessage": "重置信息已发送到您的电子邮件。请检查您的电子邮件。" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/auth/login.json b/worklenz-backend/src/public/locales/zh/auth/login.json new file mode 100644 index 00000000..e53d5fc5 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/auth/login.json @@ -0,0 +1,27 @@ +{ + "headerDescription": "登录到您的账户", + "emailLabel": "电子邮件", + "emailPlaceholder": "输入您的电子邮件", + "emailRequired": "请输入您的电子邮件!", + "passwordLabel": "密码", + "passwordPlaceholder": "输入您的密码", + "passwordRequired": "请输入您的密码!", + "rememberMe": "记住我", + "loginButton": "登录", + "signupButton": "注册", + "forgotPasswordButton": "忘记密码?", + "signInWithGoogleButton": "使用Google登录", + "dontHaveAccountText": "没有账户?", + "orText": "或", + "successMessage": "您已成功登录!", + "loginError": "登录失败", + "googleLoginError": "Google登录失败", + "validationMessages": { + "email": "请输入有效的电子邮件地址", + "password": "密码必须至少包含8个字符" + }, + "errorMessages": { + "loginErrorTitle": "登录失败", + "loginErrorMessage": "请检查您的电子邮件和密码并重试" + } +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/auth/signup.json b/worklenz-backend/src/public/locales/zh/auth/signup.json new file mode 100644 index 00000000..a2b34e57 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/auth/signup.json @@ -0,0 +1,29 @@ +{ + "headerDescription": "注册以开始使用", + "nameLabel": "全名", + "namePlaceholder": "输入您的全名", + "nameRequired": "请输入您的全名!", + "nameMinCharacterRequired": "全名必须至少包含4个字符!", + "emailLabel": "电子邮件", + "emailPlaceholder": "输入您的电子邮件", + "emailRequired": "请输入您的电子邮件!", + "passwordLabel": "密码", + "passwordPlaceholder": "输入您的密码", + "passwordRequired": "请输入您的密码!", + "passwordMinCharacterRequired": "密码必须至少包含8个字符!", + "passwordPatternRequired": "密码不符合要求!", + "strongPasswordPlaceholder": "输入更强的密码", + "passwordValidationAltText": "密码必须至少包含8个字符,包括大小写字母、一个数字和一个符号。", + "signupSuccessMessage": "您已成功注册!", + "privacyPolicyLink": "隐私政策", + "termsOfUseLink": "使用条款", + "bySigningUpText": "通过注册,您同意我们的", + "andText": "和", + "signupButton": "注册", + "signInWithGoogleButton": "使用Google登录", + "alreadyHaveAccountText": "已经有账户了?", + "loginButton": "登录", + "orText": "或", + "reCAPTCHAVerificationError": "reCAPTCHA验证错误", + "reCAPTCHAVerificationErrorMessage": "我们无法验证您的reCAPTCHA。请重试。" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/auth/verify-reset-email.json b/worklenz-backend/src/public/locales/zh/auth/verify-reset-email.json new file mode 100644 index 00000000..11222523 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/auth/verify-reset-email.json @@ -0,0 +1,14 @@ +{ + "title": "验证重置电子邮件", + "description": "输入您的新密码", + "placeholder": "输入您的新密码", + "confirmPasswordPlaceholder": "确认您的新密码", + "passwordHint": "至少8个字符,包括大小写字母、一个数字和一个符号。", + "resetPasswordButton": "重置密码", + "orText": "或", + "resendResetEmail": "重新发送重置电子邮件", + "passwordRequired": "请输入您的新密码", + "returnToLoginButton": "返回登录", + "confirmPasswordRequired": "请确认您的新密码", + "passwordMismatch": "两次输入的密码不匹配" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/common.json b/worklenz-backend/src/public/locales/zh/common.json new file mode 100644 index 00000000..520ee5e2 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/common.json @@ -0,0 +1,9 @@ +{ + "login-success": "登录成功!", + "login-failed": "登录失败。请检查您的凭据并重试。", + "signup-success": "注册成功!欢迎加入。", + "signup-failed": "注册失败。请确保填写所有必填字段并重试。", + "reconnecting": "与服务器断开连接。", + "connection-lost": "无法连接到服务器。请检查您的互联网连接。", + "connection-restored": "成功连接到服务器" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/create-first-project-form.json b/worklenz-backend/src/public/locales/zh/create-first-project-form.json new file mode 100644 index 00000000..95ea4099 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/create-first-project-form.json @@ -0,0 +1,13 @@ +{ + "formTitle": "创建您的第一个项目", + "inputLabel": "您现在正在做什么项目?", + "or": "或", + "templateButton": "从模板导入", + "createFromTemplate": "从模板创建", + "goBack": "返回", + "continue": "继续", + "cancel": "取消", + "create": "创建", + "templateDrawerTitle": "从模板中选择", + "createProject": "创建项目" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/create-first-tasks.json b/worklenz-backend/src/public/locales/zh/create-first-tasks.json new file mode 100644 index 00000000..810d5aff --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/create-first-tasks.json @@ -0,0 +1,7 @@ +{ + "formTitle": "创建您的第一个任务。", + "inputLable": "输入您将在其中完成的几个任务", + "addAnother": "添加另一个", + "goBack": "返回", + "continue": "继续" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/home.json b/worklenz-backend/src/public/locales/zh/home.json new file mode 100644 index 00000000..184b4f1a --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/home.json @@ -0,0 +1,46 @@ +{ + "todoList": { + "title": "待办事项列表", + "refreshTasks": "刷新任务", + "addTask": "+ 添加任务", + "noTasks": "没有任务", + "pressEnter": "按", + "toCreate": "创建。", + "markAsDone": "标记为完成" + }, + "projects": { + "title": "项目", + "refreshProjects": "刷新项目", + "noRecentProjects": "您当前未被分配到任何项目。", + "noFavouriteProjects": "没有项目被标记为收藏。", + "recent": "最近", + "favourites": "收藏" + }, + "tasks": { + "assignedToMe": "分配给我", + "assignedByMe": "由我分配", + "all": "全部", + "today": "今天", + "upcoming": "即将到来", + "overdue": "逾期", + "noDueDate": "没有截止日期", + "noTasks": "没有任务可显示。", + "addTask": "+ 添加任务", + "name": "名称", + "project": "项目", + "status": "状态", + "dueDate": "截止日期", + "dueDatePlaceholder": "设置截止日期", + "tomorrow": "明天", + "nextWeek": "下周", + "nextMonth": "下个月", + "projectRequired": "请选择一个项目", + "pressTabToSelectDueDateAndProject": "按Tab键选择截止日期和项目", + "dueOn": "任务截止于", + "taskRequired": "请添加一个任务", + "list": "列表", + "calendar": "日历", + "tasks": "任务", + "refresh": "刷新" + } +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/invite-initial-team-members.json b/worklenz-backend/src/public/locales/zh/invite-initial-team-members.json new file mode 100644 index 00000000..6ebb9fbf --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/invite-initial-team-members.json @@ -0,0 +1,8 @@ +{ + "formTitle": "邀请您的团队一起工作", + "inputLable": "通过电子邮件邀请", + "addAnother": "添加另一个", + "goBack": "返回", + "continue": "继续", + "skipForNow": "暂时跳过" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/kanban-board.json b/worklenz-backend/src/public/locales/zh/kanban-board.json new file mode 100644 index 00000000..7b72c5d5 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/kanban-board.json @@ -0,0 +1,19 @@ +{ + "rename": "重命名", + "delete": "删除", + "addTask": "添加任务", + "addSectionButton": "添加部分", + "changeCategory": "更改类别", + "deleteTooltip": "删除", + "deleteConfirmationTitle": "您确定吗?", + "deleteConfirmationOk": "是", + "deleteConfirmationCancel": "取消", + "dueDate": "截止日期", + "cancel": "取消", + "today": "今天", + "tomorrow": "明天", + "assignToMe": "分配给我", + "archive": "归档", + "newTaskNamePlaceholder": "写一个任务名称", + "newSubtaskNamePlaceholder": "写一个子任务名称" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/license-expired.json b/worklenz-backend/src/public/locales/zh/license-expired.json new file mode 100644 index 00000000..838125c2 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/license-expired.json @@ -0,0 +1,6 @@ +{ + "title": "您的Worklenz试用已过期!", + "subtitle": "请立即升级。", + "button": "立即升级", + "checking": "正在检查订阅状态..." +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/navbar.json b/worklenz-backend/src/public/locales/zh/navbar.json new file mode 100644 index 00000000..c4ed67ab --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/navbar.json @@ -0,0 +1,31 @@ +{ + "logoAlt": "Worklenz Logo", + "home": "首页", + "projects": "项目", + "schedule": "日程", + "reporting": "报告", + "clients": "客户", + "teams": "团队", + "labels": "标签", + "jobTitles": "职位", + "upgradePlan": "升级计划", + "upgradePlanTooltip": "升级计划", + "invite": "邀请", + "inviteTooltip": "邀请团队成员加入", + "switchTeamTooltip": "切换团队", + "help": "帮助", + "notificationTooltip": "查看通知", + "profileTooltip": "查看个人资料", + "adminCenter": "管理中心", + "settings": "设置", + "logOut": "登出", + "notificationsDrawer": { + "read": "已读通知", + "unread": "未读通知", + "markAsRead": "标记为已读", + "readAndJoin": "阅读并加入", + "accept": "接受", + "acceptAndJoin": "接受并加入", + "noNotifications": "没有通知" + } +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/organization-name-form.json b/worklenz-backend/src/public/locales/zh/organization-name-form.json new file mode 100644 index 00000000..df8727d8 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/organization-name-form.json @@ -0,0 +1,5 @@ +{ + "nameYourOrganization": "命名您的组织。", + "worklenzAccountTitle": "为您的Worklenz账户选择一个名称。", + "continue": "继续" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/phases-drawer.json b/worklenz-backend/src/public/locales/zh/phases-drawer.json new file mode 100644 index 00000000..24d21b38 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/phases-drawer.json @@ -0,0 +1,19 @@ +{ + "configurePhases": "配置阶段", + "phaseLabel": "阶段标签", + "enterPhaseName": "输入阶段标签名称", + "addOption": "添加选项", + "phaseOptions": "阶段选项:", + "dragToReorderPhases": "拖拽阶段以重新排序。每个阶段可以有不同的颜色。", + "enterNewPhaseName": "输入新阶段名称...", + "addPhase": "添加阶段", + "noPhasesFound": "未找到阶段。请在上面创建您的第一个阶段。", + "deletePhase": "删除阶段", + "deletePhaseConfirm": "您确定要删除此阶段吗?此操作无法撤销。", + "rename": "重命名", + "delete": "删除", + "enterPhaseName": "输入阶段名称", + "selectColor": "选择颜色", + "managePhases": "管理阶段", + "close": "关闭" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/project-drawer.json b/worklenz-backend/src/public/locales/zh/project-drawer.json new file mode 100644 index 00000000..1649dfde --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/project-drawer.json @@ -0,0 +1,42 @@ +{ + "createProject": "创建项目", + "editProject": "编辑项目", + "enterCategoryName": "输入类别名称", + "hitEnterToCreate": "按回车键创建!", + "enterNotes": "备注", + "youCanManageClientsUnderSettings": "您可以在设置中管理客户", + "addCategory": "向项目添加类别", + "newCategory": "新类别", + "notes": "备注", + "startDate": "开始日期", + "endDate": "结束日期", + "estimateWorkingDays": "估算工作日", + "estimateManDays": "估算人天", + "hoursPerDay": "每天小时数", + "create": "创建", + "update": "更新", + "delete": "删除", + "typeToSearchClients": "输入以搜索客户", + "projectColor": "项目颜色", + "pleaseEnterAName": "请输入名称", + "enterProjectName": "输入项目名称", + "name": "名称", + "status": "状态", + "health": "健康状况", + "category": "类别", + "projectManager": "项目经理", + "client": "客户", + "deleteConfirmation": "您确定要删除吗?", + "deleteConfirmationDescription": "这将删除所有相关数据且无法撤销。", + "yes": "是", + "no": "否", + "createdAt": "创建于", + "updatedAt": "更新于", + "by": "由", + "add": "添加", + "asClient": "作为客户", + "createClient": "创建客户", + "searchInputPlaceholder": "按名称或电子邮件搜索", + "hoursPerDayValidationMessage": "每天小时数必须是1到24之间的数字", + "noPermission": "无权限" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/project-view-files.json b/worklenz-backend/src/public/locales/zh/project-view-files.json new file mode 100644 index 00000000..9cbf8ef6 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/project-view-files.json @@ -0,0 +1,14 @@ +{ + "nameColumn": "名称", + "attachedTaskColumn": "附加任务", + "sizeColumn": "大小", + "uploadedByColumn": "上传者", + "uploadedAtColumn": "上传时间", + "fileIconAlt": "文件图标", + "titleDescriptionText": "此项目中任务的所有附件将显示在这里。", + "deleteConfirmationTitle": "您确定吗?", + "deleteConfirmationOk": "是", + "deleteConfirmationCancel": "取消", + "segmentedTooltip": "即将推出!在列表视图和缩略图视图之间切换。", + "emptyText": "项目中没有附件。" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/project-view-insights.json b/worklenz-backend/src/public/locales/zh/project-view-insights.json new file mode 100644 index 00000000..903d73d2 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/project-view-insights.json @@ -0,0 +1,41 @@ +{ + "overview": { + "title": "概览", + "statusOverview": "状态概览", + "priorityOverview": "优先级概览", + "lastUpdatedTasks": "最近更新的任务" + }, + "members": { + "title": "成员", + "tooltip": "成员", + "tasksByMembers": "按成员分类任务", + "tasksByMembersTooltip": "按成员分类任务", + "name": "名称", + "taskCount": "任务计数", + "contribution": "贡献", + "completed": "已完成", + "incomplete": "未完成", + "overdue": "逾期", + "progress": "进度" + }, + "tasks": { + "overdueTasks": "逾期任务", + "overLoggedTasks": "超额记录任务", + "tasksCompletedEarly": "提前完成的任务", + "tasksCompletedLate": "延迟完成的任务", + "overLoggedTasksTooltip": "记录时间超过预计时间的任务", + "overdueTasksTooltip": "超过截止日期的任务" + }, + "common": { + "seeAll": "查看全部", + "totalLoggedHours": "总记录小时数", + "totalEstimation": "总估算", + "completedTasks": "已完成任务", + "incompleteTasks": "未完成任务", + "overdueTasks": "逾期任务", + "overdueTasksTooltip": "超过截止日期的任务", + "totalLoggedHoursTooltip": "任务估算和任务记录时间。", + "includeArchivedTasks": "包含已归档任务", + "export": "导出" + } +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/project-view-members.json b/worklenz-backend/src/public/locales/zh/project-view-members.json new file mode 100644 index 00000000..3d217694 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/project-view-members.json @@ -0,0 +1,17 @@ +{ + "nameColumn": "名称", + "jobTitleColumn": "职位", + "emailColumn": "电子邮件", + "tasksColumn": "任务", + "taskProgressColumn": "任务进度", + "accessColumn": "访问权限", + "fileIconAlt": "文件图标", + "deleteConfirmationTitle": "您确定吗?", + "deleteConfirmationOk": "是", + "deleteConfirmationCancel": "取消", + "refreshButtonTooltip": "刷新成员", + "deleteButtonTooltip": "从项目中移除", + "memberCount": "成员", + "membersCountPlural": "成员", + "emptyText": "项目中没有附件。" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/project-view-updates.json b/worklenz-backend/src/public/locales/zh/project-view-updates.json new file mode 100644 index 00000000..b34c71ea --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/project-view-updates.json @@ -0,0 +1,6 @@ +{ + "inputPlaceholder": "添加评论", + "addButton": "添加", + "cancelButton": "取消", + "deleteButton": "删除" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/project-view.json b/worklenz-backend/src/public/locales/zh/project-view.json new file mode 100644 index 00000000..ff756ea5 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/project-view.json @@ -0,0 +1,14 @@ +{ + "taskList": "任务列表", + "board": "看板", + "insights": "数据洞察", + "files": "文件", + "members": "成员", + "updates": "动态更新", + "projectView": "项目视图", + "loading": "正在加载项目...", + "error": "加载项目时出错", + "pinnedTab": "已固定为默认标签页", + "pinTab": "固定为默认标签页", + "unpinTab": "取消固定默认标签页" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/project-view/import-task-templates.json b/worklenz-backend/src/public/locales/zh/project-view/import-task-templates.json new file mode 100644 index 00000000..3dae9403 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/project-view/import-task-templates.json @@ -0,0 +1,11 @@ +{ + "importTaskTemplate": "导入任务模板", + "templateName": "模板名称", + "templateDescription": "模板描述", + "selectedTasks": "已选任务", + "tasks": "任务", + "templates": "模板", + "remove": "移除", + "cancel": "取消", + "import": "导入" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/project-view/project-member-drawer.json b/worklenz-backend/src/public/locales/zh/project-view/project-member-drawer.json new file mode 100644 index 00000000..f412f22b --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/project-view/project-member-drawer.json @@ -0,0 +1,7 @@ +{ + "title": "项目成员", + "searchLabel": "通过添加名称或电子邮件添加成员", + "searchPlaceholder": "输入名称或电子邮件", + "inviteAsAMember": "邀请为成员", + "inviteNewMemberByEmail": "通过电子邮件邀请新成员" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/project-view/project-view-header.json b/worklenz-backend/src/public/locales/zh/project-view/project-view-header.json new file mode 100644 index 00000000..9f8ca8ed --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/project-view/project-view-header.json @@ -0,0 +1,30 @@ +{ + "importTasks": "导入任务", + "importTask": "导入任务", + "createTask": "创建任务", + "settings": "设置", + "subscribe": "订阅", + "unsubscribe": "取消订阅", + "deleteProject": "删除项目", + "startDate": "开始日期", + "endDate": "结束日期", + "projectSettings": "项目设置", + "projectSummary": "项目摘要", + "receiveProjectSummary": "每晚接收项目摘要。", + "refreshProject": "刷新项目", + "saveAsTemplate": "保存为模板", + "invite": "邀请", + "share": "分享", + "subscribeTooltip": "订阅项目通知", + "unsubscribeTooltip": "取消订阅项目通知", + "refreshTooltip": "刷新项目数据", + "settingsTooltip": "打开项目设置", + "saveAsTemplateTooltip": "将此项目保存为模板", + "inviteTooltip": "邀请团队成员加入此项目", + "createTaskTooltip": "创建新任务", + "importTaskTooltip": "从模板导入任务", + "navigateBackTooltip": "返回项目列表", + "projectStatusTooltip": "项目状态", + "projectDatesInfo": "项目时间安排信息", + "projectCategoryTooltip": "项目类别" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/project-view/save-as-template.json b/worklenz-backend/src/public/locales/zh/project-view/save-as-template.json new file mode 100644 index 00000000..d1d3dfa8 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/project-view/save-as-template.json @@ -0,0 +1,27 @@ +{ + "title": "保存为模板", + "templateName": "模板名称", + "includes": "项目中应包含哪些内容到模板中?", + "includesOptions": { + "statuses": "状态", + "phases": "阶段", + "labels": "标签" + }, + "taskIncludes": "任务中应包含哪些内容到模板中?", + "taskIncludesOptions": { + "statuses": "状态", + "phases": "阶段", + "labels": "标签", + "name": "名称", + "priority": "优先级", + "status": "状态", + "phase": "阶段", + "label": "标签", + "timeEstimate": "预计用时", + "description": "描述", + "subTasks": "子任务" + }, + "cancel": "取消", + "save": "保存", + "templateNamePlaceholder": "输入模板名称" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/reporting-members-drawer.json b/worklenz-backend/src/public/locales/zh/reporting-members-drawer.json new file mode 100644 index 00000000..db42a74b --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/reporting-members-drawer.json @@ -0,0 +1,76 @@ +{ + "exportButton": "导出", + "timeLogsButton": "时间日志", + "activityLogsButton": "活动日志", + "tasksButton": "任务", + "searchByNameInputPlaceholder": "按名称搜索", + "overviewTab": "概览", + "timeLogsTab": "时间日志", + "activityLogsTab": "活动日志", + "tasksTab": "任务", + "projectsText": "项目", + "totalTasksText": "任务总数", + "assignedTasksText": "已分配任务", + "completedTasksText": "已完成任务", + "ongoingTasksText": "进行中任务", + "overdueTasksText": "逾期任务", + "loggedHoursText": "记录小时数", + "tasksText": "任务", + "allText": "全部", + "tasksByProjectsText": "按项目分类任务", + "tasksByStatusText": "按状态分类任务", + "tasksByPriorityText": "按优先级分类任务", + "todoText": "待办", + "doingText": "进行中", + "doneText": "已完成", + "lowText": "低", + "mediumText": "中", + "highText": "高", + "billableButton": "可计费", + "billableText": "可计费", + "nonBillableText": "不可计费", + "timeLogsEmptyPlaceholder": "没有时间日志可显示", + "loggedText": "记录", + "forText": "为", + "inText": "在", + "updatedText": "更新", + "fromText": "从", + "toText": "到", + "withinText": "在...之内", + "activityLogsEmptyPlaceholder": "没有活动日志可显示", + "filterByText": "筛选依据:", + "selectProjectPlaceholder": "选择项目", + "taskColumn": "任务", + "nameColumn": "名称", + "projectColumn": "项目", + "statusColumn": "状态", + "priorityColumn": "优先级", + "dueDateColumn": "截止日期", + "completedDateColumn": "完成日期", + "estimatedTimeColumn": "预计用时", + "loggedTimeColumn": "记录时间", + "overloggedTimeColumn": "超额记录时间", + "daysLeftColumn": "剩余天数/逾期", + "startDateColumn": "开始日期", + "endDateColumn": "结束日期", + "actualTimeColumn": "实际时间", + "projectHealthColumn": "项目健康状况", + "categoryColumn": "类别", + "projectManagerColumn": "项目经理", + "tasksStatsOverviewDrawerTitle": "的任务", + "projectsStatsOverviewDrawerTitle": "的项目", + "cancelledText": "已取消", + "blockedText": "已阻塞", + "onHoldText": "暂停", + "proposedText": "提议", + "inPlanningText": "规划中", + "inProgressText": "进行中", + "completedText": "已完成", + "continuousText": "持续", + "daysLeftText": "天剩余", + "daysOverdueText": "天逾期", + "notSetText": "未设置", + "needsAttentionText": "需要关注", + "atRiskText": "有风险", + "goodText": "良好" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/reporting-members.json b/worklenz-backend/src/public/locales/zh/reporting-members.json new file mode 100644 index 00000000..de4c23bb --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/reporting-members.json @@ -0,0 +1,31 @@ +{ + "yesterdayText": "昨天", + "lastSevenDaysText": "过去7天", + "lastWeekText": "上周", + "lastThirtyDaysText": "过去30天", + "lastMonthText": "上个月", + "lastThreeMonthsText": "过去3个月", + "allTimeText": "所有时间", + "customRangeText": "自定义范围", + "startDateInputPlaceholder": "开始日期", + "EndDateInputPlaceholder": "结束日期", + "filterButton": "筛选", + "membersTitle": "成员", + "includeArchivedButton": "包含已归档项目", + "exportButton": "导出", + "excelButton": "Excel", + "searchByNameInputPlaceholder": "按名称搜索", + "memberColumn": "成员", + "tasksProgressColumn": "任务进度", + "tasksAssignedColumn": "分配任务", + "completedTasksColumn": "已完成任务", + "overdueTasksColumn": "逾期任务", + "ongoingTasksColumn": "进行中任务", + "tasksAssignedColumnTooltip": "在选定日期范围内分配的任务", + "overdueTasksColumnTooltip": "在选定日期范围结束时逾期的任务", + "completedTasksColumnTooltip": "在选定日期范围内完成的任务", + "ongoingTasksColumnTooltip": "已开始但尚未完成的任务", + "todoText": "待办", + "doingText": "进行中", + "doneText": "已完成" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/reporting-overview-drawer.json b/worklenz-backend/src/public/locales/zh/reporting-overview-drawer.json new file mode 100644 index 00000000..a02b318f --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/reporting-overview-drawer.json @@ -0,0 +1,33 @@ +{ + "exportButton": "导出", + "projectsButton": "项目", + "membersButton": "成员", + "searchByNameInputPlaceholder": "按名称搜索", + "overviewTab": "概览", + "projectsTab": "项目", + "membersTab": "成员", + "projectsByStatusText": "按状态分类项目", + "projectsByCategoryText": "按类别分类项目", + "projectsByHealthText": "按健康状况分类项目", + "projectsText": "项目", + "allText": "全部", + "cancelledText": "已取消", + "blockedText": "已阻塞", + "onHoldText": "暂停", + "proposedText": "提议", + "inPlanningText": "规划中", + "inProgressText": "进行中", + "completedText": "已完成", + "continuousText": "持续", + "notSetText": "未设置", + "needsAttentionText": "需要关注", + "atRiskText": "有风险", + "goodText": "良好", + "nameColumn": "名称", + "emailColumn": "电子邮件", + "projectsColumn": "项目", + "tasksColumn": "任务", + "overdueTasksColumn": "逾期任务", + "completedTasksColumn": "已完成任务", + "ongoingTasksColumn": "进行中任务" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/reporting-overview.json b/worklenz-backend/src/public/locales/zh/reporting-overview.json new file mode 100644 index 00000000..fb172817 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/reporting-overview.json @@ -0,0 +1,22 @@ +{ + "overviewTitle": "概览", + "includeArchivedButton": "包含已归档项目", + "teamCount": "团队", + "teamCountPlural": "团队", + "projectCount": "项目", + "projectCountPlural": "项目", + "memberCount": "成员", + "memberCountPlural": "成员", + "activeProjectCount": "活跃项目", + "activeProjectCountPlural": "活跃项目", + "overdueProjectCount": "逾期项目", + "overdueProjectCountPlural": "逾期项目", + "unassignedMemberCount": "未分配成员", + "unassignedMemberCountPlural": "未分配成员", + "memberWithOverdueTaskCount": "有逾期任务的成员", + "memberWithOverdueTaskCountPlural": "有逾期任务的成员", + "teamsText": "团队", + "nameColumn": "名称", + "projectsColumn": "项目", + "membersColumn": "成员" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/reporting-projects-drawer.json b/worklenz-backend/src/public/locales/zh/reporting-projects-drawer.json new file mode 100644 index 00000000..d2f2f6ef --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/reporting-projects-drawer.json @@ -0,0 +1,52 @@ +{ + "exportButton": "导出", + "membersButton": "成员", + "tasksButton": "任务", + "searchByNameInputPlaceholder": "按名称搜索", + "overviewTab": "概览", + "membersTab": "成员", + "tasksTab": "任务", + "completedTasksText": "已完成任务", + "incompleteTasksText": "未完成任务", + "overdueTasksText": "逾期任务", + "allocatedHoursText": "已分配小时数", + "loggedHoursText": "已记录小时数", + "tasksText": "任务", + "allText": "全部", + "tasksByStatusText": "按状态分类任务", + "tasksByPriorityText": "按优先级分类任务", + "tasksByDueDateText": "按截止日期分类任务", + "todoText": "待办", + "doingText": "进行中", + "doneText": "已完成", + "lowText": "低", + "mediumText": "中", + "highText": "高", + "completedText": "已完成", + "upcomingText": "即将到来", + "overdueText": "逾期", + "noDueDateText": "无截止日期", + "nameColumn": "名称", + "tasksCountColumn": "任务计数", + "completedTasksColumn": "已完成任务", + "incompleteTasksColumn": "未完成任务", + "overdueTasksColumn": "逾期任务", + "contributionColumn": "贡献", + "progressColumn": "进度", + "loggedTimeColumn": "记录时间", + "taskColumn": "任务", + "projectColumn": "项目", + "statusColumn": "状态", + "priorityColumn": "优先级", + "phaseColumn": "阶段", + "dueDateColumn": "截止日期", + "completedDateColumn": "完成日期", + "estimatedTimeColumn": "预计用时", + "overloggedTimeColumn": "超额记录时间", + "completedOnColumn": "完成于", + "daysOverdueColumn": "逾期天数", + "groupByText": "分组依据:", + "statusText": "状态", + "priorityText": "优先级", + "phaseText": "阶段" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/reporting-projects-filters.json b/worklenz-backend/src/public/locales/zh/reporting-projects-filters.json new file mode 100644 index 00000000..ddfbe104 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/reporting-projects-filters.json @@ -0,0 +1,31 @@ +{ + "searchByNamePlaceholder": "按名称搜索", + "searchByCategoryPlaceholder": "按类别搜索", + "statusText": "状态", + "healthText": "健康状况", + "categoryText": "类别", + "projectManagerText": "项目经理", + "showFieldsText": "显示字段", + "cancelledText": "已取消", + "blockedText": "已阻塞", + "onHoldText": "暂停", + "proposedText": "提议", + "inPlanningText": "规划中", + "inProgressText": "进行中", + "completedText": "已完成", + "continuousText": "持续", + "notSetText": "未设置", + "needsAttentionText": "需要关注", + "atRiskText": "有风险", + "goodText": "良好", + "nameText": "项目", + "estimatedVsActualText": "预计用时 vs 实际用时", + "tasksProgressText": "任务进度", + "lastActivityText": "最后活动", + "datesText": "开始/结束日期", + "daysLeftText": "剩余天数/逾期", + "projectHealthText": "项目健康状况", + "projectUpdateText": "项目更新", + "clientText": "客户", + "teamText": "团队" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/reporting-projects.json b/worklenz-backend/src/public/locales/zh/reporting-projects.json new file mode 100644 index 00000000..0ff7d415 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/reporting-projects.json @@ -0,0 +1,44 @@ +{ + "projectCount": "项目", + "projectCountPlural": "项目", + "includeArchivedButton": "包含已归档项目", + "exportButton": "导出", + "excelButton": "Excel", + "projectColumn": "项目", + "estimatedVsActualColumn": "预计用时 vs 实际用时", + "tasksProgressColumn": "任务进度", + "lastActivityColumn": "最后活动", + "statusColumn": "状态", + "datesColumn": "开始/结束日期", + "daysLeftColumn": "剩余天数/逾期", + "projectHealthColumn": "项目健康状况", + "categoryColumn": "类别", + "projectUpdateColumn": "项目更新", + "clientColumn": "客户", + "teamColumn": "团队", + "projectManagerColumn": "项目经理", + "openButton": "打开", + "estimatedText": "预计", + "actualText": "实际", + "todoText": "待办", + "doingText": "进行中", + "doneText": "已完成", + "cancelledText": "已取消", + "blockedText": "已阻塞", + "onHoldText": "暂停", + "proposedText": "提议", + "inPlanningText": "规划中", + "inProgressText": "进行中", + "completedText": "已完成", + "continuousText": "持续", + "daysLeftText": "天剩余", + "dayLeftText": "天剩余", + "daysOverdueText": "天逾期", + "notSetText": "未设置", + "needsAttentionText": "需要关注", + "atRiskText": "有风险", + "goodText": "良好", + "setCategoryText": "设置类别", + "searchByNameInputPlaceholder": "按名称搜索", + "todayText": "今天" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/reporting-sidebar.json b/worklenz-backend/src/public/locales/zh/reporting-sidebar.json new file mode 100644 index 00000000..8a8206fb --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/reporting-sidebar.json @@ -0,0 +1,8 @@ +{ + "overview": "概览", + "projects": "项目", + "members": "成员", + "timeReports": "用时报告", + "estimateVsActual": "预计用时 vs 实际用时", + "currentOrganizationTooltip": "当前的组织" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/schedule.json b/worklenz-backend/src/public/locales/zh/schedule.json new file mode 100644 index 00000000..53fa8a97 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/schedule.json @@ -0,0 +1,34 @@ +{ + "today": "今天", + "week": "周", + "month": "月", + "settings": "设置", + "workingDays": "工作日", + "monday": "星期一", + "tuesday": "星期二", + "wednesday": "星期三", + "thursday": "星期四", + "friday": "星期五", + "saturday": "星期六", + "sunday": "星期日", + "workingHours": "工作时间", + "hours": "小时", + "saveButton": "保存", + "totalAllocation": "总分配", + "timeLogged": "记录时间", + "remainingTime": "剩余时间", + "total": "总计", + "perDay": "每天", + "tasks": "任务", + "startDate": "开始日期", + "endDate": "结束日期", + "hoursPerDay": "每天小时数", + "totalHours": "总小时数", + "deleteButton": "删除", + "cancelButton": "取消", + "tabTitle": "没有开始和结束日期的任务", + "allocatedTime": "分配时间", + "totalLogged": "总记录", + "loggedBillable": "已记录可计费", + "loggedNonBillable": "已记录不可计费" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/settings/categories.json b/worklenz-backend/src/public/locales/zh/settings/categories.json new file mode 100644 index 00000000..00027081 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/settings/categories.json @@ -0,0 +1,10 @@ +{ + "categoryColumn": "类别", + "deleteConfirmationTitle": "您确定吗?", + "deleteConfirmationOk": "是", + "deleteConfirmationCancel": "取消", + "associatedTaskColumn": "关联项目", + "searchPlaceholder": "按名称搜索", + "emptyText": "在更新或创建项目时可以创建类别。", + "colorChangeTooltip": "点击更改颜色" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/settings/change-password.json b/worklenz-backend/src/public/locales/zh/settings/change-password.json new file mode 100644 index 00000000..30cec581 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/settings/change-password.json @@ -0,0 +1,15 @@ +{ + "title": "更改密码", + "currentPassword": "当前密码", + "newPassword": "新密码", + "confirmPassword": "确认密码", + "currentPasswordPlaceholder": "输入您的当前密码", + "newPasswordPlaceholder": "新密码", + "confirmPasswordPlaceholder": "确认密码", + "currentPasswordRequired": "请输入您的当前密码!", + "newPasswordRequired": "请输入您的新密码!", + "passwordValidationError": "密码必须至少包含8个字符,包括一个大写字母、一个数字和一个符号。", + "passwordMismatch": "密码不匹配!", + "passwordRequirements": "新密码应至少包含8个字符,包括一个大写字母、一个数字和一个符号。", + "updateButton": "更新密码" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/settings/clients.json b/worklenz-backend/src/public/locales/zh/settings/clients.json new file mode 100644 index 00000000..c06b1adc --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/settings/clients.json @@ -0,0 +1,22 @@ +{ + "nameColumn": "名称", + "projectColumn": "项目", + "noProjectsAvailable": "没有可用的项目", + "deleteConfirmationTitle": "您确定吗?", + "deleteConfirmationOk": "是", + "deleteConfirmationCancel": "取消", + "searchPlaceholder": "按名称搜索", + "createClient": "创建客户", + "pinTooltip": "点击将其固定到主菜单", + "createClientDrawerTitle": "创建客户", + "updateClientDrawerTitle": "更新客户", + "nameLabel": "名称", + "namePlaceholder": "名称", + "nameRequiredError": "请输入名称", + "createButton": "创建", + "updateButton": "更新", + "createClientSuccessMessage": "客户创建成功!", + "createClientErrorMessage": "客户创建失败!", + "updateClientSuccessMessage": "客户更新成功!", + "updateClientErrorMessage": "客户更新失败!" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/settings/job-titles.json b/worklenz-backend/src/public/locales/zh/settings/job-titles.json new file mode 100644 index 00000000..c0458bb6 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/settings/job-titles.json @@ -0,0 +1,20 @@ +{ + "nameColumn": "名称", + "deleteConfirmationTitle": "您确定吗?", + "deleteConfirmationOk": "是", + "deleteConfirmationCancel": "取消", + "searchPlaceholder": "按名称搜索", + "createJobTitleButton": "创建职位", + "pinTooltip": "点击将其固定到主菜单", + "createJobTitleDrawerTitle": "创建职位", + "updateJobTitleDrawerTitle": "更新职位", + "nameLabel": "名称", + "namePlaceholder": "名称", + "nameRequiredError": "请输入名称", + "createButton": "创建", + "updateButton": "更新", + "createJobTitleSuccessMessage": "职位创建成功!", + "createJobTitleErrorMessage": "职位创建失败!", + "updateJobTitleSuccessMessage": "职位更新成功!", + "updateJobTitleErrorMessage": "职位更新失败!" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/settings/labels.json b/worklenz-backend/src/public/locales/zh/settings/labels.json new file mode 100644 index 00000000..ab0d01cd --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/settings/labels.json @@ -0,0 +1,11 @@ +{ + "labelColumn": "标签", + "deleteConfirmationTitle": "您确定吗?", + "deleteConfirmationOk": "是", + "deleteConfirmationCancel": "取消", + "associatedTaskColumn": "关联任务计数", + "searchPlaceholder": "按名称搜索", + "emptyText": "标签可以在更新或创建任务时创建。", + "pinTooltip": "点击将其固定到主菜单", + "colorChangeTooltip": "点击更改颜色" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/settings/language.json b/worklenz-backend/src/public/locales/zh/settings/language.json new file mode 100644 index 00000000..631eac11 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/settings/language.json @@ -0,0 +1,7 @@ +{ + "language": "语言", + "language_required": "语言是必需的", + "time_zone": "时区", + "time_zone_required": "时区是必需的", + "save_changes": "保存更改" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/settings/notifications.json b/worklenz-backend/src/public/locales/zh/settings/notifications.json new file mode 100644 index 00000000..f15784bf --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/settings/notifications.json @@ -0,0 +1,11 @@ +{ + "title": "通知设置", + "emailTitle": "向我发送电子邮件通知", + "emailDescription": "包括新的任务分配", + "dailyDigestTitle": "向我发送每日摘要", + "dailyDigestDescription": "每天晚上,您将收到任务中最近活动的摘要。", + "popupTitle": "当Worklenz打开时,在我的电脑上弹出通知", + "popupDescription": "弹出通知可能会被您的浏览器禁用。更改您的浏览器设置以允许它们。", + "unreadItemsTitle": "显示未读项目的数量", + "unreadItemsDescription": "您将看到每个通知的计数。" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/settings/profile.json b/worklenz-backend/src/public/locales/zh/settings/profile.json new file mode 100644 index 00000000..cfafeb12 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/settings/profile.json @@ -0,0 +1,14 @@ +{ + "uploadError": "您只能上传JPG/PNG文件!", + "uploadSizeError": "图片必须小于2MB!", + "upload": "上传", + "nameLabel": "名称", + "nameRequiredError": "名称是必需的", + "emailLabel": "电子邮件", + "emailRequiredError": "电子邮件是必需的", + "saveChanges": "保存更改", + "profileJoinedText": "一个月前加入", + "profileLastUpdatedText": "一个月前更新", + "avatarTooltip": "点击上传头像", + "title": "个人资料设置" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/settings/project-templates.json b/worklenz-backend/src/public/locales/zh/settings/project-templates.json new file mode 100644 index 00000000..5dcc866c --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/settings/project-templates.json @@ -0,0 +1,8 @@ +{ + "nameColumn": "名称", + "editToolTip": "编辑", + "deleteToolTip": "删除", + "confirmText": "您确定吗?", + "okText": "是", + "cancelText": "取消" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/settings/sidebar.json b/worklenz-backend/src/public/locales/zh/settings/sidebar.json new file mode 100644 index 00000000..b9f74709 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/settings/sidebar.json @@ -0,0 +1,15 @@ +{ + "profile": "个人资料", + "appearance": "外观", + "notifications": "通知", + "clients": "客户", + "job-titles": "职位", + "labels": "标签", + "categories": "类别", + "project-templates": "项目模板", + "task-templates": "任务模板", + "team-members": "团队成员", + "teams": "团队", + "change-password": "更改密码", + "language-and-region": "语言和地区" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/settings/task-templates.json b/worklenz-backend/src/public/locales/zh/settings/task-templates.json new file mode 100644 index 00000000..3fd9124a --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/settings/task-templates.json @@ -0,0 +1,9 @@ +{ + "nameColumn": "名称", + "createdColumn": "创建时间", + "editToolTip": "编辑", + "deleteToolTip": "删除", + "confirmText": "您确定吗?", + "okText": "是", + "cancelText": "取消" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/settings/team-members.json b/worklenz-backend/src/public/locales/zh/settings/team-members.json new file mode 100644 index 00000000..8b39483c --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/settings/team-members.json @@ -0,0 +1,47 @@ +{ + "title": "团队成员", + "nameColumn": "名称", + "projectsColumn": "项目", + "emailColumn": "电子邮件", + "teamAccessColumn": "团队访问", + "memberCount": "成员", + "membersCountPlural": "成员", + "searchPlaceholder": "按名称搜索成员", + "pinTooltip": "刷新成员列表", + "addMemberButton": "添加新成员", + "editTooltip": "编辑成员", + "deactivateTooltip": "停用成员", + "activateTooltip": "激活成员", + "deleteTooltip": "删除成员", + "confirmDeleteTitle": "您确定要删除此成员吗?", + "confirmActivateTitle": "您确定要更改此成员的状态吗?", + "okText": "是,继续", + "cancelText": "否,取消", + "deactivatedText": "(当前已停用)", + "pendingInvitationText": "(邀请待处理)", + "addMemberDrawerTitle": "添加新团队成员", + "updateMemberDrawerTitle": "更新团队成员", + "addMemberEmailHint": "无论是否接受邀请,成员都将被添加到团队中", + "memberEmailLabel": "电子邮件", + "memberEmailPlaceholder": "输入团队成员的电子邮件地址", + "memberEmailRequiredError": "请输入有效的电子邮件地址", + "jobTitleLabel": "职位", + "jobTitlePlaceholder": "选择或搜索职位(可选)", + "memberAccessLabel": "访问级别", + "addToTeamButton": "将成员添加到团队", + "updateButton": "保存更改", + "resendInvitationButton": "重新发送邀请邮件", + "invitationSentSuccessMessage": "团队邀请已成功发送!", + "createMemberSuccessMessage": "新团队成员已成功添加!", + "createMemberErrorMessage": "添加团队成员失败。请重试。", + "updateMemberSuccessMessage": "团队成员已成功更新!", + "updateMemberErrorMessage": "更新团队成员失败。请重试。", + "memberText": "成员", + "adminText": "管理员", + "ownerText": "团队所有者", + "addedText": "已添加", + "updatedText": "已更新", + "noResultFound": "输入电子邮件地址并按回车键...", + "jobTitlesFetchError": "获取职位失败", + "invitationResent": "邀请重新发送成功!" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/settings/teams.json b/worklenz-backend/src/public/locales/zh/settings/teams.json new file mode 100644 index 00000000..af2064ae --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/settings/teams.json @@ -0,0 +1,16 @@ +{ + "title": "团队", + "team": "团队", + "teams": "团队", + "name": "名称", + "created": "创建时间", + "ownsBy": "所有者", + "edit": "编辑", + "editTeam": "编辑团队", + "pinTooltip": "点击将此项固定到主菜单", + "editTeamName": "编辑团队名称", + "updateName": "更新名称", + "namePlaceholder": "名称", + "nameRequired": "请输入名称", + "updateFailed": "团队名称更改失败!" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/task-drawer/task-drawer-info-tab.json b/worklenz-backend/src/public/locales/zh/task-drawer/task-drawer-info-tab.json new file mode 100644 index 00000000..b0b36689 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/task-drawer/task-drawer-info-tab.json @@ -0,0 +1,29 @@ +{ + "details": { + "task-key": "任务ID", + "phase": "阶段", + "assignees": "受托人", + "due-date": "截止日期", + "time-estimation": "估计时间", + "priority": "优先级", + "labels": "标签", + "billable": "可计费", + "notify": "通知", + "when-done-notify": "完成时通知", + "start-date": "开始日期", + "end-date": "结束日期", + "hide-start-date": "隐藏开始日期", + "show-start-date": "显示开始日期", + "hours": "小时", + "minutes": "分钟" + }, + "description": { + "title": "描述", + "placeholder": "添加更详细的描述..." + }, + "subTasks": { + "title": "子任务", + "add-sub-task": "+ 添加子任务", + "refresh-sub-tasks": "刷新子任务" + } +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/task-drawer/task-drawer.json b/worklenz-backend/src/public/locales/zh/task-drawer/task-drawer.json new file mode 100644 index 00000000..dfe304fe --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/task-drawer/task-drawer.json @@ -0,0 +1,123 @@ +{ + "taskHeader": { + "taskNamePlaceholder": "输入您的任务", + "deleteTask": "删除任务" + }, + "taskInfoTab": { + "title": "信息", + "details": { + "title": "详情", + "task-key": "任务键", + "phase": "阶段", + "assignees": "受让人", + "due-date": "截止日期", + "time-estimation": "时间估算", + "priority": "优先级", + "labels": "标签", + "billable": "可计费", + "notify": "通知", + "when-done-notify": "完成时,通知", + "start-date": "开始日期", + "end-date": "结束日期", + "hide-start-date": "隐藏开始日期", + "show-start-date": "显示开始日期", + "hours": "小时", + "minutes": "分钟", + "progressValue": "进度值", + "progressValueTooltip": "设置进度百分比(0-100%)", + "progressValueRequired": "请输入进度值", + "progressValueRange": "进度必须在0到100之间", + "taskWeight": "任务权重", + "taskWeightTooltip": "设置此子任务的权重(百分比)", + "taskWeightRequired": "请输入任务权重", + "taskWeightRange": "权重必须在0到100之间", + "recurring": "重复" + }, + "labels": { + "labelInputPlaceholder": "搜索或创建", + "labelsSelectorInputTip": "按回车创建" + }, + "description": { + "title": "描述", + "placeholder": "添加更详细的描述..." + }, + "subTasks": { + "title": "子任务", + "addSubTask": "添加子任务", + "addSubTaskInputPlaceholder": "输入您的任务并按回车", + "refreshSubTasks": "刷新子任务", + "edit": "编辑", + "delete": "删除", + "confirmDeleteSubTask": "您确定要删除此子任务吗?", + "deleteSubTask": "删除子任务" + }, + "dependencies": { + "title": "依赖关系", + "addDependency": "+ 添加新依赖", + "blockedBy": "被阻止", + "searchTask": "输入搜索任务", + "noTasksFound": "未找到任务", + "confirmDeleteDependency": "您确定要删除吗?" + }, + "attachments": { + "title": "附件", + "chooseOrDropFileToUpload": "选择或拖放文件上传", + "uploading": "上传中..." + }, + "comments": { + "title": "评论", + "addComment": "+ 添加新评论", + "noComments": "还没有评论。成为第一个评论的人!", + "delete": "删除", + "confirmDeleteComment": "您确定要删除此评论吗?", + "addCommentPlaceholder": "添加评论...", + "cancel": "取消", + "commentButton": "评论", + "attachFiles": "附加文件", + "addMoreFiles": "添加更多文件", + "selectedFiles": "已选择的文件(最多25MB,最大{count}个)", + "maxFilesError": "您最多只能上传{count}个文件", + "processFilesError": "处理文件失败", + "addCommentError": "请添加评论或附加文件", + "createdBy": "{{time}}由{{user}}创建", + "updatedTime": "更新于{{time}}" + }, + "searchInputPlaceholder": "按名称搜索", + "pendingInvitation": "待处理邀请" + }, + "taskTimeLogTab": { + "title": "时间日志", + "addTimeLog": "添加新时间日志", + "totalLogged": "总记录时间", + "exportToExcel": "导出到Excel", + "noTimeLogsFound": "未找到时间日志", + "timeLogForm": { + "date": "日期", + "startTime": "开始时间", + "endTime": "结束时间", + "workDescription": "工作描述", + "descriptionPlaceholder": "添加描述", + "logTime": "记录时间", + "updateTime": "更新时间", + "cancel": "取消", + "selectDateError": "请选择日期", + "selectStartTimeError": "请选择开始时间", + "selectEndTimeError": "请选择结束时间", + "endTimeAfterStartError": "结束时间必须在开始时间之后" + } + }, + "taskActivityLogTab": { + "title": "活动日志", + "add": "添加", + "remove": "移除", + "none": "无", + "weight": "权重", + "createdTask": "创建了任务。" + }, + "taskProgress": { + "markAsDoneTitle": "将任务标记为完成?", + "confirmMarkAsDone": "是的,标记为完成", + "cancelMarkAsDone": "不,保持当前状态", + "markAsDoneDescription": "您已将进度设置为100%。您想将任务状态更新为\"完成\"吗?" + } +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/task-list-filters.json b/worklenz-backend/src/public/locales/zh/task-list-filters.json new file mode 100644 index 00000000..84387509 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/task-list-filters.json @@ -0,0 +1,79 @@ +{ + "searchButton": "搜索", + "resetButton": "重置", + "searchInputPlaceholder": "按名称搜索", + "sortText": "排序", + "statusText": "状态", + "phaseText": "阶段", + "memberText": "成员", + "assigneesText": "受托人", + "priorityText": "优先级", + "labelsText": "标签", + "membersText": "成员", + "groupByText": "分组依据", + "showArchivedText": "显示已归档的任务", + "showFieldsText": "显示字段", + "keyText": "ID", + "taskText": "任务", + "descriptionText": "描述", + "phasesText": "阶段", + "listText": "列表", + "progressText": "进度", + "timeTrackingText": "时间跟踪", + "timetrackingText": "时间跟踪", + "estimationText": "估计", + "startDateText": "开始日期", + "startdateText": "开始日期", + "endDateText": "结束日期", + "dueDateText": "截止日期", + "duedateText": "截止日期", + "completedDateText": "完成日期", + "completeddateText": "完成日期", + "createdDateText": "创建日期", + "createddateText": "创建日期", + "lastUpdatedText": "最后更新", + "lastupdatedText": "最后更新", + "reporterText": "报告人", + "dueTimeText": "截止时间", + "duetimeText": "截止时间", + "lowText": "低", + "mediumText": "中", + "highText": "高", + "createStatusButtonTooltip": "状态设置", + "configPhaseButtonTooltip": "阶段设置", + "noLabelsFound": "未找到标签", + "addStatusButton": "添加状态", + "addPhaseButton": "添加阶段", + "createStatus": "创建状态", + "name": "名称", + "category": "类别", + "selectCategory": "选择类别", + "pleaseEnterAName": "请输入名称", + "pleaseSelectACategory": "请选择类别", + "create": "创建", + "searchTasks": "搜索任务...", + "searchPlaceholder": "搜索...", + "fieldsText": "字段", + "loadingFilters": "加载筛选器...", + "noOptionsFound": "未找到选项", + "filtersActive": "个筛选器已激活", + "filterActive": "个筛选器已激活", + "clearAll": "清除全部", + "clearing": "清除中...", + "cancel": "取消", + "search": "搜索", + "groupedBy": "分组依据", + "manageStatuses": "管理状态", + "managePhases": "管理阶段", + "dragToReorderStatuses": "拖拽状态以重新排序。每个状态可以有不同的类别。", + "enterNewStatusName": "输入新状态名称...", + "addStatus": "添加状态", + "noStatusesFound": "未找到状态。请在上面创建您的第一个状态。", + "deleteStatus": "删除状态", + "deleteStatusConfirm": "您确定要删除此状态吗?此操作无法撤销。", + "rename": "重命名", + "delete": "删除", + "enterStatusName": "输入状态名称", + "selectCategory": "选择类别", + "close": "关闭" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/task-list-table.json b/worklenz-backend/src/public/locales/zh/task-list-table.json new file mode 100644 index 00000000..f3ec040f --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/task-list-table.json @@ -0,0 +1,129 @@ +{ + "keyColumn": "ID", + "taskColumn": "任务", + "descriptionColumn": "描述", + "progressColumn": "进度", + "membersColumn": "成员", + "assigneesColumn": "受托人", + "labelsColumn": "标签", + "phasesColumn": "阶段", + "phaseColumn": "阶段", + "statusColumn": "状态", + "priorityColumn": "优先级", + "timeTrackingColumn": "时间追踪", + "timetrackingColumn": "时间追踪", + "estimationColumn": "估算", + "startDateColumn": "开始日期", + "startdateColumn": "开始日期", + "dueDateColumn": "截止日期", + "duedateColumn": "截止日期", + "completedDateColumn": "完成日期", + "completeddateColumn": "完成日期", + "createdDateColumn": "创建日期", + "createddateColumn": "创建日期", + "lastUpdatedColumn": "最后更新", + "lastupdatedColumn": "最后更新", + "reporterColumn": "报告人", + "dueTimeColumn": "截止时间", + "todoSelectorText": "待办", + "doingSelectorText": "进行中", + "doneSelectorText": "已完成", + "lowSelectorText": "低", + "mediumSelectorText": "中", + "highSelectorText": "高", + "selectText": "选择", + "labelsSelectorInputTip": "按回车键创建!", + "addTaskText": "+ 添加任务", + "addSubTaskText": "+ 添加子任务", + "addTaskInputPlaceholder": "输入任务并按回车键", + "noTasksInGroup": "此组中没有任务", + "openButton": "打开", + "okButton": "确定", + "noLabelsFound": "未找到标签", + "searchInputPlaceholder": "搜索或创建", + "assigneeSelectorInviteButton": "通过电子邮件邀请新成员", + "labelInputPlaceholder": "搜索或创建", + "searchLabelsPlaceholder": "搜索标签...", + "createLabelButton": "创建 \"{{name}}\"", + "manageLabelsPath": "设置 → 标签", + "pendingInvitation": "待处理邀请", + "contextMenu": { + "assignToMe": "分配给我", + "moveTo": "移动到", + "unarchive": "取消归档", + "archive": "归档", + "convertToSubTask": "转换为子任务", + "convertToTask": "转换为任务", + "delete": "删除", + "searchByNameInputPlaceholder": "按名称搜索" + }, + "setDueDate": "设置截止日期", + "setStartDate": "设置开始日期", + "clearDueDate": "清除截止日期", + "clearStartDate": "清除开始日期", + "dueDatePlaceholder": "截止日期", + "startDatePlaceholder": "开始日期", + + "emptyStates": { + "noTaskGroups": "未找到任务组", + "noTaskGroupsDescription": "创建任务或应用筛选器后,任务将显示在此处。", + "errorPrefix": "错误:", + "dragTaskFallback": "任务" + }, + + "customColumns": { + "addCustomColumn": "添加自定义列", + "customColumnHeader": "自定义列", + "customColumnSettings": "自定义列设置", + "noCustomValue": "无值", + "peopleField": "人员字段", + "noDate": "无日期", + "unsupportedField": "不支持的字段类型", + + "modal": { + "addFieldTitle": "添加字段", + "editFieldTitle": "编辑字段", + "fieldTitle": "字段标题", + "fieldTitleRequired": "字段标题为必填项", + "columnTitlePlaceholder": "列标题", + "type": "类型", + "deleteConfirmTitle": "确定要删除此自定义列吗?", + "deleteConfirmDescription": "此操作无法撤销。与此列关联的所有数据将被永久删除。", + "deleteButton": "删除", + "cancelButton": "取消", + "createButton": "创建", + "updateButton": "更新", + "createSuccessMessage": "自定义列创建成功", + "updateSuccessMessage": "自定义列更新成功", + "deleteSuccessMessage": "自定义列删除成功", + "deleteErrorMessage": "删除自定义列失败", + "createErrorMessage": "创建自定义列失败", + "updateErrorMessage": "更新自定义列失败" + }, + + "fieldTypes": { + "people": "人员", + "number": "数字", + "date": "日期", + "selection": "选择", + "checkbox": "复选框", + "labels": "标签", + "key": "键", + "formula": "公式" + } + }, + + "indicators": { + "tooltips": { + "subtasks": "{{count}} 个子任务", + "subtasks_plural": "{{count}} 个子任务", + "comments": "{{count}} 条评论", + "comments_plural": "{{count}} 条评论", + "attachments": "{{count}} 个附件", + "attachments_plural": "{{count}} 个附件", + "subscribers": "任务有订阅者", + "dependencies": "任务有依赖项", + "recurring": "重复任务" + } + } +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/task-management.json b/worklenz-backend/src/public/locales/zh/task-management.json new file mode 100644 index 00000000..341ecc64 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/task-management.json @@ -0,0 +1,35 @@ +{ + "noTasksInGroup": "此组中没有任务", + "noTasksInGroupDescription": "添加任务开始使用", + "addFirstTask": "添加你的第一个任务", + "openTask": "打开", + "subtask": "子任务", + "subtasks": "子任务", + "comment": "评论", + "comments": "评论", + "attachment": "附件", + "attachments": "附件", + "enterSubtaskName": "输入子任务名称...", + "add": "添加", + "cancel": "取消", + "renameGroup": "重命名组", + "renameStatus": "重命名状态", + "renamePhase": "重命名阶段", + "changeCategory": "更改类别", + "clickToEditGroupName": "点击编辑组名称", + "enterGroupName": "输入组名称", + + "indicators": { + "tooltips": { + "subtasks": "{{count}} 个子任务", + "subtasks_plural": "{{count}} 个子任务", + "comments": "{{count}} 条评论", + "comments_plural": "{{count}} 条评论", + "attachments": "{{count}} 个附件", + "attachments_plural": "{{count}} 个附件", + "subscribers": "任务有订阅者", + "dependencies": "任务有依赖项", + "recurring": "重复任务" + } + } +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/task-template-drawer.json b/worklenz-backend/src/public/locales/zh/task-template-drawer.json new file mode 100644 index 00000000..53e99119 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/task-template-drawer.json @@ -0,0 +1,11 @@ +{ + "createTaskTemplate": "创建任务模板", + "editTaskTemplate": "编辑任务模板", + "cancelText": "取消", + "saveText": "保存", + "templateNameText": "模板名称", + "selectedTasks": "已选任务", + "removeTask": "移除", + "cancelButton": "取消", + "saveButton": "保存" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/tasks/task-table-bulk-actions.json b/worklenz-backend/src/public/locales/zh/tasks/task-table-bulk-actions.json new file mode 100644 index 00000000..2a4c89d6 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/tasks/task-table-bulk-actions.json @@ -0,0 +1,24 @@ +{ + "taskSelected": "任务已选择", + "tasksSelected": "任务已选择", + "changeStatus": "更改状态/优先级/阶段", + "changeLabel": "更改标签", + "assignToMe": "分配给我", + "changeAssignees": "更改受托人", + "archive": "归档", + "unarchive": "取消归档", + "delete": "删除", + "moreOptions": "更多选项", + "deselectAll": "取消全选", + "status": "状态", + "priority": "优先级", + "phase": "阶段", + "member": "成员", + "createTaskTemplate": "创建任务模板", + "apply": "应用", + "createLabel": "+ 创建标签", + "hitEnterToCreate": "按回车键创建", + "pendingInvitation": "待处理邀请", + "noMatchingLabels": "没有匹配的标签", + "noLabels": "没有标签" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/template-drawer.json b/worklenz-backend/src/public/locales/zh/template-drawer.json new file mode 100644 index 00000000..64fd242f --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/template-drawer.json @@ -0,0 +1,19 @@ +{ + "title": "编辑任务模板", + "cancelText": "取消", + "saveText": "保存", + "templateNameText": "模板名称", + "selectedTasks": "已选任务", + "removeTask": "移除", + "description": "描述", + "phase": "阶段", + "statuses": "状态", + "priorities": "优先级", + "labels": "标签", + "tasks": "任务", + "noTemplateSelected": "未选择模板", + "noDescription": "无描述", + "worklenzTemplates": "Worklenz模板", + "yourTemplatesLibrary": "您的模板库", + "searchTemplates": "搜索模板" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/templateDrawer.json b/worklenz-backend/src/public/locales/zh/templateDrawer.json new file mode 100644 index 00000000..8405f8ab --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/templateDrawer.json @@ -0,0 +1,23 @@ +{ + "bugTracking": "错误跟踪", + "construction": "建筑与施工", + "designCreative": "设计与创意", + "education": "教育", + "finance": "金融", + "hrRecruiting": "人力资源与招聘", + "informationTechnology": "信息技术", + "legal": "法律", + "manufacturing": "制造业", + "marketing": "市场营销", + "nonprofit": "非营利", + "personalUse": "个人使用", + "salesCRM": "销售与客户关系管理", + "serviceConsulting": "服务与咨询", + "softwareDevelopment": "软件开发", + "description": "描述", + "phase": "阶段", + "statuses": "状态", + "priorities": "优先级", + "labels": "标签", + "tasks": "任务" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/time-report.json b/worklenz-backend/src/public/locales/zh/time-report.json new file mode 100644 index 00000000..c376954a --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/time-report.json @@ -0,0 +1,33 @@ +{ + "includeArchivedProjects": "包含已归档项目", + "export": "导出", + "timeSheet": "时间表", + "searchByName": "按名称搜索", + "selectAll": "全选", + "teams": "团队", + "searchByProject": "按项目名称搜索", + "projects": "项目", + "searchByCategory": "按类别名称搜索", + "categories": "类别", + "billable": "可计费", + "nonBillable": "不可计费", + "total": "总计", + "projectsTimeSheet": "项目时间表", + "loggedTime": "已记录时间(小时)", + "exportToExcel": "导出到Excel", + "logged": "已记录", + "for": "为", + "membersTimeSheet": "成员时间表", + "member": "成员", + "estimatedVsActual": "预计用时 vs 实际用时", + "workingDays": "工作日", + "manDays": "人天", + "days": "天", + "estimatedDays": "预计天数", + "actualDays": "实际天数", + "noCategories": "未找到类别", + "noCategory": "无类别", + "noProjects": "未找到项目", + "noTeams": "未找到团队", + "noData": "未找到数据" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/locales/zh/unauthorized.json b/worklenz-backend/src/public/locales/zh/unauthorized.json new file mode 100644 index 00000000..985b1d08 --- /dev/null +++ b/worklenz-backend/src/public/locales/zh/unauthorized.json @@ -0,0 +1,5 @@ +{ + "title": "未授权!", + "subtitle": "您无权访问此页面", + "button": "返回首页" +} \ No newline at end of file diff --git a/worklenz-backend/src/public/tinymce/package-lock.json b/worklenz-backend/src/public/tinymce/package-lock.json new file mode 100644 index 00000000..686dcc86 --- /dev/null +++ b/worklenz-backend/src/public/tinymce/package-lock.json @@ -0,0 +1,20 @@ +{ + "name": "tinymce", + "version": "6.8.4", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tinymce", + "version": "6.8.4", + "license": "MIT", + "dependencies": { + "tinymce": "file:" + } + }, + "node_modules/tinymce": { + "resolved": "", + "link": true + } + } +} diff --git a/worklenz-backend/src/public/tinymce/package.json b/worklenz-backend/src/public/tinymce/package.json index 151b0166..39711f51 100644 --- a/worklenz-backend/src/public/tinymce/package.json +++ b/worklenz-backend/src/public/tinymce/package.json @@ -28,5 +28,8 @@ "homepage": "https://www.tiny.cloud/", "bugs": { "url": "https://github.com/tinymce/tinymce/issues" + }, + "dependencies": { + "tinymce": "file:" } -} \ No newline at end of file +} diff --git a/worklenz-backend/src/routes/apis/projects-api-router.ts b/worklenz-backend/src/routes/apis/projects-api-router.ts index 6718b370..56fd2654 100644 --- a/worklenz-backend/src/routes/apis/projects-api-router.ts +++ b/worklenz-backend/src/routes/apis/projects-api-router.ts @@ -18,6 +18,7 @@ projectsApiRouter.get("/update-exist-sort-order", safeControllerFunction(Project projectsApiRouter.post("/", teamOwnerOrAdminValidator, projectsBodyValidator, safeControllerFunction(ProjectsController.create)); projectsApiRouter.get("/", safeControllerFunction(ProjectsController.get)); +projectsApiRouter.get("/grouped", safeControllerFunction(ProjectsController.getGrouped)); projectsApiRouter.get("/my-task-projects", safeControllerFunction(ProjectsController.getMyProjectsToTasks)); projectsApiRouter.get("/my-projects", safeControllerFunction(ProjectsController.getMyProjects)); projectsApiRouter.get("/all", safeControllerFunction(ProjectsController.getAllProjects)); diff --git a/worklenz-backend/src/routes/apis/statuses-api-router.ts b/worklenz-backend/src/routes/apis/statuses-api-router.ts index f9f6f560..07b00f26 100644 --- a/worklenz-backend/src/routes/apis/statuses-api-router.ts +++ b/worklenz-backend/src/routes/apis/statuses-api-router.ts @@ -18,6 +18,7 @@ statusesApiRouter.put("/order", statusOrderValidator, safeControllerFunction(Tas statusesApiRouter.get("/categories", safeControllerFunction(TaskStatusesController.getCategories)); statusesApiRouter.get("/:id", idParamValidator, safeControllerFunction(TaskStatusesController.getById)); statusesApiRouter.put("/name/:id", projectManagerValidator, idParamValidator, taskStatusBodyValidator, safeControllerFunction(TaskStatusesController.updateName)); +statusesApiRouter.put("/category/:id", projectManagerValidator, idParamValidator, safeControllerFunction(TaskStatusesController.updateCategory)); statusesApiRouter.put("/:id", projectManagerValidator, idParamValidator, taskStatusBodyValidator, safeControllerFunction(TaskStatusesController.update)); statusesApiRouter.delete("/:id", projectManagerValidator, idParamValidator, statusDeleteValidator, safeControllerFunction(TaskStatusesController.deleteById)); diff --git a/worklenz-backend/src/routes/apis/tasks-api-router.ts b/worklenz-backend/src/routes/apis/tasks-api-router.ts index bb6af547..6a192abe 100644 --- a/worklenz-backend/src/routes/apis/tasks-api-router.ts +++ b/worklenz-backend/src/routes/apis/tasks-api-router.ts @@ -42,6 +42,9 @@ tasksApiRouter.get("/list/columns/:id", idParamValidator, safeControllerFunction tasksApiRouter.put("/list/columns/:id", idParamValidator, safeControllerFunction(TaskListColumnsController.toggleColumn)); tasksApiRouter.get("/list/v2/:id", idParamValidator, safeControllerFunction(getList)); +tasksApiRouter.get("/list/v3/:id", idParamValidator, safeControllerFunction(TasksControllerV2.getTasksV3)); +tasksApiRouter.post("/refresh-progress/:id", idParamValidator, safeControllerFunction(TasksControllerV2.refreshTaskProgress)); +tasksApiRouter.get("/progress-status/:id", idParamValidator, safeControllerFunction(TasksControllerV2.getTaskProgressStatus)); tasksApiRouter.get("/assignees/:id", idParamValidator, safeControllerFunction(TasksController.getProjectTaskAssignees)); tasksApiRouter.put("/bulk/status", mapTasksToBulkUpdate, bulkTasksStatusValidator, safeControllerFunction(TasksController.bulkChangeStatus)); diff --git a/worklenz-backend/src/services/activity-logs/activity-logs.service.ts b/worklenz-backend/src/services/activity-logs/activity-logs.service.ts index 85d20977..cf049f7e 100644 --- a/worklenz-backend/src/services/activity-logs/activity-logs.service.ts +++ b/worklenz-backend/src/services/activity-logs/activity-logs.service.ts @@ -204,3 +204,29 @@ export async function logPhaseChange(activityLog: IActivityLog) { insertToActivityLogs(activityLog); } } + +export async function logProgressChange(activityLog: IActivityLog) { + const { task_id, new_value, old_value } = activityLog; + if (!task_id || !activityLog.socket) return; + + if (old_value !== new_value) { + activityLog.user_id = getLoggedInUserIdFromSocket(activityLog.socket); + activityLog.attribute_type = IActivityLogAttributeTypes.PROGRESS; + activityLog.log_type = IActivityLogChangeType.UPDATE; + + insertToActivityLogs(activityLog); + } +} + +export async function logWeightChange(activityLog: IActivityLog) { + const { task_id, new_value, old_value } = activityLog; + if (!task_id || !activityLog.socket) return; + + if (old_value !== new_value) { + activityLog.user_id = getLoggedInUserIdFromSocket(activityLog.socket); + activityLog.attribute_type = IActivityLogAttributeTypes.WEIGHT; + activityLog.log_type = IActivityLogChangeType.UPDATE; + + insertToActivityLogs(activityLog); + } +} diff --git a/worklenz-backend/src/services/activity-logs/interfaces.ts b/worklenz-backend/src/services/activity-logs/interfaces.ts index c0c43143..bab97e11 100644 --- a/worklenz-backend/src/services/activity-logs/interfaces.ts +++ b/worklenz-backend/src/services/activity-logs/interfaces.ts @@ -29,6 +29,8 @@ export enum IActivityLogAttributeTypes { COMMENT = "comment", ARCHIVE = "archive", PHASE = "phase", + PROGRESS = "progress", + WEIGHT = "weight", } export enum IActivityLogChangeType { diff --git a/worklenz-backend/src/shared/constants.ts b/worklenz-backend/src/shared/constants.ts index 28fc60fc..c814c603 100644 --- a/worklenz-backend/src/shared/constants.ts +++ b/worklenz-backend/src/shared/constants.ts @@ -117,11 +117,11 @@ export const TASK_DUE_NO_DUE_COLOR = "#a9a9a9"; export const DEFAULT_PAGE_SIZE = 20; // S3 Credentials -export const REGION = process.env.AWS_REGION || "us-east-1"; -export const BUCKET = process.env.AWS_BUCKET || "your-bucket-name"; +export const REGION = process.env.S3_REGION || "us-east-1"; +export const BUCKET = process.env.S3_BUCKET || "your-bucket-name"; export const S3_URL = process.env.S3_URL || "https://your-s3-url"; -export const S3_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID || ""; -export const S3_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY || ""; +export const S3_ACCESS_KEY_ID = process.env.S3_ACCESS_KEY_ID || ""; +export const S3_SECRET_ACCESS_KEY = process.env.S3_SECRET_ACCESS_KEY || ""; // Azure Blob Storage Credentials export const STORAGE_PROVIDER = process.env.STORAGE_PROVIDER || "s3"; diff --git a/worklenz-backend/src/shared/email.ts b/worklenz-backend/src/shared/email.ts index 83e93651..e0a0f679 100644 --- a/worklenz-backend/src/shared/email.ts +++ b/worklenz-backend/src/shared/email.ts @@ -1,7 +1,7 @@ import {SendEmailCommand, SESClient} from "@aws-sdk/client-ses"; import {Validator} from "jsonschema"; import {QueryResult} from "pg"; -import {log_error} from "./utils"; +import {log_error, isValidateEmail} from "./utils"; import emailRequestSchema from "../json_schemas/email-request-schema"; import db from "../config/db"; @@ -33,7 +33,7 @@ function isValidMailBody(body: IEmail) { async function removeMails(query: string, emails: string[]) { const result: QueryResult<{ email: string; }> = await db.query(query, []); const bouncedEmails = result.rows.map(e => e.email); - for (let i = 0; i < emails.length; i++) { + for (let i = emails.length - 1; i >= 0; i--) { const email = emails[i]; if (bouncedEmails.includes(email)) { emails.splice(i, 1); @@ -54,11 +54,20 @@ export async function sendEmail(email: IEmail): Promise { const options = {...email} as IEmail; options.to = Array.isArray(options.to) ? Array.from(new Set(options.to)) : []; + // Filter out empty, null, undefined, and invalid emails + options.to = options.to + .filter(email => email && typeof email === 'string' && email.trim().length > 0) + .map(email => email.trim()) + .filter(email => isValidateEmail(email)); + if (options.to.length) { await filterBouncedEmails(options.to); await filterSpamEmails(options.to); } + // Double-check that we still have valid emails after filtering + if (!options.to.length) return null; + if (!isValidMailBody(options)) return null; const charset = "UTF-8"; diff --git a/worklenz-backend/src/socket.io/commands/on-get-done-statuses.ts b/worklenz-backend/src/socket.io/commands/on-get-done-statuses.ts new file mode 100644 index 00000000..4783f4f5 --- /dev/null +++ b/worklenz-backend/src/socket.io/commands/on-get-done-statuses.ts @@ -0,0 +1,49 @@ +import { Socket } from "socket.io"; +import db from "../../config/db"; +import { log_error } from "../util"; + +// Define a type for the callback function +type DoneStatusesCallback = (statuses: Array<{ + id: string; + name: string; + sort_order: number; + color_code: string; +}>) => void; + +/** + * Socket handler to get task statuses in the "done" category for a project + * Used when prompting users to mark a task as done when progress reaches 100% + */ +export async function on_get_done_statuses( + io: any, + socket: Socket, + projectId: string, + callback: DoneStatusesCallback +) { + try { + if (!projectId) { + return callback([]); + } + + // Query to get all statuses in the "done" category for the project + const result = await db.query(` + SELECT ts.id, ts.name, ts.sort_order, stsc.color_code + FROM task_statuses ts + INNER JOIN sys_task_status_categories stsc ON ts.category_id = stsc.id + WHERE ts.project_id = $1 + AND stsc.is_done = TRUE + ORDER BY ts.sort_order ASC + `, [projectId]); + + const doneStatuses = result.rows; + + console.log(`Found ${doneStatuses.length} "done" statuses for project ${projectId}`); + + // Use callback to return the result + callback(doneStatuses); + + } catch (error) { + log_error(`Error getting "done" statuses for project ${projectId}: ${error}`); + callback([]); + } +} \ No newline at end of file diff --git a/worklenz-backend/src/socket.io/commands/on-get-task-progress.ts b/worklenz-backend/src/socket.io/commands/on-get-task-progress.ts index 586bfc9f..2471a149 100644 --- a/worklenz-backend/src/socket.io/commands/on-get-task-progress.ts +++ b/worklenz-backend/src/socket.io/commands/on-get-task-progress.ts @@ -5,6 +5,8 @@ import TasksControllerV2 from "../../controllers/tasks-controller-v2"; export async function on_get_task_progress(_io: Server, socket: Socket, taskId?: string) { try { + console.log(`GET_TASK_PROGRESS requested for task: ${taskId}`); + const task: any = {}; task.id = taskId; @@ -13,6 +15,8 @@ export async function on_get_task_progress(_io: Server, socket: Socket, taskId?: task.complete_ratio = info.ratio; task.completed_count = info.total_completed; task.total_tasks_count = info.total_tasks; + + console.log(`Sending task progress for task ${taskId}: complete_ratio=${task.complete_ratio}`); } return socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), task); diff --git a/worklenz-backend/src/socket.io/commands/on-get-task-subtasks-count.ts b/worklenz-backend/src/socket.io/commands/on-get-task-subtasks-count.ts new file mode 100644 index 00000000..c0c14cfe --- /dev/null +++ b/worklenz-backend/src/socket.io/commands/on-get-task-subtasks-count.ts @@ -0,0 +1,89 @@ +import { Socket } from "socket.io"; +import db from "../../config/db"; +import { SocketEvents } from "../events"; +import { log_error } from "../util"; + +/** + * Socket handler to retrieve the number of subtasks for a given task + * Used to validate on the client side whether a task should show progress inputs + */ +export async function on_get_task_subtasks_count(io: any, socket: Socket, taskId: string) { + try { + if (!taskId) { + return; + } + + // Get the count of subtasks for this task + const result = await db.query( + "SELECT COUNT(*) as subtask_count FROM tasks WHERE parent_task_id = $1 AND archived IS FALSE", + [taskId] + ); + + const subtaskCount = parseInt(result.rows[0]?.subtask_count || "0"); + + // Emit the subtask count back to the client + socket.emit( + "TASK_SUBTASKS_COUNT", + { + task_id: taskId, + subtask_count: subtaskCount, + has_subtasks: subtaskCount > 0 + } + ); + + console.log(`Emitted subtask count for task ${taskId}: ${subtaskCount}`); + + // If there are subtasks, also get their progress information + if (subtaskCount > 0) { + // Get all subtasks for this parent task with their progress information + const subtasksResult = await db.query(` + SELECT + t.id, + t.progress_value, + t.manual_progress, + t.weight, + CASE + WHEN t.manual_progress = TRUE THEN t.progress_value + ELSE COALESCE( + (SELECT (CASE WHEN tl.total_minutes > 0 THEN + (tl.total_minutes_spent / tl.total_minutes * 100) + ELSE 0 END) + FROM ( + SELECT + t2.id, + t2.total_minutes, + COALESCE(SUM(twl.time_spent), 0) as total_minutes_spent + FROM tasks t2 + LEFT JOIN task_work_log twl ON t2.id = twl.task_id + WHERE t2.id = t.id + GROUP BY t2.id, t2.total_minutes + ) tl + ), 0) + END as calculated_progress + FROM tasks t + WHERE t.parent_task_id = $1 AND t.archived IS FALSE + `, [taskId]); + + // Emit progress updates for each subtask + for (const subtask of subtasksResult.rows) { + const progressValue = subtask.manual_progress ? + subtask.progress_value : + Math.floor(subtask.calculated_progress); + + socket.emit( + SocketEvents.TASK_PROGRESS_UPDATED.toString(), + { + task_id: subtask.id, + progress_value: progressValue, + weight: subtask.weight + } + ); + } + + console.log(`Emitted progress updates for ${subtasksResult.rows.length} subtasks of task ${taskId}`); + } + + } catch (error) { + log_error(`Error getting subtask count for task ${taskId}: ${error}`); + } +} \ No newline at end of file diff --git a/worklenz-backend/src/socket.io/commands/on-project-subscriber-change.ts b/worklenz-backend/src/socket.io/commands/on-project-subscriber-change.ts index 6057e88f..bbe90425 100644 --- a/worklenz-backend/src/socket.io/commands/on-project-subscriber-change.ts +++ b/worklenz-backend/src/socket.io/commands/on-project-subscriber-change.ts @@ -19,7 +19,8 @@ export async function on_project_subscriber_change(_io: Server, socket: Socket, const isSubscribe = data.mode == 0; const q = isSubscribe ? `INSERT INTO project_subscribers (user_id, project_id, team_member_id) - VALUES ($1, $2, $3);` + VALUES ($1, $2, $3) + ON CONFLICT (user_id, project_id, team_member_id) DO NOTHING;` : `DELETE FROM project_subscribers WHERE user_id = $1 @@ -27,7 +28,7 @@ export async function on_project_subscriber_change(_io: Server, socket: Socket, AND team_member_id = $3;`; await db.query(q, [data.user_id, data.project_id, data.team_member_id]); - const subscribers = await TasksControllerV2.getTaskSubscribers(data.project_id); + const subscribers = await TasksControllerV2.getProjectSubscribers(data.project_id); socket.emit(SocketEvents.PROJECT_SUBSCRIBERS_CHANGE.toString(), subscribers); return; diff --git a/worklenz-backend/src/socket.io/commands/on-quick-task.ts b/worklenz-backend/src/socket.io/commands/on-quick-task.ts index 859cbf58..066b52d0 100644 --- a/worklenz-backend/src/socket.io/commands/on-quick-task.ts +++ b/worklenz-backend/src/socket.io/commands/on-quick-task.ts @@ -56,6 +56,8 @@ export async function on_quick_task(_io: Server, socket: Socket, data?: string) const q = `SELECT create_quick_task($1) AS task;`; const body = JSON.parse(data as string); + + body.name = (body.name || "").trim(); body.priority_id = body.priority_id?.trim() || null; body.status_id = body.status_id?.trim() || null; @@ -111,10 +113,12 @@ export async function on_quick_task(_io: Server, socket: Socket, data?: string) notifyProjectUpdates(socket, d.task.id); } + } else { + // Empty task name, emit null to indicate no task was created + socket.emit(SocketEvents.QUICK_TASK.toString(), null); } } catch (error) { log_error(error); + socket.emit(SocketEvents.QUICK_TASK.toString(), null); } - - socket.emit(SocketEvents.QUICK_TASK.toString(), null); } diff --git a/worklenz-backend/src/socket.io/commands/on-task-sort-order-change.ts b/worklenz-backend/src/socket.io/commands/on-task-sort-order-change.ts index 13875901..11ec09cd 100644 --- a/worklenz-backend/src/socket.io/commands/on-task-sort-order-change.ts +++ b/worklenz-backend/src/socket.io/commands/on-task-sort-order-change.ts @@ -24,6 +24,14 @@ interface ChangeRequest { priority: string; }; team_id: string; + // New simplified approach + task_updates?: Array<{ + task_id: string; + sort_order: number; + status_id?: string; + priority_id?: string; + phase_id?: string; + }>; } interface Config { @@ -64,38 +72,72 @@ function updateUnmappedStatus(config: Config) { export async function on_task_sort_order_change(_io: Server, socket: Socket, data: ChangeRequest) { try { - const q = `SELECT handle_task_list_sort_order_change($1);`; - - const config: Config = { - from_index: data.from_index, - to_index: data.to_index, - task_id: data.task.id, - from_group: data.from_group, - to_group: data.to_group, - project_id: data.project_id, - group_by: data.group_by, - to_last_index: Boolean(data.to_last_index) - }; - - if ((config.group_by === GroupBy.STATUS) && config.to_group) { - const canContinue = await TasksControllerV2.checkForCompletedDependencies(config.task_id, config?.to_group); - if (!canContinue) { - return socket.emit(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), { - completed_deps: canContinue - }); + // New simplified approach - use bulk updates if provided + if (data.task_updates && data.task_updates.length > 0) { + // Check dependencies for status changes + if (data.group_by === GroupBy.STATUS && data.to_group) { + const canContinue = await TasksControllerV2.checkForCompletedDependencies(data.task.id, data.to_group); + if (!canContinue) { + return socket.emit(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), { + completed_deps: canContinue + }); + } } - notifyStatusChange(socket, config); + // Use the simple bulk update function + const q = `SELECT update_task_sort_orders_bulk($1);`; + await db.query(q, [JSON.stringify(data.task_updates)]); + await emitSortOrderChange(data, socket); + + // Handle notifications and logging + if (data.group_by === GroupBy.STATUS && data.to_group) { + notifyStatusChange(socket, { + task_id: data.task.id, + to_group: data.to_group, + from_group: data.from_group, + from_index: data.from_index, + to_index: data.to_index, + project_id: data.project_id, + group_by: data.group_by, + to_last_index: data.to_last_index + }); + } + } else { + // Fallback to old complex method + const q = `SELECT handle_task_list_sort_order_change($1);`; + + const config: Config = { + from_index: data.from_index, + to_index: data.to_index, + task_id: data.task.id, + from_group: data.from_group, + to_group: data.to_group, + project_id: data.project_id, + group_by: data.group_by, + to_last_index: Boolean(data.to_last_index) + }; + + if ((config.group_by === GroupBy.STATUS) && config.to_group) { + const canContinue = await TasksControllerV2.checkForCompletedDependencies(config.task_id, config?.to_group); + if (!canContinue) { + return socket.emit(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), { + completed_deps: canContinue + }); + } + + notifyStatusChange(socket, config); + } + + if (config.group_by === GroupBy.PHASE) { + updateUnmappedStatus(config); + } + + await db.query(q, [JSON.stringify(config)]); + await emitSortOrderChange(data, socket); } - if (config.group_by === GroupBy.PHASE) { - updateUnmappedStatus(config); - } - - await db.query(q, [JSON.stringify(config)]); - await emitSortOrderChange(data, socket); - - if (config.group_by === GroupBy.STATUS) { + // Common post-processing logic for both approaches + if (data.group_by === GroupBy.STATUS) { const userId = getLoggedInUserIdFromSocket(socket); const isAlreadyAssigned = await TasksControllerV2.checkUserAssignedToTask(data.task.id, userId as string, data.team_id); @@ -104,7 +146,7 @@ export async function on_task_sort_order_change(_io: Server, socket: Socket, dat } } - if (config.group_by === GroupBy.PHASE) { + if (data.group_by === GroupBy.PHASE) { void logPhaseChange({ task_id: data.task.id, socket, @@ -113,7 +155,7 @@ export async function on_task_sort_order_change(_io: Server, socket: Socket, dat }); } - if (config.group_by === GroupBy.STATUS) { + if (data.group_by === GroupBy.STATUS) { void logStatusChange({ task_id: data.task.id, socket, @@ -122,7 +164,7 @@ export async function on_task_sort_order_change(_io: Server, socket: Socket, dat }); } - if (config.group_by === GroupBy.PRIORITY) { + if (data.group_by === GroupBy.PRIORITY) { void logPriorityChange({ task_id: data.task.id, socket, @@ -131,11 +173,11 @@ export async function on_task_sort_order_change(_io: Server, socket: Socket, dat }); } - void notifyProjectUpdates(socket, config.task_id); + void notifyProjectUpdates(socket, data.task.id); return; } catch (error) { log_error(error); } socket.emit(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), []); -} +} \ No newline at end of file diff --git a/worklenz-backend/src/socket.io/commands/on-task-status-change.ts b/worklenz-backend/src/socket.io/commands/on-task-status-change.ts index cce0531c..e59e6b59 100644 --- a/worklenz-backend/src/socket.io/commands/on-task-status-change.ts +++ b/worklenz-backend/src/socket.io/commands/on-task-status-change.ts @@ -4,10 +4,11 @@ import db from "../../config/db"; import {NotificationsService} from "../../services/notifications/notifications.service"; import {TASK_STATUS_COLOR_ALPHA} from "../../shared/constants"; import {SocketEvents} from "../events"; -import {getLoggedInUserIdFromSocket, log_error, notifyProjectUpdates} from "../util"; +import {getLoggedInUserIdFromSocket, log, log_error, notifyProjectUpdates} from "../util"; import TasksControllerV2 from "../../controllers/tasks-controller-v2"; -import {getTaskDetails, logStatusChange} from "../../services/activity-logs/activity-logs.service"; +import {getTaskDetails, logProgressChange, logStatusChange} from "../../services/activity-logs/activity-logs.service"; import { assignMemberIfNot } from "./on-quick-assign-or-remove"; +import logger from "../../utils/logger"; export async function on_task_status_change(_io: Server, socket: Socket, data?: string) { try { @@ -49,6 +50,63 @@ export async function on_task_status_change(_io: Server, socket: Socket, data?: }); } + // Check if the new status is in a "done" category + if (changeResponse.status_category?.is_done) { + // Get current progress value + const progressResult = await db.query(` + SELECT progress_value, manual_progress + FROM tasks + WHERE id = $1 + `, [body.task_id]); + + const currentProgress = progressResult.rows[0]?.progress_value; + const isManualProgress = progressResult.rows[0]?.manual_progress; + + // Only update if not already 100% + if (currentProgress !== 100) { + // Update progress to 100% + await db.query(` + UPDATE tasks + SET progress_value = 100, manual_progress = TRUE + WHERE id = $1 + `, [body.task_id]); + + log(`Task ${body.task_id} moved to done status - progress automatically set to 100%`, null); + + // Log the progress change to activity logs + await logProgressChange({ + task_id: body.task_id, + old_value: currentProgress !== null ? currentProgress.toString() : "0", + new_value: "100", + socket + }); + + // If this is a subtask, update parent task progress + if (body.parent_task) { + setTimeout(() => { + socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), body.parent_task); + }, 100); + } + } + } else { + // Task is moving from "done" to "todo" or "doing" - reset manual_progress to FALSE + // so progress can be recalculated based on subtasks + await db.query(` + UPDATE tasks + SET manual_progress = FALSE + WHERE id = $1 + `, [body.task_id]); + + log(`Task ${body.task_id} moved from done status - manual_progress reset to FALSE`, null); + + // If this is a subtask, update parent task progress + if (body.parent_task) { + setTimeout(() => { + socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), body.parent_task); + }, 100); + } + } + const info = await TasksControllerV2.getTaskCompleteRatio(body.parent_task || body.task_id); socket.emit(SocketEvents.TASK_STATUS_CHANGE.toString(), { diff --git a/worklenz-backend/src/socket.io/commands/on-time-estimation-change.ts b/worklenz-backend/src/socket.io/commands/on-time-estimation-change.ts index d6c5e606..32517845 100644 --- a/worklenz-backend/src/socket.io/commands/on-time-estimation-change.ts +++ b/worklenz-backend/src/socket.io/commands/on-time-estimation-change.ts @@ -6,10 +6,76 @@ import { SocketEvents } from "../events"; import { log_error, notifyProjectUpdates } from "../util"; import { getTaskDetails, logTotalMinutes } from "../../services/activity-logs/activity-logs.service"; -export async function on_time_estimation_change(_io: Server, socket: Socket, data?: string) { +/** + * Recursively updates all ancestor tasks' progress when a subtask changes + * @param io Socket.io instance + * @param socket Socket instance for emitting events + * @param projectId Project ID for room broadcasting + * @param taskId The task ID to update (starts with the parent task) + */ +async function updateTaskAncestors(io: any, socket: Socket, projectId: string, taskId: string | null) { + if (!taskId) return; + + try { + // Get the current task's progress ratio + const progressRatio = await db.query( + "SELECT get_task_complete_ratio($1) as ratio", + [taskId] + ); + + const ratio = progressRatio?.rows[0]?.ratio?.ratio || 0; + console.log(`Updated task ${taskId} progress after time estimation change: ${ratio}`); + + // Check if this task needs a "done" status prompt + let shouldPromptForDone = false; + + if (ratio >= 100) { + // Get the task's current status + const taskStatusResult = await db.query(` + SELECT ts.id, stsc.is_done + FROM tasks t + JOIN task_statuses ts ON t.status_id = ts.id + JOIN sys_task_status_categories stsc ON ts.category_id = stsc.id + WHERE t.id = $1 + `, [taskId]); + + // If the task isn't already in a "done" category, we should prompt the user + if (taskStatusResult.rows.length > 0 && !taskStatusResult.rows[0].is_done) { + shouldPromptForDone = true; + } + } + + // Emit the updated progress + socket.emit( + SocketEvents.TASK_PROGRESS_UPDATED.toString(), + { + task_id: taskId, + progress_value: ratio, + should_prompt_for_done: shouldPromptForDone + } + ); + + // Find this task's parent to continue the recursive update + const parentResult = await db.query( + "SELECT parent_task_id FROM tasks WHERE id = $1", + [taskId] + ); + + const parentTaskId = parentResult.rows[0]?.parent_task_id; + + // If there's a parent, recursively update it + if (parentTaskId) { + await updateTaskAncestors(io, socket, projectId, parentTaskId); + } + } catch (error) { + log_error(`Error updating ancestor task ${taskId}: ${error}`); + } +} + +export async function on_time_estimation_change(io: Server, socket: Socket, data?: string) { try { // (SELECT SUM(time_spent) FROM task_work_log WHERE task_id = t.id) AS total_minutes_spent, - const q = `UPDATE tasks SET total_minutes = $2 WHERE id = $1 RETURNING total_minutes;`; + const q = `UPDATE tasks SET total_minutes = $2 WHERE id = $1 RETURNING total_minutes, project_id, parent_task_id;`; const body = JSON.parse(data as string); const hours = body.total_hours || 0; @@ -19,7 +85,10 @@ export async function on_time_estimation_change(_io: Server, socket: Socket, dat const task_data = await getTaskDetails(body.task_id, "total_minutes"); const result0 = await db.query(q, [body.task_id, totalMinutes]); - const [data0] = result0.rows; + const [taskData] = result0.rows; + + const projectId = taskData.project_id; + const parentTaskId = taskData.parent_task_id; const result = await db.query("SELECT SUM(time_spent) AS total_minutes_spent FROM task_work_log WHERE task_id = $1;", [body.task_id]); const [dd] = result.rows; @@ -31,6 +100,22 @@ export async function on_time_estimation_change(_io: Server, socket: Socket, dat total_minutes_spent: dd.total_minutes_spent || 0 }; socket.emit(SocketEvents.TASK_TIME_ESTIMATION_CHANGE.toString(), TasksController.updateTaskViewModel(d)); + + // If this is a subtask in time-based mode, update parent task progress + if (parentTaskId) { + const projectSettingsResult = await db.query( + "SELECT use_time_progress FROM projects WHERE id = $1", + [projectId] + ); + + const useTimeProgress = projectSettingsResult.rows[0]?.use_time_progress; + + if (useTimeProgress) { + // Recalculate parent task progress when subtask time estimation changes + await updateTaskAncestors(io, socket, projectId, parentTaskId); + } + } + notifyProjectUpdates(socket, d.id); logTotalMinutes({ diff --git a/worklenz-backend/src/socket.io/commands/on-update-task-progress.ts b/worklenz-backend/src/socket.io/commands/on-update-task-progress.ts new file mode 100644 index 00000000..239b7ff8 --- /dev/null +++ b/worklenz-backend/src/socket.io/commands/on-update-task-progress.ts @@ -0,0 +1,177 @@ +import { Socket } from "socket.io"; +import db from "../../config/db"; +import { SocketEvents } from "../events"; +import { log, log_error, notifyProjectUpdates } from "../util"; +import { logProgressChange } from "../../services/activity-logs/activity-logs.service"; +import TasksControllerV2 from "../../controllers/tasks-controller-v2"; + +interface UpdateTaskProgressData { + task_id: string; + progress_value: number; + parent_task_id: string | null; +} + +/** + * Recursively updates all ancestor tasks' progress when a subtask changes + * @param io Socket.io instance + * @param socket Socket instance for emitting events + * @param projectId Project ID for room broadcasting + * @param taskId The task ID to update (starts with the parent task) + */ +async function updateTaskAncestors(io: any, socket: Socket, projectId: string, taskId: string | null) { + if (!taskId) return; + + try { + // Use the new controller method to update the task progress + await TasksControllerV2.updateTaskProgress(taskId); + + // Get the current task's progress ratio + const progressRatio = await db.query( + "SELECT get_task_complete_ratio($1) as ratio", + [taskId] + ); + + const ratio = progressRatio?.rows[0]?.ratio?.ratio || 0; + console.log(`Updated task ${taskId} progress: ${ratio}`); + + // Check if this task needs a "done" status prompt + let shouldPromptForDone = false; + + if (ratio >= 100) { + // Get the task's current status + const taskStatusResult = await db.query(` + SELECT ts.id, stsc.is_done + FROM tasks t + JOIN task_statuses ts ON t.status_id = ts.id + JOIN sys_task_status_categories stsc ON ts.category_id = stsc.id + WHERE t.id = $1 + `, [taskId]); + + // If the task isn't already in a "done" category, we should prompt the user + if (taskStatusResult.rows.length > 0 && !taskStatusResult.rows[0].is_done) { + shouldPromptForDone = true; + } + } + + // Emit the updated progress + socket.emit( + SocketEvents.TASK_PROGRESS_UPDATED.toString(), + { + task_id: taskId, + progress_value: ratio, + should_prompt_for_done: shouldPromptForDone + } + ); + + // Find this task's parent to continue the recursive update + const parentResult = await db.query( + "SELECT parent_task_id FROM tasks WHERE id = $1", + [taskId] + ); + + const parentTaskId = parentResult.rows[0]?.parent_task_id; + + // If there's a parent, recursively update it + if (parentTaskId) { + await updateTaskAncestors(io, socket, projectId, parentTaskId); + } + } catch (error) { + log_error(`Error updating ancestor task ${taskId}: ${error}`); + } +} + +export async function on_update_task_progress(io: any, socket: Socket, data: string) { + try { + const parsedData = JSON.parse(data) as UpdateTaskProgressData; + const { task_id, progress_value, parent_task_id } = parsedData; + + if (!task_id || progress_value === undefined) { + return; + } + + // Check if this is a parent task (has subtasks) + const subTasksResult = await db.query( + "SELECT COUNT(*) as subtask_count FROM tasks WHERE parent_task_id = $1", + [task_id] + ); + + const subtaskCount = parseInt(subTasksResult.rows[0]?.subtask_count || "0"); + + // If this is a parent task, we shouldn't set manual progress + if (subtaskCount > 0) { + log_error(`Cannot set manual progress on parent task ${task_id} with ${subtaskCount} subtasks`); + return; + } + + // Get the current progress value to log the change + const currentProgressResult = await db.query( + "SELECT progress_value, project_id, status_id FROM tasks WHERE id = $1", + [task_id] + ); + + const currentProgress = currentProgressResult.rows[0]?.progress_value; + const projectId = currentProgressResult.rows[0]?.project_id; + const statusId = currentProgressResult.rows[0]?.status_id; + + // Update the task progress in the database + await db.query( + `UPDATE tasks + SET progress_value = $1, manual_progress = true, updated_at = NOW() + WHERE id = $2`, + [progress_value, task_id] + ); + + // Log the progress change using the activity logs service + await logProgressChange({ + task_id, + old_value: currentProgress !== null ? currentProgress.toString() : "0", + new_value: progress_value.toString(), + socket + }); + + if (projectId) { + // Check if progress is 100% and the task isn't already in a "done" status category + let shouldPromptForDone = false; + + if (progress_value >= 100) { + // Check if the task's current status is in a "done" category + const statusCategoryResult = await db.query(` + SELECT stsc.is_done + FROM task_statuses ts + JOIN sys_task_status_categories stsc ON ts.category_id = stsc.id + WHERE ts.id = $1 + `, [statusId]); + + // If the task isn't already in a "done" category, we should prompt the user + if (statusCategoryResult.rows.length > 0 && !statusCategoryResult.rows[0].is_done) { + shouldPromptForDone = true; + } + } + + // Emit the update to all clients in the project room + socket.emit( + SocketEvents.TASK_PROGRESS_UPDATED.toString(), + { + task_id, + progress_value, + should_prompt_for_done: shouldPromptForDone + } + ); + + log(`Emitted progress update for task ${task_id} to project room ${projectId}`, null); + + // If this task has a parent, use our controller to update all ancestors + if (parent_task_id) { + // Use the controller method to update the parent task's progress + await TasksControllerV2.updateTaskProgress(parent_task_id); + // Also use the existing method for socket notifications + await updateTaskAncestors(io, socket, projectId, parent_task_id); + } + + // Notify that project updates are available + notifyProjectUpdates(socket, task_id); + } + } catch (error) { + log_error(error); + } +} diff --git a/worklenz-backend/src/socket.io/commands/on-update-task-weight.ts b/worklenz-backend/src/socket.io/commands/on-update-task-weight.ts new file mode 100644 index 00000000..7d0f65bf --- /dev/null +++ b/worklenz-backend/src/socket.io/commands/on-update-task-weight.ts @@ -0,0 +1,107 @@ +import { Socket } from "socket.io"; +import db from "../../config/db"; +import { SocketEvents } from "../events"; +import { log, log_error, notifyProjectUpdates } from "../util"; +import { logWeightChange } from "../../services/activity-logs/activity-logs.service"; +import TasksControllerV2 from "../../controllers/tasks-controller-v2"; + +interface UpdateTaskWeightData { + task_id: string; + weight: number; + parent_task_id: string | null; +} + +export async function on_update_task_weight(io: any, socket: Socket, data: string) { + try { + + const parsedData = JSON.parse(data) as UpdateTaskWeightData; + const { task_id, weight, parent_task_id } = parsedData; + + if (!task_id || weight === undefined) { + return; + } + + // Get the current weight value to log the change + const currentWeightResult = await db.query( + "SELECT weight, project_id FROM tasks WHERE id = $1", + [task_id] + ); + + const currentWeight = currentWeightResult.rows[0]?.weight; + const projectId = currentWeightResult.rows[0]?.project_id; + + // Update the task weight using our controller method + await TasksControllerV2.updateTaskWeight(task_id, weight); + + // Log the weight change using the activity logs service + await logWeightChange({ + task_id, + old_value: currentWeight !== null ? currentWeight.toString() : "100", + new_value: weight.toString(), + socket + }); + + if (projectId) { + // Emit the update to all clients in the project room + socket.emit( + SocketEvents.TASK_PROGRESS_UPDATED.toString(), + { + task_id, + weight + } + ); + + // If this is a subtask, update the parent task's progress + if (parent_task_id) { + // Use the controller to update the parent task progress + await TasksControllerV2.updateTaskProgress(parent_task_id); + + // Get the updated progress to emit to clients + const progressRatio = await db.query( + "SELECT get_task_complete_ratio($1) as ratio", + [parent_task_id] + ); + + // Emit the parent task's updated progress + socket.emit( + SocketEvents.TASK_PROGRESS_UPDATED.toString(), + { + task_id: parent_task_id, + progress_value: progressRatio?.rows[0]?.ratio?.ratio || 0 + } + ); + + // We also need to update any grandparent tasks + const grandparentResult = await db.query( + "SELECT parent_task_id FROM tasks WHERE id = $1", + [parent_task_id] + ); + + const grandparentId = grandparentResult.rows[0]?.parent_task_id; + + if (grandparentId) { + await TasksControllerV2.updateTaskProgress(grandparentId); + + // Emit the grandparent's updated progress + const grandparentProgressRatio = await db.query( + "SELECT get_task_complete_ratio($1) as ratio", + [grandparentId] + ); + + socket.emit( + SocketEvents.TASK_PROGRESS_UPDATED.toString(), + { + task_id: grandparentId, + progress_value: grandparentProgressRatio?.rows[0]?.ratio?.ratio || 0 + } + ); + } + } + + // Notify that project updates are available + notifyProjectUpdates(socket, task_id); + } + } catch (error) { + log_error(error); + } +} \ No newline at end of file diff --git a/worklenz-backend/src/socket.io/events.ts b/worklenz-backend/src/socket.io/events.ts index 398ff030..c0a58008 100644 --- a/worklenz-backend/src/socket.io/events.ts +++ b/worklenz-backend/src/socket.io/events.ts @@ -57,4 +57,17 @@ export enum SocketEvents { TASK_ASSIGNEES_CHANGE, TASK_CUSTOM_COLUMN_UPDATE, CUSTOM_COLUMN_PINNED_CHANGE, + TEAM_MEMBER_ROLE_CHANGE, + + // Task progress events + UPDATE_TASK_PROGRESS, + UPDATE_TASK_WEIGHT, + TASK_PROGRESS_UPDATED, + + // Task subtasks count events + GET_TASK_SUBTASKS_COUNT, + TASK_SUBTASKS_COUNT, + + // Task completion events + GET_DONE_STATUSES, } diff --git a/worklenz-backend/src/socket.io/index.ts b/worklenz-backend/src/socket.io/index.ts index 29c4b147..04927214 100644 --- a/worklenz-backend/src/socket.io/index.ts +++ b/worklenz-backend/src/socket.io/index.ts @@ -52,6 +52,10 @@ import { on_task_recurring_change } from "./commands/on-task-recurring-change"; import { on_task_assignees_change } from "./commands/on-task-assignees-change"; import { on_task_custom_column_update } from "./commands/on_custom_column_update"; import { on_custom_column_pinned_change } from "./commands/on_custom_column_pinned_change"; +import { on_update_task_progress } from "./commands/on-update-task-progress"; +import { on_update_task_weight } from "./commands/on-update-task-weight"; +import { on_get_task_subtasks_count } from "./commands/on-get-task-subtasks-count"; +import { on_get_done_statuses } from "./commands/on-get-done-statuses"; export function register(io: any, socket: Socket) { log(socket.id, "client registered"); @@ -69,7 +73,6 @@ export function register(io: any, socket: Socket) { socket.on(SocketEvents.TASK_TIME_ESTIMATION_CHANGE.toString(), data => on_time_estimation_change(io, socket, data)); socket.on(SocketEvents.TASK_DESCRIPTION_CHANGE.toString(), data => on_task_description_change(io, socket, data)); socket.on(SocketEvents.GET_TASK_PROGRESS.toString(), data => on_get_task_progress(io, socket, data)); - socket.on(SocketEvents.GET_TASK_PROGRESS.toString(), data => on_get_task_progress(io, socket, data)); socket.on(SocketEvents.TASK_TIMER_START.toString(), data => on_task_timer_start(io, socket, data)); socket.on(SocketEvents.TASK_TIMER_STOP.toString(), data => on_task_timer_stop(io, socket, data)); socket.on(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), data => on_task_sort_order_change(io, socket, data)); @@ -106,6 +109,10 @@ export function register(io: any, socket: Socket) { socket.on(SocketEvents.TASK_ASSIGNEES_CHANGE.toString(), data => on_task_assignees_change(io, socket, data)); socket.on(SocketEvents.TASK_CUSTOM_COLUMN_UPDATE.toString(), data => on_task_custom_column_update(io, socket, data)); socket.on(SocketEvents.CUSTOM_COLUMN_PINNED_CHANGE.toString(), data => on_custom_column_pinned_change(io, socket, data)); + socket.on(SocketEvents.UPDATE_TASK_PROGRESS.toString(), data => on_update_task_progress(io, socket, data)); + socket.on(SocketEvents.UPDATE_TASK_WEIGHT.toString(), data => on_update_task_weight(io, socket, data)); + socket.on(SocketEvents.GET_TASK_SUBTASKS_COUNT.toString(), (taskId) => on_get_task_subtasks_count(io, socket, taskId)); + socket.on(SocketEvents.GET_DONE_STATUSES.toString(), (projectId, callback) => on_get_done_statuses(io, socket, projectId, callback)); // socket.io built-in event socket.on("disconnect", (reason) => on_disconnect(io, socket, reason)); diff --git a/worklenz-backend/worklenz-email-templates/password-changed-notification.html b/worklenz-backend/worklenz-email-templates/password-changed-notification.html index f734d8a8..2c8e2d3a 100644 --- a/worklenz-backend/worklenz-email-templates/password-changed-notification.html +++ b/worklenz-backend/worklenz-email-templates/password-changed-notification.html @@ -2,31 +2,30 @@ - + Password Changed | Worklenz + - - - - + + + + + + + + + diff --git a/worklenz-backend/worklenz-email-templates/release-note-template.html b/worklenz-backend/worklenz-email-templates/release-note-template.html index 592917bf..f445e122 100644 --- a/worklenz-backend/worklenz-email-templates/release-note-template.html +++ b/worklenz-backend/worklenz-email-templates/release-note-template.html @@ -2,31 +2,35 @@ - + Worklenz 2.1.1 Release + - - - - - - - - -
-

- Click here to unsubscribe and manage your email preferences. -

+ + + + + +
+ + + + + + + + + + +
+ + Worklenz Light Logo + + +
+
+

🆕 Manage Statuses Easily

+
    +
  • Add, rename, delete, sort, and change category of statuses with a new popup.
  • +
  • Group by status and click the "Manage Status" button next to the group by option in the task filter.
  • +
+ Manage Status Modal +
+
+

🆕 Manage Phases Easily

+
    +
  • Group by phase and click the "Manage Status" button next to the group by option in the task filter.
  • +
  • Rename, add, delete, change color, and sort phases with a new popup.
  • +
+ Manage Phases Modal +
+
+

📊 Task Progress Bar in Groups

+
    +
  • When grouped by priority or phase, see the progress of tasks with a task progress bar in To Do, Doing, Done categories.
  • +
+ Task Group Progress Bar +
+
+

🖱️ Right Click Context Menu

+
    +
  • Quick actions available via right click context menu in the task list.
  • +
+ Task List Context Menu +
+
+

✨ UI Enhancements

+
    +
  • Added borders to task rows for better clarity.
  • +
  • Various bug fixes and UI improvements across the platform.
  • +
+ Task Row Borders +
+ +
+
+

+ Click here to unsubscribe and + manage your email preferences. +

+
+
- + \ No newline at end of file diff --git a/worklenz-backend/worklenz-email-templates/reset-password.html b/worklenz-backend/worklenz-email-templates/reset-password.html index d6f7e4d7..9c5f2c24 100644 --- a/worklenz-backend/worklenz-email-templates/reset-password.html +++ b/worklenz-backend/worklenz-email-templates/reset-password.html @@ -2,31 +2,30 @@ - + Reset Your Password | Worklenz + - - - - - - - - + + + + diff --git a/worklenz-backend/worklenz-email-templates/team-invitation.html b/worklenz-backend/worklenz-email-templates/team-invitation.html index 921e845d..f0d17e33 100644 --- a/worklenz-backend/worklenz-email-templates/team-invitation.html +++ b/worklenz-backend/worklenz-email-templates/team-invitation.html @@ -2,31 +2,30 @@ - + Join Your Team on Worklenz + - - - - - - - - + + + + diff --git a/worklenz-backend/worklenz-email-templates/unregistered-team-invitation-notification.html b/worklenz-backend/worklenz-email-templates/unregistered-team-invitation-notification.html index a231f9ad..2db5cfc2 100644 --- a/worklenz-backend/worklenz-email-templates/unregistered-team-invitation-notification.html +++ b/worklenz-backend/worklenz-email-templates/unregistered-team-invitation-notification.html @@ -2,31 +2,30 @@ - + Join Your Team on Worklenz + - - - - - + + + + + + + + + + + + + + + + diff --git a/worklenz-backend/worklenz-email-templates/welcome.html b/worklenz-backend/worklenz-email-templates/welcome.html index bc258a6d..7bb62821 100644 --- a/worklenz-backend/worklenz-email-templates/welcome.html +++ b/worklenz-backend/worklenz-email-templates/welcome.html @@ -2,31 +2,30 @@ - + Welcome to Worklenz + - - - - - + + + + + + + + + + + + + + + + diff --git a/worklenz-frontend/README.md b/worklenz-frontend/README.md index 46c4a267..15622f91 100644 --- a/worklenz-frontend/README.md +++ b/worklenz-frontend/README.md @@ -3,6 +3,7 @@ Worklenz is a project management application built with React, TypeScript, and Ant Design. The project is bundled using [Vite](https://vitejs.dev/). ## Table of Contents + - [Getting Started](#getting-started) - [Available Scripts](#available-scripts) - [Project Structure](#project-structure) diff --git a/worklenz-frontend/index.html b/worklenz-frontend/index.html index b95b837b..5ac671f0 100644 --- a/worklenz-frontend/index.html +++ b/worklenz-frontend/index.html @@ -5,42 +5,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Worklenz + - + + + diff --git a/worklenz-frontend/package-lock.json b/worklenz-frontend/package-lock.json index f75f2fce..721124f0 100644 --- a/worklenz-frontend/package-lock.json +++ b/worklenz-frontend/package-lock.json @@ -10,31 +10,36 @@ "dependencies": { "@ant-design/colors": "^7.1.0", "@ant-design/compatible": "^5.1.4", - "@ant-design/icons": "^5.4.0", + "@ant-design/icons": "^4.7.0", "@ant-design/pro-components": "^2.7.19", "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", "@emotion/react": "^11.14.0", + "@heroicons/react": "^2.2.0", "@paddle/paddle-js": "^1.3.3", "@reduxjs/toolkit": "^2.2.7", + "@tailwindcss/forms": "^0.5.10", "@tanstack/react-table": "^8.20.6", "@tanstack/react-virtual": "^3.11.2", "@tinymce/tinymce-react": "^5.1.1", - "antd": "^5.24.1", - "axios": "^1.7.9", + "antd": "^5.26.2", + "axios": "^1.9.0", "chart.js": "^4.4.7", "chartjs-plugin-datalabels": "^2.2.0", + "cors": "^2.8.5", "date-fns": "^4.1.0", - "dompurify": "^3.2.4", + "dompurify": "^3.2.5", "gantt-task-react": "^0.3.9", "html2canvas": "^1.4.1", "i18next": "^23.16.8", - "i18next-browser-languagedetector": "^8.0.3", + "i18next-browser-languagedetector": "^8.2.0", "i18next-http-backend": "^2.7.3", + "i18next-localstorage-backend": "^4.2.0", "jspdf": "^3.0.0", "mixpanel-browser": "^2.56.0", + "nanoid": "^5.1.5", "primereact": "^10.8.4", "re-resizable": "^6.10.3", "react": "^18.3.1", @@ -46,10 +51,13 @@ "react-responsive": "^10.0.0", "react-router-dom": "^6.28.1", "react-timer-hook": "^3.0.8", + "react-virtuoso": "^4.13.0", "react-window": "^1.8.11", + "react-window-infinite-loader": "^1.0.10", "socket.io-client": "^4.8.1", "tinymce": "^7.7.2", - "web-vitals": "^4.2.4" + "web-vitals": "^4.2.4", + "worklenz": "file:" }, "devDependencies": { "@testing-library/jest-dom": "^6.6.3", @@ -63,22 +71,24 @@ "@types/node": "^20.8.4", "@types/react": "19.0.0", "@types/react-dom": "19.0.0", + "@types/react-window": "^1.8.8", "@vitejs/plugin-react": "^4.3.4", - "autoprefixer": "^10.4.20", + "autoprefixer": "^10.4.21", "postcss": "^8.5.2", - "prettier-plugin-tailwindcss": "^0.6.8", + "prettier-plugin-tailwindcss": "^0.6.13", + "rollup": "^4.40.2", "tailwindcss": "^3.4.17", "terser": "^5.39.0", "typescript": "^5.7.3", - "vite": "^6.2.5", + "vite": "^6.3.5", "vite-tsconfig-paths": "^5.1.4", "vitest": "^3.0.5" } }, "node_modules/@adobe/css-tools": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.2.tgz", - "integrity": "sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.3.tgz", + "integrity": "sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==", "dev": true, "license": "MIT" }, @@ -86,7 +96,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -110,9 +119,9 @@ } }, "node_modules/@ant-design/colors": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.0.tgz", - "integrity": "sha512-bjTObSnZ9C/O8MB/B4OUtd/q9COomuJAR2SYfhxLyHvCKn4EKwCN3e+fWGMo7H5InAyV0wL17jdE9ALrdOW/6A==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.1.tgz", + "integrity": "sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==", "license": "MIT", "dependencies": { "@ant-design/fast-color": "^2.0.6" @@ -192,16 +201,17 @@ } }, "node_modules/@ant-design/icons": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz", - "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.8.3.tgz", + "integrity": "sha512-HGlIQZzrEbAhpJR6+IGdzfbPym94Owr6JZkJ2QCCnOkPVIWMO2xgIVcOKnl8YcpijIo39V7l2qQL5fmtw56cMw==", "license": "MIT", "dependencies": { - "@ant-design/colors": "^7.0.0", - "@ant-design/icons-svg": "^4.4.0", - "@babel/runtime": "^7.24.8", + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-svg": "^4.3.0", + "@babel/runtime": "^7.11.2", "classnames": "^2.2.6", - "rc-util": "^5.31.1" + "lodash": "^4.17.15", + "rc-util": "^5.9.4" }, "engines": { "node": ">=8" @@ -217,6 +227,15 @@ "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", "license": "MIT" }, + "node_modules/@ant-design/icons/node_modules/@ant-design/colors": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz", + "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.4.0" + } + }, "node_modules/@ant-design/pro-card": { "version": "2.9.7", "resolved": "https://registry.npmjs.org/@ant-design/pro-card/-/pro-card-2.9.7.tgz", @@ -237,6 +256,26 @@ "react": ">=17.0.0" } }, + "node_modules/@ant-design/pro-card/node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, "node_modules/@ant-design/pro-components": { "version": "2.8.7", "resolved": "https://registry.npmjs.org/@ant-design/pro-components/-/pro-components-2.8.7.tgz", @@ -320,6 +359,26 @@ "react": ">=17.0.0" } }, + "node_modules/@ant-design/pro-field/node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, "node_modules/@ant-design/pro-form": { "version": "2.31.7", "resolved": "https://registry.npmjs.org/@ant-design/pro-form/-/pro-form-2.31.7.tgz", @@ -347,6 +406,26 @@ "react-dom": ">=17.0.0" } }, + "node_modules/@ant-design/pro-form/node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, "node_modules/@ant-design/pro-layout": { "version": "7.22.4", "resolved": "https://registry.npmjs.org/@ant-design/pro-layout/-/pro-layout-7.22.4.tgz", @@ -375,6 +454,26 @@ "react-dom": ">=17.0.0" } }, + "node_modules/@ant-design/pro-layout/node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, "node_modules/@ant-design/pro-list": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/@ant-design/pro-list/-/pro-list-2.6.7.tgz", @@ -399,6 +498,46 @@ "react-dom": ">=17.0.0" } }, + "node_modules/@ant-design/pro-list/node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/pro-list/node_modules/@ant-design/icons/node_modules/rc-util": { + "version": "5.44.4", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.44.4.tgz", + "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@ant-design/pro-list/node_modules/@ant-design/icons/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, "node_modules/@ant-design/pro-list/node_modules/rc-util": { "version": "4.21.1", "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.21.1.tgz", @@ -412,12 +551,6 @@ "shallowequal": "^1.1.0" } }, - "node_modules/@ant-design/pro-list/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, "node_modules/@ant-design/pro-provider": { "version": "2.15.4", "resolved": "https://registry.npmjs.org/@ant-design/pro-provider/-/pro-provider-2.15.4.tgz", @@ -483,6 +616,26 @@ "react-dom": ">=17.0.0" } }, + "node_modules/@ant-design/pro-table/node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, "node_modules/@ant-design/pro-table/node_modules/@dnd-kit/modifiers": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/@dnd-kit/modifiers/-/modifiers-6.0.1.tgz", @@ -534,6 +687,26 @@ "react-dom": ">=17.0.0" } }, + "node_modules/@ant-design/pro-utils/node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, "node_modules/@ant-design/react-slick": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.1.2.tgz", @@ -550,24 +723,49 @@ "react": ">=16.9.0" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true + }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", - "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz", + "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==", "dev": true, "license": "MIT", "engines": { @@ -575,22 +773,22 @@ } }, "node_modules/@babel/core": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", - "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", + "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.10", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.10", - "@babel/parser": "^7.26.10", - "@babel/template": "^7.26.9", - "@babel/traverse": "^7.26.10", - "@babel/types": "^7.26.10", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helpers": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -613,13 +811,13 @@ "license": "MIT" }, "node_modules/@babel/generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", - "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", + "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0", + "@babel/parser": "^7.27.1", + "@babel/types": "^7.27.1", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -629,14 +827,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", - "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.26.8", - "@babel/helper-validator-option": "^7.25.9", + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -646,28 +844,28 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", + "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -677,9 +875,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", - "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", "dev": true, "license": "MIT", "engines": { @@ -687,27 +885,27 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", "engines": { @@ -715,26 +913,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", - "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", + "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0" + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", - "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", + "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.0" + "@babel/types": "^7.27.1" }, "bin": { "parser": "bin/babel-parser.js" @@ -744,13 +942,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", - "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -760,13 +958,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", - "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -776,42 +974,39 @@ } }, "node_modules/@babel/runtime": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", - "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", + "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", - "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", - "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", + "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.27.0", - "@babel/parser": "^7.27.0", - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -820,13 +1015,13 @@ } }, "node_modules/@babel/types": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", - "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -845,6 +1040,131 @@ "react": ">=16.12.0" } }, + "node_modules/@csstools/color-helpers": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", + "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "optional": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", + "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@csstools/color-helpers": "^5.0.2", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, "node_modules/@ctrl/tinycolor": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", @@ -1066,9 +1386,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", - "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", "cpu": [ "ppc64" ], @@ -1083,9 +1403,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", - "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", "cpu": [ "arm" ], @@ -1100,9 +1420,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", - "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", "cpu": [ "arm64" ], @@ -1117,9 +1437,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", - "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", "cpu": [ "x64" ], @@ -1134,9 +1454,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", - "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", "cpu": [ "arm64" ], @@ -1151,9 +1471,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", - "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", "cpu": [ "x64" ], @@ -1168,9 +1488,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", - "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", "cpu": [ "arm64" ], @@ -1185,9 +1505,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", - "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", "cpu": [ "x64" ], @@ -1202,9 +1522,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", - "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", "cpu": [ "arm" ], @@ -1219,9 +1539,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", - "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", "cpu": [ "arm64" ], @@ -1236,9 +1556,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", - "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", "cpu": [ "ia32" ], @@ -1253,9 +1573,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", - "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", "cpu": [ "loong64" ], @@ -1270,9 +1590,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", - "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", "cpu": [ "mips64el" ], @@ -1287,9 +1607,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", - "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", "cpu": [ "ppc64" ], @@ -1304,9 +1624,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", - "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", "cpu": [ "riscv64" ], @@ -1321,9 +1641,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", - "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", "cpu": [ "s390x" ], @@ -1338,9 +1658,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", - "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", "cpu": [ "x64" ], @@ -1355,9 +1675,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", - "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", "cpu": [ "arm64" ], @@ -1372,9 +1692,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", - "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", "cpu": [ "x64" ], @@ -1389,9 +1709,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", - "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", "cpu": [ "arm64" ], @@ -1406,9 +1726,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", - "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", "cpu": [ "x64" ], @@ -1423,9 +1743,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", - "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", "cpu": [ "x64" ], @@ -1440,9 +1760,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", - "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", "cpu": [ "arm64" ], @@ -1457,9 +1777,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", - "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", "cpu": [ "ia32" ], @@ -1474,9 +1794,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", - "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", "cpu": [ "x64" ], @@ -1490,11 +1810,19 @@ "node": ">=18" } }, + "node_modules/@heroicons/react": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz", + "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16 || ^19.0.0-rc" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -1577,7 +1905,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -1591,7 +1918,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -1601,7 +1927,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -1612,16 +1937,15 @@ } }, "node_modules/@paddle/paddle-js": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@paddle/paddle-js/-/paddle-js-1.4.0.tgz", - "integrity": "sha512-pX6Yx+RswB1rHMuYl8RKcAAVZhVJ6nd5f8w8l4kVM63pM3HNeQ5/Xuk4sK/X9P5fUE2dmN0mTti7+gZ8cZtqvg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@paddle/paddle-js/-/paddle-js-1.4.1.tgz", + "integrity": "sha512-GKuXVnUAIGq4H1AxrPRRMZXl+pTSGiKMStpRlvF6+dv03BwhkqbyHJJZ39e6bMquVbYSa33/9cu6fuW8pie8aQ==", "license": "Apache-2.0" }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -1757,9 +2081,9 @@ } }, "node_modules/@rc-component/trigger": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.2.6.tgz", - "integrity": "sha512-/9zuTnWwhQ3S3WT1T8BubuFTT46kvnXgaERR9f4BTKyn61/wpf/BvbImzYBubzJibU707FxwbKszLlHjcLiv1Q==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.2.7.tgz", + "integrity": "sha512-Qggj4Z0AA2i5dJhzlfFSmg1Qrziu8dsdHOihROL5Kl18seO2Eh/ZaTYt2c8a/CyGaTChnFry7BEYew1+/fhSbA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.2", @@ -1778,11 +2102,13 @@ } }, "node_modules/@reduxjs/toolkit": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.6.1.tgz", - "integrity": "sha512-SSlIqZNYhqm/oMkXbtofwZSt9lrncblzo6YcZ9zoX+zLngRBrCOjK4lNLdkNucJF58RHOWrD9txT3bT3piH7Zw==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.8.2.tgz", + "integrity": "sha512-MYlOhQ0sLdw4ud48FoC5w0dH9VfWQjtCjreKwYTT3l+r427qYC5Y8PihNutepr8XrNaBUDQo9khWUwQxZaqt5A==", "license": "MIT", "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", "immer": "^10.0.3", "redux": "^5.0.1", "redux-thunk": "^3.1.0", @@ -1810,10 +2136,17 @@ "node": ">=14.0.0" } }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.9", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.9.tgz", + "integrity": "sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.39.0.tgz", - "integrity": "sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz", + "integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==", "cpu": [ "arm" ], @@ -1825,9 +2158,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.39.0.tgz", - "integrity": "sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz", + "integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==", "cpu": [ "arm64" ], @@ -1839,9 +2172,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.39.0.tgz", - "integrity": "sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz", + "integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==", "cpu": [ "arm64" ], @@ -1853,9 +2186,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.39.0.tgz", - "integrity": "sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz", + "integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==", "cpu": [ "x64" ], @@ -1867,9 +2200,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.39.0.tgz", - "integrity": "sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz", + "integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==", "cpu": [ "arm64" ], @@ -1881,9 +2214,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.39.0.tgz", - "integrity": "sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz", + "integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==", "cpu": [ "x64" ], @@ -1895,9 +2228,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.39.0.tgz", - "integrity": "sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz", + "integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==", "cpu": [ "arm" ], @@ -1909,9 +2242,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.39.0.tgz", - "integrity": "sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz", + "integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==", "cpu": [ "arm" ], @@ -1923,9 +2256,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.39.0.tgz", - "integrity": "sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz", + "integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==", "cpu": [ "arm64" ], @@ -1937,9 +2270,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.39.0.tgz", - "integrity": "sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz", + "integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==", "cpu": [ "arm64" ], @@ -1951,9 +2284,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.39.0.tgz", - "integrity": "sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz", + "integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==", "cpu": [ "loong64" ], @@ -1965,9 +2298,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.39.0.tgz", - "integrity": "sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz", + "integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==", "cpu": [ "ppc64" ], @@ -1979,9 +2312,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.39.0.tgz", - "integrity": "sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz", + "integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==", "cpu": [ "riscv64" ], @@ -1993,9 +2326,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.39.0.tgz", - "integrity": "sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz", + "integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==", "cpu": [ "riscv64" ], @@ -2007,9 +2340,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.39.0.tgz", - "integrity": "sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz", + "integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==", "cpu": [ "s390x" ], @@ -2021,9 +2354,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.39.0.tgz", - "integrity": "sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz", + "integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==", "cpu": [ "x64" ], @@ -2035,9 +2368,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.39.0.tgz", - "integrity": "sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz", + "integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==", "cpu": [ "x64" ], @@ -2049,9 +2382,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.39.0.tgz", - "integrity": "sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz", + "integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==", "cpu": [ "arm64" ], @@ -2063,9 +2396,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.39.0.tgz", - "integrity": "sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz", + "integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==", "cpu": [ "ia32" ], @@ -2077,9 +2410,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.39.0.tgz", - "integrity": "sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz", + "integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==", "cpu": [ "x64" ], @@ -2108,13 +2441,37 @@ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "license": "MIT" }, - "node_modules/@tanstack/react-table": { - "version": "8.21.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.2.tgz", - "integrity": "sha512-11tNlEDTdIhMJba2RBH+ecJ9l1zgS2kjmexDPAraulc8jeNA4xocSNeyzextT0XJyASil4XsCYlJmf5jEWAtYg==", + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@tailwindcss/forms": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz", + "integrity": "sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==", "license": "MIT", "dependencies": { - "@tanstack/table-core": "8.21.2" + "mini-svg-data-uri": "^1.2.3" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" + } + }, + "node_modules/@tanstack/react-table": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", + "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.21.3" }, "engines": { "node": ">=12" @@ -2129,12 +2486,12 @@ } }, "node_modules/@tanstack/react-virtual": { - "version": "3.13.6", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.6.tgz", - "integrity": "sha512-WT7nWs8ximoQ0CDx/ngoFP7HbQF9Q2wQe4nh2NB+u2486eX3nZRE40P9g6ccCVq7ZfTSH5gFOuCoVH5DLNS/aA==", + "version": "3.13.9", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.9.tgz", + "integrity": "sha512-SPWC8kwG/dWBf7Py7cfheAPOxuvIv4fFQ54PdmYbg7CpXfsKxkucak43Q0qKsxVthhUJQ1A7CIMAIplq4BjVwA==", "license": "MIT", "dependencies": { - "@tanstack/virtual-core": "3.13.6" + "@tanstack/virtual-core": "3.13.9" }, "funding": { "type": "github", @@ -2146,9 +2503,9 @@ } }, "node_modules/@tanstack/table-core": { - "version": "8.21.2", - "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.2.tgz", - "integrity": "sha512-uvXk/U4cBiFMxt+p9/G7yUWI/UbHYbyghLCjlpWZ3mLeIZiUBSKcUnw9UnKkdRz7Z/N4UBuFLWQdJCjUe7HjvA==", + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", "license": "MIT", "engines": { "node": ">=12" @@ -2159,9 +2516,9 @@ } }, "node_modules/@tanstack/virtual-core": { - "version": "3.13.6", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.6.tgz", - "integrity": "sha512-cnQUeWnhNP8tJ4WsGcYiX24Gjkc9ALstLbHcBj1t3E7EimN6n6kHH+DPV4PpDnuw00NApQp+ViojMj1GRdwYQg==", + "version": "3.13.9", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.9.tgz", + "integrity": "sha512-3jztt0jpaoJO5TARe2WIHC1UQC3VMLAFUW5mmMo0yrkwtDB2AQP0+sh10BVUpWrnvHjSLvzFizydtEGLCJKFoQ==", "license": "MIT", "funding": { "type": "github", @@ -2310,9 +2667,9 @@ } }, "node_modules/@types/babel__generator": { - "version": "7.6.8", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", - "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", "dev": true, "license": "MIT", "dependencies": { @@ -2385,23 +2742,23 @@ } }, "node_modules/@types/lodash": { - "version": "4.17.16", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.16.tgz", - "integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==", + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.17.tgz", + "integrity": "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==", "dev": true, "license": "MIT" }, "node_modules/@types/mixpanel-browser": { - "version": "2.54.0", - "resolved": "https://registry.npmjs.org/@types/mixpanel-browser/-/mixpanel-browser-2.54.0.tgz", - "integrity": "sha512-7DMzIH0M9TlpCTMZidaeXris+aMUyAgMMEZtV1xeD6fSQgpCGklUKqyRgidq5hKPKuNEOWBp73549Gusig/xBA==", + "version": "2.60.0", + "resolved": "https://registry.npmjs.org/@types/mixpanel-browser/-/mixpanel-browser-2.60.0.tgz", + "integrity": "sha512-70oe8T3KdxHwsSo5aZphALdoqcsIorQBrlisnouIn9Do4dmC2C6/D56978CmSE/BO2QHgb85ojPGa4R8OFvVHA==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { - "version": "20.17.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.30.tgz", - "integrity": "sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==", + "version": "20.17.50", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.50.tgz", + "integrity": "sha512-Mxiq0ULv/zo1OzOhwPqOA13I81CV/W3nvd3ChtQZRT5Cwz3cr0FKo/wMSsbTqL3EXpaBAEQhva2B8ByRkOIh9A==", "dev": true, "license": "MIT", "dependencies": { @@ -2449,6 +2806,16 @@ "@types/react": "*" } }, + "node_modules/@types/react-window": { + "version": "1.8.8", + "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz", + "integrity": "sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -2478,17 +2845,18 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", - "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.0.tgz", + "integrity": "sha512-JuLWaEqypaJmOJPLWwO335Ig6jSgC1FTONCWAxnqcQthLTK/Yc9aH6hr9z/87xciejbQcnP3GnA1FWUSWeXaeg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.26.0", + "@babel/core": "^7.26.10", "@babel/plugin-transform-react-jsx-self": "^7.25.9", "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@rolldown/pluginutils": "1.0.0-beta.9", "@types/babel__core": "^7.20.5", - "react-refresh": "^0.14.2" + "react-refresh": "^0.17.0" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -2498,14 +2866,14 @@ } }, "node_modules/@vitest/expect": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.1.tgz", - "integrity": "sha512-q/zjrW9lgynctNbwvFtQkGK9+vvHA5UzVi2V8APrp1C6fG6/MuYYkmlx4FubuqLycCeSdHD5aadWfua/Vr0EUA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.4.tgz", + "integrity": "sha512-xkD/ljeliyaClDYqHPNCiJ0plY5YIcM0OlRiZizLhlPmpXWpxnGMyTZXOHFhFeG7w9P5PBeL4IdtJ/HeQwTbQA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.1.1", - "@vitest/utils": "3.1.1", + "@vitest/spy": "3.1.4", + "@vitest/utils": "3.1.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -2514,13 +2882,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.1.tgz", - "integrity": "sha512-bmpJJm7Y7i9BBELlLuuM1J1Q6EQ6K5Ye4wcyOpOMXMcePYKSIYlpcrCm4l/O6ja4VJA5G2aMJiuZkZdnxlC3SA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.4.tgz", + "integrity": "sha512-8IJ3CvwtSw/EFXqWFL8aCMu+YyYXG2WUSrQbViOZkWTKTVicVwZ/YiEZDSqD00kX+v/+W+OnxhNWoeVKorHygA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.1.1", + "@vitest/spy": "3.1.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -2541,9 +2909,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.1.tgz", - "integrity": "sha512-dg0CIzNx+hMMYfNmSqJlLSXEmnNhMswcn3sXO7Tpldr0LiGmg3eXdLLhwkv2ZqgHb/d5xg5F7ezNFRA1fA13yA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.4.tgz", + "integrity": "sha512-cqv9H9GvAEoTaoq+cYqUTCGscUjKqlJZC7PRwY5FMySVj5J+xOm1KQcCiYHJOEzOKRUhLH4R2pTwvFlWCEScsg==", "dev": true, "license": "MIT", "dependencies": { @@ -2554,13 +2922,13 @@ } }, "node_modules/@vitest/runner": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.1.tgz", - "integrity": "sha512-X/d46qzJuEDO8ueyjtKfxffiXraPRfmYasoC4i5+mlLEJ10UvPb0XH5M9C3gWuxd7BAQhpK42cJgJtq53YnWVA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.4.tgz", + "integrity": "sha512-djTeF1/vt985I/wpKVFBMWUlk/I7mb5hmD5oP8K9ACRmVXgKTae3TUOtXAEBfslNKPzUQvnKhNd34nnRSYgLNQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.1.1", + "@vitest/utils": "3.1.4", "pathe": "^2.0.3" }, "funding": { @@ -2568,13 +2936,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.1.tgz", - "integrity": "sha512-bByMwaVWe/+1WDf9exFxWWgAixelSdiwo2p33tpqIlM14vW7PRV5ppayVXtfycqze4Qhtwag5sVhX400MLBOOw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.4.tgz", + "integrity": "sha512-JPHf68DvuO7vilmvwdPr9TS0SuuIzHvxeaCkxYcCD4jTk67XwL45ZhEHFKIuCm8CYstgI6LZ4XbwD6ANrwMpFg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.1.1", + "@vitest/pretty-format": "3.1.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -2583,9 +2951,9 @@ } }, "node_modules/@vitest/spy": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.1.tgz", - "integrity": "sha512-+EmrUOOXbKzLkTDwlsc/xrwOlPDXyVk3Z6P6K4oiCndxz7YLpp/0R0UsWVOKT0IXWjjBJuSMk6D27qipaupcvQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.4.tgz", + "integrity": "sha512-Xg1bXhu+vtPXIodYN369M86K8shGLouNjoVI78g8iAq2rFoHFdajNvJJ5A/9bPMFcfQqdaCpOgWKEoMQg/s0Yg==", "dev": true, "license": "MIT", "dependencies": { @@ -2596,13 +2964,13 @@ } }, "node_modules/@vitest/utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.1.tgz", - "integrity": "sha512-1XIjflyaU2k3HMArJ50bwSh3wKWPD6Q47wz/NUSmRV0zNywPc4w79ARjg/i/aNINHwA+mIALhUVqD9/aUvZNgg==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.4.tgz", + "integrity": "sha512-yriMuO1cfFhmiGc8ataN51+9ooHRuURdfAZfwFd3usWynjzpLslZdYnRegTv32qdgtJTsj15FoeZe2g15fY1gg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.1.1", + "@vitest/pretty-format": "3.1.4", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" }, @@ -2638,11 +3006,22 @@ "object-assign": "4.x" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2652,7 +3031,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -2665,12 +3043,12 @@ } }, "node_modules/antd": { - "version": "5.24.6", - "resolved": "https://registry.npmjs.org/antd/-/antd-5.24.6.tgz", - "integrity": "sha512-xIlTa/1CTbgkZsdU/dOXkYvJXb9VoiMwsaCzpKFH2zAEY3xqOfwQ57/DdG7lAdrWP7QORtSld4UA6suxzuTHXw==", + "version": "5.26.2", + "resolved": "https://registry.npmjs.org/antd/-/antd-5.26.2.tgz", + "integrity": "sha512-C8dBgwSzXfUS5ousUN+mfcaGFhEOd9wuyhvmw0lQnU9gukpRoFe1B0UKzvr6Z50QgapIl+s03nYlQJUghKqVjQ==", "license": "MIT", "dependencies": { - "@ant-design/colors": "^7.2.0", + "@ant-design/colors": "^7.2.1", "@ant-design/cssinjs": "^1.23.0", "@ant-design/cssinjs-utils": "^1.1.3", "@ant-design/fast-color": "^2.0.6", @@ -2681,41 +3059,41 @@ "@rc-component/mutate-observer": "^1.1.0", "@rc-component/qrcode": "~1.0.0", "@rc-component/tour": "~1.15.1", - "@rc-component/trigger": "^2.2.6", + "@rc-component/trigger": "^2.2.7", "classnames": "^2.5.1", "copy-to-clipboard": "^3.3.3", "dayjs": "^1.11.11", - "rc-cascader": "~3.33.1", + "rc-cascader": "~3.34.0", "rc-checkbox": "~3.5.0", "rc-collapse": "~3.9.0", "rc-dialog": "~9.6.0", - "rc-drawer": "~7.2.0", + "rc-drawer": "~7.3.0", "rc-dropdown": "~4.2.1", "rc-field-form": "~2.7.0", - "rc-image": "~7.11.1", - "rc-input": "~1.7.3", - "rc-input-number": "~9.4.0", - "rc-mentions": "~2.19.1", + "rc-image": "~7.12.0", + "rc-input": "~1.8.0", + "rc-input-number": "~9.5.0", + "rc-mentions": "~2.20.0", "rc-menu": "~9.16.1", "rc-motion": "^2.9.5", - "rc-notification": "~5.6.3", + "rc-notification": "~5.6.4", "rc-pagination": "~5.1.0", "rc-picker": "~4.11.3", "rc-progress": "~4.0.0", "rc-rate": "~2.13.1", "rc-resize-observer": "^1.4.3", "rc-segmented": "~2.7.0", - "rc-select": "~14.16.6", + "rc-select": "~14.16.8", "rc-slider": "~11.1.8", "rc-steps": "~6.0.1", "rc-switch": "~4.1.0", - "rc-table": "~7.50.4", - "rc-tabs": "~15.5.1", - "rc-textarea": "~1.9.0", + "rc-table": "~7.51.1", + "rc-tabs": "~15.6.1", + "rc-textarea": "~1.10.0", "rc-tooltip": "~6.4.0", "rc-tree": "~5.13.1", "rc-tree-select": "~5.27.0", - "rc-upload": "~4.8.1", + "rc-upload": "~4.9.2", "rc-util": "^5.44.4", "scroll-into-view-if-needed": "^3.1.0", "throttle-debounce": "^5.0.2" @@ -2729,18 +3107,36 @@ "react-dom": ">=16.9.0" } }, + "node_modules/antd/node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, "license": "MIT" }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -2754,7 +3150,6 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true, "license": "MIT" }, "node_modules/aria-query": { @@ -2839,9 +3234,9 @@ } }, "node_modules/axios": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", - "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -2892,7 +3287,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/base64-arraybuffer": { @@ -2908,7 +3302,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2918,10 +3311,9 @@ } }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -2931,7 +3323,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -2941,9 +3332,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", + "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", "dev": true, "funding": [ { @@ -2961,10 +3352,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", + "caniuse-lite": "^1.0.30001716", + "electron-to-chromium": "^1.5.149", "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -3028,16 +3419,15 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" } }, "node_modules/caniuse-lite": { - "version": "1.0.30001709", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001709.tgz", - "integrity": "sha512-NgL3vUTnDrPCZ3zTahp4fsugQ4dc7EKTSzwQDPEel6DMoMnfH2jhry9n2Zm8onbSR+f/QtKHFOA+iAQu4kbtWA==", + "version": "1.0.30001718", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", + "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", "dev": true, "funding": [ { @@ -3075,13 +3465,6 @@ "node": ">=10.0.0" } }, - "node_modules/canvg/node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "license": "MIT", - "optional": true - }, "node_modules/chai": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", @@ -3117,9 +3500,9 @@ } }, "node_modules/chart.js": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.8.tgz", - "integrity": "sha512-IkGZlVpXP+83QpMm4uxEiGqSI7jFizwVtF3+n5Pc3k7sMO+tkd0qxh2OzLhenM0K80xtmAONWGBn082EiBQSDA==", + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz", + "integrity": "sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg==", "license": "MIT", "dependencies": { "@kurkle/color": "^0.3.0" @@ -3151,7 +3534,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -3176,7 +3558,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -3195,7 +3576,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -3208,7 +3588,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/combined-stream": { @@ -3227,7 +3606,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -3255,9 +3633,9 @@ } }, "node_modules/core-js": { - "version": "3.41.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.41.0.tgz", - "integrity": "sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA==", + "version": "3.42.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.42.0.tgz", + "integrity": "sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==", "hasInstallScript": true, "license": "MIT", "optional": true, @@ -3266,6 +3644,19 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -3314,7 +3705,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -3351,7 +3741,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -3360,12 +3749,87 @@ "node": ">=4" } }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/date-fns": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", @@ -3383,9 +3847,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -3399,6 +3863,15 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/deep-eql": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", @@ -3428,9 +3901,9 @@ } }, "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", "dev": true, "license": "Apache-2.0", "optional": true, @@ -3443,7 +3916,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true, "license": "Apache-2.0" }, "node_modules/diff-sequences": { @@ -3460,7 +3932,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true, "license": "MIT" }, "node_modules/dom-accessibility-api": { @@ -3488,9 +3959,9 @@ "license": "MIT" }, "node_modules/dompurify": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.5.tgz", - "integrity": "sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", + "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -3514,13 +3985,12 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.130", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.130.tgz", - "integrity": "sha512-Ou2u7L9j2XLZbhqzyX0jWDj6gA8D3jIfVzt4rikLf3cGBa0VdReuFimBKS9tQJA4+XpeCxj1NoWlfBXzbMa9IA==", + "version": "1.5.157", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.157.tgz", + "integrity": "sha512-/0ybgsQd1muo8QlnuTpKwtl0oX5YMlUGbm8xyqgDU00motRkKFFbUJySAQBWcY79rVqNLWIWa87BGVGClwAB2w==", "dev": true, "license": "ISC" }, @@ -3528,7 +3998,6 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, "license": "MIT" }, "node_modules/engine.io-client": { @@ -3570,6 +4039,21 @@ "node": ">=10.0.0" } }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3598,9 +4082,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", - "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", "dev": true, "license": "MIT" }, @@ -3632,9 +4116,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", - "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3645,31 +4129,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.2", - "@esbuild/android-arm": "0.25.2", - "@esbuild/android-arm64": "0.25.2", - "@esbuild/android-x64": "0.25.2", - "@esbuild/darwin-arm64": "0.25.2", - "@esbuild/darwin-x64": "0.25.2", - "@esbuild/freebsd-arm64": "0.25.2", - "@esbuild/freebsd-x64": "0.25.2", - "@esbuild/linux-arm": "0.25.2", - "@esbuild/linux-arm64": "0.25.2", - "@esbuild/linux-ia32": "0.25.2", - "@esbuild/linux-loong64": "0.25.2", - "@esbuild/linux-mips64el": "0.25.2", - "@esbuild/linux-ppc64": "0.25.2", - "@esbuild/linux-riscv64": "0.25.2", - "@esbuild/linux-s390x": "0.25.2", - "@esbuild/linux-x64": "0.25.2", - "@esbuild/netbsd-arm64": "0.25.2", - "@esbuild/netbsd-x64": "0.25.2", - "@esbuild/openbsd-arm64": "0.25.2", - "@esbuild/openbsd-x64": "0.25.2", - "@esbuild/sunos-x64": "0.25.2", - "@esbuild/win32-arm64": "0.25.2", - "@esbuild/win32-ia32": "0.25.2", - "@esbuild/win32-x64": "0.25.2" + "@esbuild/aix-ppc64": "0.25.4", + "@esbuild/android-arm": "0.25.4", + "@esbuild/android-arm64": "0.25.4", + "@esbuild/android-x64": "0.25.4", + "@esbuild/darwin-arm64": "0.25.4", + "@esbuild/darwin-x64": "0.25.4", + "@esbuild/freebsd-arm64": "0.25.4", + "@esbuild/freebsd-x64": "0.25.4", + "@esbuild/linux-arm": "0.25.4", + "@esbuild/linux-arm64": "0.25.4", + "@esbuild/linux-ia32": "0.25.4", + "@esbuild/linux-loong64": "0.25.4", + "@esbuild/linux-mips64el": "0.25.4", + "@esbuild/linux-ppc64": "0.25.4", + "@esbuild/linux-riscv64": "0.25.4", + "@esbuild/linux-s390x": "0.25.4", + "@esbuild/linux-x64": "0.25.4", + "@esbuild/netbsd-arm64": "0.25.4", + "@esbuild/netbsd-x64": "0.25.4", + "@esbuild/openbsd-arm64": "0.25.4", + "@esbuild/openbsd-x64": "0.25.4", + "@esbuild/sunos-x64": "0.25.4", + "@esbuild/win32-arm64": "0.25.4", + "@esbuild/win32-ia32": "0.25.4", + "@esbuild/win32-x64": "0.25.4" } }, "node_modules/escalade": { @@ -3718,7 +4202,6 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -3735,7 +4218,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -3748,7 +4230,6 @@ "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -3764,7 +4245,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -3803,7 +4283,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", @@ -3849,7 +4328,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -3932,7 +4410,6 @@ "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -3953,7 +4430,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -4048,11 +4524,20 @@ "react-is": "^16.7.0" } }, - "node_modules/hoist-non-react-statics/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } }, "node_modules/html-parse-stringify": { "version": "3.0.1", @@ -4076,6 +4561,38 @@ "node": ">=8.0.0" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/hyphenate-style-name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", @@ -4106,9 +4623,9 @@ } }, "node_modules/i18next-browser-languagedetector": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.4.tgz", - "integrity": "sha512-f3frU3pIxD50/Tz20zx9TD9HobKYg47fmAETb117GKGPrhwcSSPJDoCposXlVycVebQ9GQohC3Efbpq7/nnJ5w==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.0.tgz", + "integrity": "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.2" @@ -4123,6 +4640,30 @@ "cross-fetch": "4.0.0" } }, + "node_modules/i18next-localstorage-backend": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/i18next-localstorage-backend/-/i18next-localstorage-backend-4.2.0.tgz", + "integrity": "sha512-vglEQF0AnLriX7dLA2drHnqAYzHxnLwWQzBDw8YxcIDjOvYZz5rvpal59Dq4In+IHNmGNM32YgF0TDjBT0fHmA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.22.15" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/immer": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", @@ -4169,7 +4710,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -4197,7 +4737,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4207,7 +4746,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4217,7 +4755,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -4230,24 +4767,30 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -4302,13 +4845,15 @@ } }, "node_modules/jiti": { - "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "bin": { - "jiti": "bin/jiti.js" + "jiti": "lib/jiti-cli.mjs" } }, "node_modules/js-tokens": { @@ -4317,6 +4862,115 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jsdom/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jsdom/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -4376,9 +5030,9 @@ } }, "node_modules/lightningcss": { - "version": "1.29.3", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.3.tgz", - "integrity": "sha512-GlOJwTIP6TMIlrTFsxTerwC0W6OpQpCGuX1ECRLBUVRh6fpJH3xTqjCjRgQHTb4ZXexH9rtHou1Lf03GKzmhhQ==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", "dev": true, "license": "MPL-2.0", "optional": true, @@ -4394,22 +5048,22 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-darwin-arm64": "1.29.3", - "lightningcss-darwin-x64": "1.29.3", - "lightningcss-freebsd-x64": "1.29.3", - "lightningcss-linux-arm-gnueabihf": "1.29.3", - "lightningcss-linux-arm64-gnu": "1.29.3", - "lightningcss-linux-arm64-musl": "1.29.3", - "lightningcss-linux-x64-gnu": "1.29.3", - "lightningcss-linux-x64-musl": "1.29.3", - "lightningcss-win32-arm64-msvc": "1.29.3", - "lightningcss-win32-x64-msvc": "1.29.3" + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.29.3", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.3.tgz", - "integrity": "sha512-fb7raKO3pXtlNbQbiMeEu8RbBVHnpyqAoxTyTRMEWFQWmscGC2wZxoHzZ+YKAepUuKT9uIW5vL2QbFivTgprZg==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", "cpu": [ "arm64" ], @@ -4429,9 +5083,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.29.3", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.3.tgz", - "integrity": "sha512-KF2XZ4ZdmDGGtEYmx5wpzn6u8vg7AdBHaEOvDKu8GOs7xDL/vcU2vMKtTeNe1d4dogkDdi3B9zC77jkatWBwEQ==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", "cpu": [ "x64" ], @@ -4451,9 +5105,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.29.3", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.3.tgz", - "integrity": "sha512-VUWeVf+V1UM54jv9M4wen9vMlIAyT69Krl9XjI8SsRxz4tdNV/7QEPlW6JASev/pYdiynUCW0pwaFquDRYdxMw==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", "cpu": [ "x64" ], @@ -4473,9 +5127,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.29.3", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.3.tgz", - "integrity": "sha512-UhgZ/XVNfXQVEJrMIWeK1Laj8KbhjbIz7F4znUk7G4zeGw7TRoJxhb66uWrEsonn1+O45w//0i0Fu0wIovYdYg==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", "cpu": [ "arm" ], @@ -4495,9 +5149,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.29.3", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.3.tgz", - "integrity": "sha512-Pqau7jtgJNmQ/esugfmAT1aCFy/Gxc92FOxI+3n+LbMHBheBnk41xHDhc0HeYlx9G0xP5tK4t0Koy3QGGNqypw==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", "cpu": [ "arm64" ], @@ -4517,9 +5171,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.29.3", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.3.tgz", - "integrity": "sha512-dxakOk66pf7KLS7VRYFO7B8WOJLecE5OPL2YOk52eriFd/yeyxt2Km5H0BjLfElokIaR+qWi33gB8MQLrdAY3A==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", "cpu": [ "arm64" ], @@ -4539,9 +5193,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.29.3", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.3.tgz", - "integrity": "sha512-ySZTNCpbfbK8rqpKJeJR2S0g/8UqqV3QnzcuWvpI60LWxnFN91nxpSSwCbzfOXkzKfar9j5eOuOplf+klKtINg==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", "cpu": [ "x64" ], @@ -4561,9 +5215,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.29.3", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.3.tgz", - "integrity": "sha512-3pVZhIzW09nzi10usAXfIGTTSTYQ141dk88vGFNCgawIzayiIzZQxEcxVtIkdvlEq2YuFsL9Wcj/h61JHHzuFQ==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", "cpu": [ "x64" ], @@ -4583,9 +5237,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.29.3", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.3.tgz", - "integrity": "sha512-VRnkAvtIkeWuoBJeGOTrZxsNp4HogXtcaaLm8agmbYtLDOhQdpgxW6NjZZjDXbvGF+eOehGulXZ3C1TiwHY4QQ==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", "cpu": [ "arm64" ], @@ -4605,9 +5259,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.29.3", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.3.tgz", - "integrity": "sha512-IszwRPu2cPnDQsZpd7/EAr0x2W7jkaWqQ1SwCVIZ/tSbZVXPLt6k8s6FkcyBjViCzvB5CW0We0QbbP7zp2aBjQ==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", "cpu": [ "x64" ], @@ -4630,7 +5284,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, "license": "MIT", "engines": { "node": ">=14" @@ -4747,7 +5400,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -4757,7 +5409,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -4798,11 +5449,19 @@ "node": ">=4" } }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "license": "MIT", + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -4818,7 +5477,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -4831,9 +5489,9 @@ "license": "MIT" }, "node_modules/mixpanel-browser": { - "version": "2.63.0", - "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.63.0.tgz", - "integrity": "sha512-h7M0J/LR/5YLWCVuvPaYuzwV7CgV9jkJz0m94uaTDPebWkhNQPEir63rf/ZpBZgntyvYjO1yMZp2pIpwQ1sBMQ==", + "version": "2.65.0", + "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.65.0.tgz", + "integrity": "sha512-BtrVYqilloAqx3TIhoIpNikHznTocEy/z3QIf6WEiz4PFxrgI6LgSMFIVKqLqGZJ8svrPlHbpp/CJp5wQYUZWw==", "license": "Apache-2.0", "dependencies": { "rrweb": "2.0.0-alpha.18" @@ -4859,7 +5517,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, "license": "MIT", "dependencies": { "any-promise": "^1.0.0", @@ -4868,9 +5525,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", "funding": [ { "type": "github", @@ -4879,10 +5536,10 @@ ], "license": "MIT", "bin": { - "nanoid": "bin/nanoid.cjs" + "nanoid": "bin/nanoid.js" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": "^18 || >=20" } }, "node_modules/node-fetch": { @@ -4916,7 +5573,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4932,6 +5588,15 @@ "node": ">=0.10.0" } }, + "node_modules/nwsapi": { + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", + "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4945,7 +5610,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -4955,7 +5619,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { @@ -4988,11 +5651,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5008,7 +5685,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -5025,7 +5701,6 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, "license": "ISC" }, "node_modules/path-to-regexp": { @@ -5085,7 +5760,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -5098,7 +5772,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5108,7 +5781,6 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -5146,7 +5818,6 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.0.0", @@ -5164,7 +5835,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, "license": "MIT", "dependencies": { "camelcase-css": "^2.0.1" @@ -5184,7 +5854,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -5220,7 +5889,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -5246,7 +5914,6 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -5260,9 +5927,26 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, "license": "MIT" }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/prettier": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", @@ -5281,9 +5965,9 @@ } }, "node_modules/prettier-plugin-tailwindcss": { - "version": "0.6.11", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.11.tgz", - "integrity": "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==", + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.13.tgz", + "integrity": "sha512-uQ0asli1+ic8xrrSmIOaElDu0FacR4x69GynTh2oZjFY10JUt6EEumTQl5tB4fMeD6I1naKd+4rXQQ7esT2i1g==", "dev": true, "license": "MIT", "engines": { @@ -5395,9 +6079,9 @@ "license": "MIT" }, "node_modules/primereact": { - "version": "10.9.4", - "resolved": "https://registry.npmjs.org/primereact/-/primereact-10.9.4.tgz", - "integrity": "sha512-GMrelh07Wd1cwKjHpay3LCpwP346D43qBVkt8H/anGYC3z7kv5/AP0pizZv+aGQs2Fg5ufTTf+SI7IKWmyzgGg==", + "version": "10.9.5", + "resolved": "https://registry.npmjs.org/primereact/-/primereact-10.9.5.tgz", + "integrity": "sha512-4O6gm0LrKF7Ml8zQmb8mGiWS/ugJ94KBOAS/CAxWFQh9qyNgfNw/qcpCeomPIkjWd98jrM2XDiEbgq+W0395Hw==", "license": "MIT", "dependencies": { "@types/react-transition-group": "^4.4.1", @@ -5428,23 +6112,28 @@ "react-is": "^16.13.1" } }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -5495,16 +6184,10 @@ "shallowequal": "^1.1.0" } }, - "node_modules/rc-animate/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, "node_modules/rc-cascader": { - "version": "3.33.1", - "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.33.1.tgz", - "integrity": "sha512-Kyl4EJ7ZfCBuidmZVieegcbFw0RcU5bHHSbtEdmuLYd0fYHCAiYKZ6zon7fWAVyC6rWWOOib0XKdTSf7ElC9rg==", + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.34.0.tgz", + "integrity": "sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.7", @@ -5567,9 +6250,9 @@ } }, "node_modules/rc-drawer": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-7.2.0.tgz", - "integrity": "sha512-9lOQ7kBekEJRdEpScHvtmEtXnAsy+NGDXiRWc2ZVC7QXAazNVbeT4EraQKYwCME8BJLa8Bxqxvs5swwyOepRwg==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-7.3.0.tgz", + "integrity": "sha512-DX6CIgiBWNpJIMGFO8BAISFkxiuKitoizooj4BDyee8/SnBn0zwO2FHrNDpqqepj0E/TFTDpmEBCyFuTgC7MOg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", @@ -5650,16 +6333,10 @@ "shallowequal": "^1.1.0" } }, - "node_modules/rc-form/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, "node_modules/rc-image": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-7.11.1.tgz", - "integrity": "sha512-XuoWx4KUXg7hNy5mRTy1i8c8p3K8boWg6UajbHpDXS5AlRVucNfTi5YxTtPBTBzegxAZpvuLfh3emXFt6ybUdA==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-7.12.0.tgz", + "integrity": "sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.11.2", @@ -5675,9 +6352,9 @@ } }, "node_modules/rc-input": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.7.3.tgz", - "integrity": "sha512-A5w4egJq8+4JzlQ55FfQjDnPvOaAbzwC3VLOAdOytyek3TboSOP9qxN+Gifup+shVXfvecBLBbWBpWxmk02SWQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.8.0.tgz", + "integrity": "sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.11.1", @@ -5690,15 +6367,15 @@ } }, "node_modules/rc-input-number": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.4.0.tgz", - "integrity": "sha512-Tiy4DcXcFXAf9wDhN8aUAyMeCLHJUHA/VA/t7Hj8ZEx5ETvxG7MArDOSE6psbiSCo+vJPm4E3fGN710ITVn6GA==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.5.0.tgz", + "integrity": "sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/mini-decimal": "^1.0.1", "classnames": "^2.2.5", - "rc-input": "~1.7.1", + "rc-input": "~1.8.0", "rc-util": "^5.40.1" }, "peerDependencies": { @@ -5707,17 +6384,17 @@ } }, "node_modules/rc-mentions": { - "version": "2.19.1", - "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.19.1.tgz", - "integrity": "sha512-KK3bAc/bPFI993J3necmaMXD2reZTzytZdlTvkeBbp50IGH1BDPDvxLdHDUrpQx2b2TGaVJsn+86BvYa03kGqA==", + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.20.0.tgz", + "integrity": "sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.22.5", "@rc-component/trigger": "^2.0.0", "classnames": "^2.2.6", - "rc-input": "~1.7.1", + "rc-input": "~1.8.0", "rc-menu": "~9.16.0", - "rc-textarea": "~1.9.0", + "rc-textarea": "~1.10.0", "rc-util": "^5.34.1" }, "peerDependencies": { @@ -5759,9 +6436,9 @@ } }, "node_modules/rc-notification": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-5.6.3.tgz", - "integrity": "sha512-42szwnn8VYQoT6GnjO00i1iwqV9D1TTMvxObWsuLwgl0TsOokzhkYiufdtQBsJMFjJravS1hfDKVMHLKLcPE4g==", + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-5.6.4.tgz", + "integrity": "sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -5913,9 +6590,9 @@ } }, "node_modules/rc-select": { - "version": "14.16.6", - "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.6.tgz", - "integrity": "sha512-YPMtRPqfZWOm2XGTbx5/YVr1HT0vn//8QS77At0Gjb3Lv+Lbut0IORJPKLWu1hQ3u4GsA0SrDzs7nI8JG7Zmyg==", + "version": "14.16.8", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.8.tgz", + "integrity": "sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -5986,9 +6663,9 @@ } }, "node_modules/rc-table": { - "version": "7.50.4", - "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.50.4.tgz", - "integrity": "sha512-Y+YuncnQqoS5e7yHvfvlv8BmCvwDYDX/2VixTBEhkMDk9itS9aBINp4nhzXFKiBP/frG4w0pS9d9Rgisl0T1Bw==", + "version": "7.51.1", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.51.1.tgz", + "integrity": "sha512-5iq15mTHhvC42TlBLRCoCBLoCmGlbRZAlyF21FonFnS/DIC8DeRqnmdyVREwt2CFbPceM0zSNdEeVfiGaqYsKw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -6007,9 +6684,9 @@ } }, "node_modules/rc-tabs": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-15.5.2.tgz", - "integrity": "sha512-Hbqf2IV6k/jPgfMjPtIDmPV0D0C9c/fN4B/fYcoh9qqaUzUZQoK0PYzsV3UaV+3UsmyoYt48p74m/HkLhGTw+w==", + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-15.6.1.tgz", + "integrity": "sha512-/HzDV1VqOsUWyuC0c6AkxVYFjvx9+rFPKZ32ejxX0Uc7QCzcEjTA9/xMgv4HemPKwzBNX8KhGVbbumDjnj92aA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.11.2", @@ -6029,14 +6706,14 @@ } }, "node_modules/rc-textarea": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.9.0.tgz", - "integrity": "sha512-dQW/Bc/MriPBTugj2Kx9PMS5eXCCGn2cxoIaichjbNvOiARlaHdI99j4DTxLl/V8+PIfW06uFy7kjfUIDDKyxQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.10.0.tgz", + "integrity": "sha512-ai9IkanNuyBS4x6sOL8qu/Ld40e6cEs6pgk93R+XLYg0mDSjNBGey6/ZpDs5+gNLD7urQ14po3V6Ck2dJLt9SA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.1", - "rc-input": "~1.7.1", + "rc-input": "~1.8.0", "rc-resize-observer": "^1.0.0", "rc-util": "^5.27.0" }, @@ -6099,9 +6776,9 @@ } }, "node_modules/rc-upload": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.8.1.tgz", - "integrity": "sha512-toEAhwl4hjLAI1u8/CgKWt30BR06ulPa4iGQSMvSXoHzO88gPCslxqV/mnn4gJU7PDoltGIC9Eh+wkeudqgHyw==", + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.9.2.tgz", + "integrity": "sha512-nHx+9rbd1FKMiMRYsqQ3NkXUv7COHPBo3X1Obwq9SWS6/diF/A0aJ5OHubvwUAIDs+4RMleljV0pcrNUc823GQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", @@ -6134,9 +6811,9 @@ "license": "MIT" }, "node_modules/rc-virtual-list": { - "version": "3.18.5", - "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.18.5.tgz", - "integrity": "sha512-1FuxVSxhzTj3y8k5xMPbhXCB0t2TOiI3Tq+qE2Bu+GGV7f+ECVuQl4OUg6lZ2qT5fordTW7CBpr9czdzXCI7Pg==", + "version": "3.18.6", + "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.18.6.tgz", + "integrity": "sha512-TQ5SsutL3McvWmmxqQtMIbfeoE3dGjJrRSfKekgby7WQMpPIFvv4ghytp5Z0s3D8Nik9i9YNOCqHBfk86AwgAA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.20.0", @@ -6198,9 +6875,9 @@ } }, "node_modules/react-i18next": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.4.1.tgz", - "integrity": "sha512-ahGab+IaSgZmNPYXdV1n+OYky95TGpFwnKRflX/16dY04DsYYKHtVLjeny7sBSCREEcoMbAgSkFiGLF5g5Oofw==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.2.tgz", + "integrity": "sha512-ePODyXgmZQAOYTbZXQn5rRsSBu3Gszo69jxW6aKmlSgxKAI1fOhDwSu6bT4EKHciWPKQ7v7lPrjeiadR6Gi+1A==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.0", @@ -6208,7 +6885,8 @@ }, "peerDependencies": { "i18next": ">= 23.2.3", - "react": ">= 16.8.0" + "react": ">= 16.8.0", + "typescript": "^5" }, "peerDependenciesMeta": { "react-dom": { @@ -6216,9 +6894,18 @@ }, "react-native": { "optional": true + }, + "typescript": { + "optional": true } } }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", @@ -6263,9 +6950,9 @@ } }, "node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", "dev": true, "license": "MIT", "engines": { @@ -6291,9 +6978,9 @@ } }, "node_modules/react-router": { - "version": "6.30.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.0.tgz", - "integrity": "sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==", + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz", + "integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==", "license": "MIT", "dependencies": { "@remix-run/router": "1.23.0" @@ -6306,13 +6993,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.30.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.0.tgz", - "integrity": "sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==", + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz", + "integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==", "license": "MIT", "dependencies": { "@remix-run/router": "1.23.0", - "react-router": "6.30.0" + "react-router": "6.30.1" }, "engines": { "node": ">=14.0.0" @@ -6347,6 +7034,16 @@ "react-dom": ">=16.6.0" } }, + "node_modules/react-virtuoso": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.13.0.tgz", + "integrity": "sha512-XHv2Fglpx80yFPdjZkV9d1baACKghg/ucpDFEXwaix7z0AfVQj+mF6lM+YQR6UC/TwzXG2rJKydRMb3+7iV3PA==", + "license": "MIT", + "peerDependencies": { + "react": ">=16 || >=17 || >= 18 || >= 19", + "react-dom": ">=16 || >=17 || >= 18 || >=19" + } + }, "node_modules/react-window": { "version": "1.8.11", "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.11.tgz", @@ -6364,6 +7061,19 @@ "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/react-window-infinite-loader": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/react-window-infinite-loader/-/react-window-infinite-loader-1.0.10.tgz", + "integrity": "sha512-NO/csdHlxjWqA2RJZfzQgagAjGHspbO2ik9GtWZb0BY1Nnapq0auG8ErI+OhGCzpjYJsCYerqUlK6hkq9dfAAA==", + "license": "MIT", + "engines": { + "node": ">8.0.0" + }, + "peerDependencies": { + "react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/reactcss": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", @@ -6377,7 +7087,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, "license": "MIT", "dependencies": { "pify": "^2.3.0" @@ -6387,7 +7096,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -6426,10 +7134,11 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT" + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT", + "optional": true }, "node_modules/reselect": { "version": "5.1.1", @@ -6476,7 +7185,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -6494,9 +7202,9 @@ } }, "node_modules/rollup": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.39.0.tgz", - "integrity": "sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz", + "integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==", "dev": true, "license": "MIT", "dependencies": { @@ -6510,26 +7218,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.39.0", - "@rollup/rollup-android-arm64": "4.39.0", - "@rollup/rollup-darwin-arm64": "4.39.0", - "@rollup/rollup-darwin-x64": "4.39.0", - "@rollup/rollup-freebsd-arm64": "4.39.0", - "@rollup/rollup-freebsd-x64": "4.39.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.39.0", - "@rollup/rollup-linux-arm-musleabihf": "4.39.0", - "@rollup/rollup-linux-arm64-gnu": "4.39.0", - "@rollup/rollup-linux-arm64-musl": "4.39.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.39.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.39.0", - "@rollup/rollup-linux-riscv64-gnu": "4.39.0", - "@rollup/rollup-linux-riscv64-musl": "4.39.0", - "@rollup/rollup-linux-s390x-gnu": "4.39.0", - "@rollup/rollup-linux-x64-gnu": "4.39.0", - "@rollup/rollup-linux-x64-musl": "4.39.0", - "@rollup/rollup-win32-arm64-msvc": "4.39.0", - "@rollup/rollup-win32-ia32-msvc": "4.39.0", - "@rollup/rollup-win32-x64-msvc": "4.39.0", + "@rollup/rollup-android-arm-eabi": "4.41.1", + "@rollup/rollup-android-arm64": "4.41.1", + "@rollup/rollup-darwin-arm64": "4.41.1", + "@rollup/rollup-darwin-x64": "4.41.1", + "@rollup/rollup-freebsd-arm64": "4.41.1", + "@rollup/rollup-freebsd-x64": "4.41.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", + "@rollup/rollup-linux-arm-musleabihf": "4.41.1", + "@rollup/rollup-linux-arm64-gnu": "4.41.1", + "@rollup/rollup-linux-arm64-musl": "4.41.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", + "@rollup/rollup-linux-riscv64-gnu": "4.41.1", + "@rollup/rollup-linux-riscv64-musl": "4.41.1", + "@rollup/rollup-linux-s390x-gnu": "4.41.1", + "@rollup/rollup-linux-x64-gnu": "4.41.1", + "@rollup/rollup-linux-x64-musl": "4.41.1", + "@rollup/rollup-win32-arm64-msvc": "4.41.1", + "@rollup/rollup-win32-ia32-msvc": "4.41.1", + "@rollup/rollup-win32-x64-msvc": "4.41.1", "fsevents": "~2.3.2" } }, @@ -6558,6 +7266,15 @@ "rrweb-snapshot": "^2.0.0-alpha.18" } }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/rrweb-snapshot": { "version": "2.0.0-alpha.18", "resolved": "https://registry.npmjs.org/rrweb-snapshot/-/rrweb-snapshot-2.0.0-alpha.18.tgz", @@ -6571,7 +7288,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -6600,6 +7316,30 @@ "node": ">=10" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -6644,7 +7384,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -6657,7 +7396,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6674,7 +7412,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -6802,9 +7539,9 @@ } }, "node_modules/std-env": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.1.tgz", - "integrity": "sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", "dev": true, "license": "MIT" }, @@ -6818,7 +7555,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -6837,7 +7573,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -6852,14 +7587,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -6872,7 +7605,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -6889,7 +7621,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -6902,7 +7633,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -6934,7 +7664,6 @@ "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", @@ -7001,11 +7730,19 @@ "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/tailwindcss": { "version": "3.4.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", - "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -7039,15 +7776,24 @@ "node": ">=14.0.0" } }, + "node_modules/tailwindcss/node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, "node_modules/terser": { - "version": "5.39.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", - "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", + "version": "5.39.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.2.tgz", + "integrity": "sha512-yEPUmWve+VA78bI71BW70Dh0TuV4HHd+I5SHOAfS1+QBOmvmCiiffgjR8ryyEd3KIfvPGFqoADt8LdQ6XpXIvg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.14.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -7078,7 +7824,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, "license": "MIT", "dependencies": { "any-promise": "^1.0.0" @@ -7088,7 +7833,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, "license": "MIT", "dependencies": { "thenify": ">= 3.1.0 < 4" @@ -7126,10 +7870,55 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tinymce": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-7.7.2.tgz", - "integrity": "sha512-GX7Jd0ac9ph3QM2yei4uOoxytKX096CyG6VkkgQNikY39T6cDldoNgaqzHHlcm62WtdBMCd7Ch+PYaRnQo+NLA==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-7.9.0.tgz", + "integrity": "sha512-tTrUmUGWqy1BY1WwDYh4WiuHm23LiRTcE1Xq3WLO8HKFzde/d0bTF/hXWOa97zqGh0ndJHx/nysQaNC9Gcd16g==", "license": "GPL-2.0-or-later" }, "node_modules/tinypool": { @@ -7162,11 +7951,34 @@ "node": ">=14.0.0" } }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -7181,6 +7993,21 @@ "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", "license": "MIT" }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -7191,13 +8018,12 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true, "license": "Apache-2.0" }, "node_modules/tsconfck": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.5.tgz", - "integrity": "sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", "dev": true, "license": "MIT", "bin": { @@ -7225,7 +8051,7 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -7286,7 +8112,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, "node_modules/utrie": { @@ -7298,16 +8123,28 @@ "base64-arraybuffer": "^1.0.2" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vite": { - "version": "6.2.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz", - "integrity": "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==", + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", "postcss": "^8.5.3", - "rollup": "^4.30.1" + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" @@ -7371,15 +8208,15 @@ } }, "node_modules/vite-node": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.1.tgz", - "integrity": "sha512-V+IxPAE2FvXpTCHXyNem0M+gWm6J7eRyWPR6vYoG/Gl+IscNOjXzztUhimQgTxaAoUoj40Qqimaa0NLIOOAH4w==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.4.tgz", + "integrity": "sha512-6enNwYnpyDo4hEgytbmc6mYWHXDHYEn0D1/rw4Q+tnHUGtKTJsn8T1YkX6Q18wI5LCrS8CTYlBaiCqxOy2kvUA==", "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", "debug": "^4.4.0", - "es-module-lexer": "^1.6.0", + "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0" }, @@ -7413,32 +8250,61 @@ } } }, + "node_modules/vite/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/vitest": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.1.tgz", - "integrity": "sha512-kiZc/IYmKICeBAZr9DQ5rT7/6bD9G7uqQEki4fxazi1jdVl2mWGzedtBs5s6llz59yQhVb7FFY2MbHzHCnT79Q==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.4.tgz", + "integrity": "sha512-Ta56rT7uWxCSJXlBtKgIlApJnT6e6IGmTYxYcmxjJ4ujuZDI59GUQgVDObXXJujOmPDBYXHK1qmaGtneu6TNIQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "3.1.1", - "@vitest/mocker": "3.1.1", - "@vitest/pretty-format": "^3.1.1", - "@vitest/runner": "3.1.1", - "@vitest/snapshot": "3.1.1", - "@vitest/spy": "3.1.1", - "@vitest/utils": "3.1.1", + "@vitest/expect": "3.1.4", + "@vitest/mocker": "3.1.4", + "@vitest/pretty-format": "^3.1.4", + "@vitest/runner": "3.1.4", + "@vitest/snapshot": "3.1.4", + "@vitest/spy": "3.1.4", + "@vitest/utils": "3.1.4", "chai": "^5.2.0", "debug": "^4.4.0", - "expect-type": "^1.2.0", + "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", - "std-env": "^3.8.1", + "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.13", "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", - "vite-node": "3.1.1", + "vite-node": "3.1.4", "why-is-node-running": "^2.3.0" }, "bin": { @@ -7454,8 +8320,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.1.1", - "@vitest/ui": "3.1.1", + "@vitest/browser": "3.1.4", + "@vitest/ui": "3.1.4", "happy-dom": "*", "jsdom": "*" }, @@ -7492,6 +8358,21 @@ "node": ">=0.10.0" } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/warning": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", @@ -7513,6 +8394,33 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "license": "BSD-2-Clause" }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -7527,7 +8435,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -7556,11 +8463,14 @@ "node": ">=8" } }, + "node_modules/worklenz": { + "resolved": "", + "link": true + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -7579,7 +8489,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -7597,14 +8506,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -7619,7 +8526,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -7632,7 +8538,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -7662,6 +8567,27 @@ } } }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/xmlhttprequest-ssl": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", @@ -7678,16 +8604,15 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", - "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", - "dev": true, + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", "license": "ISC", "bin": { "yaml": "bin.mjs" }, "engines": { - "node": ">= 14" + "node": ">= 14.6" } } } diff --git a/worklenz-frontend/package.json b/worklenz-frontend/package.json index 31b7f8bf..7e25181c 100644 --- a/worklenz-frontend/package.json +++ b/worklenz-frontend/package.json @@ -3,7 +3,8 @@ "version": "1.0.0", "private": true, "scripts": { - "start": "vite", + "start": "vite dev", + "dev": "vite dev", "prebuild": "node scripts/copy-tinymce.js", "build": "vite build", "dev-build": "vite build", @@ -13,31 +14,36 @@ "dependencies": { "@ant-design/colors": "^7.1.0", "@ant-design/compatible": "^5.1.4", - "@ant-design/icons": "^5.4.0", + "@ant-design/icons": "^4.7.0", "@ant-design/pro-components": "^2.7.19", "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", "@emotion/react": "^11.14.0", + "@heroicons/react": "^2.2.0", "@paddle/paddle-js": "^1.3.3", "@reduxjs/toolkit": "^2.2.7", + "@tailwindcss/forms": "^0.5.10", "@tanstack/react-table": "^8.20.6", "@tanstack/react-virtual": "^3.11.2", "@tinymce/tinymce-react": "^5.1.1", - "antd": "^5.24.1", - "axios": "^1.7.9", + "antd": "^5.26.2", + "axios": "^1.9.0", "chart.js": "^4.4.7", "chartjs-plugin-datalabels": "^2.2.0", + "cors": "^2.8.5", "date-fns": "^4.1.0", - "dompurify": "^3.2.4", + "dompurify": "^3.2.5", "gantt-task-react": "^0.3.9", "html2canvas": "^1.4.1", "i18next": "^23.16.8", - "i18next-browser-languagedetector": "^8.0.3", + "i18next-browser-languagedetector": "^8.2.0", "i18next-http-backend": "^2.7.3", + "i18next-localstorage-backend": "^4.2.0", "jspdf": "^3.0.0", "mixpanel-browser": "^2.56.0", + "nanoid": "^5.1.5", "primereact": "^10.8.4", "re-resizable": "^6.10.3", "react": "^18.3.1", @@ -49,10 +55,13 @@ "react-responsive": "^10.0.0", "react-router-dom": "^6.28.1", "react-timer-hook": "^3.0.8", + "react-virtuoso": "^4.13.0", "react-window": "^1.8.11", + "react-window-infinite-loader": "^1.0.10", "socket.io-client": "^4.8.1", "tinymce": "^7.7.2", - "web-vitals": "^4.2.4" + "web-vitals": "^4.2.4", + "worklenz": "file:" }, "devDependencies": { "@testing-library/jest-dom": "^6.6.3", @@ -66,14 +75,16 @@ "@types/node": "^20.8.4", "@types/react": "19.0.0", "@types/react-dom": "19.0.0", + "@types/react-window": "^1.8.8", "@vitejs/plugin-react": "^4.3.4", - "autoprefixer": "^10.4.20", + "autoprefixer": "^10.4.21", "postcss": "^8.5.2", - "prettier-plugin-tailwindcss": "^0.6.8", + "prettier-plugin-tailwindcss": "^0.6.13", + "rollup": "^4.40.2", "tailwindcss": "^3.4.17", "terser": "^5.39.0", "typescript": "^5.7.3", - "vite": "^6.2.5", + "vite": "^6.3.5", "vite-tsconfig-paths": "^5.1.4", "vitest": "^3.0.5" }, diff --git a/worklenz-frontend/path/to/members-reports-drawer.tsx b/worklenz-frontend/path/to/members-reports-drawer.tsx deleted file mode 100644 index b9671dc1..00000000 --- a/worklenz-frontend/path/to/members-reports-drawer.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import MembersReportsTimeLogsTab from './members-reports-time-logs-tab'; - -type MembersReportsDrawerProps = { - memberId: string | null; - exportTimeLogs: () => void; -}; - -const MembersReportsDrawer = ({ memberId, exportTimeLogs }: MembersReportsDrawerProps) => { - return ( - - - {selectedMember.name} - - - - - - - - - - ) - } - > - {selectedMember && } - {selectedMember && } - {selectedMember && } - - ); -}; - -export default MembersReportsDrawer; \ No newline at end of file diff --git a/worklenz-frontend/path/to/members-reports-time-logs-tab.tsx b/worklenz-frontend/path/to/members-reports-time-logs-tab.tsx deleted file mode 100644 index a86c66ba..00000000 --- a/worklenz-frontend/path/to/members-reports-time-logs-tab.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React, { useState } from 'react'; -import { Flex, Skeleton } from 'antd'; -import { useTranslation } from 'react-i18next'; -import { useTimeLogs } from '../contexts/TimeLogsContext'; -import { BillableFilter } from './BillableFilter'; -import { TimeLogCard } from './TimeLogCard'; -import { EmptyListPlaceholder } from './EmptyListPlaceholder'; -import { TaskDrawer } from './TaskDrawer'; -import MembersReportsDrawer from './members-reports-drawer'; - -const MembersReportsTimeLogsTab: React.FC = () => { - const { t } = useTranslation(); - const { timeLogsData, billable, setBillable, exportTimeLogs, exporting } = useTimeLogs(); - - return ( - - - - - - - {timeLogsData.length > 0 ? ( - - {timeLogsData.map((logs, index) => ( - - ))} - - ) : ( - - )} - - - {createPortal(, document.body)} - - - ); -}; - -export default MembersReportsTimeLogsTab; \ No newline at end of file diff --git a/worklenz-frontend/project-report-table.css b/worklenz-frontend/project-report-table.css index 3e67444c..ec1909a8 100644 --- a/worklenz-frontend/project-report-table.css +++ b/worklenz-frontend/project-report-table.css @@ -14,4 +14,4 @@ /* Maintain hover state */ .table-body-row:hover .sticky-column { background-color: var(--background-hover); -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/env-config.js b/worklenz-frontend/public/env-config.js new file mode 100644 index 00000000..52a87582 --- /dev/null +++ b/worklenz-frontend/public/env-config.js @@ -0,0 +1,7 @@ +// Development placeholder for env-config.js +// In production, this file is dynamically generated with actual environment values +// For development, we let the application fall back to import.meta.env variables + +// Set undefined values so the application falls back to build-time env vars +window.VITE_API_URL = undefined; +window.VITE_SOCKET_URL = undefined; diff --git a/worklenz-frontend/public/locales/alb/all-project-list.json b/worklenz-frontend/public/locales/alb/all-project-list.json index 0e090edb..8079f13d 100644 --- a/worklenz-frontend/public/locales/alb/all-project-list.json +++ b/worklenz-frontend/public/locales/alb/all-project-list.json @@ -3,21 +3,32 @@ "client": "Klienti", "category": "Kategoria", "status": "Statusi", - "tasksProgress": "Progresi i Detyrave", - "updated_at": "Përditësuar Së Fundi", + "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ë Preferuarat", - "archived": "Të Arkivuara", + "all": "Të gjitha", + "favorites": "Të preferuarit", + "archived": "E arkivuar", "placeholder": "Kërko sipas emrit", "archive": "Arkivo", - "unarchive": "Ç'arkivo", - "archiveConfirm": "Jeni i sigurt që doni ta arkivoni këtë projekt?", - "unarchiveConfirm": "Jeni i sigurt që doni ta çarkivoni këtë projekt?", - "clickToFilter": "Klikoni për të filtruar sipas", + "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 në të preferuarat" + "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" } diff --git a/worklenz-frontend/public/locales/alb/kanban-board.json b/worklenz-frontend/public/locales/alb/kanban-board.json index e14f338b..def705aa 100644 --- a/worklenz-frontend/public/locales/alb/kanban-board.json +++ b/worklenz-frontend/public/locales/alb/kanban-board.json @@ -19,5 +19,12 @@ "archive": "Arkivo", "newTaskNamePlaceholder": "Shkruaj emrin e detyrës", - "newSubtaskNamePlaceholder": "Shkruaj emrin e nëndetyrë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" } diff --git a/worklenz-frontend/public/locales/alb/phases-drawer.json b/worklenz-frontend/public/locales/alb/phases-drawer.json index de34c740..b0ba817b 100644 --- a/worklenz-frontend/public/locales/alb/phases-drawer.json +++ b/worklenz-frontend/public/locales/alb/phases-drawer.json @@ -1,7 +1,24 @@ { "configurePhases": "Konfiguro Fazat", + "configure": "Konfiguro", "phaseLabel": "Etiketa e Fazës", - "enterPhaseName": "Vendosni një emër për etiketën e fazës", + "enterPhaseName": "Shkruaj emrin e fazës", "addOption": "Shto Opsion", - "phaseOptions": "Opsionet e Fazës:" + "phaseOptions": "Opsionet e Fazës", + "optionsText": "Opsione", + "dragToReorderPhases": "Tërhiq fazat për t'i rirenditur. Çdo fazë mund të ketë një ngjyrë të ndryshme.", + "enterNewPhaseName": "Shkruaj emrin e fazës së re...", + "addPhase": "Shto Fazë", + "noPhasesFound": "Nuk u gjetën faza", + "no": "Asnjë", + "found": "u gjet", + "deletePhase": "Fshi Fazën", + "deletePhaseConfirm": "Jeni i sigurt që doni të fshini këtë fazë? Ky veprim nuk mund të zhbëhet.", + "rename": "Riemërto", + "delete": "Fshi", + "create": "Krijo", + "cancel": "Anulo", + "selectColor": "Zgjidh ngjyrën", + "managePhases": "Menaxho Fazat", + "close": "Mbyll" } diff --git a/worklenz-frontend/public/locales/alb/project-view.json b/worklenz-frontend/public/locales/alb/project-view.json new file mode 100644 index 00000000..2bc256fe --- /dev/null +++ b/worklenz-frontend/public/locales/alb/project-view.json @@ -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" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/alb/project-view/project-view-header.json b/worklenz-frontend/public/locales/alb/project-view/project-view-header.json index 8d7b9d39..e786f22e 100644 --- a/worklenz-frontend/public/locales/alb/project-view/project-view-header.json +++ b/worklenz-frontend/public/locales/alb/project-view/project-view-header.json @@ -1,13 +1,31 @@ { - "importTasks": "Importo detyra", + "importTasks": "Importo detyrat", + "importTask": "Importo detyrën", "createTask": "Krijo detyrë", "settings": "Cilësimet", "subscribe": "Abonohu", - "unsubscribe": "Ç'abonohu", - "deleteProject": "Fshi projektin", + "unsubscribe": "Çabonohu", + "deleteProject": "Fshij projektin", "startDate": "Data e fillimit", "endDate": "Data e përfundimit", "projectSettings": "Cilësimet e projektit", "projectSummary": "Përmbledhja e projektit", - "receiveProjectSummary": "Merrni një përmbledhje të projektit çdo mbrëmje." + "receiveProjectSummary": "Merr një përmbledhje të projektit çdo mbrëmje.", + "refreshProject": "Rifresko projektin", + "saveAsTemplate": "Ruaj si shabllon", + "invite": "Fto", + "share": "Ndaj", + "subscribeTooltip": "Abonohu në njoftimet e projektit", + "unsubscribeTooltip": "Çabonohu nga njoftimet e projektit", + "refreshTooltip": "Rifresko të dhënat e projektit", + "settingsTooltip": "Hap cilësimet e projektit", + "saveAsTemplateTooltip": "Ruaj këtë projekt si shabllon", + "inviteTooltip": "Fto anëtarët e ekipit në këtë projekt", + "createTaskTooltip": "Krijo një detyrë të re", + "importTaskTooltip": "Importo detyrë nga shablloni", + "navigateBackTooltip": "Kthehu në listën e projekteve", + "projectStatusTooltip": "Statusi i projektit", + "projectDatesInfo": "Informacioni i afateve të projektit", + "projectCategoryTooltip": "Kategoria e projektit", + "defaultTaskName": "Detyrë Pa Emër" } diff --git a/worklenz-frontend/public/locales/alb/settings/profile.json b/worklenz-frontend/public/locales/alb/settings/profile.json index c3ad210d..dcce50d5 100644 --- a/worklenz-frontend/public/locales/alb/settings/profile.json +++ b/worklenz-frontend/public/locales/alb/settings/profile.json @@ -9,5 +9,6 @@ "saveChanges": "Ruaj Ndryshimet", "profileJoinedText": "U bashkua një muaj më parë", "profileLastUpdatedText": "Përditësuar një muaj më parë", - "avatarTooltip": "Klikoni për të ngarkuar një avatar" + "avatarTooltip": "Klikoni për të ngarkuar një avatar", + "title": "Cilësimet e Profilit" } diff --git a/worklenz-frontend/public/locales/alb/settings/team-members.json b/worklenz-frontend/public/locales/alb/settings/team-members.json index 0ebdb3b5..955954dc 100644 --- a/worklenz-frontend/public/locales/alb/settings/team-members.json +++ b/worklenz-frontend/public/locales/alb/settings/team-members.json @@ -1,4 +1,5 @@ { + "title": "Anëtarët e Ekipit", "nameColumn": "Emri", "projectsColumn": "Projektet", "emailColumn": "Email", @@ -40,5 +41,7 @@ "ownerText": "Pronar i Ekipit", "addedText": "Shtuar", "updatedText": "Përditësuar", - "noResultFound": "Shkruani një adresë email dhe shtypni Enter..." + "noResultFound": "Shkruani një adresë email dhe shtypni Enter...", + "jobTitlesFetchError": "Dështoi marrja e titujve të punës", + "invitationResent": "Ftesa u dërgua sërish me sukses!" } diff --git a/worklenz-frontend/public/locales/alb/settings/teams.json b/worklenz-frontend/public/locales/alb/settings/teams.json new file mode 100644 index 00000000..30f87d79 --- /dev/null +++ b/worklenz-frontend/public/locales/alb/settings/teams.json @@ -0,0 +1,16 @@ +{ + "title": "Ekipet", + "team": "Ekip", + "teams": "Ekipet", + "name": "Emri", + "created": "Krijuar", + "ownsBy": "I përket", + "edit": "Ndrysho", + "editTeam": "Ndrysho Ekipin", + "pinTooltip": "Kliko për ta fiksuar në menunë kryesore", + "editTeamName": "Ndrysho Emrin e Ekipit", + "updateName": "Përditëso Emrin", + "namePlaceholder": "Emri", + "nameRequired": "Ju lutem shkruani një Emër", + "updateFailed": "Ndryshimi i emrit të ekipit dështoi!" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/alb/task-drawer/task-drawer.json b/worklenz-frontend/public/locales/alb/task-drawer/task-drawer.json index d2f7ef99..9d6c022f 100644 --- a/worklenz-frontend/public/locales/alb/task-drawer/task-drawer.json +++ b/worklenz-frontend/public/locales/alb/task-drawer/task-drawer.json @@ -1,28 +1,37 @@ { "taskHeader": { - "taskNamePlaceholder": "Shkruani detyrën tuaj", + "taskNamePlaceholder": "Shkruani Detyrën tuaj", "deleteTask": "Fshi Detyrën" }, "taskInfoTab": { - "title": "Info", + "title": "Informacioni", "details": { "title": "Detajet", "task-key": "Çelësi i Detyrës", "phase": "Faza", - "assignees": "Përgjegjësit", - "due-date": "Afati i Përfundimit", + "assignees": "Të Caktuar", + "due-date": "Data e Përfundimit", "time-estimation": "Vlerësimi i Kohës", "priority": "Prioriteti", - "labels": "Etiketa", - "billable": "Fakturueshme", + "labels": "Etiketat", + "billable": "E Faturueshme", "notify": "Njofto", - "when-done-notify": "Kur të përfundojë, njofto", + "when-done-notify": "Kur përfundon, njofto", "start-date": "Data e Fillimit", "end-date": "Data e Përfundimit", "hide-start-date": "Fshih Datën e Fillimit", "show-start-date": "Shfaq Datën e Fillimit", "hours": "Orë", - "minutes": "Minuta" + "minutes": "Minuta", + "progressValue": "Vlera e Progresit", + "progressValueTooltip": "Vendosni përqindjen e progresit (0-100%)", + "progressValueRequired": "Ju lutemi vendosni një vlerë progresi", + "progressValueRange": "Progresi duhet të jetë midis 0 dhe 100", + "taskWeight": "Pesha e Detyrës", + "taskWeightTooltip": "Vendosni peshën e kësaj nëndetyre (përqindje)", + "taskWeightRequired": "Ju lutemi vendosni një peshë detyre", + "taskWeightRange": "Pesha duhet të jetë midis 0 dhe 100", + "recurring": "E Përsëritur" }, "labels": { "labelInputPlaceholder": "Kërko ose krijo", @@ -30,37 +39,48 @@ }, "description": { "title": "Përshkrimi", - "placeholder": "Shtoni një përshkrim më të detajuar..." + "placeholder": "Shto një përshkrim më të detajuar..." }, "subTasks": { - "title": "Nën-Detyrat", - "addSubTask": "+ Shto Nën-Detyrë", - "addSubTaskInputPlaceholder": "Shkruani detyrën dhe shtypni Enter", - "refreshSubTasks": "Rifresko Nën-Detyrat", + "title": "Nëndetyrat", + "addSubTask": "Shto Nëndetyrë", + "addSubTaskInputPlaceholder": "Shkruani detyrën tuaj dhe shtypni enter", + "refreshSubTasks": "Rifresko Nëndetyrat", "edit": "Modifiko", "delete": "Fshi", - "confirmDeleteSubTask": "Jeni i sigurt që doni të fshini këtë nën-detyrë?", - "deleteSubTask": "Fshi Nën-Detyrën" + "confirmDeleteSubTask": "Jeni i sigurt që doni të fshini këtë nëndetyrë?", + "deleteSubTask": "Fshi Nëndetyrën" }, "dependencies": { "title": "Varësitë", "addDependency": "+ Shto varësi të re", - "blockedBy": "I bllokuar nga", - "searchTask": "Shkruani për të kërkuar detyra", - "noTasksFound": "Asnjë detyrë nuk u gjet", + "blockedBy": "Bllokuar nga", + "searchTask": "Shkruani për të kërkuar detyrë", + "noTasksFound": "Nuk u gjetën detyra", "confirmDeleteDependency": "Jeni i sigurt që doni të fshini?" }, "attachments": { "title": "Bashkëngjitjet", - "chooseOrDropFileToUpload": "Zgjidhni ose lëshoni skedar për ngarkim", - "uploading": "Po ngarkohet..." + "chooseOrDropFileToUpload": "Zgjidhni ose hidhni skedar për të ngarkuar", + "uploading": "Duke ngarkuar..." }, "comments": { "title": "Komentet", "addComment": "+ Shto koment të ri", - "noComments": "Asnjë koment ende. Bëhu i pari që komenton!", + "noComments": "Ende pa komente. Bëhu i pari që komenton!", "delete": "Fshi", - "confirmDeleteComment": "Jeni i sigurt që doni të fshini këtë koment?" + "confirmDeleteComment": "Jeni i sigurt që doni të fshini këtë koment?", + "addCommentPlaceholder": "Shto një koment...", + "cancel": "Anulo", + "commentButton": "Komento", + "attachFiles": "Bashkëngjit skedarë", + "addMoreFiles": "Shto më shumë skedarë", + "selectedFiles": "Skedarët e Zgjedhur (Deri në 25MB, Maksimumi {count})", + "maxFilesError": "Mund të ngarkoni maksimum {count} skedarë", + "processFilesError": "Dështoi përpunimi i skedarëve", + "addCommentError": "Ju lutemi shtoni një koment ose bashkëngjitni skedarë", + "createdBy": "Krijuar {{time}} nga {{user}}", + "updatedTime": "Përditësuar {{time}}" }, "searchInputPlaceholder": "Kërko sipas emrit", "pendingInvitation": "Ftesë në Pritje" @@ -68,11 +88,36 @@ "taskTimeLogTab": { "title": "Regjistri i Kohës", "addTimeLog": "Shto regjistrim të ri kohe", - "totalLogged": "Koha totale e regjistruar", + "totalLogged": "Totali i Regjistruar", "exportToExcel": "Eksporto në Excel", - "noTimeLogsFound": "Asnjë regjistrim kohe nuk u gjet" + "noTimeLogsFound": "Nuk u gjetën regjistra kohe", + "timeLogForm": { + "date": "Data", + "startTime": "Koha e Fillimit", + "endTime": "Koha e Përfundimit", + "workDescription": "Përshkrimi i Punës", + "descriptionPlaceholder": "Shto një përshkrim", + "logTime": "Regjistro kohën", + "updateTime": "Përditëso kohën", + "cancel": "Anulo", + "selectDateError": "Ju lutemi zgjidhni një datë", + "selectStartTimeError": "Ju lutemi zgjidhni kohën e fillimit", + "selectEndTimeError": "Ju lutemi zgjidhni kohën e përfundimit", + "endTimeAfterStartError": "Koha e përfundimit duhet të jetë pas kohës së fillimit" + } }, "taskActivityLogTab": { - "title": "Regjistri i Aktivitetit" + "title": "Regjistri i Aktivitetit", + "add": "SHTO", + "remove": "HIQE", + "none": "Asnjë", + "weight": "Pesha", + "createdTask": "krijoi detyrën." + }, + "taskProgress": { + "markAsDoneTitle": "Shëno Detyrën si të Kryer?", + "confirmMarkAsDone": "Po, shëno si të kryer", + "cancelMarkAsDone": "Jo, mbaj statusin aktual", + "markAsDoneDescription": "Keni vendosur progresin në 100%. Doni të përditësoni statusin e detyrës në \"Kryer\"?" } } diff --git a/worklenz-frontend/public/locales/alb/task-list-filters.json b/worklenz-frontend/public/locales/alb/task-list-filters.json index 1e1b649d..4fc4dbdf 100644 --- a/worklenz-frontend/public/locales/alb/task-list-filters.json +++ b/worklenz-frontend/public/locales/alb/task-list-filters.json @@ -7,53 +7,82 @@ "statusText": "Statusi", "phaseText": "Faza", "memberText": "Anëtarët", - "assigneesText": "Përgjegjësit", + "assigneesText": "Të caktuarit", "priorityText": "Prioriteti", - "labelsText": "Etiketa", + "labelsText": "Etiketat", "membersText": "Anëtarët", "groupByText": "Grupo sipas", - "showArchivedText": "Shfaq të arkivuara", + "showArchivedText": "Shfaq të arkivuarat", "showFieldsText": "Shfaq fushat", "keyText": "Çelësi", "taskText": "Detyra", "descriptionText": "Përshkrimi", "phasesText": "Fazat", - "listText": "Listë", + "listText": "Lista", "progressText": "Progresi", - "timeTrackingText": "Gjurmimi i Kohës", - "timetrackingText": "Gjurmimi i Kohës", + "timeTrackingText": "Ndjekja e kohës", + "timetrackingText": "Ndjekja e kohës", "estimationText": "Vlerësimi", - "startDateText": "Data e Fillimit", - "startdateText": "Data e Fillimit", - "endDateText": "Data e Përfundimit", - "dueDateText": "Afati", - "duedateText": "Afati", - "completedDateText": "Data e Përfundimit", - "completeddateText": "Data e Përfundimit", - "createdDateText": "Data e Krijimit", - "createddateText": "Data e Krijimit", - "lastUpdatedText": "Përditësuar Së Fundi", - "lastupdatedText": "Përditësuar Së Fundi", + "startDateText": "Data e fillimit", + "startdateText": "Data e fillimit", + "endDateText": "Data e mbarimit", + "dueDateText": "Data e afatit", + "duedateText": "Data e afatit", + "completedDateText": "Data e përfundimit", + "completeddateText": "Data e përfundimit", + "createdDateText": "Data e krijimit", + "createddateText": "Data e krijimit", + "lastUpdatedText": "Përditësimi i fundit", + "lastupdatedText": "Përditësimi i fundit", "reporterText": "Raportuesi", - "dueTimeText": "Koha e Afatit", - "duetimeText": "Koha e Afatit", + "dueTimeText": "Koha e afatit", + "duetimeText": "Koha e afatit", - "lowText": "I ulët", - "mediumText": "I mesëm", - "highText": "I lartë", + "lowText": "E ulët", + "mediumText": "E mesme", + "highText": "E lartë", "createStatusButtonTooltip": "Cilësimet e statusit", "configPhaseButtonTooltip": "Cilësimet e fazës", "noLabelsFound": "Nuk u gjetën etiketa", - "addStatusButton": "Shto Status", - "addPhaseButton": "Shto Fazë", + "addStatusButton": "Shto status", + "addPhaseButton": "Shto fazë", - "createStatus": "Krijo Status", + "createStatus": "Krijo status", "name": "Emri", "category": "Kategoria", "selectCategory": "Zgjidh një kategori", - "pleaseEnterAName": "Ju lutemi vendosni një emër", + "pleaseEnterAName": "Ju lutemi shkruani një emër", "pleaseSelectACategory": "Ju lutemi zgjidhni një kategori", - "create": "Krijo" + "create": "Krijo", + + "searchTasks": "Kërko detyra...", + "searchPlaceholder": "Kërko...", + "fieldsText": "Fushat", + "loadingFilters": "Po ngarkohen filtrat...", + "noOptionsFound": "Nuk u gjetën opcione", + "filtersActive": "filtra aktiv", + "filterActive": "filtër aktiv", + "clearAll": "Pastro të gjitha", + "clearing": "Po pastron...", + "cancel": "Anulo", + "search": "Kërko", + "groupedBy": "Grupuar sipas", + "manage": "Menaxho", + "manageStatuses": "Menaxho Statuset", + "managePhases": "Menaxho Fazat", + "dragToReorderStatuses": "Statuset janë të organizuara sipas kategorive. Tërhiq për të rirenditur brenda kategorive. Kliko 'Shto status' për të krijuar statuse të reja në çdo kategori.", + "enterNewStatusName": "Shkruani emrin e statusit të ri...", + "addStatus": "Shto status", + "noStatusesFound": "Nuk ka statuse në këtë kategori", + "deleteStatus": "Fshi statusin", + "deleteStatusConfirm": "Jeni të sigurt që doni të fshini këtë status? Ky veprim nuk mund të zhbëhet.", + "rename": "Riemërto", + "delete": "Fshi", + "enterStatusName": "Shkruani emrin e statusit", + "close": "Mbyll", + "cannotMoveStatus": "Nuk mund të lëvizet statusi", + "cannotMoveStatusMessage": "Nuk mund të lëvizet ky status sepse do të linte kategorinë '{{categoryName}}' bosh. Çdo kategori duhet të ketë të paktën një status.", + "ok": "OK" } diff --git a/worklenz-frontend/public/locales/alb/task-list-table.json b/worklenz-frontend/public/locales/alb/task-list-table.json index cfb2d398..7e3f83dd 100644 --- a/worklenz-frontend/public/locales/alb/task-list-table.json +++ b/worklenz-frontend/public/locales/alb/task-list-table.json @@ -36,8 +36,9 @@ "selectText": "Zgjidh", "labelsSelectorInputTip": "Shtyp Enter për të krijuar!", - "addTaskText": "+ Shto Detyrë", + "addTaskText": "Shto Detyrë", "addSubTaskText": "+ Shto Nën-Detyrë", + "noTasksInGroup": "Nuk ka detyra në këtë grup", "addTaskInputPlaceholder": "Shkruaj detyrën dhe shtyp Enter", "openButton": "Hap", @@ -47,6 +48,9 @@ "searchInputPlaceholder": "Kërko ose krijo", "assigneeSelectorInviteButton": "Fto një anëtar të ri me email", "labelInputPlaceholder": "Kërko ose krijo", + "searchLabelsPlaceholder": "Kërko etiketa...", + "createLabelButton": "Krijo \"{{name}}\"", + "manageLabelsPath": "Cilësimet → Etiketat", "pendingInvitation": "Ftesë në Pritje", @@ -59,5 +63,74 @@ "convertToTask": "Shndërro në Detyrë", "delete": "Fshi", "searchByNameInputPlaceholder": "Kërko sipas emrit" + }, + "setDueDate": "Cakto datën e afatit", + "setStartDate": "Cakto datën e fillimit", + "clearDueDate": "Pastro datën e afatit", + "clearStartDate": "Pastro datën e fillimit", + "dueDatePlaceholder": "Data e afatit", + "startDatePlaceholder": "Data e fillimit", + + "emptyStates": { + "noTaskGroups": "Nuk u gjetën grupe detyrash", + "noTaskGroupsDescription": "Detyrat do të shfaqen këtu kur krijohen ose kur aplikohen filtra.", + "errorPrefix": "Gabim:", + "dragTaskFallback": "Detyrë" + }, + + "customColumns": { + "addCustomColumn": "Shto një kolonë të personalizuar", + "customColumnHeader": "Kolona e Personalizuar", + "customColumnSettings": "Cilësimet e kolonës së personalizuar", + "noCustomValue": "Asnjë vlerë", + "peopleField": "Fusha e njerëzve", + "noDate": "Asnjë datë", + "unsupportedField": "Lloj fushe i pambështetur", + + "modal": { + "addFieldTitle": "Shto fushë", + "editFieldTitle": "Redakto fushën", + "fieldTitle": "Titulli i fushës", + "fieldTitleRequired": "Titulli i fushës është i kërkuar", + "columnTitlePlaceholder": "Titulli i kolonës", + "type": "Lloji", + "deleteConfirmTitle": "Jeni i sigurt që doni të fshini këtë kolonë të personalizuar?", + "deleteConfirmDescription": "Kjo veprim nuk mund të zhbëhet. Të gjitha të dhënat e lidhura me këtë kolonë do të fshihen përgjithmonë.", + "deleteButton": "Fshi", + "cancelButton": "Anulo", + "createButton": "Krijo", + "updateButton": "Përditëso", + "createSuccessMessage": "Kolona e personalizuar u krijua me sukses", + "updateSuccessMessage": "Kolona e personalizuar u përditësua me sukses", + "deleteSuccessMessage": "Kolona e personalizuar u fshi me sukses", + "deleteErrorMessage": "Dështoi në fshirjen e kolonës së personalizuar", + "createErrorMessage": "Dështoi në krijimin e kolonës së personalizuar", + "updateErrorMessage": "Dështoi në përditësimin e kolonës së personalizuar" + }, + + "fieldTypes": { + "people": "Njerëz", + "number": "Numër", + "date": "Data", + "selection": "Zgjedhje", + "checkbox": "Kutia e kontrollit", + "labels": "Etiketat", + "key": "Çelësi", + "formula": "Formula" + } + }, + + "indicators": { + "tooltips": { + "subtasks": "{{count}} nën-detyrë", + "subtasks_plural": "{{count}} nën-detyra", + "comments": "{{count}} koment", + "comments_plural": "{{count}} komente", + "attachments": "{{count}} bashkëngjitje", + "attachments_plural": "{{count}} bashkëngjitje", + "subscribers": "Detyra ka pajtues", + "dependencies": "Detyra ka varësi", + "recurring": "Detyrë përsëritëse" + } } } diff --git a/worklenz-frontend/public/locales/alb/task-management.json b/worklenz-frontend/public/locales/alb/task-management.json new file mode 100644 index 00000000..ca85096b --- /dev/null +++ b/worklenz-frontend/public/locales/alb/task-management.json @@ -0,0 +1,39 @@ +{ + "noTasksInGroup": "Nuk ka detyra në këtë grup", + "noTasksInGroupDescription": "Shto një detyrë për të filluar", + "addFirstTask": "Shto detyrën e parë", + "openTask": "Hap", + "subtask": "nëndetyrë", + "subtasks": "nëndetyra", + "comment": "koment", + "comments": "komente", + "attachment": "bashkëngjitje", + "attachments": "bashkëngjitje", + "enterSubtaskName": "Shkruani emrin e nëndetyrës...", + "add": "Shto", + "cancel": "Anulo", + "renameGroup": "Riemërto Grupin", + "renameStatus": "Riemërto Statusin", + "renamePhase": "Riemërto Fazën", + "changeCategory": "Ndrysho Kategorinë", + "clickToEditGroupName": "Kliko për të redaktuar emrin e grupit", + "enterGroupName": "Shkruani emrin e grupit", + "todo": "Për t'u Bërë", + "inProgress": "Në Progres", + "done": "E Kryer", + "defaultTaskName": "Detyrë Pa Emër", + + "indicators": { + "tooltips": { + "subtasks": "{{count}} nëndetyrë", + "subtasks_plural": "{{count}} nëndetyra", + "comments": "{{count}} koment", + "comments_plural": "{{count}} komente", + "attachments": "{{count}} bashkëngjitje", + "attachments_plural": "{{count}} bashkëngjitje", + "subscribers": "Detyra ka abonues", + "dependencies": "Detyra ka varësi", + "recurring": "Detyrë e përsëritur" + } + } +} diff --git a/worklenz-frontend/public/locales/alb/tasks/task-table-bulk-actions.json b/worklenz-frontend/public/locales/alb/tasks/task-table-bulk-actions.json index cb433bf9..45980b24 100644 --- a/worklenz-frontend/public/locales/alb/tasks/task-table-bulk-actions.json +++ b/worklenz-frontend/public/locales/alb/tasks/task-table-bulk-actions.json @@ -17,7 +17,9 @@ "createTaskTemplate": "Krijo Shabllon Detyre", "apply": "Apliko", "createLabel": "+ Krijo Etiketë", + "searchOrCreateLabel": "Kërko ose krijo etiketë...", "hitEnterToCreate": "Shtyp Enter për të krijuar", + "labelExists": "Etiketa ekziston tashmë", "pendingInvitation": "Ftesë në Pritje", "noMatchingLabels": "Asnjë etiketë që përputhet", "noLabels": "Asnjë etiketë" diff --git a/worklenz-frontend/public/locales/de/all-project-list.json b/worklenz-frontend/public/locales/de/all-project-list.json index b11fbbcd..89a9803d 100644 --- a/worklenz-frontend/public/locales/de/all-project-list.json +++ b/worklenz-frontend/public/locales/de/all-project-list.json @@ -17,7 +17,18 @@ "unarchive": "Dearchivieren", "archiveConfirm": "Sind Sie sicher, dass Sie dieses Projekt archivieren möchten?", "unarchiveConfirm": "Sind Sie sicher, dass Sie dieses Projekt dearchivieren möchten?", + "yes": "Ja", + "no": "Nein", "clickToFilter": "Zum Filtern klicken nach", "noProjects": "Keine Projekte gefunden", - "addToFavourites": "Zu Favoriten hinzufügen" + "addToFavourites": "Zu Favoriten hinzufügen", + "list": "Liste", + "group": "Gruppe", + "listView": "Listenansicht", + "groupView": "Gruppenansicht", + "groupBy": { + "category": "Kategorie", + "client": "Kunde" + }, + "noPermission": "Sie haben keine Berechtigung, diese Aktion durchzuführen" } diff --git a/worklenz-frontend/public/locales/de/kanban-board.json b/worklenz-frontend/public/locales/de/kanban-board.json index 47f8bef4..70e1f6ca 100644 --- a/worklenz-frontend/public/locales/de/kanban-board.json +++ b/worklenz-frontend/public/locales/de/kanban-board.json @@ -19,5 +19,12 @@ "archive": "Archivieren", "newTaskNamePlaceholder": "Aufgabenname eingeben", - "newSubtaskNamePlaceholder": "Unteraufgabenname eingeben" + "newSubtaskNamePlaceholder": "Unteraufgabenname eingeben", + "untitledSection": "Unbenannter Abschnitt", + "unmapped": "Nicht zugeordnet", + "clickToChangeDate": "Klicken Sie, um das Datum zu ändern", + "noDueDate": "Kein Fälligkeitsdatum", + "save": "Speichern", + "clear": "Löschen", + "nextWeek": "Nächste Woche" } diff --git a/worklenz-frontend/public/locales/de/phases-drawer.json b/worklenz-frontend/public/locales/de/phases-drawer.json index d06e3d05..3cdfb255 100644 --- a/worklenz-frontend/public/locales/de/phases-drawer.json +++ b/worklenz-frontend/public/locales/de/phases-drawer.json @@ -1,7 +1,24 @@ { "configurePhases": "Phasen konfigurieren", - "phaseLabel": "Phasenbezeichnung", - "enterPhaseName": "Namen für Phasenbezeichnung eingeben", + "configure": "Konfigurieren", + "phaseLabel": "Phasen-Label", + "enterPhaseName": "Phasenname eingeben", "addOption": "Option hinzufügen", - "phaseOptions": "Phasenoptionen:" + "phaseOptions": "Phasenoptionen", + "optionsText": "Optionen", + "dragToReorderPhases": "Ziehen Sie Phasen, um sie neu zu ordnen. Jede Phase kann eine andere Farbe haben.", + "enterNewPhaseName": "Neuen Phasennamen eingeben...", + "addPhase": "Phase hinzufügen", + "noPhasesFound": "Keine Phasen gefunden", + "no": "Keine", + "found": "gefunden", + "deletePhase": "Phase löschen", + "deletePhaseConfirm": "Sind Sie sicher, dass Sie diese Phase löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "rename": "Umbenennen", + "delete": "Löschen", + "create": "Erstellen", + "cancel": "Abbrechen", + "selectColor": "Farbe auswählen", + "managePhases": "Phasen verwalten", + "close": "Schließen" } diff --git a/worklenz-frontend/public/locales/de/project-view.json b/worklenz-frontend/public/locales/de/project-view.json new file mode 100644 index 00000000..448a7249 --- /dev/null +++ b/worklenz-frontend/public/locales/de/project-view.json @@ -0,0 +1,14 @@ +{ + "taskList": "Aufgabenliste", + "board": "Kanban-Board", + "insights": "Insights", + "files": "Dateien", + "members": "Mitglieder", + "updates": "Aktualisierungen", + "projectView": "Projektansicht", + "loading": "Projekt wird geladen...", + "error": "Fehler beim Laden des Projekts", + "pinnedTab": "Als Standard-Registerkarte festgesetzt", + "pinTab": "Als Standard-Registerkarte festsetzen", + "unpinTab": "Standard-Registerkarte lösen" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/de/project-view/project-view-header.json b/worklenz-frontend/public/locales/de/project-view/project-view-header.json index ad236a04..348574f2 100644 --- a/worklenz-frontend/public/locales/de/project-view/project-view-header.json +++ b/worklenz-frontend/public/locales/de/project-view/project-view-header.json @@ -1,13 +1,31 @@ { "importTasks": "Aufgaben importieren", + "importTask": "Aufgabe importieren", "createTask": "Aufgabe erstellen", "settings": "Einstellungen", "subscribe": "Abonnieren", - "unsubscribe": "Abbestellen", + "unsubscribe": "Abmelden", "deleteProject": "Projekt löschen", "startDate": "Startdatum", "endDate": "Enddatum", "projectSettings": "Projekteinstellungen", "projectSummary": "Projektzusammenfassung", - "receiveProjectSummary": "Erhalten Sie jeden Abend eine Projektzusammenfassung." + "receiveProjectSummary": "Jeden Abend eine Projektzusammenfassung erhalten.", + "refreshProject": "Projekt aktualisieren", + "saveAsTemplate": "Als Vorlage speichern", + "invite": "Einladen", + "share": "Teilen", + "subscribeTooltip": "Projektbenachrichtigungen abonnieren", + "unsubscribeTooltip": "Projektbenachrichtigungen abmelden", + "refreshTooltip": "Projektdaten aktualisieren", + "settingsTooltip": "Projekteinstellungen öffnen", + "saveAsTemplateTooltip": "Dieses Projekt als Vorlage speichern", + "inviteTooltip": "Teammitglieder zu diesem Projekt einladen", + "createTaskTooltip": "Eine neue Aufgabe erstellen", + "importTaskTooltip": "Aufgabe aus Vorlage importieren", + "navigateBackTooltip": "Zurück zur Projektliste", + "projectStatusTooltip": "Projektstatus", + "projectDatesInfo": "Projekt-Zeitleisten-Informationen", + "projectCategoryTooltip": "Projektkategorie", + "defaultTaskName": "Unbenannte Aufgabe" } diff --git a/worklenz-frontend/public/locales/de/settings/profile.json b/worklenz-frontend/public/locales/de/settings/profile.json index f896e1f8..4d7fc4cd 100644 --- a/worklenz-frontend/public/locales/de/settings/profile.json +++ b/worklenz-frontend/public/locales/de/settings/profile.json @@ -9,5 +9,6 @@ "saveChanges": "Änderungen speichern", "profileJoinedText": "Vor einem Monat beigetreten", "profileLastUpdatedText": "Vor einem Monat aktualisiert", - "avatarTooltip": "Klicken Sie zum Hochladen eines Avatars" + "avatarTooltip": "Klicken Sie zum Hochladen eines Avatars", + "title": "Profil-Einstellungen" } diff --git a/worklenz-frontend/public/locales/de/settings/team-members.json b/worklenz-frontend/public/locales/de/settings/team-members.json index 6f2add12..d223f08e 100644 --- a/worklenz-frontend/public/locales/de/settings/team-members.json +++ b/worklenz-frontend/public/locales/de/settings/team-members.json @@ -1,4 +1,5 @@ { + "title": "Teammitglieder", "nameColumn": "Name", "projectsColumn": "Projekte", "emailColumn": "E-Mail", @@ -40,5 +41,7 @@ "ownerText": "Team-Besitzer", "addedText": "Hinzugefügt", "updatedText": "Aktualisiert", - "noResultFound": "Geben Sie eine E-Mail-Adresse ein und drücken Sie Enter..." + "noResultFound": "Geben Sie eine E-Mail-Adresse ein und drücken Sie Enter...", + "jobTitlesFetchError": "Fehler beim Abrufen der Jobtitel", + "invitationResent": "Einladung erfolgreich erneut gesendet!" } diff --git a/worklenz-frontend/public/locales/de/settings/teams.json b/worklenz-frontend/public/locales/de/settings/teams.json new file mode 100644 index 00000000..bf39215d --- /dev/null +++ b/worklenz-frontend/public/locales/de/settings/teams.json @@ -0,0 +1,16 @@ +{ + "title": "Teams", + "team": "Team", + "teams": "Teams", + "name": "Name", + "created": "Erstellt", + "ownsBy": "Gehört zu", + "edit": "Bearbeiten", + "editTeam": "Team bearbeiten", + "pinTooltip": "Klicken Sie hier, um dies im Hauptmenü zu fixieren", + "editTeamName": "Team-Name bearbeiten", + "updateName": "Name aktualisieren", + "namePlaceholder": "Name", + "nameRequired": "Bitte geben Sie einen Namen ein", + "updateFailed": "Änderung des Team-Namens fehlgeschlagen!" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/de/task-drawer/task-drawer-info-tab.json b/worklenz-frontend/public/locales/de/task-drawer/task-drawer-info-tab.json index ed79d6bf..aece79f0 100644 --- a/worklenz-frontend/public/locales/de/task-drawer/task-drawer-info-tab.json +++ b/worklenz-frontend/public/locales/de/task-drawer/task-drawer-info-tab.json @@ -26,4 +26,4 @@ "add-sub-task": "+ Unteraufgabe hinzufügen", "refresh-sub-tasks": "Unteraufgaben aktualisieren" } -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/de/task-drawer/task-drawer.json b/worklenz-frontend/public/locales/de/task-drawer/task-drawer.json index b4aa0525..62e3f881 100644 --- a/worklenz-frontend/public/locales/de/task-drawer/task-drawer.json +++ b/worklenz-frontend/public/locales/de/task-drawer/task-drawer.json @@ -1,6 +1,6 @@ { "taskHeader": { - "taskNamePlaceholder": "Aufgabe eingeben", + "taskNamePlaceholder": "Geben Sie Ihre Aufgabe ein", "deleteTask": "Aufgabe löschen" }, "taskInfoTab": { @@ -9,20 +9,29 @@ "title": "Details", "task-key": "Aufgaben-Schlüssel", "phase": "Phase", - "assignees": "Zugewiesene", + "assignees": "Beauftragte", "due-date": "Fälligkeitsdatum", "time-estimation": "Zeitschätzung", "priority": "Priorität", "labels": "Labels", "billable": "Abrechenbar", "notify": "Benachrichtigen", - "when-done-notify": "Bei Fertigstellung benachrichtigen", + "when-done-notify": "Bei Abschluss benachrichtigen", "start-date": "Startdatum", "end-date": "Enddatum", "hide-start-date": "Startdatum ausblenden", "show-start-date": "Startdatum anzeigen", "hours": "Stunden", - "minutes": "Minuten" + "minutes": "Minuten", + "progressValue": "Fortschrittswert", + "progressValueTooltip": "Fortschritt in Prozent einstellen (0-100%)", + "progressValueRequired": "Bitte geben Sie einen Fortschrittswert ein", + "progressValueRange": "Fortschritt muss zwischen 0 und 100 liegen", + "taskWeight": "Aufgabengewicht", + "taskWeightTooltip": "Gewicht dieser Teilaufgabe festlegen (Prozent)", + "taskWeightRequired": "Bitte geben Sie ein Aufgabengewicht ein", + "taskWeightRange": "Gewicht muss zwischen 0 und 100 liegen", + "recurring": "Wiederkehrend" }, "labels": { "labelInputPlaceholder": "Suchen oder erstellen", @@ -30,29 +39,29 @@ }, "description": { "title": "Beschreibung", - "placeholder": "Detaillierte Beschreibung hinzufügen..." + "placeholder": "Detailliertere Beschreibung hinzufügen..." }, "subTasks": { - "title": "Unteraufgaben", - "addSubTask": "+ Unteraufgabe hinzufügen", - "addSubTaskInputPlaceholder": "Aufgabe eingeben und Enter drücken", - "refreshSubTasks": "Unteraufgaben aktualisieren", + "title": "Teilaufgaben", + "addSubTask": "Teilaufgabe hinzufügen", + "addSubTaskInputPlaceholder": "Geben Sie Ihre Aufgabe ein und drücken Sie Enter", + "refreshSubTasks": "Teilaufgaben aktualisieren", "edit": "Bearbeiten", "delete": "Löschen", - "confirmDeleteSubTask": "Sind Sie sicher, dass Sie diese Unteraufgabe löschen möchten?", - "deleteSubTask": "Unteraufgabe löschen" + "confirmDeleteSubTask": "Sind Sie sicher, dass Sie diese Teilaufgabe löschen möchten?", + "deleteSubTask": "Teilaufgabe löschen" }, "dependencies": { "title": "Abhängigkeiten", "addDependency": "+ Neue Abhängigkeit hinzufügen", - "blockedBy": "Blockiert durch", + "blockedBy": "Blockiert von", "searchTask": "Aufgabe suchen", "noTasksFound": "Keine Aufgaben gefunden", - "confirmDeleteDependency": "Sind Sie sicher, dass Sie dies löschen möchten?" + "confirmDeleteDependency": "Sind Sie sicher, dass Sie löschen möchten?" }, "attachments": { "title": "Anhänge", - "chooseOrDropFileToUpload": "Datei auswählen oder zum Hochladen ablegen", + "chooseOrDropFileToUpload": "Datei zum Hochladen wählen oder ablegen", "uploading": "Wird hochgeladen..." }, "comments": { @@ -60,19 +69,55 @@ "addComment": "+ Neuen Kommentar hinzufügen", "noComments": "Noch keine Kommentare. Seien Sie der Erste!", "delete": "Löschen", - "confirmDeleteComment": "Sind Sie sicher, dass Sie diesen Kommentar löschen möchten?" + "confirmDeleteComment": "Sind Sie sicher, dass Sie diesen Kommentar löschen möchten?", + "addCommentPlaceholder": "Kommentar hinzufügen...", + "cancel": "Abbrechen", + "commentButton": "Kommentieren", + "attachFiles": "Dateien anhängen", + "addMoreFiles": "Weitere Dateien hinzufügen", + "selectedFiles": "Ausgewählte Dateien (Bis zu 25MB, Maximum {count})", + "maxFilesError": "Sie können maximal {count} Dateien hochladen", + "processFilesError": "Fehler beim Verarbeiten der Dateien", + "addCommentError": "Bitte fügen Sie einen Kommentar hinzu oder hängen Sie Dateien an", + "createdBy": "Erstellt {{time}} von {{user}}", + "updatedTime": "Aktualisiert {{time}}" }, - "searchInputPlaceholder": "Nach Namen suchen", - "pendingInvitation": "Einladung ausstehend" + "searchInputPlaceholder": "Nach Name suchen", + "pendingInvitation": "Ausstehende Einladung" }, "taskTimeLogTab": { "title": "Zeiterfassung", "addTimeLog": "Neuen Zeiteintrag hinzufügen", "totalLogged": "Gesamt erfasst", "exportToExcel": "Nach Excel exportieren", - "noTimeLogsFound": "Keine Zeiterfassungen gefunden" + "noTimeLogsFound": "Keine Zeiteinträge gefunden", + "timeLogForm": { + "date": "Datum", + "startTime": "Startzeit", + "endTime": "Endzeit", + "workDescription": "Arbeitsbeschreibung", + "descriptionPlaceholder": "Beschreibung hinzufügen", + "logTime": "Zeit erfassen", + "updateTime": "Zeit aktualisieren", + "cancel": "Abbrechen", + "selectDateError": "Bitte wählen Sie ein Datum", + "selectStartTimeError": "Bitte wählen Sie eine Startzeit", + "selectEndTimeError": "Bitte wählen Sie eine Endzeit", + "endTimeAfterStartError": "Endzeit muss nach der Startzeit liegen" + } }, "taskActivityLogTab": { - "title": "Aktivitätsprotokoll" + "title": "Aktivitätsprotokoll", + "add": "HINZUFÜGEN", + "remove": "ENTFERNEN", + "none": "Keine", + "weight": "Gewicht", + "createdTask": "hat die Aufgabe erstellt." + }, + "taskProgress": { + "markAsDoneTitle": "Aufgabe als erledigt markieren?", + "confirmMarkAsDone": "Ja, als erledigt markieren", + "cancelMarkAsDone": "Nein, aktuellen Status beibehalten", + "markAsDoneDescription": "Sie haben den Fortschritt auf 100% gesetzt. Möchten Sie den Aufgabenstatus auf \"Erledigt\" aktualisieren?" } } diff --git a/worklenz-frontend/public/locales/de/task-list-filters.json b/worklenz-frontend/public/locales/de/task-list-filters.json index 4cc157c8..18d50b6c 100644 --- a/worklenz-frontend/public/locales/de/task-list-filters.json +++ b/worklenz-frontend/public/locales/de/task-list-filters.json @@ -28,15 +28,15 @@ "endDateText": "Enddatum", "dueDateText": "Fälligkeitsdatum", "duedateText": "Fälligkeitsdatum", - "completedDateText": "Abschlussdatum", - "completeddateText": "Abschlussdatum", - "createdDateText": "Erstellungsdatum", - "createddateText": "Erstellungsdatum", + "completedDateText": "Abgeschlossen am", + "completeddateText": "Abgeschlossen am", + "createdDateText": "Erstellt am", + "createddateText": "Erstellt am", "lastUpdatedText": "Zuletzt aktualisiert", "lastupdatedText": "Zuletzt aktualisiert", - "reporterText": "Melder", - "dueTimeText": "Fällige Zeit", - "duetimeText": "Fällige Zeit", + "reporterText": "Berichterstatter", + "dueTimeText": "Fälligkeitszeit", + "duetimeText": "Fälligkeitszeit", "lowText": "Niedrig", "mediumText": "Mittel", @@ -54,6 +54,35 @@ "category": "Kategorie", "selectCategory": "Kategorie auswählen", "pleaseEnterAName": "Bitte geben Sie einen Namen ein", - "pleaseSelectACategory": "Bitte wählen Sie eine Kategorie aus", - "create": "Erstellen" + "pleaseSelectACategory": "Bitte wählen Sie eine Kategorie", + "create": "Erstellen", + + "searchTasks": "Aufgaben suchen...", + "searchPlaceholder": "Suchen...", + "fieldsText": "Felder", + "loadingFilters": "Filter werden geladen...", + "noOptionsFound": "Keine Optionen gefunden", + "filtersActive": "Filter aktiv", + "filterActive": "Filter aktiv", + "clearAll": "Alle löschen", + "clearing": "Wird gelöscht...", + "cancel": "Abbrechen", + "search": "Suchen", + "groupedBy": "Gruppiert nach", + "manage": "Verwalten", + "manageStatuses": "Status verwalten", + "managePhases": "Phasen verwalten", + "dragToReorderStatuses": "Status sind nach Kategorien organisiert. Ziehen Sie, um innerhalb von Kategorien neu zu ordnen. Klicken Sie auf 'Status hinzufügen', um neue Status in jeder Kategorie zu erstellen.", + "enterNewStatusName": "Neuen Status-Namen eingeben...", + "addStatus": "Status hinzufügen", + "noStatusesFound": "Keine Status in dieser Kategorie", + "deleteStatus": "Status löschen", + "deleteStatusConfirm": "Sind Sie sicher, dass Sie diesen Status löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "rename": "Umbenennen", + "delete": "Löschen", + "enterStatusName": "Status-Namen eingeben", + "close": "Schließen", + "cannotMoveStatus": "Status kann nicht verschoben werden", + "cannotMoveStatusMessage": "Dieser Status kann nicht verschoben werden, da die Kategorie '{{categoryName}}' leer bleiben würde. Jede Kategorie muss mindestens einen Status haben.", + "ok": "OK" } diff --git a/worklenz-frontend/public/locales/de/task-list-table.json b/worklenz-frontend/public/locales/de/task-list-table.json index d399dea4..9c2ff314 100644 --- a/worklenz-frontend/public/locales/de/task-list-table.json +++ b/worklenz-frontend/public/locales/de/task-list-table.json @@ -36,9 +36,10 @@ "selectText": "Auswählen", "labelsSelectorInputTip": "Enter drücken zum Erstellen!", - "addTaskText": "+ Aufgabe hinzufügen", + "addTaskText": "Aufgabe hinzufügen", "addSubTaskText": "+ Unteraufgabe hinzufügen", "addTaskInputPlaceholder": "Aufgabe eingeben und Enter drücken", + "noTasksInGroup": "Keine Aufgaben in dieser Gruppe", "openButton": "Öffnen", "okButton": "OK", @@ -47,6 +48,9 @@ "searchInputPlaceholder": "Suchen oder erstellen", "assigneeSelectorInviteButton": "Neues Mitglied per E-Mail einladen", "labelInputPlaceholder": "Suchen oder erstellen", + "searchLabelsPlaceholder": "Labels suchen...", + "createLabelButton": "\"{{name}}\" erstellen", + "manageLabelsPath": "Einstellungen → Labels", "pendingInvitation": "Einladung ausstehend", @@ -59,5 +63,74 @@ "convertToTask": "In Aufgabe umwandeln", "delete": "Löschen", "searchByNameInputPlaceholder": "Nach Namen suchen" + }, + "setDueDate": "Fälligkeitsdatum festlegen", + "setStartDate": "Startdatum festlegen", + "clearDueDate": "Fälligkeitsdatum löschen", + "clearStartDate": "Startdatum löschen", + "dueDatePlaceholder": "Fälligkeitsdatum", + "startDatePlaceholder": "Startdatum", + + "emptyStates": { + "noTaskGroups": "Keine Aufgabengruppen gefunden", + "noTaskGroupsDescription": "Aufgaben werden hier angezeigt, wenn sie erstellt oder Filter angewendet werden.", + "errorPrefix": "Fehler:", + "dragTaskFallback": "Aufgabe" + }, + + "customColumns": { + "addCustomColumn": "Benutzerdefinierte Spalte hinzufügen", + "customColumnHeader": "Benutzerdefinierte Spalte", + "customColumnSettings": "Einstellungen für benutzerdefinierte Spalte", + "noCustomValue": "Kein Wert", + "peopleField": "Personenfeld", + "noDate": "Kein Datum", + "unsupportedField": "Nicht unterstützter Feldtyp", + + "modal": { + "addFieldTitle": "Feld hinzufügen", + "editFieldTitle": "Feld bearbeiten", + "fieldTitle": "Feldtitel", + "fieldTitleRequired": "Feldtitel ist erforderlich", + "columnTitlePlaceholder": "Spaltentitel", + "type": "Typ", + "deleteConfirmTitle": "Sind Sie sicher, dass Sie diese benutzerdefinierte Spalte löschen möchten?", + "deleteConfirmDescription": "Diese Aktion kann nicht rückgängig gemacht werden. Alle mit dieser Spalte verbundenen Daten werden dauerhaft gelöscht.", + "deleteButton": "Löschen", + "cancelButton": "Abbrechen", + "createButton": "Erstellen", + "updateButton": "Aktualisieren", + "createSuccessMessage": "Benutzerdefinierte Spalte erfolgreich erstellt", + "updateSuccessMessage": "Benutzerdefinierte Spalte erfolgreich aktualisiert", + "deleteSuccessMessage": "Benutzerdefinierte Spalte erfolgreich gelöscht", + "deleteErrorMessage": "Fehler beim Löschen der benutzerdefinierten Spalte", + "createErrorMessage": "Fehler beim Erstellen der benutzerdefinierten Spalte", + "updateErrorMessage": "Fehler beim Aktualisieren der benutzerdefinierten Spalte" + }, + + "fieldTypes": { + "people": "Personen", + "number": "Zahl", + "date": "Datum", + "selection": "Auswahl", + "checkbox": "Kontrollkästchen", + "labels": "Etiketten", + "key": "Schlüssel", + "formula": "Formel" + } + }, + + "indicators": { + "tooltips": { + "subtasks": "{{count}} Unteraufgabe", + "subtasks_plural": "{{count}} Unteraufgaben", + "comments": "{{count}} Kommentar", + "comments_plural": "{{count}} Kommentare", + "attachments": "{{count}} Anhang", + "attachments_plural": "{{count}} Anhänge", + "subscribers": "Aufgabe hat Abonnenten", + "dependencies": "Aufgabe hat Abhängigkeiten", + "recurring": "Wiederkehrende Aufgabe" + } } } diff --git a/worklenz-frontend/public/locales/de/task-management.json b/worklenz-frontend/public/locales/de/task-management.json new file mode 100644 index 00000000..87b43ef7 --- /dev/null +++ b/worklenz-frontend/public/locales/de/task-management.json @@ -0,0 +1,39 @@ +{ + "noTasksInGroup": "Keine Aufgaben in dieser Gruppe", + "noTasksInGroupDescription": "Fügen Sie eine Aufgabe hinzu, um zu beginnen", + "addFirstTask": "Fügen Sie Ihre erste Aufgabe hinzu", + "openTask": "Öffnen", + "subtask": "Unteraufgabe", + "subtasks": "Unteraufgaben", + "comment": "Kommentar", + "comments": "Kommentare", + "attachment": "Anhang", + "attachments": "Anhänge", + "enterSubtaskName": "Geben Sie den Namen der Unteraufgabe ein...", + "add": "Hinzufügen", + "cancel": "Abbrechen", + "renameGroup": "Gruppe umbenennen", + "renameStatus": "Status umbenennen", + "renamePhase": "Phase umbenennen", + "changeCategory": "Kategorie ändern", + "clickToEditGroupName": "Klicken Sie, um den Gruppennamen zu bearbeiten", + "enterGroupName": "Geben Sie den Gruppennamen ein", + "todo": "Zu erledigen", + "inProgress": "In Bearbeitung", + "done": "Erledigt", + "defaultTaskName": "Unbenannte Aufgabe", + + "indicators": { + "tooltips": { + "subtasks": "{{count}} Unteraufgabe", + "subtasks_plural": "{{count}} Unteraufgaben", + "comments": "{{count}} Kommentar", + "comments_plural": "{{count}} Kommentare", + "attachments": "{{count}} Anhang", + "attachments_plural": "{{count}} Anhänge", + "subscribers": "Aufgabe hat Abonnenten", + "dependencies": "Aufgabe hat Abhängigkeiten", + "recurring": "Wiederkehrende Aufgabe" + } + } +} diff --git a/worklenz-frontend/public/locales/de/tasks/task-table-bulk-actions.json b/worklenz-frontend/public/locales/de/tasks/task-table-bulk-actions.json index 3c2d7132..e8b039f2 100644 --- a/worklenz-frontend/public/locales/de/tasks/task-table-bulk-actions.json +++ b/worklenz-frontend/public/locales/de/tasks/task-table-bulk-actions.json @@ -17,8 +17,25 @@ "createTaskTemplate": "Aufgabenvorlage erstellen", "apply": "Anwenden", "createLabel": "+ Label erstellen", + "searchOrCreateLabel": "Label suchen oder erstellen...", "hitEnterToCreate": "Enter drücken zum Erstellen", + "labelExists": "Label existiert bereits", "pendingInvitation": "Einladung ausstehend", "noMatchingLabels": "Keine passenden Labels", - "noLabels": "Keine Labels" + "noLabels": "Keine Labels", + "CHANGE_STATUS": "Status ändern", + "CHANGE_PRIORITY": "Priorität ändern", + "CHANGE_PHASE": "Phase ändern", + "ADD_LABELS": "Labels hinzufügen", + "ASSIGN_TO_ME": "Mir zuweisen", + "ASSIGN_MEMBERS": "Mitglieder zuweisen", + "ARCHIVE": "Archivieren", + "DELETE": "Löschen", + "CANCEL": "Abbrechen", + "CLEAR_SELECTION": "Auswahl löschen", + "TASKS_SELECTED": "{{count}} Aufgabe ausgewählt", + "TASKS_SELECTED_plural": "{{count}} Aufgaben ausgewählt", + "DELETE_TASKS_CONFIRM": "{{count}} Aufgabe löschen?", + "DELETE_TASKS_CONFIRM_plural": "{{count}} Aufgaben löschen?", + "DELETE_TASKS_WARNING": "Diese Aktion kann nicht rückgängig gemacht werden." } diff --git a/worklenz-frontend/public/locales/en/admin-center/current-bill.json b/worklenz-frontend/public/locales/en/admin-center/current-bill.json index e5846b79..fe840789 100644 --- a/worklenz-frontend/public/locales/en/admin-center/current-bill.json +++ b/worklenz-frontend/public/locales/en/admin-center/current-bill.json @@ -25,7 +25,7 @@ "paymentMethod": "Payment Method", "status": "Status", "ltdUsers": "You can add up to {{ltd_users}} users.", - + "totalSeats": "Total seats", "availableSeats": "Available seats", "addMoreSeats": "Add more seats", @@ -103,11 +103,19 @@ "perMonthPerUser": "per user/month", "viewInvoice": "View Invoice", "switchToFreePlan": "Switch to Free Plan", - + "expirestoday": "today", "expirestomorrow": "tomorrow", "expiredDaysAgo": "{{days}} days ago", - + "continueWith": "Continue with {{plan}}", - "changeToPlan": "Change to {{plan}}" + "changeToPlan": "Change to {{plan}}", + "creditPlan": "Credit Plan", + "customPlan": "Custom Plan", + "planValidTill": "Your plan is valid till {{date}}", + "purchaseSeatsText": "To continue, you'll need to purchase additional seats.", + "currentSeatsText": "You currently have {{seats}} seats available.", + "selectSeatsText": "Please select the number of additional seats to purchase.", + "purchase": "Purchase", + "contactSales": "Contact sales" } diff --git a/worklenz-frontend/public/locales/en/admin-center/teams.json b/worklenz-frontend/public/locales/en/admin-center/teams.json index e03f8515..bf829a87 100644 --- a/worklenz-frontend/public/locales/en/admin-center/teams.json +++ b/worklenz-frontend/public/locales/en/admin-center/teams.json @@ -29,5 +29,7 @@ "role": "Role", "owner": "Owner", "admin": "Admin", - "member": "Member" + "member": "Member", + "cannotChangeOwnerRole": "Owner role cannot be changed", + "pendingInvitation": "Pending invitation" } diff --git a/worklenz-frontend/public/locales/en/all-project-list.json b/worklenz-frontend/public/locales/en/all-project-list.json index 308c414e..ab98cb6b 100644 --- a/worklenz-frontend/public/locales/en/all-project-list.json +++ b/worklenz-frontend/public/locales/en/all-project-list.json @@ -17,7 +17,18 @@ "unarchive": "Unarchive", "archiveConfirm": "Are you sure you want to archive this project?", "unarchiveConfirm": "Are you sure you want to unarchive this project?", + "yes": "Yes", + "no": "No", "clickToFilter": "Click to filter by", "noProjects": "No projects found", - "addToFavourites": "Add to favourites" + "addToFavourites": "Add to favourites", + "list": "List", + "group": "Group", + "listView": "List View", + "groupView": "Group View", + "groupBy": { + "category": "Category", + "client": "Client" + }, + "noPermission": "You don't have permission to perform this action" } diff --git a/worklenz-frontend/public/locales/en/kanban-board.json b/worklenz-frontend/public/locales/en/kanban-board.json index 59b4b293..e295a6c6 100644 --- a/worklenz-frontend/public/locales/en/kanban-board.json +++ b/worklenz-frontend/public/locales/en/kanban-board.json @@ -19,5 +19,15 @@ "archive": "Archive", "newTaskNamePlaceholder": "Write a task Name", - "newSubtaskNamePlaceholder": "Write a subtask Name" + "newSubtaskNamePlaceholder": "Write a subtask Name", + "untitledSection": "Untitled section", + "unmapped": "Unmapped", + "clickToChangeDate": "Click to change date", + "noDueDate": "No due date", + "save": "Save", + "clear": "Clear", + "nextWeek": "Next week", + "noSubtasks": "No subtasks", + "showSubtasks": "Show subtasks", + "hideSubtasks": "Hide subtasks" } diff --git a/worklenz-frontend/public/locales/en/phases-drawer.json b/worklenz-frontend/public/locales/en/phases-drawer.json index 51ac7899..9eb24582 100644 --- a/worklenz-frontend/public/locales/en/phases-drawer.json +++ b/worklenz-frontend/public/locales/en/phases-drawer.json @@ -1,7 +1,24 @@ { - "configurePhases": "Configure Phases", - "phaseLabel": "Phase Label", - "enterPhaseName": "Enter a name for phase label", - "addOption": "Add Option", - "phaseOptions": "Phase Options:" -} \ No newline at end of file + "configurePhases": "Configure Phases", + "configure": "Configure", + "phaseLabel": "Phase Label", + "enterPhaseName": "Enter phase name", + "addOption": "Add Option", + "phaseOptions": "Phase Options", + "optionsText": "Options", + "dragToReorderPhases": "Drag phases to reorder them. Each phase can have a different color.", + "enterNewPhaseName": "Enter new phase name...", + "addPhase": "Add Phase", + "noPhasesFound": "No phases found", + "no": "No", + "found": "found", + "deletePhase": "Delete Phase", + "deletePhaseConfirm": "Are you sure you want to delete this phase? This action cannot be undone.", + "rename": "Rename", + "delete": "Delete", + "create": "Create", + "cancel": "Cancel", + "selectColor": "Select color", + "managePhases": "Manage Phases", + "close": "Close" +} diff --git a/worklenz-frontend/public/locales/en/project-drawer.json b/worklenz-frontend/public/locales/en/project-drawer.json index d72138d6..be553a01 100644 --- a/worklenz-frontend/public/locales/en/project-drawer.json +++ b/worklenz-frontend/public/locales/en/project-drawer.json @@ -38,5 +38,15 @@ "createClient": "Create client", "searchInputPlaceholder": "Search by name or email", "hoursPerDayValidationMessage": "Hours per day must be a number between 1 and 24", - "noPermission": "No permission" + "workingDaysValidationMessage": "Working days must be a positive number", + "manDaysValidationMessage": "Man days must be a positive number", + "noPermission": "No permission", + "progressSettings": "Progress Settings", + "manualProgress": "Manual Progress", + "manualProgressTooltip": "Allow manual progress updates for tasks without subtasks", + "weightedProgress": "Weighted Progress", + "weightedProgressTooltip": "Calculate progress based on subtask weights", + "timeProgress": "Time-based Progress", + "timeProgressTooltip": "Calculate progress based on estimated time", + "enterProjectKey": "Enter project key" } diff --git a/worklenz-frontend/public/locales/en/project-view.json b/worklenz-frontend/public/locales/en/project-view.json new file mode 100644 index 00000000..82ab21f2 --- /dev/null +++ b/worklenz-frontend/public/locales/en/project-view.json @@ -0,0 +1,14 @@ +{ + "taskList": "Task List", + "board": "Board", + "insights": "Insights", + "files": "Files", + "members": "Members", + "updates": "Updates", + "projectView": "Project View", + "loading": "Loading project...", + "error": "Error loading project", + "pinnedTab": "Pinned as default tab", + "pinTab": "Pin as default tab", + "unpinTab": "Unpin default tab" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/en/project-view/import-task-templates.json b/worklenz-frontend/public/locales/en/project-view/import-task-templates.json index 6057a524..d732aa08 100644 --- a/worklenz-frontend/public/locales/en/project-view/import-task-templates.json +++ b/worklenz-frontend/public/locales/en/project-view/import-task-templates.json @@ -1,11 +1,11 @@ { - "importTaskTemplate": "Import Task Template", - "templateName": "Template Name", - "templateDescription": "Template Description", - "selectedTasks": "Selected Tasks", - "tasks": "Tasks", - "templates": "Templates", - "remove": "Remove", - "cancel": "Cancel", - "import": "Import" + "importTaskTemplate": "Import Task Template", + "templateName": "Template Name", + "templateDescription": "Template Description", + "selectedTasks": "Selected Tasks", + "tasks": "Tasks", + "templates": "Templates", + "remove": "Remove", + "cancel": "Cancel", + "import": "Import" } diff --git a/worklenz-frontend/public/locales/en/project-view/project-member-drawer.json b/worklenz-frontend/public/locales/en/project-view/project-member-drawer.json index 4b54e2b5..ad2d60c8 100644 --- a/worklenz-frontend/public/locales/en/project-view/project-member-drawer.json +++ b/worklenz-frontend/public/locales/en/project-view/project-member-drawer.json @@ -1,8 +1,7 @@ { - "title": "Project Members", - "searchLabel": "Add members by adding their name or email", - "searchPlaceholder": "Type name or email", - "inviteAsAMember": "Invite as a member", - "inviteNewMemberByEmail": "Invite new member by email" - -} \ No newline at end of file + "title": "Project Members", + "searchLabel": "Add members by adding their name or email", + "searchPlaceholder": "Type name or email", + "inviteAsAMember": "Invite as a member", + "inviteNewMemberByEmail": "Invite new member by email" +} diff --git a/worklenz-frontend/public/locales/en/project-view/project-view-header.json b/worklenz-frontend/public/locales/en/project-view/project-view-header.json index 8100e068..aa3d71f7 100644 --- a/worklenz-frontend/public/locales/en/project-view/project-view-header.json +++ b/worklenz-frontend/public/locales/en/project-view/project-view-header.json @@ -1,13 +1,31 @@ { - "importTasks": "Import tasks", - "createTask": "Create task", - "settings": "Settings", - "subscribe": "Subscribe", - "unsubscribe": "Unsubscribe", - "deleteProject": "Delete project", - "startDate": "Start date", - "endDate": "End date", - "projectSettings": "Project settings", - "projectSummary": "Project summary", - "receiveProjectSummary": "Receive a project summary every evening." -} \ No newline at end of file + "importTasks": "Import tasks", + "importTask": "Import task", + "createTask": "Create task", + "settings": "Settings", + "subscribe": "Subscribe", + "unsubscribe": "Unsubscribe", + "deleteProject": "Delete project", + "startDate": "Start date", + "endDate": "End date", + "projectSettings": "Project settings", + "projectSummary": "Project summary", + "receiveProjectSummary": "Receive a project summary every evening.", + "refreshProject": "Refresh project", + "saveAsTemplate": "Save as template", + "invite": "Invite", + "share": "Share", + "subscribeTooltip": "Subscribe to project notifications", + "unsubscribeTooltip": "Unsubscribe from project notifications", + "refreshTooltip": "Refresh project data", + "settingsTooltip": "Open project settings", + "saveAsTemplateTooltip": "Save this project as a template", + "inviteTooltip": "Invite team members to this project", + "createTaskTooltip": "Create a new task", + "importTaskTooltip": "Import task from template", + "navigateBackTooltip": "Go back to projects list", + "projectStatusTooltip": "Project status", + "projectDatesInfo": "Project timeline information", + "projectCategoryTooltip": "Project category", + "defaultTaskName": "Untitled Task" +} diff --git a/worklenz-frontend/public/locales/en/settings/appearance.json b/worklenz-frontend/public/locales/en/settings/appearance.json new file mode 100644 index 00000000..9ce8de64 --- /dev/null +++ b/worklenz-frontend/public/locales/en/settings/appearance.json @@ -0,0 +1,5 @@ +{ + "title": "Appearance", + "darkMode": "Dark Mode", + "darkModeDescription": "Switch between light and dark mode to customize your viewing experience." +} diff --git a/worklenz-frontend/public/locales/en/settings/profile.json b/worklenz-frontend/public/locales/en/settings/profile.json index 5dd49095..43ce2f41 100644 --- a/worklenz-frontend/public/locales/en/settings/profile.json +++ b/worklenz-frontend/public/locales/en/settings/profile.json @@ -9,5 +9,6 @@ "saveChanges": "Save Changes", "profileJoinedText": "Joined a month ago", "profileLastUpdatedText": "Last updated a month ago", - "avatarTooltip": "Click to upload an avatar" + "avatarTooltip": "Click to upload an avatar", + "title": "Profile Settings" } diff --git a/worklenz-frontend/public/locales/en/settings/sidebar.json b/worklenz-frontend/public/locales/en/settings/sidebar.json index 41bc3e0f..d0b64829 100644 --- a/worklenz-frontend/public/locales/en/settings/sidebar.json +++ b/worklenz-frontend/public/locales/en/settings/sidebar.json @@ -10,5 +10,6 @@ "team-members": "Team Members", "teams": "Teams", "change-password": "Change Password", - "language-and-region": "Language and Region" + "language-and-region": "Language and Region", + "appearance": "Appearance" } diff --git a/worklenz-frontend/public/locales/en/settings/team-members.json b/worklenz-frontend/public/locales/en/settings/team-members.json index 35e77f6e..36918b90 100644 --- a/worklenz-frontend/public/locales/en/settings/team-members.json +++ b/worklenz-frontend/public/locales/en/settings/team-members.json @@ -1,4 +1,5 @@ { + "title": "Team Members", "nameColumn": "Name", "projectsColumn": "Projects", "emailColumn": "Email", @@ -40,5 +41,7 @@ "ownerText": "Team Owner", "addedText": "Added", "updatedText": "Updated", - "noResultFound": "Type an email address and hit enter..." + "noResultFound": "Type an email address and hit enter...", + "jobTitlesFetchError": "Failed to fetch job titles", + "invitationResent": "Invitation resent successfully!" } diff --git a/worklenz-frontend/public/locales/en/settings/teams.json b/worklenz-frontend/public/locales/en/settings/teams.json new file mode 100644 index 00000000..57a1df51 --- /dev/null +++ b/worklenz-frontend/public/locales/en/settings/teams.json @@ -0,0 +1,16 @@ +{ + "title": "Teams", + "team": "Team", + "teams": "Teams", + "name": "Name", + "created": "Created", + "ownsBy": "Owns By", + "edit": "Edit", + "editTeam": "Edit Team", + "pinTooltip": "Click to pin this into the main menu", + "editTeamName": "Edit Team Name", + "updateName": "Update Name", + "namePlaceholder": "Name", + "nameRequired": "Please enter a Name", + "updateFailed": "Team name change failed!" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/en/task-drawer/task-drawer-info-tab.json b/worklenz-frontend/public/locales/en/task-drawer/task-drawer-info-tab.json index 42ffdc83..f88ecde9 100644 --- a/worklenz-frontend/public/locales/en/task-drawer/task-drawer-info-tab.json +++ b/worklenz-frontend/public/locales/en/task-drawer/task-drawer-info-tab.json @@ -15,7 +15,8 @@ "hide-start-date": "Hide Start Date", "show-start-date": "Show Start Date", "hours": "Hours", - "minutes": "Minutes" + "minutes": "Minutes", + "recurring": "Recurring" }, "description": { "title": "Description", @@ -23,7 +24,7 @@ }, "subTasks": { "title": "Sub Tasks", - "add-sub-task": "+ Add Sub Task", + "add-sub-task": "Add Sub Task", "refresh-sub-tasks": "Refresh Sub Tasks" } -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/en/task-drawer/task-drawer-recurring-config.json b/worklenz-frontend/public/locales/en/task-drawer/task-drawer-recurring-config.json new file mode 100644 index 00000000..1d22e41b --- /dev/null +++ b/worklenz-frontend/public/locales/en/task-drawer/task-drawer-recurring-config.json @@ -0,0 +1,34 @@ +{ + "recurring": "Recurring", + "recurringTaskConfiguration": "Recurring task configuration", + "repeats": "Repeats", + "daily": "Daily", + "weekly": "Weekly", + "everyXDays": "Every X Days", + "everyXWeeks": "Every X Weeks", + "everyXMonths": "Every X Months", + "monthly": "Monthly", + "selectDaysOfWeek": "Select Days of the Week", + "mon": "Mon", + "tue": "Tue", + "wed": "Wed", + "thu": "Thu", + "fri": "Fri", + "sat": "Sat", + "sun": "Sun", + "monthlyRepeatType": "Monthly repeat type", + "onSpecificDate": "On a specific date", + "onSpecificDay": "On a specific day", + "dateOfMonth": "Date of the month", + "weekOfMonth": "Week of the month", + "dayOfWeek": "Day of the week", + "first": "First", + "second": "Second", + "third": "Third", + "fourth": "Fourth", + "last": "Last", + "intervalDays": "Interval (days)", + "intervalWeeks": "Interval (weeks)", + "intervalMonths": "Interval (months)", + "saveChanges": "Save Changes" +} diff --git a/worklenz-frontend/public/locales/en/task-drawer/task-drawer.json b/worklenz-frontend/public/locales/en/task-drawer/task-drawer.json index 003fa112..b5147324 100644 --- a/worklenz-frontend/public/locales/en/task-drawer/task-drawer.json +++ b/worklenz-frontend/public/locales/en/task-drawer/task-drawer.json @@ -22,7 +22,16 @@ "hide-start-date": "Hide Start Date", "show-start-date": "Show Start Date", "hours": "Hours", - "minutes": "Minutes" + "minutes": "Minutes", + "progressValue": "Progress Value", + "progressValueTooltip": "Set the progress percentage (0-100%)", + "progressValueRequired": "Please enter a progress value", + "progressValueRange": "Progress must be between 0 and 100", + "taskWeight": "Task Weight", + "taskWeightTooltip": "Set the weight of this subtask (percentage)", + "taskWeightRequired": "Please enter a task weight", + "taskWeightRange": "Weight must be between 0 and 100", + "recurring": "Recurring" }, "labels": { "labelInputPlaceholder": "Search or create", @@ -34,7 +43,7 @@ }, "subTasks": { "title": "Sub Tasks", - "addSubTask": "+ Add Sub Task", + "addSubTask": "Add Sub Task", "addSubTaskInputPlaceholder": "Type your task and hit enter", "refreshSubTasks": "Refresh Sub Tasks", "edit": "Edit", @@ -60,7 +69,18 @@ "addComment": "+ Add new comment", "noComments": "No comments yet. Be the first to comment!", "delete": "Delete", - "confirmDeleteComment": "Are you sure you want to delete this comment?" + "confirmDeleteComment": "Are you sure you want to delete this comment?", + "addCommentPlaceholder": "Add a comment...", + "cancel": "Cancel", + "commentButton": "Comment", + "attachFiles": "Attach files", + "addMoreFiles": "Add more files", + "selectedFiles": "Selected Files (Up to 25MB, Maximum of {count})", + "maxFilesError": "You can only upload a maximum of {count} files", + "processFilesError": "Failed to process files", + "addCommentError": "Please add a comment or attach files", + "createdBy": "Created {{time}} by {{user}}", + "updatedTime": "Updated {{time}}" }, "searchInputPlaceholder": "Search by name", "pendingInvitation": "Pending Invitation" @@ -70,9 +90,34 @@ "addTimeLog": "Add new time log", "totalLogged": "Total Logged", "exportToExcel": "Export to Excel", - "noTimeLogsFound": "No time logs found" + "noTimeLogsFound": "No time logs found", + "timeLogForm": { + "date": "Date", + "startTime": "Start Time", + "endTime": "End Time", + "workDescription": "Work Description", + "descriptionPlaceholder": "Add a description", + "logTime": "Log time", + "updateTime": "Update time", + "cancel": "Cancel", + "selectDateError": "Please select a date", + "selectStartTimeError": "Please select start time", + "selectEndTimeError": "Please select end time", + "endTimeAfterStartError": "End time must be after start time" + } }, "taskActivityLogTab": { - "title": "Activity Log" + "title": "Activity Log", + "add": "ADD", + "remove": "REMOVE", + "none": "None", + "weight": "Weight", + "createdTask": "created the task." + }, + "taskProgress": { + "markAsDoneTitle": "Mark Task as Done?", + "confirmMarkAsDone": "Yes, mark as done", + "cancelMarkAsDone": "No, keep current status", + "markAsDoneDescription": "You've set the progress to 100%. Would you like to update the task status to \"Done\"?" } } diff --git a/worklenz-frontend/public/locales/en/task-list-filters.json b/worklenz-frontend/public/locales/en/task-list-filters.json index 72c79823..118ac4ce 100644 --- a/worklenz-frontend/public/locales/en/task-list-filters.json +++ b/worklenz-frontend/public/locales/en/task-list-filters.json @@ -55,5 +55,34 @@ "selectCategory": "Select a category", "pleaseEnterAName": "Please enter a name", "pleaseSelectACategory": "Please select a category", - "create": "Create" + "create": "Create", + + "searchTasks": "Search tasks...", + "searchPlaceholder": "Search...", + "fieldsText": "Fields", + "loadingFilters": "Loading filters...", + "noOptionsFound": "No options found", + "filtersActive": "filters active", + "filterActive": "filter active", + "clearAll": "Clear all", + "clearing": "Clearing...", + "cancel": "Cancel", + "search": "Search", + "groupedBy": "Grouped by", + "manage": "Manage", + "manageStatuses": "Manage Statuses", + "managePhases": "Manage Phases", + "dragToReorderStatuses": "Statuses are organized by categories. Drag to reorder within categories. Click 'Add Status' to create new statuses in each category.", + "enterNewStatusName": "Enter new status name...", + "addStatus": "Add Status", + "noStatusesFound": "No statuses in this category", + "deleteStatus": "Delete Status", + "deleteStatusConfirm": "Are you sure you want to delete this status? This action cannot be undone.", + "rename": "Rename", + "delete": "Delete", + "enterStatusName": "Enter status name", + "close": "Close", + "cannotMoveStatus": "Cannot Move Status", + "cannotMoveStatusMessage": "Cannot move this status because it would leave the '{{categoryName}}' category empty. Each category must have at least one status.", + "ok": "OK" } diff --git a/worklenz-frontend/public/locales/en/task-list-table.json b/worklenz-frontend/public/locales/en/task-list-table.json index e05b7790..5c03f203 100644 --- a/worklenz-frontend/public/locales/en/task-list-table.json +++ b/worklenz-frontend/public/locales/en/task-list-table.json @@ -36,9 +36,10 @@ "selectText": "Select", "labelsSelectorInputTip": "Hit enter to create!", - "addTaskText": "+ Add Task", - "addSubTaskText": "+ Add Sub Task", + "addTaskText": "Add Task", + "addSubTaskText": "Add Sub Task", "addTaskInputPlaceholder": "Type your task and hit enter", + "noTasksInGroup": "No tasks in this group", "openButton": "Open", "okButton": "Ok", @@ -47,7 +48,10 @@ "searchInputPlaceholder": "Search or create", "assigneeSelectorInviteButton": "Invite a new member by email", "labelInputPlaceholder": "Search or create", - + "searchLabelsPlaceholder": "Search labels...", + "createLabelButton": "Create \"{{name}}\"", + "manageLabelsPath": "Settings → Labels", + "pendingInvitation": "Pending Invitation", "contextMenu": { @@ -59,5 +63,74 @@ "convertToTask": "Convert to Task", "delete": "Delete", "searchByNameInputPlaceholder": "Search by name" + }, + "setDueDate": "Set due date", + "setStartDate": "Set start date", + "clearDueDate": "Clear due date", + "clearStartDate": "Clear start date", + "dueDatePlaceholder": "Due Date", + "startDatePlaceholder": "Start Date", + + "emptyStates": { + "noTaskGroups": "No task groups found", + "noTaskGroupsDescription": "Tasks will appear here when they are created or when filters are applied.", + "errorPrefix": "Error:", + "dragTaskFallback": "Task" + }, + + "customColumns": { + "addCustomColumn": "Add a custom column", + "customColumnHeader": "Custom Column", + "customColumnSettings": "Custom column settings", + "noCustomValue": "No value", + "peopleField": "People field", + "noDate": "No date", + "unsupportedField": "Unsupported field type", + + "modal": { + "addFieldTitle": "Add field", + "editFieldTitle": "Edit field", + "fieldTitle": "Field title", + "fieldTitleRequired": "Field title is required", + "columnTitlePlaceholder": "Column title", + "type": "Type", + "deleteConfirmTitle": "Are you sure you want to delete this custom column?", + "deleteConfirmDescription": "This action cannot be undone. All data associated with this column will be permanently deleted.", + "deleteButton": "Delete", + "cancelButton": "Cancel", + "createButton": "Create", + "updateButton": "Update", + "createSuccessMessage": "Custom column created successfully", + "updateSuccessMessage": "Custom column updated successfully", + "deleteSuccessMessage": "Custom column deleted successfully", + "deleteErrorMessage": "Failed to delete custom column", + "createErrorMessage": "Failed to create custom column", + "updateErrorMessage": "Failed to update custom column" + }, + + "fieldTypes": { + "people": "People", + "number": "Number", + "date": "Date", + "selection": "Selection", + "checkbox": "Checkbox", + "labels": "Labels", + "key": "Key", + "formula": "Formula" + } + }, + + "indicators": { + "tooltips": { + "subtasks": "{{count}} subtask", + "subtasks_plural": "{{count}} subtasks", + "comments": "{{count}} comment", + "comments_plural": "{{count}} comments", + "attachments": "{{count}} attachment", + "attachments_plural": "{{count}} attachments", + "subscribers": "Task has subscribers", + "dependencies": "Task has dependencies", + "recurring": "Recurring task" + } } } diff --git a/worklenz-frontend/public/locales/en/task-management.json b/worklenz-frontend/public/locales/en/task-management.json new file mode 100644 index 00000000..1b771420 --- /dev/null +++ b/worklenz-frontend/public/locales/en/task-management.json @@ -0,0 +1,39 @@ +{ + "noTasksInGroup": "No tasks in this group", + "noTasksInGroupDescription": "Add a task to get started", + "addFirstTask": "Add your first task", + "openTask": "Open", + "subtask": "subtask", + "subtasks": "subtasks", + "comment": "comment", + "comments": "comments", + "attachment": "attachment", + "attachments": "attachments", + "enterSubtaskName": "Enter subtask name...", + "add": "Add", + "cancel": "Cancel", + "renameGroup": "Rename Group", + "renameStatus": "Rename Status", + "renamePhase": "Rename Phase", + "changeCategory": "Change Category", + "clickToEditGroupName": "Click to edit group name", + "enterGroupName": "Enter group name", + "todo": "To Do", + "inProgress": "Doing", + "done": "Done", + "defaultTaskName": "Untitled Task", + + "indicators": { + "tooltips": { + "subtasks": "{{count}} subtask", + "subtasks_plural": "{{count}} subtasks", + "comments": "{{count}} comment", + "comments_plural": "{{count}} comments", + "attachments": "{{count}} attachment", + "attachments_plural": "{{count}} attachments", + "subscribers": "Task has subscribers", + "dependencies": "Task has dependencies", + "recurring": "Recurring task" + } + } +} diff --git a/worklenz-frontend/public/locales/en/task-template-drawer.json b/worklenz-frontend/public/locales/en/task-template-drawer.json index f2e23bee..9bc59126 100644 --- a/worklenz-frontend/public/locales/en/task-template-drawer.json +++ b/worklenz-frontend/public/locales/en/task-template-drawer.json @@ -4,6 +4,7 @@ "cancelText": "Cancel", "saveText": "Save", "templateNameText": "Template Name", + "templateNameRequired": "Template name is required", "selectedTasks": "Selected Tasks", "removeTask": "Remove", "cancelButton": "Cancel", diff --git a/worklenz-frontend/public/locales/en/tasks/task-table-bulk-actions.json b/worklenz-frontend/public/locales/en/tasks/task-table-bulk-actions.json index 2434aea4..42fcc024 100644 --- a/worklenz-frontend/public/locales/en/tasks/task-table-bulk-actions.json +++ b/worklenz-frontend/public/locales/en/tasks/task-table-bulk-actions.json @@ -1,24 +1,41 @@ { - "taskSelected": "task selected", - "tasksSelected": "tasks selected", - "changeStatus": "Change Status/ Prioriy/ Phases", - "changeLabel": "Change Label", - "assignToMe": "Assign to me", - "changeAssignees": "Change Assignees", - "archive": "Archive", - "unarchive": "Unarchive", - "delete": "Delete", - "moreOptions": "More options", - "deselectAll": "Deselect all", - "status": "Status", - "priority": "Priority", - "phase": "Phase", - "member": "Member", - "createTaskTemplate": "Create Task Template", - "apply": "Apply", - "createLabel": "+ Create Label", - "hitEnterToCreate": "Press Enter to create", - "pendingInvitation": "Pending Invitation", - "noMatchingLabels": "No matching labels", - "noLabels": "No labels" -} \ No newline at end of file + "taskSelected": "task selected", + "tasksSelected": "tasks selected", + "changeStatus": "Change Status/ Prioriy/ Phases", + "changeLabel": "Change Label", + "assignToMe": "Assign to me", + "changeAssignees": "Change Assignees", + "archive": "Archive", + "unarchive": "Unarchive", + "delete": "Delete", + "moreOptions": "More options", + "deselectAll": "Deselect all", + "status": "Status", + "priority": "Priority", + "phase": "Phase", + "member": "Member", + "createTaskTemplate": "Create Task Template", + "apply": "Apply", + "createLabel": "+ Create Label", + "searchOrCreateLabel": "Search or create label...", + "hitEnterToCreate": "Press Enter to create", + "labelExists": "Label already exists", + "pendingInvitation": "Pending Invitation", + "noMatchingLabels": "No matching labels", + "noLabels": "No labels", + "CHANGE_STATUS": "Change Status", + "CHANGE_PRIORITY": "Change Priority", + "CHANGE_PHASE": "Change Phase", + "ADD_LABELS": "Add Labels", + "ASSIGN_TO_ME": "Assign to Me", + "ASSIGN_MEMBERS": "Assign Members", + "ARCHIVE": "Archive", + "DELETE": "Delete", + "CANCEL": "Cancel", + "CLEAR_SELECTION": "Clear Selection", + "TASKS_SELECTED": "{{count}} task selected", + "TASKS_SELECTED_plural": "{{count}} tasks selected", + "DELETE_TASKS_CONFIRM": "Delete {{count}} task?", + "DELETE_TASKS_CONFIRM_plural": "Delete {{count}} tasks?", + "DELETE_TASKS_WARNING": "This action cannot be undone." +} diff --git a/worklenz-frontend/public/locales/en/time-report.json b/worklenz-frontend/public/locales/en/time-report.json index b5da8dd2..00aa3c7f 100644 --- a/worklenz-frontend/public/locales/en/time-report.json +++ b/worklenz-frontend/public/locales/en/time-report.json @@ -40,5 +40,18 @@ "noCategory": "No Category", "noProjects": "No projects found", "noTeams": "No teams found", - "noData": "No data found" + "noData": "No data found", + + "groupBy": "Group by", + "groupByCategory": "Category", + "groupByTeam": "Team", + "groupByStatus": "Status", + "groupByNone": "None", + "clearSearch": "Clear search", + "selectedProjects": "Selected Projects", + "projectsSelected": "projects selected", + "showSelected": "Show Selected Only", + "expandAll": "Expand All", + "collapseAll": "Collapse All", + "ungrouped": "Ungrouped" } diff --git a/worklenz-frontend/public/locales/en/unauthorized.json b/worklenz-frontend/public/locales/en/unauthorized.json index ad92a7c5..5233250a 100644 --- a/worklenz-frontend/public/locales/en/unauthorized.json +++ b/worklenz-frontend/public/locales/en/unauthorized.json @@ -1,5 +1,5 @@ { - "title": "Unauthorized!", - "subtitle": "You are not authorized to access this page", - "button": "Go to Home" -} \ No newline at end of file + "title": "Unauthorized!", + "subtitle": "You are not authorized to access this page", + "button": "Go to Home" +} diff --git a/worklenz-frontend/public/locales/es/admin-center/current-bill.json b/worklenz-frontend/public/locales/es/admin-center/current-bill.json index 9278dfe1..52a4bdbb 100644 --- a/worklenz-frontend/public/locales/es/admin-center/current-bill.json +++ b/worklenz-frontend/public/locales/es/admin-center/current-bill.json @@ -24,7 +24,7 @@ "paymentMethod": "Método de Pago", "status": "Estado", "ltdUsers": "Puedes agregar hasta {{ltd_users}} usuarios.", - + "drawerTitle": "Canjear Código", "label": "Canjear Código", "drawerPlaceholder": "Ingrese su código de canje", @@ -98,8 +98,16 @@ "perMonthPerUser": "por usuario / mes", "viewInvoice": "Ver Factura", "switchToFreePlan": "Cambiar a Plan Gratuito", - + "expirestoday": "hoy", "expirestomorrow": "mañana", - "expiredDaysAgo": "hace {{days}} días" + "expiredDaysAgo": "hace {{days}} días", + "creditPlan": "Plan de Crédito", + "customPlan": "Plan Personalizado", + "planValidTill": "Su plan es válido hasta {{date}}", + "purchaseSeatsText": "Para continuar, deberá comprar asientos adicionales.", + "currentSeatsText": "Actualmente tiene {{seats}} asientos disponibles.", + "selectSeatsText": "Seleccione el número de asientos adicionales a comprar.", + "purchase": "Comprar", + "contactSales": "Contactar ventas" } diff --git a/worklenz-frontend/public/locales/es/admin-center/teams.json b/worklenz-frontend/public/locales/es/admin-center/teams.json index 98e3b188..13453656 100644 --- a/worklenz-frontend/public/locales/es/admin-center/teams.json +++ b/worklenz-frontend/public/locales/es/admin-center/teams.json @@ -29,5 +29,7 @@ "role": "Rol", "owner": "Propietario", "admin": "Administrador", - "member": "Miembro" + "member": "Miembro", + "cannotChangeOwnerRole": "El rol de Propietario no puede ser cambiado", + "pendingInvitation": "Invitación pendiente" } diff --git a/worklenz-frontend/public/locales/es/all-project-list.json b/worklenz-frontend/public/locales/es/all-project-list.json index 60e9108a..4a72d9c7 100644 --- a/worklenz-frontend/public/locales/es/all-project-list.json +++ b/worklenz-frontend/public/locales/es/all-project-list.json @@ -3,21 +3,32 @@ "client": "Cliente", "category": "Categoría", "status": "Estado", - "tasksProgress": "Progreso de tareas", - "updated_at": "Última actualización", + "tasksProgress": "Progreso de Tareas", + "updated_at": "Última Actualización", "members": "Miembros", "setting": "Configuración", - "archive": "Archivar", "projects": "Proyectos", "refreshProjects": "Actualizar proyectos", "all": "Todos", "favorites": "Favoritos", "archived": "Archivados", "placeholder": "Buscar por nombre", + "archive": "Archivar", "unarchive": "Desarchivar", - "archiveConfirm": "¿Estás seguro de que deseas archivar este proyecto?", - "unarchiveConfirm": "¿Estás seguro de que deseas desarchivar este proyecto?", - "clickToFilter": "Clique para filtrar por", + "archiveConfirm": "¿Está seguro de que desea archivar este proyecto?", + "unarchiveConfirm": "¿Está seguro de que desea desarchivar este proyecto?", + "yes": "Sí", + "no": "No", + "clickToFilter": "Haga clic para filtrar por", "noProjects": "No se encontraron proyectos", - "addToFavourites": "Añadir a favoritos" + "addToFavourites": "Agregar a favoritos", + "list": "Lista", + "group": "Grupo", + "listView": "Vista de Lista", + "groupView": "Vista de Grupo", + "groupBy": { + "category": "Categoría", + "client": "Cliente" + }, + "noPermission": "No tienes permiso para realizar esta acción" } diff --git a/worklenz-frontend/public/locales/es/kanban-board.json b/worklenz-frontend/public/locales/es/kanban-board.json index 71de992c..6e8d5975 100644 --- a/worklenz-frontend/public/locales/es/kanban-board.json +++ b/worklenz-frontend/public/locales/es/kanban-board.json @@ -19,5 +19,12 @@ "archive": "Archivar", "newTaskNamePlaceholder": "Escribe un nombre de tarea", - "newSubtaskNamePlaceholder": "Escribe un nombre de subtarea" -} \ No newline at end of file + "newSubtaskNamePlaceholder": "Escribe un nombre de subtarea", + "untitledSection": "Sección sin título", + "unmapped": "Sin asignar", + "clickToChangeDate": "Haz clic para cambiar la fecha", + "noDueDate": "Sin fecha de vencimiento", + "save": "Guardar", + "clear": "Limpiar", + "nextWeek": "Próxima semana" +} diff --git a/worklenz-frontend/public/locales/es/phases-drawer.json b/worklenz-frontend/public/locales/es/phases-drawer.json index 0363c69c..ed912ac7 100644 --- a/worklenz-frontend/public/locales/es/phases-drawer.json +++ b/worklenz-frontend/public/locales/es/phases-drawer.json @@ -1,7 +1,24 @@ { - "configurePhases": "Configurar fases", - "phaseLabel": "Etiqueta de fase", - "enterPhaseName": "Ingrese un nombre para la etiqueta de fase", - "addOption": "Agregar opción", - "phaseOptions": "Opciones de fase:" -} \ No newline at end of file + "configurePhases": "Configurar Fases", + "configure": "Configurar", + "phaseLabel": "Etiqueta de Fase", + "enterPhaseName": "Ingresa el nombre de la fase", + "addOption": "Agregar Opción", + "phaseOptions": "Opciones de Fase", + "optionsText": "Opciones", + "dragToReorderPhases": "Arrastra las fases para reordenarlas. Cada fase puede tener un color diferente.", + "enterNewPhaseName": "Ingresa el nombre de la nueva fase...", + "addPhase": "Agregar Fase", + "noPhasesFound": "No se encontraron fases", + "no": "No", + "found": "encontrado", + "deletePhase": "Eliminar Fase", + "deletePhaseConfirm": "¿Estás seguro de que quieres eliminar esta fase? Esta acción no se puede deshacer.", + "rename": "Renombrar", + "delete": "Eliminar", + "create": "Crear", + "cancel": "Cancelar", + "selectColor": "Seleccionar color", + "managePhases": "Gestionar Fases", + "close": "Cerrar" +} diff --git a/worklenz-frontend/public/locales/es/project-drawer.json b/worklenz-frontend/public/locales/es/project-drawer.json index 2dc114cc..447ad4f1 100644 --- a/worklenz-frontend/public/locales/es/project-drawer.json +++ b/worklenz-frontend/public/locales/es/project-drawer.json @@ -38,5 +38,15 @@ "createClient": "Crear cliente", "searchInputPlaceholder": "Busca por nombre o email", "hoursPerDayValidationMessage": "Las horas por día deben ser un número entre 1 y 24", - "noPermission": "Sin permiso" + "workingDaysValidationMessage": "Los días de trabajo deben ser un número positivo", + "manDaysValidationMessage": "Los días hombre deben ser un número positivo", + "noPermission": "Sin permiso", + "progressSettings": "Configuración de Progreso", + "manualProgress": "Progreso Manual", + "manualProgressTooltip": "Permitir actualizaciones manuales de progreso para tareas sin subtareas", + "weightedProgress": "Progreso Ponderado", + "weightedProgressTooltip": "Calcular el progreso basado en los pesos de las subtareas", + "timeProgress": "Progreso Basado en Tiempo", + "timeProgressTooltip": "Calcular el progreso basado en el tiempo estimado", + "enterProjectKey": "Ingresa la clave del proyecto" } diff --git a/worklenz-frontend/public/locales/es/project-view.json b/worklenz-frontend/public/locales/es/project-view.json new file mode 100644 index 00000000..a4c12d9f --- /dev/null +++ b/worklenz-frontend/public/locales/es/project-view.json @@ -0,0 +1,14 @@ +{ + "taskList": "Lista de Tareas", + "board": "Tablero Kanban", + "insights": "Análisis", + "files": "Archivos", + "members": "Miembros", + "updates": "Actualizaciones", + "projectView": "Vista del Proyecto", + "loading": "Cargando proyecto...", + "error": "Error al cargar el proyecto", + "pinnedTab": "Fijado como pestaña predeterminada", + "pinTab": "Fijar como pestaña predeterminada", + "unpinTab": "Desfijar pestaña predeterminada" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/es/project-view/import-task-templates.json b/worklenz-frontend/public/locales/es/project-view/import-task-templates.json index c47edc71..7be1539b 100644 --- a/worklenz-frontend/public/locales/es/project-view/import-task-templates.json +++ b/worklenz-frontend/public/locales/es/project-view/import-task-templates.json @@ -1,11 +1,11 @@ { - "importTaskTemplate": "Importar plantilla de tarea", - "templateName": "Nombre de la plantilla", - "templateDescription": "Descripción de la plantilla", - "selectedTasks": "Tareas seleccionadas", - "tasks": "Tareas", - "templates": "Plantillas", - "remove": "Eliminar", - "cancel": "Cancelar", - "import": "Importar" -} \ No newline at end of file + "importTaskTemplate": "Importar plantilla de tarea", + "templateName": "Nombre de la plantilla", + "templateDescription": "Descripción de la plantilla", + "selectedTasks": "Tareas seleccionadas", + "tasks": "Tareas", + "templates": "Plantillas", + "remove": "Eliminar", + "cancel": "Cancelar", + "import": "Importar" +} diff --git a/worklenz-frontend/public/locales/es/project-view/project-member-drawer.json b/worklenz-frontend/public/locales/es/project-view/project-member-drawer.json index 1a90bbd6..ab7570fd 100644 --- a/worklenz-frontend/public/locales/es/project-view/project-member-drawer.json +++ b/worklenz-frontend/public/locales/es/project-view/project-member-drawer.json @@ -1,8 +1,7 @@ { - "title": "Miembros del Proyecto", - "searchLabel": "Agregar miembros ingresando su nombre o correo electrónico", - "searchPlaceholder": "Escriba nombre o correo electrónico", - "inviteAsAMember": "Invitar como miembro", - "inviteNewMemberByEmail": "Invitar nuevo miembro por correo electrónico" - -} \ No newline at end of file + "title": "Miembros del Proyecto", + "searchLabel": "Agregar miembros ingresando su nombre o correo electrónico", + "searchPlaceholder": "Escriba nombre o correo electrónico", + "inviteAsAMember": "Invitar como miembro", + "inviteNewMemberByEmail": "Invitar nuevo miembro por correo electrónico" +} diff --git a/worklenz-frontend/public/locales/es/project-view/project-view-header.json b/worklenz-frontend/public/locales/es/project-view/project-view-header.json index de6020cf..e2356e2f 100644 --- a/worklenz-frontend/public/locales/es/project-view/project-view-header.json +++ b/worklenz-frontend/public/locales/es/project-view/project-view-header.json @@ -1,13 +1,31 @@ { - "importTasks": "Importar tareas", - "createTask": "Crear tarea", - "settings": "Ajustes", - "subscribe": "Suscribirse", - "unsubscribe": "Cancelar suscripción", - "deleteProject": "Eliminar proyecto", - "startDate": "Fecha de inicio", - "endDate": "Fecha de finalización", - "projectSettings": "Ajustes del proyecto", - "projectSummary": "Resumen del proyecto", - "receiveProjectSummary": "Recibir un resumen del proyecto todas las noches." -} \ No newline at end of file + "importTasks": "Importar tareas", + "importTask": "Importar tarea", + "createTask": "Crear tarea", + "settings": "Configuración", + "subscribe": "Suscribirse", + "unsubscribe": "Desuscribirse", + "deleteProject": "Eliminar proyecto", + "startDate": "Fecha de inicio", + "endDate": "Fecha de fin", + "projectSettings": "Configuración del proyecto", + "projectSummary": "Resumen del proyecto", + "receiveProjectSummary": "Recibir un resumen del proyecto cada noche.", + "refreshProject": "Actualizar proyecto", + "saveAsTemplate": "Guardar como plantilla", + "invite": "Invitar", + "share": "Compartir", + "subscribeTooltip": "Suscribirse a las notificaciones del proyecto", + "unsubscribeTooltip": "Desuscribirse de las notificaciones del proyecto", + "refreshTooltip": "Actualizar datos del proyecto", + "settingsTooltip": "Abrir configuración del proyecto", + "saveAsTemplateTooltip": "Guardar este proyecto como plantilla", + "inviteTooltip": "Invitar miembros del equipo a este proyecto", + "createTaskTooltip": "Crear una nueva tarea", + "importTaskTooltip": "Importar tarea desde plantilla", + "navigateBackTooltip": "Volver a la lista de proyectos", + "projectStatusTooltip": "Estado del proyecto", + "projectDatesInfo": "Información de la cronología del proyecto", + "projectCategoryTooltip": "Categoría del proyecto", + "defaultTaskName": "Tarea Sin Título" +} diff --git a/worklenz-frontend/public/locales/es/project-view/save-as-template.json b/worklenz-frontend/public/locales/es/project-view/save-as-template.json index 6ad67182..4d7e9354 100644 --- a/worklenz-frontend/public/locales/es/project-view/save-as-template.json +++ b/worklenz-frontend/public/locales/es/project-view/save-as-template.json @@ -10,7 +10,7 @@ "taskIncludes": "¿Qué se debe incluir en la plantilla de las tareas?", "taskIncludesOptions": { "statuses": "Estados", - "phases": "Fases", + "phases": "Fases", "labels": "Etiquetas", "name": "Nombre", "priority": "Prioridad", diff --git a/worklenz-frontend/public/locales/es/settings/appearance.json b/worklenz-frontend/public/locales/es/settings/appearance.json new file mode 100644 index 00000000..d6b196da --- /dev/null +++ b/worklenz-frontend/public/locales/es/settings/appearance.json @@ -0,0 +1,5 @@ +{ + "title": "Apariencia", + "darkMode": "Modo Oscuro", + "darkModeDescription": "Cambia entre el modo claro y oscuro para personalizar tu experiencia visual." +} diff --git a/worklenz-frontend/public/locales/es/settings/profile.json b/worklenz-frontend/public/locales/es/settings/profile.json index 9c43a470..1a1698c8 100644 --- a/worklenz-frontend/public/locales/es/settings/profile.json +++ b/worklenz-frontend/public/locales/es/settings/profile.json @@ -9,5 +9,6 @@ "saveChanges": "Guardar cambios", "profileJoinedText": "Se unió hace un mes", "profileLastUpdatedText": "Última actualización hace un mes", - "avatarTooltip": "Haz clic para subir un avatar" + "avatarTooltip": "Haz clic para subir un avatar", + "title": "Configuración del Perfil" } diff --git a/worklenz-frontend/public/locales/es/settings/sidebar.json b/worklenz-frontend/public/locales/es/settings/sidebar.json index 32d529ea..3793e77f 100644 --- a/worklenz-frontend/public/locales/es/settings/sidebar.json +++ b/worklenz-frontend/public/locales/es/settings/sidebar.json @@ -10,5 +10,6 @@ "team-members": "Miembros del equipo", "teams": "Equipos", "change-password": "Cambiar contraseña", - "language-and-region": "Idioma y región" + "language-and-region": "Idioma y región", + "appearance": "Apariencia" } diff --git a/worklenz-frontend/public/locales/es/settings/team-members.json b/worklenz-frontend/public/locales/es/settings/team-members.json index 8de73b84..1000bf98 100644 --- a/worklenz-frontend/public/locales/es/settings/team-members.json +++ b/worklenz-frontend/public/locales/es/settings/team-members.json @@ -1,4 +1,5 @@ { + "title": "Miembros del Equipo", "nameColumn": "Nombre", "projectsColumn": "Proyectos", "emailColumn": "Correo electrónico", @@ -40,5 +41,7 @@ "ownerText": "Propietario del equipo", "addedText": "Agregado", "updatedText": "Actualizado", - "noResultFound": "Escriba una dirección de correo electrónico y presione enter..." + "noResultFound": "Escriba una dirección de correo electrónico y presione enter...", + "jobTitlesFetchError": "Error al obtener los cargos", + "invitationResent": "¡Invitación reenviada exitosamente!" } diff --git a/worklenz-frontend/public/locales/es/settings/teams.json b/worklenz-frontend/public/locales/es/settings/teams.json new file mode 100644 index 00000000..808c1b78 --- /dev/null +++ b/worklenz-frontend/public/locales/es/settings/teams.json @@ -0,0 +1,16 @@ +{ + "title": "Equipos", + "team": "Equipo", + "teams": "Equipos", + "name": "Nombre", + "created": "Creado", + "ownsBy": "Pertenece a", + "edit": "Editar", + "editTeam": "Editar Equipo", + "pinTooltip": "Haz clic para fijar esto en el menú principal", + "editTeamName": "Editar Nombre del Equipo", + "updateName": "Actualizar Nombre", + "namePlaceholder": "Nombre", + "nameRequired": "Por favor ingresa un Nombre", + "updateFailed": "¡Falló el cambio de nombre del equipo!" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/es/task-drawer/task-drawer-info-tab.json b/worklenz-frontend/public/locales/es/task-drawer/task-drawer-info-tab.json index 58c5715e..02b3038a 100644 --- a/worklenz-frontend/public/locales/es/task-drawer/task-drawer-info-tab.json +++ b/worklenz-frontend/public/locales/es/task-drawer/task-drawer-info-tab.json @@ -15,7 +15,8 @@ "hide-start-date": "Ocultar fecha de inicio", "show-start-date": "Mostrar fecha de inicio", "hours": "Horas", - "minutes": "Minutos" + "minutes": "Minutos", + "recurring": "Recurrente" }, "description": { "title": "Descripción", @@ -26,4 +27,4 @@ "add-sub-task": "+ Añadir subtarea", "refresh-sub-tasks": "Actualizar subtareas" } -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/es/task-drawer/task-drawer-recurring-config.json b/worklenz-frontend/public/locales/es/task-drawer/task-drawer-recurring-config.json new file mode 100644 index 00000000..c1ef9e83 --- /dev/null +++ b/worklenz-frontend/public/locales/es/task-drawer/task-drawer-recurring-config.json @@ -0,0 +1,34 @@ +{ + "recurring": "Recurrente", + "recurringTaskConfiguration": "Configuración de tarea recurrente", + "repeats": "Repeticiones", + "daily": "Diario", + "weekly": "Semanal", + "everyXDays": "Cada X días", + "everyXWeeks": "Cada X semanas", + "everyXMonths": "Cada X meses", + "monthly": "Mensual", + "selectDaysOfWeek": "Seleccionar días de la semana", + "mon": "Lun", + "tue": "Mar", + "wed": "Mié", + "thu": "Jue", + "fri": "Vie", + "sat": "Sáb", + "sun": "Dom", + "monthlyRepeatType": "Tipo de repetición mensual", + "onSpecificDate": "En una fecha específica", + "onSpecificDay": "En un día específico", + "dateOfMonth": "Fecha del mes", + "weekOfMonth": "Semana del mes", + "dayOfWeek": "Día de la semana", + "first": "Primero", + "second": "Segundo", + "third": "Tercero", + "fourth": "Cuarto", + "last": "Último", + "intervalDays": "Intervalo (días)", + "intervalWeeks": "Intervalo (semanas)", + "intervalMonths": "Intervalo (meses)", + "saveChanges": "Guardar cambios" +} diff --git a/worklenz-frontend/public/locales/es/task-drawer/task-drawer.json b/worklenz-frontend/public/locales/es/task-drawer/task-drawer.json index 387968e9..8e438716 100644 --- a/worklenz-frontend/public/locales/es/task-drawer/task-drawer.json +++ b/worklenz-frontend/public/locales/es/task-drawer/task-drawer.json @@ -1,78 +1,123 @@ { "taskHeader": { - "taskNamePlaceholder": "Escribe tu tarea", - "deleteTask": "Eliminar tarea" + "taskNamePlaceholder": "Escriba su Tarea", + "deleteTask": "Eliminar Tarea" }, "taskInfoTab": { "title": "Información", "details": { "title": "Detalles", - "task-key": "Clave de tarea", + "task-key": "Clave de Tarea", "phase": "Fase", "assignees": "Asignados", - "due-date": "Fecha de vencimiento", - "time-estimation": "Estimación de tiempo", + "due-date": "Fecha de Vencimiento", + "time-estimation": "Estimación de Tiempo", "priority": "Prioridad", "labels": "Etiquetas", "billable": "Facturable", "notify": "Notificar", "when-done-notify": "Al terminar, notificar", - "start-date": "Fecha de inicio", - "end-date": "Fecha de finalización", - "hide-start-date": "Ocultar fecha de inicio", - "show-start-date": "Mostrar fecha de inicio", + "start-date": "Fecha de Inicio", + "end-date": "Fecha de Fin", + "hide-start-date": "Ocultar Fecha de Inicio", + "show-start-date": "Mostrar Fecha de Inicio", "hours": "Horas", - "minutes": "Minutos" + "minutes": "Minutos", + "progressValue": "Valor de Progreso", + "progressValueTooltip": "Establecer el porcentaje de progreso (0-100%)", + "progressValueRequired": "Por favor, introduzca un valor de progreso", + "progressValueRange": "El progreso debe estar entre 0 y 100", + "taskWeight": "Peso de la Tarea", + "taskWeightTooltip": "Establecer el peso de esta subtarea (porcentaje)", + "taskWeightRequired": "Por favor, introduzca un peso de tarea", + "taskWeightRange": "El peso debe estar entre 0 y 100", + "recurring": "Recurrente" }, "labels": { "labelInputPlaceholder": "Buscar o crear", - "labelsSelectorInputTip": "Pulse Enter para crear" + "labelsSelectorInputTip": "Presiona Enter para crear" }, "description": { "title": "Descripción", "placeholder": "Añadir una descripción más detallada..." }, "subTasks": { - "title": "Subtareas", - "addSubTask": "+ Añadir subtarea", - "addSubTaskInputPlaceholder": "Escribe tu tarea y pulsa enter", - "refreshSubTasks": "Actualizar subtareas", + "title": "Sub Tareas", + "addSubTask": "Agregar Sub Tarea", + "addSubTaskInputPlaceholder": "Escriba su tarea y presione enter", + "refreshSubTasks": "Actualizar Sub Tareas", "edit": "Editar", "delete": "Eliminar", - "confirmDeleteSubTask": "¿Estás seguro de que quieres eliminar esta subtarea?", - "deleteSubTask": "Eliminar subtarea" + "confirmDeleteSubTask": "¿Está seguro de que desea eliminar esta subtarea?", + "deleteSubTask": "Eliminar Sub Tarea" }, "dependencies": { "title": "Dependencias", - "addDependency": "+ Añadir nueva dependencia", + "addDependency": "+ Agregar nueva dependencia", "blockedBy": "Bloqueado por", - "searchTask": "Escribe para buscar tarea", + "searchTask": "Escribir para buscar tarea", "noTasksFound": "No se encontraron tareas", - "confirmDeleteDependency": "¿Estás seguro de que quieres eliminar?" + "confirmDeleteDependency": "¿Está seguro de que desea eliminar?" }, "attachments": { "title": "Adjuntos", - "chooseOrDropFileToUpload": "Elige o arrastra un archivo para subir", + "chooseOrDropFileToUpload": "Elija o arrastre un archivo para subir", "uploading": "Subiendo..." }, "comments": { "title": "Comentarios", - "addComment": "+ Añadir nuevo comentario", - "noComments": "No hay comentarios todavía. ¡Sé el primero en comentar!", + "addComment": "+ Agregar nuevo comentario", + "noComments": "Aún no hay comentarios. ¡Sé el primero en comentar!", "delete": "Eliminar", - "confirmDeleteComment": "¿Estás seguro de que quieres eliminar este comentario?" + "confirmDeleteComment": "¿Está seguro de que desea eliminar este comentario?", + "addCommentPlaceholder": "Agregar un comentario...", + "cancel": "Cancelar", + "commentButton": "Comentar", + "attachFiles": "Adjuntar archivos", + "addMoreFiles": "Agregar más archivos", + "selectedFiles": "Archivos Seleccionados (Hasta 25MB, Máximo {count})", + "maxFilesError": "Solo puede subir un máximo de {count} archivos", + "processFilesError": "Error al procesar archivos", + "addCommentError": "Por favor agregue un comentario o adjunte archivos", + "createdBy": "Creado {{time}} por {{user}}", + "updatedTime": "Actualizado {{time}}" }, "searchInputPlaceholder": "Buscar por nombre", - "pendingInvitation": "Invitación pendiente" + "pendingInvitation": "Invitación Pendiente" }, "taskTimeLogTab": { - "title": "Registro de tiempo", + "title": "Registro de Tiempo", "addTimeLog": "Añadir nuevo registro de tiempo", - "totalLogged": "Total registrado", + "totalLogged": "Total Registrado", "exportToExcel": "Exportar a Excel", - "noTimeLogsFound": "No se encontraron registros de tiempo" + "noTimeLogsFound": "No se encontraron registros de tiempo", + "timeLogForm": { + "date": "Fecha", + "startTime": "Hora de Inicio", + "endTime": "Hora de Fin", + "workDescription": "Descripción del Trabajo", + "descriptionPlaceholder": "Agregar una descripción", + "logTime": "Registrar tiempo", + "updateTime": "Actualizar tiempo", + "cancel": "Cancelar", + "selectDateError": "Por favor seleccione una fecha", + "selectStartTimeError": "Por favor seleccione la hora de inicio", + "selectEndTimeError": "Por favor seleccione la hora de fin", + "endTimeAfterStartError": "La hora de fin debe ser posterior a la hora de inicio" + } }, "taskActivityLogTab": { - "title": "Registro de actividad" + "title": "Registro de Actividad", + "add": "AGREGAR", + "remove": "QUITAR", + "none": "Ninguno", + "weight": "Peso", + "createdTask": "creó la tarea." + }, + "taskProgress": { + "markAsDoneTitle": "¿Marcar Tarea como Completada?", + "confirmMarkAsDone": "Sí, marcar como completada", + "cancelMarkAsDone": "No, mantener estado actual", + "markAsDoneDescription": "Ha establecido el progreso al 100%. ¿Le gustaría actualizar el estado de la tarea a \"Completada\"?" } -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/es/task-list-filters.json b/worklenz-frontend/public/locales/es/task-list-filters.json index c6cf8c99..00c27f16 100644 --- a/worklenz-frontend/public/locales/es/task-list-filters.json +++ b/worklenz-frontend/public/locales/es/task-list-filters.json @@ -6,6 +6,8 @@ "sortText": "Ordenar", "statusText": "Estado", "phaseText": "Fase", + "memberText": "Miembros", + "assigneesText": "Asignados", "priorityText": "Prioridad", "labelsText": "Etiquetas", "membersText": "Miembros", @@ -16,30 +18,32 @@ "taskText": "Tarea", "descriptionText": "Descripción", "phasesText": "Fases", + "listText": "Lista", "progressText": "Progreso", "timeTrackingText": "Seguimiento de tiempo", + "timetrackingText": "Seguimiento de tiempo", "estimationText": "Estimación", "startDateText": "Fecha de inicio", + "startdateText": "Fecha de inicio", "endDateText": "Fecha de fin", "dueDateText": "Fecha de vencimiento", + "duedateText": "Fecha de vencimiento", "completedDateText": "Fecha de finalización", + "completeddateText": "Fecha de finalización", "createdDateText": "Fecha de creación", + "createddateText": "Fecha de creación", "lastUpdatedText": "Última actualización", + "lastupdatedText": "Última actualización", "reporterText": "Reportero", "dueTimeText": "Hora de vencimiento", - "lowText": "Baja", - "mediumText": "Media", - "highText": "Alta", - "assigneesText": "Asignados", - "timetrackingText": "Seguimiento de tiempo", - "startdateText": "Fecha de inicio", - "duedateText": "Fecha de vencimiento", - "completeddateText": "Fecha de finalización", - "createddateText": "Fecha de creación", - "lastupdatedText": "Última actualización", "duetimeText": "Hora de vencimiento", - "createStatusButtonTooltip": "Configuración de estados", - "configPhaseButtonTooltip": "Configuración de fases", + + "lowText": "Bajo", + "mediumText": "Medio", + "highText": "Alto", + + "createStatusButtonTooltip": "Configuración de estado", + "configPhaseButtonTooltip": "Configuración de fase", "noLabelsFound": "No se encontraron etiquetas", "addStatusButton": "Agregar estado", @@ -49,7 +53,36 @@ "name": "Nombre", "category": "Categoría", "selectCategory": "Seleccionar una categoría", - "pleaseEnterAName": "Por favor, ingrese un nombre", - "pleaseSelectACategory": "Por favor, seleccione una categoría", - "create": "Crear" + "pleaseEnterAName": "Por favor ingrese un nombre", + "pleaseSelectACategory": "Por favor seleccione una categoría", + "create": "Crear", + + "searchTasks": "Buscar tareas...", + "searchPlaceholder": "Buscar...", + "fieldsText": "Campos", + "loadingFilters": "Cargando filtros...", + "noOptionsFound": "No se encontraron opciones", + "filtersActive": "filtros activos", + "filterActive": "filtro activo", + "clearAll": "Limpiar todo", + "clearing": "Limpiando...", + "cancel": "Cancelar", + "search": "Buscar", + "groupedBy": "Agrupado por", + "manage": "Gestionar", + "manageStatuses": "Gestionar Estados", + "managePhases": "Gestionar Fases", + "dragToReorderStatuses": "Los estados están organizados por categorías. Arrastra para reordenar dentro de las categorías. Haz clic en 'Agregar estado' para crear nuevos estados en cada categoría.", + "enterNewStatusName": "Ingrese el nombre del nuevo estado...", + "addStatus": "Agregar estado", + "noStatusesFound": "No hay estados en esta categoría", + "deleteStatus": "Eliminar estado", + "deleteStatusConfirm": "¿Está seguro de que desea eliminar este estado? Esta acción no se puede deshacer.", + "rename": "Renombrar", + "delete": "Eliminar", + "enterStatusName": "Ingrese el nombre del estado", + "close": "Cerrar", + "cannotMoveStatus": "No se puede mover el estado", + "cannotMoveStatusMessage": "No se puede mover este estado porque dejaría vacía la categoría '{{categoryName}}'. Cada categoría debe tener al menos un estado.", + "ok": "OK" } diff --git a/worklenz-frontend/public/locales/es/task-list-table.json b/worklenz-frontend/public/locales/es/task-list-table.json index 659cb8c1..0648c2ff 100644 --- a/worklenz-frontend/public/locales/es/task-list-table.json +++ b/worklenz-frontend/public/locales/es/task-list-table.json @@ -36,8 +36,9 @@ "selectText": "Seleccionar", "labelsSelectorInputTip": "¡Presiona enter para crear!", - "addTaskText": "+ Agregar tarea", - "addSubTaskText": "+ Agregar subtarea", + "addTaskText": "Agregar tarea", + "addSubTaskText": "Agregar subtarea", + "noTasksInGroup": "No hay tareas en este grupo", "addTaskInputPlaceholder": "Escribe tu tarea y presiona enter", "openButton": "Abrir", @@ -47,7 +48,10 @@ "searchInputPlaceholder": "Buscar o crear", "assigneeSelectorInviteButton": "Invitar a un nuevo miembro por correo", "labelInputPlaceholder": "Buscar o crear", - + "searchLabelsPlaceholder": "Buscar etiquetas...", + "createLabelButton": "Crear \"{{name}}\"", + "manageLabelsPath": "Configuración → Etiquetas", + "pendingInvitation": "Invitación pendiente", "contextMenu": { @@ -59,5 +63,74 @@ "convertToTask": "Convertir en tarea", "delete": "Eliminar", "searchByNameInputPlaceholder": "Buscar por nombre" + }, + "setDueDate": "Establecer fecha de vencimiento", + "setStartDate": "Establecer fecha de inicio", + "clearDueDate": "Limpiar fecha de vencimiento", + "clearStartDate": "Limpiar fecha de inicio", + "dueDatePlaceholder": "Fecha de vencimiento", + "startDatePlaceholder": "Fecha de inicio", + + "emptyStates": { + "noTaskGroups": "No se encontraron grupos de tareas", + "noTaskGroupsDescription": "Las tareas aparecerán aquí cuando se creen o cuando se apliquen filtros.", + "errorPrefix": "Error:", + "dragTaskFallback": "Tarea" + }, + + "customColumns": { + "addCustomColumn": "Agregar una columna personalizada", + "customColumnHeader": "Columna Personalizada", + "customColumnSettings": "Configuración de columna personalizada", + "noCustomValue": "Sin valor", + "peopleField": "Campo de personas", + "noDate": "Sin fecha", + "unsupportedField": "Tipo de campo no compatible", + + "modal": { + "addFieldTitle": "Agregar campo", + "editFieldTitle": "Editar campo", + "fieldTitle": "Título del campo", + "fieldTitleRequired": "El título del campo es obligatorio", + "columnTitlePlaceholder": "Título de la columna", + "type": "Tipo", + "deleteConfirmTitle": "¿Está seguro de que desea eliminar esta columna personalizada?", + "deleteConfirmDescription": "Esta acción no se puede deshacer. Todos los datos asociados con esta columna se eliminarán permanentemente.", + "deleteButton": "Eliminar", + "cancelButton": "Cancelar", + "createButton": "Crear", + "updateButton": "Actualizar", + "createSuccessMessage": "Columna personalizada creada exitosamente", + "updateSuccessMessage": "Columna personalizada actualizada exitosamente", + "deleteSuccessMessage": "Columna personalizada eliminada exitosamente", + "deleteErrorMessage": "Error al eliminar la columna personalizada", + "createErrorMessage": "Error al crear la columna personalizada", + "updateErrorMessage": "Error al actualizar la columna personalizada" + }, + + "fieldTypes": { + "people": "Personas", + "number": "Número", + "date": "Fecha", + "selection": "Selección", + "checkbox": "Casilla de verificación", + "labels": "Etiquetas", + "key": "Clave", + "formula": "Fórmula" + } + }, + + "indicators": { + "tooltips": { + "subtasks": "{{count}} subtarea", + "subtasks_plural": "{{count}} subtareas", + "comments": "{{count}} comentario", + "comments_plural": "{{count}} comentarios", + "attachments": "{{count}} archivo adjunto", + "attachments_plural": "{{count}} archivos adjuntos", + "subscribers": "La tarea tiene suscriptores", + "dependencies": "La tarea tiene dependencias", + "recurring": "Tarea recurrente" + } } } diff --git a/worklenz-frontend/public/locales/es/task-management.json b/worklenz-frontend/public/locales/es/task-management.json new file mode 100644 index 00000000..48f63afe --- /dev/null +++ b/worklenz-frontend/public/locales/es/task-management.json @@ -0,0 +1,39 @@ +{ + "noTasksInGroup": "No hay tareas en este grupo", + "noTasksInGroupDescription": "Añade una tarea para comenzar", + "addFirstTask": "Añade tu primera tarea", + "openTask": "Abrir", + "subtask": "subtarea", + "subtasks": "subtareas", + "comment": "comentario", + "comments": "comentarios", + "attachment": "adjunto", + "attachments": "adjuntos", + "enterSubtaskName": "Ingresa el nombre de la subtarea...", + "add": "Añadir", + "cancel": "Cancelar", + "renameGroup": "Renombrar Grupo", + "renameStatus": "Renombrar Estado", + "renamePhase": "Renombrar Fase", + "changeCategory": "Cambiar Categoría", + "clickToEditGroupName": "Haz clic para editar el nombre del grupo", + "enterGroupName": "Ingresa el nombre del grupo", + "todo": "Por Hacer", + "inProgress": "En Progreso", + "done": "Hecho", + "defaultTaskName": "Tarea Sin Título", + + "indicators": { + "tooltips": { + "subtasks": "{{count}} subtarea", + "subtasks_plural": "{{count}} subtareas", + "comments": "{{count}} comentario", + "comments_plural": "{{count}} comentarios", + "attachments": "{{count}} adjunto", + "attachments_plural": "{{count}} adjuntos", + "subscribers": "La tarea tiene suscriptores", + "dependencies": "La tarea tiene dependencias", + "recurring": "Tarea recurrente" + } + } +} diff --git a/worklenz-frontend/public/locales/es/tasks/task-table-bulk-actions.json b/worklenz-frontend/public/locales/es/tasks/task-table-bulk-actions.json index 0040c407..0f98b1a5 100644 --- a/worklenz-frontend/public/locales/es/tasks/task-table-bulk-actions.json +++ b/worklenz-frontend/public/locales/es/tasks/task-table-bulk-actions.json @@ -1,24 +1,41 @@ { - "taskSelected": "Tarea seleccionada", - "tasksSelected": "Tareas seleccionadas", - "changeStatus": "Cambiar estado/ prioridad/ fases", - "changeLabel": "Cambiar etiqueta", - "assignToMe": "Asignar a mí", - "changeAssignees": "Cambiar asignados", - "archive": "Archivar", - "unarchive": "Desarchivar", - "delete": "Eliminar", - "moreOptions": "Más opciones", - "deselectAll": "Deseleccionar todo", - "status": "Estado", - "priority": "Prioridad", - "phase": "Fase", - "member": "Miembro", - "createTaskTemplate": "Crear plantilla de tarea", - "apply": "Aplicar", - "createLabel": "+ Crear etiqueta", - "hitEnterToCreate": "Presione Enter para crear", - "pendingInvitation": "Invitación Pendiente", - "noMatchingLabels": "No hay etiquetas coincidentes", - "noLabels": "Sin etiquetas" -} \ No newline at end of file + "taskSelected": "Tarea seleccionada", + "tasksSelected": "Tareas seleccionadas", + "changeStatus": "Cambiar estado/ prioridad/ fases", + "changeLabel": "Cambiar etiqueta", + "assignToMe": "Asignar a mí", + "changeAssignees": "Cambiar asignados", + "archive": "Archivar", + "unarchive": "Desarchivar", + "delete": "Eliminar", + "moreOptions": "Más opciones", + "deselectAll": "Deseleccionar todo", + "status": "Estado", + "priority": "Prioridad", + "phase": "Fase", + "member": "Miembro", + "createTaskTemplate": "Crear plantilla de tarea", + "apply": "Aplicar", + "createLabel": "+ Crear etiqueta", + "searchOrCreateLabel": "Buscar o crear etiqueta...", + "hitEnterToCreate": "Presione Enter para crear", + "labelExists": "La etiqueta ya existe", + "pendingInvitation": "Invitación Pendiente", + "noMatchingLabels": "No hay etiquetas coincidentes", + "noLabels": "Sin etiquetas", + "CHANGE_STATUS": "Cambiar Estado", + "CHANGE_PRIORITY": "Cambiar Prioridad", + "CHANGE_PHASE": "Cambiar Fase", + "ADD_LABELS": "Agregar Etiquetas", + "ASSIGN_TO_ME": "Asignar a Mí", + "ASSIGN_MEMBERS": "Asignar Miembros", + "ARCHIVE": "Archivar", + "DELETE": "Eliminar", + "CANCEL": "Cancelar", + "CLEAR_SELECTION": "Limpiar Selección", + "TASKS_SELECTED": "{{count}} tarea seleccionada", + "TASKS_SELECTED_plural": "{{count}} tareas seleccionadas", + "DELETE_TASKS_CONFIRM": "¿Eliminar {{count}} tarea?", + "DELETE_TASKS_CONFIRM_plural": "¿Eliminar {{count}} tareas?", + "DELETE_TASKS_WARNING": "Esta acción no se puede deshacer." +} diff --git a/worklenz-frontend/public/locales/es/time-report.json b/worklenz-frontend/public/locales/es/time-report.json index a602ec1d..2646520f 100644 --- a/worklenz-frontend/public/locales/es/time-report.json +++ b/worklenz-frontend/public/locales/es/time-report.json @@ -7,7 +7,7 @@ "selectAll": "Seleccionar Todo", "teams": "Equipos", - "searchByProject": "Buscar por nombre de proyecto", + "searchByProject": "Buscar por nombre del proyecto", "projects": "Proyectos", "searchByCategory": "Buscar por nombre de categoría", @@ -37,8 +37,21 @@ "actualDays": "Días Reales", "noCategories": "No se encontraron categorías", - "noCategory": "No Categoría", + "noCategory": "Sin Categoría", "noProjects": "No se encontraron proyectos", "noTeams": "No se encontraron equipos", - "noData": "No se encontraron datos" + "noData": "No se encontraron datos", + + "groupBy": "Agrupar por", + "groupByCategory": "Categoría", + "groupByTeam": "Equipo", + "groupByStatus": "Estado", + "groupByNone": "Ninguno", + "clearSearch": "Limpiar búsqueda", + "selectedProjects": "Proyectos Seleccionados", + "projectsSelected": "proyectos seleccionados", + "showSelected": "Mostrar Solo Seleccionados", + "expandAll": "Expandir Todo", + "collapseAll": "Contraer Todo", + "ungrouped": "Sin Agrupar" } diff --git a/worklenz-frontend/public/locales/es/unauthorized.json b/worklenz-frontend/public/locales/es/unauthorized.json index 586fed6b..e28ce8f4 100644 --- a/worklenz-frontend/public/locales/es/unauthorized.json +++ b/worklenz-frontend/public/locales/es/unauthorized.json @@ -1,5 +1,5 @@ { - "title": "¡No autorizado!", - "subtitle": "No tienes permisos para acceder a esta página", - "button": "Ir a Inicio" -} \ No newline at end of file + "title": "¡No autorizado!", + "subtitle": "No tienes permisos para acceder a esta página", + "button": "Ir a Inicio" +} diff --git a/worklenz-frontend/public/locales/pt/admin-center/current-bill.json b/worklenz-frontend/public/locales/pt/admin-center/current-bill.json index c4bc4126..2e4b41d7 100644 --- a/worklenz-frontend/public/locales/pt/admin-center/current-bill.json +++ b/worklenz-frontend/public/locales/pt/admin-center/current-bill.json @@ -98,8 +98,16 @@ "perMonthPerUser": "por usuário / mês", "viewInvoice": "Ver Fatura", "switchToFreePlan": "Mudar para Plano Gratuito", - + "expirestoday": "hoje", "expirestomorrow": "amanhã", - "expiredDaysAgo": "há {{days}} dias" + "expiredDaysAgo": "há {{days}} dias", + "creditPlan": "Plano de Crédito", + "customPlan": "Plano Personalizado", + "planValidTill": "Seu plano é válido até {{date}}", + "purchaseSeatsText": "Para continuar, você precisará comprar assentos adicionais.", + "currentSeatsText": "Atualmente você tem {{seats}} assentos disponíveis.", + "selectSeatsText": "Selecione o número de assentos adicionais para comprar.", + "purchase": "Comprar", + "contactSales": "Fale com vendas" } diff --git a/worklenz-frontend/public/locales/pt/admin-center/teams.json b/worklenz-frontend/public/locales/pt/admin-center/teams.json index fea4c874..6a71b491 100644 --- a/worklenz-frontend/public/locales/pt/admin-center/teams.json +++ b/worklenz-frontend/public/locales/pt/admin-center/teams.json @@ -29,5 +29,7 @@ "role": "Rol", "owner": "Propietario", "admin": "Administrador", - "member": "Miembro" + "member": "Miembro", + "cannotChangeOwnerRole": "A função de Proprietário não pode ser alterada", + "pendingInvitation": "Convite pendente" } diff --git a/worklenz-frontend/public/locales/pt/all-project-list.json b/worklenz-frontend/public/locales/pt/all-project-list.json index a97f5223..482132eb 100644 --- a/worklenz-frontend/public/locales/pt/all-project-list.json +++ b/worklenz-frontend/public/locales/pt/all-project-list.json @@ -6,18 +6,29 @@ "tasksProgress": "Progresso das Tarefas", "updated_at": "Última Atualização", "members": "Membros", - "setting": "Configuração", - "archive": "Arquivar", + "setting": "Configurações", "projects": "Projetos", "refreshProjects": "Atualizar projetos", "all": "Todos", "favorites": "Favoritos", "archived": "Arquivados", "placeholder": "Pesquisar por nome", - "archiveConfirm": "Tem certeza de que deseja arquivar este projeto?", + "archive": "Arquivar", "unarchive": "Desarquivar", + "archiveConfirm": "Tem certeza de que deseja arquivar este projeto?", "unarchiveConfirm": "Tem certeza de que deseja desarquivar este projeto?", + "yes": "Sim", + "no": "Não", "clickToFilter": "Clique para filtrar por", "noProjects": "Nenhum projeto encontrado", - "addToFavourites": "Adicionar aos favoritos" + "addToFavourites": "Adicionar aos favoritos", + "list": "Lista", + "group": "Grupo", + "listView": "Visualização em Lista", + "groupView": "Visualização em Grupo", + "groupBy": { + "category": "Categoria", + "client": "Cliente" + }, + "noPermission": "Você não tem permissão para realizar esta ação" } diff --git a/worklenz-frontend/public/locales/pt/kanban-board.json b/worklenz-frontend/public/locales/pt/kanban-board.json index 0cd9e27b..a2034daa 100644 --- a/worklenz-frontend/public/locales/pt/kanban-board.json +++ b/worklenz-frontend/public/locales/pt/kanban-board.json @@ -19,5 +19,12 @@ "archive": "Arquivar", "newTaskNamePlaceholder": "Escreva um nome de tarefa", - "newSubtaskNamePlaceholder": "Escreva um nome de subtarefa" -} \ No newline at end of file + "newSubtaskNamePlaceholder": "Escreva um nome de subtarefa", + "untitledSection": "Seção sem título", + "unmapped": "Não mapeado", + "clickToChangeDate": "Clique para alterar a data", + "noDueDate": "Sem data de vencimento", + "save": "Salvar", + "clear": "Limpar", + "nextWeek": "Próxima semana" +} diff --git a/worklenz-frontend/public/locales/pt/phases-drawer.json b/worklenz-frontend/public/locales/pt/phases-drawer.json index 0363c69c..0d5b8cb7 100644 --- a/worklenz-frontend/public/locales/pt/phases-drawer.json +++ b/worklenz-frontend/public/locales/pt/phases-drawer.json @@ -1,7 +1,24 @@ { - "configurePhases": "Configurar fases", - "phaseLabel": "Etiqueta de fase", - "enterPhaseName": "Ingrese un nombre para la etiqueta de fase", - "addOption": "Agregar opción", - "phaseOptions": "Opciones de fase:" -} \ No newline at end of file + "configurePhases": "Configurar Fases", + "configure": "Configurar", + "phaseLabel": "Rótulo da Fase", + "enterPhaseName": "Digite o nome da fase", + "addOption": "Adicionar Opção", + "phaseOptions": "Opções de Fase", + "optionsText": "Opções", + "dragToReorderPhases": "Arraste as fases para reordená-las. Cada fase pode ter uma cor diferente.", + "enterNewPhaseName": "Digite o nome da nova fase...", + "addPhase": "Adicionar Fase", + "noPhasesFound": "Nenhuma fase encontrada", + "no": "Nenhuma", + "found": "encontrada", + "deletePhase": "Excluir Fase", + "deletePhaseConfirm": "Tem certeza de que deseja excluir esta fase? Esta ação não pode ser desfeita.", + "rename": "Renomear", + "delete": "Excluir", + "create": "Criar", + "cancel": "Cancelar", + "selectColor": "Selecionar cor", + "managePhases": "Gerenciar Fases", + "close": "Fechar" +} diff --git a/worklenz-frontend/public/locales/pt/project-drawer.json b/worklenz-frontend/public/locales/pt/project-drawer.json index 55022c4e..92e11964 100644 --- a/worklenz-frontend/public/locales/pt/project-drawer.json +++ b/worklenz-frontend/public/locales/pt/project-drawer.json @@ -38,5 +38,15 @@ "createClient": "Criar cliente", "searchInputPlaceholder": "Pesquise por nome ou email", "hoursPerDayValidationMessage": "As horas por dia devem ser um número entre 1 e 24", - "noPermission": "Sem permissão" + "workingDaysValidationMessage": "Os dias de trabalho devem ser um número positivo", + "manDaysValidationMessage": "Os dias de homem devem ser um número positivo", + "noPermission": "Sem permissão", + "progressSettings": "Configurações de Progresso", + "manualProgress": "Progresso Manual", + "manualProgressTooltip": "Permitir atualizações manuais de progresso para tarefas sem subtarefas", + "weightedProgress": "Progresso Ponderado", + "weightedProgressTooltip": "Calcular o progresso com base nos pesos das subtarefas", + "timeProgress": "Progresso Baseado em Tempo", + "timeProgressTooltip": "Calcular o progresso com base no tempo estimado", + "enterProjectKey": "Insira a chave do projeto" } diff --git a/worklenz-frontend/public/locales/pt/project-view.json b/worklenz-frontend/public/locales/pt/project-view.json new file mode 100644 index 00000000..c58337da --- /dev/null +++ b/worklenz-frontend/public/locales/pt/project-view.json @@ -0,0 +1,14 @@ +{ + "taskList": "Lista de Tarefas", + "board": "Quadro Kanban", + "insights": "Insights", + "files": "Arquivos", + "members": "Membros", + "updates": "Atualizações", + "projectView": "Visualização do Projeto", + "loading": "Carregando projeto...", + "error": "Erro ao carregar projeto", + "pinnedTab": "Fixada como aba padrão", + "pinTab": "Fixar como aba padrão", + "unpinTab": "Desfixar aba padrão" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/pt/project-view/import-task-templates.json b/worklenz-frontend/public/locales/pt/project-view/import-task-templates.json index 82b3cabb..81a64607 100644 --- a/worklenz-frontend/public/locales/pt/project-view/import-task-templates.json +++ b/worklenz-frontend/public/locales/pt/project-view/import-task-templates.json @@ -1,11 +1,11 @@ { - "importTaskTemplate": "Importar modelo de tarefa", - "templateName": "Nome do modelo", - "templateDescription": "Descrição do modelo", - "selectedTasks": "Tarefas selecionadas", - "tasks": "Tarefas", - "templates": "Modelos", - "remove": "Remover", - "cancel": "Cancelar", - "import": "Importar" -} \ No newline at end of file + "importTaskTemplate": "Importar modelo de tarefa", + "templateName": "Nome do modelo", + "templateDescription": "Descrição do modelo", + "selectedTasks": "Tarefas selecionadas", + "tasks": "Tarefas", + "templates": "Modelos", + "remove": "Remover", + "cancel": "Cancelar", + "import": "Importar" +} diff --git a/worklenz-frontend/public/locales/pt/project-view/project-member-drawer.json b/worklenz-frontend/public/locales/pt/project-view/project-member-drawer.json index b4c402e4..0afe3d87 100644 --- a/worklenz-frontend/public/locales/pt/project-view/project-member-drawer.json +++ b/worklenz-frontend/public/locales/pt/project-view/project-member-drawer.json @@ -1,8 +1,7 @@ { - "title": "Membros do Projeto", - "searchLabel": "Adicionar membros inserindo nome ou e-mail", - "searchPlaceholder": "Digite nome ou e-mail", - "inviteAsAMember": "Convidar como membro", - "inviteNewMemberByEmail": "Convidar novo membro por e-mail" - -} \ No newline at end of file + "title": "Membros do Projeto", + "searchLabel": "Adicionar membros inserindo nome ou e-mail", + "searchPlaceholder": "Digite nome ou e-mail", + "inviteAsAMember": "Convidar como membro", + "inviteNewMemberByEmail": "Convidar novo membro por e-mail" +} diff --git a/worklenz-frontend/public/locales/pt/project-view/project-view-header.json b/worklenz-frontend/public/locales/pt/project-view/project-view-header.json index 194668eb..fb710ad0 100644 --- a/worklenz-frontend/public/locales/pt/project-view/project-view-header.json +++ b/worklenz-frontend/public/locales/pt/project-view/project-view-header.json @@ -1,13 +1,31 @@ { - "importTasks": "Importar tarefas", - "createTask": "Criar tarefa", - "settings": "Configurações", - "subscribe": "Inscrever-se", - "unsubscribe": "Cancelar inscrição", - "deleteProject": "Excluir projeto", - "startDate": "Data de início", - "endDate": "Data de fim", - "projectSettings": "Configurações do projeto", - "projectSummary": "Resumo do projeto", - "receiveProjectSummary": "Receber um resumo do projeto todas as noites." -} \ No newline at end of file + "importTasks": "Importar tarefas", + "importTask": "Importar tarefa", + "createTask": "Criar tarefa", + "settings": "Configurações", + "subscribe": "Inscrever-se", + "unsubscribe": "Cancelar inscrição", + "deleteProject": "Excluir projeto", + "startDate": "Data de início", + "endDate": "Data de fim", + "projectSettings": "Configurações do projeto", + "projectSummary": "Resumo do projeto", + "receiveProjectSummary": "Receber um resumo do projeto todas as noites.", + "refreshProject": "Atualizar projeto", + "saveAsTemplate": "Salvar como modelo", + "invite": "Convidar", + "share": "Compartilhar", + "subscribeTooltip": "Inscrever-se nas notificações do projeto", + "unsubscribeTooltip": "Cancelar inscrição nas notificações do projeto", + "refreshTooltip": "Atualizar dados do projeto", + "settingsTooltip": "Abrir configurações do projeto", + "saveAsTemplateTooltip": "Salvar este projeto como modelo", + "inviteTooltip": "Convidar membros da equipe para este projeto", + "createTaskTooltip": "Criar uma nova tarefa", + "importTaskTooltip": "Importar tarefa do modelo", + "navigateBackTooltip": "Voltar para a lista de projetos", + "projectStatusTooltip": "Status do projeto", + "projectDatesInfo": "Informações da linha do tempo do projeto", + "projectCategoryTooltip": "Categoria do projeto", + "defaultTaskName": "Tarefa Sem Título" +} diff --git a/worklenz-frontend/public/locales/pt/project-view/save-as-template.json b/worklenz-frontend/public/locales/pt/project-view/save-as-template.json index 70629b2f..c67eb20e 100644 --- a/worklenz-frontend/public/locales/pt/project-view/save-as-template.json +++ b/worklenz-frontend/public/locales/pt/project-view/save-as-template.json @@ -11,7 +11,7 @@ "taskIncludesOptions": { "statuses": "Status", "phases": "Fases", - "labels": "Etiquetas", + "labels": "Etiquetas", "name": "Nome", "priority": "Prioridade", "status": "Status", diff --git a/worklenz-frontend/public/locales/pt/settings/appearance.json b/worklenz-frontend/public/locales/pt/settings/appearance.json new file mode 100644 index 00000000..13e5a1e6 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/settings/appearance.json @@ -0,0 +1,5 @@ +{ + "title": "Aparência", + "darkMode": "Modo Escuro", + "darkModeDescription": "Alterne entre o modo claro e escuro para personalizar sua experiência de visualização." +} diff --git a/worklenz-frontend/public/locales/pt/settings/categories.json b/worklenz-frontend/public/locales/pt/settings/categories.json index 2d4534c1..9972d2a9 100644 --- a/worklenz-frontend/public/locales/pt/settings/categories.json +++ b/worklenz-frontend/public/locales/pt/settings/categories.json @@ -7,4 +7,4 @@ "searchPlaceholder": "Pesquisar por nome", "emptyText": "As categorias podem ser criadas ao atualizar ou criar projetos.", "colorChangeTooltip": "Clique para mudar a cor" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/settings/clients.json b/worklenz-frontend/public/locales/pt/settings/clients.json index 4f990a6e..932a7f5e 100644 --- a/worklenz-frontend/public/locales/pt/settings/clients.json +++ b/worklenz-frontend/public/locales/pt/settings/clients.json @@ -19,4 +19,4 @@ "createClientErrorMessage": "Criar cliente falhou!", "updateClientSuccessMessage": "Atualizar cliente sucesso!", "updateClientErrorMessage": "Atualizar cliente falhou!" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/settings/job-titles.json b/worklenz-frontend/public/locales/pt/settings/job-titles.json index 9f641ba0..379ddc03 100644 --- a/worklenz-frontend/public/locales/pt/settings/job-titles.json +++ b/worklenz-frontend/public/locales/pt/settings/job-titles.json @@ -17,4 +17,4 @@ "createJobTitleErrorMessage": "Falha ao criar título de emprego!", "updateJobTitleSuccessMessage": "Atualizar título de emprego com sucesso!", "updateJobTitleErrorMessage": "Falha ao atualizar título de emprego!" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/settings/labels.json b/worklenz-frontend/public/locales/pt/settings/labels.json index 90c5450f..737dccef 100644 --- a/worklenz-frontend/public/locales/pt/settings/labels.json +++ b/worklenz-frontend/public/locales/pt/settings/labels.json @@ -8,4 +8,4 @@ "emptyText": "Os rótulos podem ser criados ao atualizar ou criar tarefas.", "pinTooltip": "Clique para fixar isso no menu principal", "colorChangeTooltip": "Clique para mudar a cor" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/settings/language.json b/worklenz-frontend/public/locales/pt/settings/language.json index 44a2cacc..f4494ff3 100644 --- a/worklenz-frontend/public/locales/pt/settings/language.json +++ b/worklenz-frontend/public/locales/pt/settings/language.json @@ -4,4 +4,4 @@ "time_zone": "Fuso horário", "time_zone_required": "O fuso horário é obrigatório", "save_changes": "Salvar alterações" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/settings/notifications.json b/worklenz-frontend/public/locales/pt/settings/notifications.json index ca402552..5a61cdf0 100644 --- a/worklenz-frontend/public/locales/pt/settings/notifications.json +++ b/worklenz-frontend/public/locales/pt/settings/notifications.json @@ -7,4 +7,4 @@ "popupDescription": "As notificações pop-up podem ser desativadas pelo seu navegador. Altere as configurações do seu navegador para permiti-las.", "unreadItemsTitle": "Mostrar o número de itens não lidos", "unreadItemsDescription": "Você verá contagens para cada notificação." -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/settings/profile.json b/worklenz-frontend/public/locales/pt/settings/profile.json index fd3c3e2c..3a4a8447 100644 --- a/worklenz-frontend/public/locales/pt/settings/profile.json +++ b/worklenz-frontend/public/locales/pt/settings/profile.json @@ -9,5 +9,6 @@ "saveChanges": "Salvar Alterações", "profileJoinedText": "Entrou há um mês", "profileLastUpdatedText": "Última atualização há um mês", - "avatarTooltip": "Clique para carregar um avatar" -} \ No newline at end of file + "avatarTooltip": "Clique para carregar um avatar", + "title": "Configurações do Perfil" +} diff --git a/worklenz-frontend/public/locales/pt/settings/project-templates.json b/worklenz-frontend/public/locales/pt/settings/project-templates.json index a4a28eef..55546630 100644 --- a/worklenz-frontend/public/locales/pt/settings/project-templates.json +++ b/worklenz-frontend/public/locales/pt/settings/project-templates.json @@ -5,4 +5,4 @@ "confirmText": "Tem a certeza?", "okText": "Sim", "cancelText": "Cancelar" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/settings/sidebar.json b/worklenz-frontend/public/locales/pt/settings/sidebar.json index b9047fae..0cb663f1 100644 --- a/worklenz-frontend/public/locales/pt/settings/sidebar.json +++ b/worklenz-frontend/public/locales/pt/settings/sidebar.json @@ -10,5 +10,6 @@ "team-members": "Membros da Equipe", "teams": "Equipes", "change-password": "Alterar Senha", - "language-and-region": "Idioma e Região" -} \ No newline at end of file + "language-and-region": "Idioma e Região", + "appearance": "Aparência" +} diff --git a/worklenz-frontend/public/locales/pt/settings/task-templates.json b/worklenz-frontend/public/locales/pt/settings/task-templates.json index 0fa425a8..fb501000 100644 --- a/worklenz-frontend/public/locales/pt/settings/task-templates.json +++ b/worklenz-frontend/public/locales/pt/settings/task-templates.json @@ -6,4 +6,4 @@ "confirmText": "Tem a certeza?", "okText": "Sim", "cancelText": "Cancelar" -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/settings/team-members.json b/worklenz-frontend/public/locales/pt/settings/team-members.json index b9ff5696..9ace1764 100644 --- a/worklenz-frontend/public/locales/pt/settings/team-members.json +++ b/worklenz-frontend/public/locales/pt/settings/team-members.json @@ -1,4 +1,5 @@ { + "title": "Membros da Equipe", "nameColumn": "Nome", "projectsColumn": "Projetos", "emailColumn": "Email", @@ -40,5 +41,7 @@ "ownerText": "Dono da Equipe", "addedText": "Adicionado", "updatedText": "Atualizado", - "noResultFound": "Digite um endereço de email e pressione enter..." -} \ No newline at end of file + "noResultFound": "Digite um endereço de email e pressione enter...", + "jobTitlesFetchError": "Falha ao buscar cargos", + "invitationResent": "Convite reenviado com sucesso!" +} diff --git a/worklenz-frontend/public/locales/pt/settings/teams.json b/worklenz-frontend/public/locales/pt/settings/teams.json new file mode 100644 index 00000000..e460318f --- /dev/null +++ b/worklenz-frontend/public/locales/pt/settings/teams.json @@ -0,0 +1,16 @@ +{ + "title": "Equipes", + "team": "Equipe", + "teams": "Equipes", + "name": "Nome", + "created": "Criado", + "ownsBy": "Pertence a", + "edit": "Editar", + "editTeam": "Editar Equipe", + "pinTooltip": "Clique para fixar isso no menu principal", + "editTeamName": "Editar Nome da Equipe", + "updateName": "Atualizar Nome", + "namePlaceholder": "Nome", + "nameRequired": "Por favor digite um Nome", + "updateFailed": "Falha na alteração do nome da equipe!" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/pt/task-drawer/task-drawer-info-tab.json b/worklenz-frontend/public/locales/pt/task-drawer/task-drawer-info-tab.json index 48922a52..cf26b1a3 100644 --- a/worklenz-frontend/public/locales/pt/task-drawer/task-drawer-info-tab.json +++ b/worklenz-frontend/public/locales/pt/task-drawer/task-drawer-info-tab.json @@ -15,7 +15,8 @@ "hide-start-date": "Ocultar data de início", "show-start-date": "Mostrar data de início", "hours": "Horas", - "minutes": "Minutos" + "minutes": "Minutos", + "recurring": "Recorrente" }, "description": { "title": "Descrição", @@ -26,4 +27,4 @@ "add-sub-task": "+ Adicionar subtarefa", "refresh-sub-tasks": "Atualizar subtarefas" } -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/task-drawer/task-drawer-recurring-config.json b/worklenz-frontend/public/locales/pt/task-drawer/task-drawer-recurring-config.json new file mode 100644 index 00000000..5592d897 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/task-drawer/task-drawer-recurring-config.json @@ -0,0 +1,34 @@ +{ + "recurring": "Recorrente", + "recurringTaskConfiguration": "Configuração de tarefa recorrente", + "repeats": "Repete", + "daily": "Diário", + "weekly": "Semanal", + "everyXDays": "A cada X dias", + "everyXWeeks": "A cada X semanas", + "everyXMonths": "A cada X meses", + "monthly": "Mensal", + "selectDaysOfWeek": "Selecionar dias da semana", + "mon": "Seg", + "tue": "Ter", + "wed": "Qua", + "thu": "Qui", + "fri": "Sex", + "sat": "Sáb", + "sun": "Dom", + "monthlyRepeatType": "Tipo de repetição mensal", + "onSpecificDate": "Em uma data específica", + "onSpecificDay": "Em um dia específico", + "dateOfMonth": "Data do mês", + "weekOfMonth": "Semana do mês", + "dayOfWeek": "Dia da semana", + "first": "Primeira", + "second": "Segunda", + "third": "Terceira", + "fourth": "Quarta", + "last": "Última", + "intervalDays": "Intervalo (dias)", + "intervalWeeks": "Intervalo (semanas)", + "intervalMonths": "Intervalo (meses)", + "saveChanges": "Salvar alterações" +} diff --git a/worklenz-frontend/public/locales/pt/task-drawer/task-drawer.json b/worklenz-frontend/public/locales/pt/task-drawer/task-drawer.json index d6e8fef6..c24e943e 100644 --- a/worklenz-frontend/public/locales/pt/task-drawer/task-drawer.json +++ b/worklenz-frontend/public/locales/pt/task-drawer/task-drawer.json @@ -1,28 +1,37 @@ { "taskHeader": { - "taskNamePlaceholder": "Digite sua tarefa", - "deleteTask": "Excluir tarefa" + "taskNamePlaceholder": "Digite sua Tarefa", + "deleteTask": "Deletar Tarefa" }, "taskInfoTab": { "title": "Informações", "details": { "title": "Detalhes", - "task-key": "Chave da tarefa", + "task-key": "Chave da Tarefa", "phase": "Fase", "assignees": "Responsáveis", - "due-date": "Data de vencimento", - "time-estimation": "Estimativa de tempo", + "due-date": "Data de Vencimento", + "time-estimation": "Estimativa de Tempo", "priority": "Prioridade", "labels": "Etiquetas", "billable": "Faturável", "notify": "Notificar", - "when-done-notify": "Quando concluída, notificar", - "start-date": "Data de início", - "end-date": "Data de término", - "hide-start-date": "Ocultar data de início", - "show-start-date": "Mostrar data de início", + "when-done-notify": "Quando concluído, notificar", + "start-date": "Data de Início", + "end-date": "Data de Fim", + "hide-start-date": "Ocultar Data de Início", + "show-start-date": "Mostrar Data de Início", "hours": "Horas", - "minutes": "Minutos" + "minutes": "Minutos", + "progressValue": "Valor do Progresso", + "progressValueTooltip": "Definir a porcentagem de progresso (0-100%)", + "progressValueRequired": "Por favor, insira um valor de progresso", + "progressValueRange": "O progresso deve estar entre 0 e 100", + "taskWeight": "Peso da Tarefa", + "taskWeightTooltip": "Definir o peso desta subtarefa (porcentagem)", + "taskWeightRequired": "Por favor, insira um peso da tarefa", + "taskWeightRange": "O peso deve estar entre 0 e 100", + "recurring": "Recorrente" }, "labels": { "labelInputPlaceholder": "Pesquisar ou criar", @@ -33,14 +42,14 @@ "placeholder": "Adicionar uma descrição mais detalhada..." }, "subTasks": { - "title": "Subtarefas", - "addSubTask": "+ Adicionar subtarefa", + "title": "Sub Tarefas", + "addSubTask": "Adicionar Sub Tarefa", "addSubTaskInputPlaceholder": "Digite sua tarefa e pressione enter", - "refreshSubTasks": "Atualizar subtarefas", + "refreshSubTasks": "Atualizar Sub Tarefas", "edit": "Editar", - "delete": "Excluir", - "confirmDeleteSubTask": "Tem certeza de que deseja excluir esta subtarefa?", - "deleteSubTask": "Excluir subtarefa" + "delete": "Deletar", + "confirmDeleteSubTask": "Tem certeza de que deseja deletar esta subtarefa?", + "deleteSubTask": "Deletar Sub Tarefa" }, "dependencies": { "title": "Dependências", @@ -48,31 +57,67 @@ "blockedBy": "Bloqueado por", "searchTask": "Digite para pesquisar tarefa", "noTasksFound": "Nenhuma tarefa encontrada", - "confirmDeleteDependency": "Tem certeza de que deseja excluir?" + "confirmDeleteDependency": "Tem certeza de que deseja deletar?" }, "attachments": { "title": "Anexos", - "chooseOrDropFileToUpload": "Escolha ou arraste um arquivo para carregar", - "uploading": "Carregando..." + "chooseOrDropFileToUpload": "Escolha ou arraste um arquivo para upload", + "uploading": "Enviando..." }, "comments": { "title": "Comentários", "addComment": "+ Adicionar novo comentário", - "noComments": "Nenhum comentário ainda. Seja o primeiro a comentar!", - "delete": "Excluir", - "confirmDeleteComment": "Tem certeza de que deseja excluir este comentário?" + "noComments": "Ainda não há comentários. Seja o primeiro a comentar!", + "delete": "Deletar", + "confirmDeleteComment": "Tem certeza de que deseja deletar este comentário?", + "addCommentPlaceholder": "Adicionar um comentário...", + "cancel": "Cancelar", + "commentButton": "Comentar", + "attachFiles": "Anexar arquivos", + "addMoreFiles": "Adicionar mais arquivos", + "selectedFiles": "Arquivos Selecionados (Até 25MB, Máximo {count})", + "maxFilesError": "Você pode fazer upload de no máximo {count} arquivos", + "processFilesError": "Falha ao processar arquivos", + "addCommentError": "Por favor adicione um comentário ou anexe arquivos", + "createdBy": "Criado {{time}} por {{user}}", + "updatedTime": "Atualizado {{time}}" }, "searchInputPlaceholder": "Pesquisar por nome", - "pendingInvitation": "Convite pendente" + "pendingInvitation": "Convite Pendente" }, "taskTimeLogTab": { - "title": "Registro de tempo", + "title": "Registro de Tempo", "addTimeLog": "Adicionar novo registro de tempo", - "totalLogged": "Total registrado", + "totalLogged": "Total Registrado", "exportToExcel": "Exportar para Excel", - "noTimeLogsFound": "Nenhum registro de tempo encontrado" + "noTimeLogsFound": "Nenhum registro de tempo encontrado", + "timeLogForm": { + "date": "Data", + "startTime": "Hora de Início", + "endTime": "Hora de Fim", + "workDescription": "Descrição do Trabalho", + "descriptionPlaceholder": "Adicionar uma descrição", + "logTime": "Registrar tempo", + "updateTime": "Atualizar tempo", + "cancel": "Cancelar", + "selectDateError": "Por favor selecione uma data", + "selectStartTimeError": "Por favor selecione a hora de início", + "selectEndTimeError": "Por favor selecione a hora de fim", + "endTimeAfterStartError": "A hora de fim deve ser posterior à hora de início" + } }, "taskActivityLogTab": { - "title": "Registro de atividade" + "title": "Registro de Atividade", + "add": "ADICIONAR", + "remove": "REMOVER", + "none": "Nenhum", + "weight": "Peso", + "createdTask": "criou a tarefa." + }, + "taskProgress": { + "markAsDoneTitle": "Marcar Tarefa como Concluída?", + "confirmMarkAsDone": "Sim, marcar como concluída", + "cancelMarkAsDone": "Não, manter status atual", + "markAsDoneDescription": "Você definiu o progresso para 100%. Gostaria de atualizar o status da tarefa para \"Concluída\"?" } -} \ No newline at end of file +} diff --git a/worklenz-frontend/public/locales/pt/task-list-filters.json b/worklenz-frontend/public/locales/pt/task-list-filters.json index cf2cb7b0..3674a29a 100644 --- a/worklenz-frontend/public/locales/pt/task-list-filters.json +++ b/worklenz-frontend/public/locales/pt/task-list-filters.json @@ -6,8 +6,10 @@ "sortText": "Ordenar", "statusText": "Status", "phaseText": "Fase", + "memberText": "Membros", + "assigneesText": "Atribuídos", "priorityText": "Prioridade", - "labelsText": "Rótulos", + "labelsText": "Etiquetas", "membersText": "Membros", "groupByText": "Agrupar por", "showArchivedText": "Mostrar arquivados", @@ -16,41 +18,71 @@ "taskText": "Tarefa", "descriptionText": "Descrição", "phasesText": "Fases", + "listText": "Lista", "progressText": "Progresso", - "timeTrackingText": "Rastreamento de Tempo", + "timeTrackingText": "Rastreamento de tempo", + "timetrackingText": "Rastreamento de tempo", "estimationText": "Estimativa", - "startDateText": "Data de Início", - "endDateText": "Data de Fim", - "dueDateText": "Data de Vencimento", - "completedDateText": "Data de Conclusão", - "createdDateText": "Data de Criação", - "lastUpdatedText": "Última Atualização", + "startDateText": "Data de início", + "startdateText": "Data de início", + "endDateText": "Data de fim", + "dueDateText": "Data de vencimento", + "duedateText": "Data de vencimento", + "completedDateText": "Data de conclusão", + "completeddateText": "Data de conclusão", + "createdDateText": "Data de criação", + "createddateText": "Data de criação", + "lastUpdatedText": "Última atualização", + "lastupdatedText": "Última atualização", "reporterText": "Relator", - "dueTimeText": "Hora de Vencimento", - "assigneesText": "Atribuições", - "timetrackingText": "Rastreamento de Tempo", - "startdateText": "Data de Início", - "duedateText": "Data de Vencimento", - "completeddateText": "Data de Conclusão", - "createddateText": "Data de Criação", - "lastupdatedText": "Última Atualização", - + "dueTimeText": "Hora de vencimento", + "duetimeText": "Hora de vencimento", + "lowText": "Baixa", "mediumText": "Média", "highText": "Alta", - "createStatusButtonTooltip": "Configurações de Status", - "configPhaseButtonTooltip": "Configurações de Fase", - "noLabelsFound": "Nenhum rótulo encontrado", + "createStatusButtonTooltip": "Configurações de status", + "configPhaseButtonTooltip": "Configurações de fase", + "noLabelsFound": "Nenhuma etiqueta encontrada", - "addStatusButton": "Adicionar Status", - "addPhaseButton": "Adicionar Fase", + "addStatusButton": "Adicionar status", + "addPhaseButton": "Adicionar fase", - "createStatus": "Criar Status", + "createStatus": "Criar status", "name": "Nome", "category": "Categoria", "selectCategory": "Selecionar uma categoria", - "pleaseEnterAName": "Por favor, insira um nome", + "pleaseEnterAName": "Por favor, digite um nome", "pleaseSelectACategory": "Por favor, selecione uma categoria", - "create": "Criar" + "create": "Criar", + + "searchTasks": "Pesquisar tarefas...", + "searchPlaceholder": "Pesquisar...", + "fieldsText": "Campos", + "loadingFilters": "Carregando filtros...", + "noOptionsFound": "Nenhuma opção encontrada", + "filtersActive": "filtros ativos", + "filterActive": "filtro ativo", + "clearAll": "Limpar tudo", + "clearing": "Limpando...", + "cancel": "Cancelar", + "search": "Pesquisar", + "groupedBy": "Agrupado por", + "manage": "Gerenciar", + "manageStatuses": "Gerenciar Status", + "managePhases": "Gerenciar Fases", + "dragToReorderStatuses": "Os status estão organizados por categorias. Arraste para reordenar dentro das categorias. Clique em 'Adicionar status' para criar novos status em cada categoria.", + "enterNewStatusName": "Digite o nome do novo status...", + "addStatus": "Adicionar status", + "noStatusesFound": "Nenhum status nesta categoria", + "deleteStatus": "Excluir status", + "deleteStatusConfirm": "Tem certeza de que deseja excluir este status? Esta ação não pode ser desfeita.", + "rename": "Renomear", + "delete": "Excluir", + "enterStatusName": "Digite o nome do status", + "close": "Fechar", + "cannotMoveStatus": "Não é possível mover o status", + "cannotMoveStatusMessage": "Não é possível mover este status porque deixaria a categoria '{{categoryName}}' vazia. Cada categoria deve ter pelo menos um status.", + "ok": "OK" } diff --git a/worklenz-frontend/public/locales/pt/task-list-table.json b/worklenz-frontend/public/locales/pt/task-list-table.json index 23240945..f53d834f 100644 --- a/worklenz-frontend/public/locales/pt/task-list-table.json +++ b/worklenz-frontend/public/locales/pt/task-list-table.json @@ -36,8 +36,9 @@ "selectText": "Selecionar", "labelsSelectorInputTip": "Pressione enter para criar!", - "addTaskText": "+ Adicionar Tarefa", + "addTaskText": "Adicionar Tarefa", "addSubTaskText": "+ Adicionar Subtarefa", + "noTasksInGroup": "Nenhuma tarefa neste grupo", "addTaskInputPlaceholder": "Digite sua tarefa e pressione enter", "openButton": "Abrir", @@ -47,7 +48,10 @@ "searchInputPlaceholder": "Buscar ou criar", "assigneeSelectorInviteButton": "Convide um novo membro por e-mail", "labelInputPlaceholder": "Buscar ou criar", - + "searchLabelsPlaceholder": "Buscar etiquetas...", + "createLabelButton": "Criar \"{{name}}\"", + "manageLabelsPath": "Configurações → Etiquetas", + "pendingInvitation": "Convite Pendente", "contextMenu": { @@ -59,5 +63,74 @@ "convertToTask": "Converter em Tarefa", "delete": "Excluir", "searchByNameInputPlaceholder": "Buscar por nome" + }, + "setDueDate": "Definir data de vencimento", + "setStartDate": "Definir data de início", + "clearDueDate": "Limpar data de vencimento", + "clearStartDate": "Limpar data de início", + "dueDatePlaceholder": "Data de vencimento", + "startDatePlaceholder": "Data de início", + + "emptyStates": { + "noTaskGroups": "Nenhum grupo de tarefas encontrado", + "noTaskGroupsDescription": "As tarefas aparecerão aqui quando forem criadas ou quando filtros forem aplicados.", + "errorPrefix": "Erro:", + "dragTaskFallback": "Tarefa" + }, + + "customColumns": { + "addCustomColumn": "Adicionar uma coluna personalizada", + "customColumnHeader": "Coluna Personalizada", + "customColumnSettings": "Configurações da coluna personalizada", + "noCustomValue": "Sem valor", + "peopleField": "Campo de pessoas", + "noDate": "Sem data", + "unsupportedField": "Tipo de campo não suportado", + + "modal": { + "addFieldTitle": "Adicionar campo", + "editFieldTitle": "Editar campo", + "fieldTitle": "Título do campo", + "fieldTitleRequired": "O título do campo é obrigatório", + "columnTitlePlaceholder": "Título da coluna", + "type": "Tipo", + "deleteConfirmTitle": "Tem certeza de que deseja excluir esta coluna personalizada?", + "deleteConfirmDescription": "Esta ação não pode ser desfeita. Todos os dados associados a esta coluna serão excluídos permanentemente.", + "deleteButton": "Excluir", + "cancelButton": "Cancelar", + "createButton": "Criar", + "updateButton": "Atualizar", + "createSuccessMessage": "Coluna personalizada criada com sucesso", + "updateSuccessMessage": "Coluna personalizada atualizada com sucesso", + "deleteSuccessMessage": "Coluna personalizada excluída com sucesso", + "deleteErrorMessage": "Falha ao excluir a coluna personalizada", + "createErrorMessage": "Falha ao criar a coluna personalizada", + "updateErrorMessage": "Falha ao atualizar a coluna personalizada" + }, + + "fieldTypes": { + "people": "Pessoas", + "number": "Número", + "date": "Data", + "selection": "Seleção", + "checkbox": "Caixa de seleção", + "labels": "Etiquetas", + "key": "Chave", + "formula": "Fórmula" + } + }, + + "indicators": { + "tooltips": { + "subtasks": "{{count}} subtarefa", + "subtasks_plural": "{{count}} subtarefas", + "comments": "{{count}} comentário", + "comments_plural": "{{count}} comentários", + "attachments": "{{count}} anexo", + "attachments_plural": "{{count}} anexos", + "subscribers": "A tarefa tem assinantes", + "dependencies": "A tarefa tem dependências", + "recurring": "Tarefa recorrente" + } } } diff --git a/worklenz-frontend/public/locales/pt/task-management.json b/worklenz-frontend/public/locales/pt/task-management.json new file mode 100644 index 00000000..a3039bbb --- /dev/null +++ b/worklenz-frontend/public/locales/pt/task-management.json @@ -0,0 +1,39 @@ +{ + "noTasksInGroup": "Nenhuma tarefa neste grupo", + "noTasksInGroupDescription": "Adicione uma tarefa para começar", + "addFirstTask": "Adicione sua primeira tarefa", + "openTask": "Abrir", + "subtask": "subtarefa", + "subtasks": "subtarefas", + "comment": "comentário", + "comments": "comentários", + "attachment": "anexo", + "attachments": "anexos", + "enterSubtaskName": "Digite o nome da subtarefa...", + "add": "Adicionar", + "cancel": "Cancelar", + "renameGroup": "Renomear Grupo", + "renameStatus": "Renomear Status", + "renamePhase": "Renomear Fase", + "changeCategory": "Alterar Categoria", + "clickToEditGroupName": "Clique para editar o nome do grupo", + "enterGroupName": "Digite o nome do grupo", + "todo": "A Fazer", + "inProgress": "Em Andamento", + "done": "Concluído", + "defaultTaskName": "Tarefa Sem Título", + + "indicators": { + "tooltips": { + "subtasks": "{{count}} subtarefa", + "subtasks_plural": "{{count}} subtarefas", + "comments": "{{count}} comentário", + "comments_plural": "{{count}} comentários", + "attachments": "{{count}} anexo", + "attachments_plural": "{{count}} anexos", + "subscribers": "Tarefa tem assinantes", + "dependencies": "Tarefa tem dependências", + "recurring": "Tarefa recorrente" + } + } +} diff --git a/worklenz-frontend/public/locales/pt/tasks/task-table-bulk-actions.json b/worklenz-frontend/public/locales/pt/tasks/task-table-bulk-actions.json index 40147210..f4a3a10e 100644 --- a/worklenz-frontend/public/locales/pt/tasks/task-table-bulk-actions.json +++ b/worklenz-frontend/public/locales/pt/tasks/task-table-bulk-actions.json @@ -1,24 +1,41 @@ { - "taskSelected": "Tarefa selecionada", - "tasksSelected": "Tarefas selecionadas", - "changeStatus": "Alterar Status/ Prioridade/ Fases", - "changeLabel": "Alterar Etiqueta", - "assignToMe": "Atribuir a mim", - "changeAssignees": "Alterar Assignados", - "archive": "Arquivar", - "unarchive": "Desarquivar", - "delete": "Deletar", - "moreOptions": "Mais opções", - "deselectAll": "Desmarcar todas", - "status": "Status", - "priority": "Prioridade", - "phase": "Fase", - "member": "Membro", - "createTaskTemplate": "Criar Modelo de Tarefa", - "apply": "Aplicar", - "createLabel": "+ Criar etiqueta", - "hitEnterToCreate": "Pressione Enter para criar", - "pendingInvitation": "Convite Pendente", - "noMatchingLabels": "Nenhuma etiqueta correspondente", - "noLabels": "Sem etiquetas" -} \ No newline at end of file + "taskSelected": "Tarefa selecionada", + "tasksSelected": "Tarefas selecionadas", + "changeStatus": "Alterar Status/ Prioridade/ Fases", + "changeLabel": "Alterar Etiqueta", + "assignToMe": "Atribuir a mim", + "changeAssignees": "Alterar Assignados", + "archive": "Arquivar", + "unarchive": "Desarquivar", + "delete": "Deletar", + "moreOptions": "Mais opções", + "deselectAll": "Desmarcar todas", + "status": "Status", + "priority": "Prioridade", + "phase": "Fase", + "member": "Membro", + "createTaskTemplate": "Criar Modelo de Tarefa", + "apply": "Aplicar", + "createLabel": "+ Criar etiqueta", + "searchOrCreateLabel": "Pesquisar ou criar etiqueta...", + "hitEnterToCreate": "Pressione Enter para criar", + "labelExists": "A etiqueta já existe", + "pendingInvitation": "Convite Pendente", + "noMatchingLabels": "Nenhuma etiqueta correspondente", + "noLabels": "Sem etiquetas", + "CHANGE_STATUS": "Alterar Status", + "CHANGE_PRIORITY": "Alterar Prioridade", + "CHANGE_PHASE": "Alterar Fase", + "ADD_LABELS": "Adicionar Etiquetas", + "ASSIGN_TO_ME": "Atribuir a Mim", + "ASSIGN_MEMBERS": "Atribuir Membros", + "ARCHIVE": "Arquivar", + "DELETE": "Deletar", + "CANCEL": "Cancelar", + "CLEAR_SELECTION": "Limpar Seleção", + "TASKS_SELECTED": "{{count}} tarefa selecionada", + "TASKS_SELECTED_plural": "{{count}} tarefas selecionadas", + "DELETE_TASKS_CONFIRM": "Deletar {{count}} tarefa?", + "DELETE_TASKS_CONFIRM_plural": "Deletar {{count}} tarefas?", + "DELETE_TASKS_WARNING": "Esta ação não pode ser desfeita." +} diff --git a/worklenz-frontend/public/locales/pt/time-report.json b/worklenz-frontend/public/locales/pt/time-report.json index 8d09db4c..b40546e9 100644 --- a/worklenz-frontend/public/locales/pt/time-report.json +++ b/worklenz-frontend/public/locales/pt/time-report.json @@ -4,7 +4,7 @@ "timeSheet": "Folha de Tempo", "searchByName": "Pesquisar por nome", - "selectAll": "Selecionar Todos", + "selectAll": "Selecionar Tudo", "teams": "Equipes", "searchByProject": "Pesquisar por nome do projeto", @@ -13,32 +13,45 @@ "searchByCategory": "Pesquisar por nome da categoria", "categories": "Categorias", - "billable": "Cobrável", - "nonBillable": "Não Cobrável", + "billable": "Faturável", + "nonBillable": "Não Faturável", "total": "Total", - "projectsTimeSheet": "Folha de Tempo dos Projetos", + "projectsTimeSheet": "Folha de Tempo de Projetos", - "loggedTime": "Tempo Registrado (horas)", + "loggedTime": "Tempo Registrado(horas)", "exportToExcel": "Exportar para Excel", "logged": "registrado", "for": "para", - "membersTimeSheet": "Folha de Tempo dos Membros", + "membersTimeSheet": "Folha de Tempo de Membros", "member": "Membro", "estimatedVsActual": "Estimado vs Real", - "workingDays": "Dias de Trabalho", - "manDays": "Dias-Homem", + "workingDays": "Dias Úteis", + "manDays": "Dias Homem", "days": "Dias", "estimatedDays": "Dias Estimados", "actualDays": "Dias Reais", "noCategories": "Nenhuma categoria encontrada", - "noCategory": "Nenhuma Categoria", + "noCategory": "Sem Categoria", "noProjects": "Nenhum projeto encontrado", - "noTeams": "Nenhum time encontrado", - "noData": "Nenhum dado encontrado" + "noTeams": "Nenhuma equipe encontrada", + "noData": "Nenhum dado encontrado", + + "groupBy": "Agrupar por", + "groupByCategory": "Categoria", + "groupByTeam": "Equipe", + "groupByStatus": "Status", + "groupByNone": "Nenhum", + "clearSearch": "Limpar pesquisa", + "selectedProjects": "Projetos Selecionados", + "projectsSelected": "projetos selecionados", + "showSelected": "Mostrar Apenas Selecionados", + "expandAll": "Expandir Tudo", + "collapseAll": "Recolher Tudo", + "ungrouped": "Não Agrupado" } diff --git a/worklenz-frontend/public/locales/pt/unauthorized.json b/worklenz-frontend/public/locales/pt/unauthorized.json index fa542df0..e67e0ffd 100644 --- a/worklenz-frontend/public/locales/pt/unauthorized.json +++ b/worklenz-frontend/public/locales/pt/unauthorized.json @@ -1,5 +1,5 @@ { - "title": "¡Não autorizado!", - "subtitle": "Você não tem permissão para acessar esta página", - "button": "Ir para Início" -} \ No newline at end of file + "title": "¡Não autorizado!", + "subtitle": "Você não tem permissão para acessar esta página", + "button": "Ir para Início" +} diff --git a/worklenz-frontend/public/locales/zh/404-page.json b/worklenz-frontend/public/locales/zh/404-page.json new file mode 100644 index 00000000..24a74b3e --- /dev/null +++ b/worklenz-frontend/public/locales/zh/404-page.json @@ -0,0 +1,4 @@ +{ + "doesNotExistText": "抱歉,您访问的页面不存在。", + "backHomeButton": "返回首页" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/account-setup.json b/worklenz-frontend/public/locales/zh/account-setup.json new file mode 100644 index 00000000..51cac1eb --- /dev/null +++ b/worklenz-frontend/public/locales/zh/account-setup.json @@ -0,0 +1,27 @@ +{ + "continue": "继续", + "setupYourAccount": "设置您的Worklenz账户。", + "organizationStepTitle": "命名您的组织", + "organizationStepLabel": "为您的Worklenz账户选择一个名称。", + "projectStepTitle": "创建您的第一个项目", + "projectStepLabel": "您现在正在做什么项目?", + "projectStepPlaceholder": "例如:营销计划", + "tasksStepTitle": "创建您的第一个任务", + "tasksStepLabel": "输入您将在其中完成的几个任务", + "tasksStepAddAnother": "添加另一个", + "emailPlaceholder": "电子邮件地址", + "invalidEmail": "请输入有效的电子邮件地址", + "or": "或", + "templateButton": "从模板导入", + "goBack": "返回", + "cancel": "取消", + "create": "创建", + "templateDrawerTitle": "从模板中选择", + "step3InputLabel": "通过电子邮件邀请", + "addAnother": "添加另一个", + "skipForNow": "暂时跳过", + "formTitle": "创建您的第一个任务。", + "step3Title": "邀请您的团队一起工作", + "maxMembers": "(您最多可以邀请5名成员)", + "maxTasks": "(您最多可以创建5个任务)" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/admin-center/current-bill.json b/worklenz-frontend/public/locales/zh/admin-center/current-bill.json new file mode 100644 index 00000000..e18e8761 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/admin-center/current-bill.json @@ -0,0 +1,96 @@ +{ + "title": "账单", + "currentBill": "当前账单", + "configuration": "配置", + "currentPlanDetails": "当前计划详情", + "upgradePlan": "升级计划", + "cardBodyText01": "免费试用", + "cardBodyText02": "(您的试用计划将在1个月19天后到期)", + "redeemCode": "兑换码", + "accountStorage": "账户存储", + "used": "已用:", + "remaining": "剩余:", + "charges": "费用", + "tooltip": "当前账单周期的费用", + "description": "描述", + "billingPeriod": "账单周期", + "billStatus": "账单状态", + "perUserValue": "每用户费用", + "users": "用户", + "amount": "金额", + "invoices": "发票", + "transactionId": "交易ID", + "transactionDate": "交易日期", + "paymentMethod": "支付方式", + "status": "状态", + "ltdUsers": "您最多可以添加{{ltd_users}}名用户。", + "totalSeats": "总席位", + "availableSeats": "可用席位", + "addMoreSeats": "添加更多席位", + "drawerTitle": "兑换码", + "label": "兑换码", + "drawerPlaceholder": "输入您的兑换码", + "redeemSubmit": "提交", + "modalTitle": "为您的团队选择最佳计划", + "seatLabel": "席位数量", + "freePlan": "免费计划", + "startup": "初创", + "business": "商业", + "tag": "最受欢迎", + "enterprise": "企业", + "freeSubtitle": "永远免费", + "freeUsers": "最适合个人使用", + "freeText01": "100MB存储", + "freeText02": "3个项目", + "freeText03": "5名团队成员", + "startupSubtitle": "固定费率/月", + "startupUsers": "最多15名用户", + "startupText01": "25GB存储", + "startupText02": "无限活跃项目", + "startupText03": "日程", + "startupText04": "报告", + "startupText05": "订阅项目", + "businessSubtitle": "每用户/月", + "businessUsers": "16 - 200名用户", + "enterpriseUsers": "200 - 500+名用户", + "footerTitle": "请提供一个我们可以联系您的电话号码。", + "footerLabel": "联系电话", + "footerButton": "联系我们", + "redeemCodePlaceHolder": "输入您的兑换码", + "submit": "提交", + "trialPlan": "免费试用", + "trialExpireDate": "有效期至{{trial_expire_date}}", + "trialExpired": "您的免费试用已于{{trial_expire_string}}到期", + "trialInProgress": "您的免费试用将在{{trial_expire_string}}到期", + "required": "此字段为必填项", + "invalidCode": "无效的代码", + "selectPlan": "为您的团队选择最佳计划", + "changeSubscriptionPlan": "更改您的订阅计划", + "noOfSeats": "席位数量", + "annualPlan": "专业 - 年度", + "monthlyPlan": "专业 - 月度", + "freeForever": "永远免费", + "bestForPersonalUse": "最适合个人使用", + "storage": "存储", + "projects": "项目", + "teamMembers": "团队成员", + "unlimitedTeamMembers": "无限团队成员", + "unlimitedActiveProjects": "无限活跃项目", + "schedule": "日程", + "reporting": "报告", + "subscribeToProjects": "订阅项目", + "billedAnnually": "按年计费", + "billedMonthly": "按月计费", + "pausePlan": "暂停计划", + "resumePlan": "恢复计划", + "changePlan": "更改计划", + "cancelPlan": "取消计划", + "perMonthPerUser": "每用户/月", + "viewInvoice": "查看发票", + "switchToFreePlan": "切换到免费计划", + "expirestoday": "今天", + "expirestomorrow": "明天", + "expiredDaysAgo": "{{days}}天前", + "continueWith": "继续使用{{plan}}", + "changeToPlan": "更改为{{plan}}" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/admin-center/overview.json b/worklenz-frontend/public/locales/zh/admin-center/overview.json new file mode 100644 index 00000000..9c70093f --- /dev/null +++ b/worklenz-frontend/public/locales/zh/admin-center/overview.json @@ -0,0 +1,8 @@ +{ + "overview": "概览", + "name": "组织名称", + "owner": "组织所有者", + "admins": "组织管理员", + "contactNumber": "添加联系电话", + "edit": "编辑" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/admin-center/projects.json b/worklenz-frontend/public/locales/zh/admin-center/projects.json new file mode 100644 index 00000000..ca2eded2 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/admin-center/projects.json @@ -0,0 +1,12 @@ +{ + "membersCount": "成员数量", + "createdAt": "创建于", + "projectName": "项目名称", + "teamName": "团队名称", + "refreshProjects": "刷新项目", + "searchPlaceholder": "按项目名称搜索", + "deleteProject": "您确定要删除此项目吗?", + "confirm": "确认", + "cancel": "取消", + "delete": "删除项目" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/admin-center/sidebar.json b/worklenz-frontend/public/locales/zh/admin-center/sidebar.json new file mode 100644 index 00000000..ab8808c3 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/admin-center/sidebar.json @@ -0,0 +1,8 @@ +{ + "overview": "概览", + "users": "用户", + "teams": "团队", + "billing": "账单", + "projects": "项目", + "adminCenter": "管理中心" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/admin-center/teams.json b/worklenz-frontend/public/locales/zh/admin-center/teams.json new file mode 100644 index 00000000..4244d848 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/admin-center/teams.json @@ -0,0 +1,33 @@ +{ + "title": "团队", + "subtitle": "团队", + "tooltip": "刷新团队", + "placeholder": "按名称搜索", + "addTeam": "添加团队", + "team": "团队", + "membersCount": "成员数量", + "members": "成员", + "drawerTitle": "创建新团队", + "label": "团队名称", + "drawerPlaceholder": "名称", + "create": "创建", + "delete": "删除", + "settings": "设置", + "popTitle": "您确定吗?", + "message": "请输入名称", + "teamSettings": "团队设置", + "teamName": "团队名称", + "teamDescription": "团队描述", + "teamMembers": "团队成员", + "teamMembersCount": "团队成员数量", + "teamMembersPlaceholder": "按名称搜索", + "addMember": "添加成员", + "add": "添加", + "update": "更新", + "teamNamePlaceholder": "团队名称", + "user": "用户", + "role": "角色", + "owner": "所有者", + "admin": "管理员", + "member": "成员" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/admin-center/users.json b/worklenz-frontend/public/locales/zh/admin-center/users.json new file mode 100644 index 00000000..83800c09 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/admin-center/users.json @@ -0,0 +1,9 @@ +{ + "title": "用户", + "subTitle": "用户", + "placeholder": "按名称搜索", + "user": "用户", + "email": "电子邮件", + "lastActivity": "最后活动", + "refresh": "刷新用户" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/all-project-list.json b/worklenz-frontend/public/locales/zh/all-project-list.json new file mode 100644 index 00000000..a6c72c06 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/all-project-list.json @@ -0,0 +1,34 @@ +{ + "name": "名称", + "client": "客户", + "category": "类别", + "status": "状态", + "tasksProgress": "任务进度", + "updated_at": "最后更新", + "members": "成员", + "setting": "设置", + "projects": "项目", + "refreshProjects": "刷新项目", + "all": "全部", + "favorites": "收藏", + "archived": "已归档", + "placeholder": "按名称搜索", + "archive": "归档", + "unarchive": "取消归档", + "archiveConfirm": "您确定要归档此项目吗?", + "unarchiveConfirm": "您确定要取消归档此项目吗?", + "yes": "是", + "no": "否", + "clickToFilter": "点击筛选", + "noProjects": "未找到项目", + "addToFavourites": "添加到收藏", + "list": "列表", + "group": "分组", + "listView": "列表视图", + "groupView": "分组视图", + "groupBy": { + "category": "类别", + "client": "客户" + }, + "noPermission": "您没有权限执行此操作" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/auth/auth-common.json b/worklenz-frontend/public/locales/zh/auth/auth-common.json new file mode 100644 index 00000000..df57a70d --- /dev/null +++ b/worklenz-frontend/public/locales/zh/auth/auth-common.json @@ -0,0 +1,5 @@ +{ + "loggingOut": "正在登出...", + "authenticating": "正在认证...", + "gettingThingsReady": "正在为您准备..." +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/auth/forgot-password.json b/worklenz-frontend/public/locales/zh/auth/forgot-password.json new file mode 100644 index 00000000..de1529a4 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/auth/forgot-password.json @@ -0,0 +1,12 @@ +{ + "headerDescription": "重置您的密码", + "emailLabel": "电子邮件", + "emailPlaceholder": "输入您的电子邮件", + "emailRequired": "请输入您的电子邮件!", + "resetPasswordButton": "重置密码", + "returnToLoginButton": "返回登录", + "passwordResetSuccessMessage": "密码重置链接已发送到您的电子邮件。", + "orText": "或", + "successTitle": "重置指令已发送!", + "successMessage": "重置信息已发送到您的电子邮件。请检查您的电子邮件。" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/auth/login.json b/worklenz-frontend/public/locales/zh/auth/login.json new file mode 100644 index 00000000..e53d5fc5 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/auth/login.json @@ -0,0 +1,27 @@ +{ + "headerDescription": "登录到您的账户", + "emailLabel": "电子邮件", + "emailPlaceholder": "输入您的电子邮件", + "emailRequired": "请输入您的电子邮件!", + "passwordLabel": "密码", + "passwordPlaceholder": "输入您的密码", + "passwordRequired": "请输入您的密码!", + "rememberMe": "记住我", + "loginButton": "登录", + "signupButton": "注册", + "forgotPasswordButton": "忘记密码?", + "signInWithGoogleButton": "使用Google登录", + "dontHaveAccountText": "没有账户?", + "orText": "或", + "successMessage": "您已成功登录!", + "loginError": "登录失败", + "googleLoginError": "Google登录失败", + "validationMessages": { + "email": "请输入有效的电子邮件地址", + "password": "密码必须至少包含8个字符" + }, + "errorMessages": { + "loginErrorTitle": "登录失败", + "loginErrorMessage": "请检查您的电子邮件和密码并重试" + } +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/auth/signup.json b/worklenz-frontend/public/locales/zh/auth/signup.json new file mode 100644 index 00000000..a2b34e57 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/auth/signup.json @@ -0,0 +1,29 @@ +{ + "headerDescription": "注册以开始使用", + "nameLabel": "全名", + "namePlaceholder": "输入您的全名", + "nameRequired": "请输入您的全名!", + "nameMinCharacterRequired": "全名必须至少包含4个字符!", + "emailLabel": "电子邮件", + "emailPlaceholder": "输入您的电子邮件", + "emailRequired": "请输入您的电子邮件!", + "passwordLabel": "密码", + "passwordPlaceholder": "输入您的密码", + "passwordRequired": "请输入您的密码!", + "passwordMinCharacterRequired": "密码必须至少包含8个字符!", + "passwordPatternRequired": "密码不符合要求!", + "strongPasswordPlaceholder": "输入更强的密码", + "passwordValidationAltText": "密码必须至少包含8个字符,包括大小写字母、一个数字和一个符号。", + "signupSuccessMessage": "您已成功注册!", + "privacyPolicyLink": "隐私政策", + "termsOfUseLink": "使用条款", + "bySigningUpText": "通过注册,您同意我们的", + "andText": "和", + "signupButton": "注册", + "signInWithGoogleButton": "使用Google登录", + "alreadyHaveAccountText": "已经有账户了?", + "loginButton": "登录", + "orText": "或", + "reCAPTCHAVerificationError": "reCAPTCHA验证错误", + "reCAPTCHAVerificationErrorMessage": "我们无法验证您的reCAPTCHA。请重试。" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/auth/verify-reset-email.json b/worklenz-frontend/public/locales/zh/auth/verify-reset-email.json new file mode 100644 index 00000000..11222523 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/auth/verify-reset-email.json @@ -0,0 +1,14 @@ +{ + "title": "验证重置电子邮件", + "description": "输入您的新密码", + "placeholder": "输入您的新密码", + "confirmPasswordPlaceholder": "确认您的新密码", + "passwordHint": "至少8个字符,包括大小写字母、一个数字和一个符号。", + "resetPasswordButton": "重置密码", + "orText": "或", + "resendResetEmail": "重新发送重置电子邮件", + "passwordRequired": "请输入您的新密码", + "returnToLoginButton": "返回登录", + "confirmPasswordRequired": "请确认您的新密码", + "passwordMismatch": "两次输入的密码不匹配" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/common.json b/worklenz-frontend/public/locales/zh/common.json new file mode 100644 index 00000000..520ee5e2 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/common.json @@ -0,0 +1,9 @@ +{ + "login-success": "登录成功!", + "login-failed": "登录失败。请检查您的凭据并重试。", + "signup-success": "注册成功!欢迎加入。", + "signup-failed": "注册失败。请确保填写所有必填字段并重试。", + "reconnecting": "与服务器断开连接。", + "connection-lost": "无法连接到服务器。请检查您的互联网连接。", + "connection-restored": "成功连接到服务器" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/create-first-project-form.json b/worklenz-frontend/public/locales/zh/create-first-project-form.json new file mode 100644 index 00000000..95ea4099 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/create-first-project-form.json @@ -0,0 +1,13 @@ +{ + "formTitle": "创建您的第一个项目", + "inputLabel": "您现在正在做什么项目?", + "or": "或", + "templateButton": "从模板导入", + "createFromTemplate": "从模板创建", + "goBack": "返回", + "continue": "继续", + "cancel": "取消", + "create": "创建", + "templateDrawerTitle": "从模板中选择", + "createProject": "创建项目" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/create-first-tasks.json b/worklenz-frontend/public/locales/zh/create-first-tasks.json new file mode 100644 index 00000000..810d5aff --- /dev/null +++ b/worklenz-frontend/public/locales/zh/create-first-tasks.json @@ -0,0 +1,7 @@ +{ + "formTitle": "创建您的第一个任务。", + "inputLable": "输入您将在其中完成的几个任务", + "addAnother": "添加另一个", + "goBack": "返回", + "continue": "继续" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/home.json b/worklenz-frontend/public/locales/zh/home.json new file mode 100644 index 00000000..184b4f1a --- /dev/null +++ b/worklenz-frontend/public/locales/zh/home.json @@ -0,0 +1,46 @@ +{ + "todoList": { + "title": "待办事项列表", + "refreshTasks": "刷新任务", + "addTask": "+ 添加任务", + "noTasks": "没有任务", + "pressEnter": "按", + "toCreate": "创建。", + "markAsDone": "标记为完成" + }, + "projects": { + "title": "项目", + "refreshProjects": "刷新项目", + "noRecentProjects": "您当前未被分配到任何项目。", + "noFavouriteProjects": "没有项目被标记为收藏。", + "recent": "最近", + "favourites": "收藏" + }, + "tasks": { + "assignedToMe": "分配给我", + "assignedByMe": "由我分配", + "all": "全部", + "today": "今天", + "upcoming": "即将到来", + "overdue": "逾期", + "noDueDate": "没有截止日期", + "noTasks": "没有任务可显示。", + "addTask": "+ 添加任务", + "name": "名称", + "project": "项目", + "status": "状态", + "dueDate": "截止日期", + "dueDatePlaceholder": "设置截止日期", + "tomorrow": "明天", + "nextWeek": "下周", + "nextMonth": "下个月", + "projectRequired": "请选择一个项目", + "pressTabToSelectDueDateAndProject": "按Tab键选择截止日期和项目", + "dueOn": "任务截止于", + "taskRequired": "请添加一个任务", + "list": "列表", + "calendar": "日历", + "tasks": "任务", + "refresh": "刷新" + } +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/invite-initial-team-members.json b/worklenz-frontend/public/locales/zh/invite-initial-team-members.json new file mode 100644 index 00000000..6ebb9fbf --- /dev/null +++ b/worklenz-frontend/public/locales/zh/invite-initial-team-members.json @@ -0,0 +1,8 @@ +{ + "formTitle": "邀请您的团队一起工作", + "inputLable": "通过电子邮件邀请", + "addAnother": "添加另一个", + "goBack": "返回", + "continue": "继续", + "skipForNow": "暂时跳过" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/kanban-board.json b/worklenz-frontend/public/locales/zh/kanban-board.json new file mode 100644 index 00000000..7b72c5d5 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/kanban-board.json @@ -0,0 +1,19 @@ +{ + "rename": "重命名", + "delete": "删除", + "addTask": "添加任务", + "addSectionButton": "添加部分", + "changeCategory": "更改类别", + "deleteTooltip": "删除", + "deleteConfirmationTitle": "您确定吗?", + "deleteConfirmationOk": "是", + "deleteConfirmationCancel": "取消", + "dueDate": "截止日期", + "cancel": "取消", + "today": "今天", + "tomorrow": "明天", + "assignToMe": "分配给我", + "archive": "归档", + "newTaskNamePlaceholder": "写一个任务名称", + "newSubtaskNamePlaceholder": "写一个子任务名称" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/license-expired.json b/worklenz-frontend/public/locales/zh/license-expired.json new file mode 100644 index 00000000..838125c2 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/license-expired.json @@ -0,0 +1,6 @@ +{ + "title": "您的Worklenz试用已过期!", + "subtitle": "请立即升级。", + "button": "立即升级", + "checking": "正在检查订阅状态..." +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/navbar.json b/worklenz-frontend/public/locales/zh/navbar.json new file mode 100644 index 00000000..c4ed67ab --- /dev/null +++ b/worklenz-frontend/public/locales/zh/navbar.json @@ -0,0 +1,31 @@ +{ + "logoAlt": "Worklenz Logo", + "home": "首页", + "projects": "项目", + "schedule": "日程", + "reporting": "报告", + "clients": "客户", + "teams": "团队", + "labels": "标签", + "jobTitles": "职位", + "upgradePlan": "升级计划", + "upgradePlanTooltip": "升级计划", + "invite": "邀请", + "inviteTooltip": "邀请团队成员加入", + "switchTeamTooltip": "切换团队", + "help": "帮助", + "notificationTooltip": "查看通知", + "profileTooltip": "查看个人资料", + "adminCenter": "管理中心", + "settings": "设置", + "logOut": "登出", + "notificationsDrawer": { + "read": "已读通知", + "unread": "未读通知", + "markAsRead": "标记为已读", + "readAndJoin": "阅读并加入", + "accept": "接受", + "acceptAndJoin": "接受并加入", + "noNotifications": "没有通知" + } +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/organization-name-form.json b/worklenz-frontend/public/locales/zh/organization-name-form.json new file mode 100644 index 00000000..df8727d8 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/organization-name-form.json @@ -0,0 +1,5 @@ +{ + "nameYourOrganization": "命名您的组织。", + "worklenzAccountTitle": "为您的Worklenz账户选择一个名称。", + "continue": "继续" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/phases-drawer.json b/worklenz-frontend/public/locales/zh/phases-drawer.json new file mode 100644 index 00000000..37d68cfb --- /dev/null +++ b/worklenz-frontend/public/locales/zh/phases-drawer.json @@ -0,0 +1,24 @@ +{ + "configurePhases": "配置阶段", + "configure": "配置", + "phaseLabel": "阶段标签", + "enterPhaseName": "输入阶段名称", + "addOption": "添加选项", + "phaseOptions": "阶段选项", + "optionsText": "选项", + "dragToReorderPhases": "拖拽阶段来重新排序。每个阶段可以有不同的颜色。", + "enterNewPhaseName": "输入新阶段名称...", + "addPhase": "添加阶段", + "noPhasesFound": "未找到阶段", + "no": "没有", + "found": "找到", + "deletePhase": "删除阶段", + "deletePhaseConfirm": "您确定要删除此阶段吗?此操作无法撤消。", + "rename": "重命名", + "delete": "删除", + "create": "创建", + "cancel": "取消", + "selectColor": "选择颜色", + "managePhases": "管理阶段", + "close": "关闭" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/project-drawer.json b/worklenz-frontend/public/locales/zh/project-drawer.json new file mode 100644 index 00000000..1649dfde --- /dev/null +++ b/worklenz-frontend/public/locales/zh/project-drawer.json @@ -0,0 +1,42 @@ +{ + "createProject": "创建项目", + "editProject": "编辑项目", + "enterCategoryName": "输入类别名称", + "hitEnterToCreate": "按回车键创建!", + "enterNotes": "备注", + "youCanManageClientsUnderSettings": "您可以在设置中管理客户", + "addCategory": "向项目添加类别", + "newCategory": "新类别", + "notes": "备注", + "startDate": "开始日期", + "endDate": "结束日期", + "estimateWorkingDays": "估算工作日", + "estimateManDays": "估算人天", + "hoursPerDay": "每天小时数", + "create": "创建", + "update": "更新", + "delete": "删除", + "typeToSearchClients": "输入以搜索客户", + "projectColor": "项目颜色", + "pleaseEnterAName": "请输入名称", + "enterProjectName": "输入项目名称", + "name": "名称", + "status": "状态", + "health": "健康状况", + "category": "类别", + "projectManager": "项目经理", + "client": "客户", + "deleteConfirmation": "您确定要删除吗?", + "deleteConfirmationDescription": "这将删除所有相关数据且无法撤销。", + "yes": "是", + "no": "否", + "createdAt": "创建于", + "updatedAt": "更新于", + "by": "由", + "add": "添加", + "asClient": "作为客户", + "createClient": "创建客户", + "searchInputPlaceholder": "按名称或电子邮件搜索", + "hoursPerDayValidationMessage": "每天小时数必须是1到24之间的数字", + "noPermission": "无权限" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/project-view-files.json b/worklenz-frontend/public/locales/zh/project-view-files.json new file mode 100644 index 00000000..9cbf8ef6 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/project-view-files.json @@ -0,0 +1,14 @@ +{ + "nameColumn": "名称", + "attachedTaskColumn": "附加任务", + "sizeColumn": "大小", + "uploadedByColumn": "上传者", + "uploadedAtColumn": "上传时间", + "fileIconAlt": "文件图标", + "titleDescriptionText": "此项目中任务的所有附件将显示在这里。", + "deleteConfirmationTitle": "您确定吗?", + "deleteConfirmationOk": "是", + "deleteConfirmationCancel": "取消", + "segmentedTooltip": "即将推出!在列表视图和缩略图视图之间切换。", + "emptyText": "项目中没有附件。" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/project-view-insights.json b/worklenz-frontend/public/locales/zh/project-view-insights.json new file mode 100644 index 00000000..903d73d2 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/project-view-insights.json @@ -0,0 +1,41 @@ +{ + "overview": { + "title": "概览", + "statusOverview": "状态概览", + "priorityOverview": "优先级概览", + "lastUpdatedTasks": "最近更新的任务" + }, + "members": { + "title": "成员", + "tooltip": "成员", + "tasksByMembers": "按成员分类任务", + "tasksByMembersTooltip": "按成员分类任务", + "name": "名称", + "taskCount": "任务计数", + "contribution": "贡献", + "completed": "已完成", + "incomplete": "未完成", + "overdue": "逾期", + "progress": "进度" + }, + "tasks": { + "overdueTasks": "逾期任务", + "overLoggedTasks": "超额记录任务", + "tasksCompletedEarly": "提前完成的任务", + "tasksCompletedLate": "延迟完成的任务", + "overLoggedTasksTooltip": "记录时间超过预计时间的任务", + "overdueTasksTooltip": "超过截止日期的任务" + }, + "common": { + "seeAll": "查看全部", + "totalLoggedHours": "总记录小时数", + "totalEstimation": "总估算", + "completedTasks": "已完成任务", + "incompleteTasks": "未完成任务", + "overdueTasks": "逾期任务", + "overdueTasksTooltip": "超过截止日期的任务", + "totalLoggedHoursTooltip": "任务估算和任务记录时间。", + "includeArchivedTasks": "包含已归档任务", + "export": "导出" + } +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/project-view-members.json b/worklenz-frontend/public/locales/zh/project-view-members.json new file mode 100644 index 00000000..3d217694 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/project-view-members.json @@ -0,0 +1,17 @@ +{ + "nameColumn": "名称", + "jobTitleColumn": "职位", + "emailColumn": "电子邮件", + "tasksColumn": "任务", + "taskProgressColumn": "任务进度", + "accessColumn": "访问权限", + "fileIconAlt": "文件图标", + "deleteConfirmationTitle": "您确定吗?", + "deleteConfirmationOk": "是", + "deleteConfirmationCancel": "取消", + "refreshButtonTooltip": "刷新成员", + "deleteButtonTooltip": "从项目中移除", + "memberCount": "成员", + "membersCountPlural": "成员", + "emptyText": "项目中没有附件。" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/project-view-updates.json b/worklenz-frontend/public/locales/zh/project-view-updates.json new file mode 100644 index 00000000..b34c71ea --- /dev/null +++ b/worklenz-frontend/public/locales/zh/project-view-updates.json @@ -0,0 +1,6 @@ +{ + "inputPlaceholder": "添加评论", + "addButton": "添加", + "cancelButton": "取消", + "deleteButton": "删除" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/project-view.json b/worklenz-frontend/public/locales/zh/project-view.json new file mode 100644 index 00000000..ff756ea5 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/project-view.json @@ -0,0 +1,14 @@ +{ + "taskList": "任务列表", + "board": "看板", + "insights": "数据洞察", + "files": "文件", + "members": "成员", + "updates": "动态更新", + "projectView": "项目视图", + "loading": "正在加载项目...", + "error": "加载项目时出错", + "pinnedTab": "已固定为默认标签页", + "pinTab": "固定为默认标签页", + "unpinTab": "取消固定默认标签页" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/project-view/import-task-templates.json b/worklenz-frontend/public/locales/zh/project-view/import-task-templates.json new file mode 100644 index 00000000..3dae9403 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/project-view/import-task-templates.json @@ -0,0 +1,11 @@ +{ + "importTaskTemplate": "导入任务模板", + "templateName": "模板名称", + "templateDescription": "模板描述", + "selectedTasks": "已选任务", + "tasks": "任务", + "templates": "模板", + "remove": "移除", + "cancel": "取消", + "import": "导入" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/project-view/project-member-drawer.json b/worklenz-frontend/public/locales/zh/project-view/project-member-drawer.json new file mode 100644 index 00000000..f412f22b --- /dev/null +++ b/worklenz-frontend/public/locales/zh/project-view/project-member-drawer.json @@ -0,0 +1,7 @@ +{ + "title": "项目成员", + "searchLabel": "通过添加名称或电子邮件添加成员", + "searchPlaceholder": "输入名称或电子邮件", + "inviteAsAMember": "邀请为成员", + "inviteNewMemberByEmail": "通过电子邮件邀请新成员" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/project-view/project-view-header.json b/worklenz-frontend/public/locales/zh/project-view/project-view-header.json new file mode 100644 index 00000000..a7bd9571 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/project-view/project-view-header.json @@ -0,0 +1,31 @@ +{ + "importTasks": "导入任务", + "importTask": "导入任务", + "createTask": "创建任务", + "settings": "设置", + "subscribe": "订阅", + "unsubscribe": "取消订阅", + "deleteProject": "删除项目", + "startDate": "开始日期", + "endDate": "结束日期", + "projectSettings": "项目设置", + "projectSummary": "项目摘要", + "receiveProjectSummary": "每天晚上接收项目摘要。", + "refreshProject": "刷新项目", + "saveAsTemplate": "保存为模板", + "invite": "邀请", + "share": "分享", + "subscribeTooltip": "订阅项目通知", + "unsubscribeTooltip": "取消订阅项目通知", + "refreshTooltip": "刷新项目数据", + "settingsTooltip": "打开项目设置", + "saveAsTemplateTooltip": "将此项目保存为模板", + "inviteTooltip": "邀请团队成员加入此项目", + "createTaskTooltip": "创建新任务", + "importTaskTooltip": "从模板导入任务", + "navigateBackTooltip": "返回项目列表", + "projectStatusTooltip": "项目状态", + "projectDatesInfo": "项目时间线信息", + "projectCategoryTooltip": "项目类别", + "defaultTaskName": "无标题任务" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/project-view/save-as-template.json b/worklenz-frontend/public/locales/zh/project-view/save-as-template.json new file mode 100644 index 00000000..d1d3dfa8 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/project-view/save-as-template.json @@ -0,0 +1,27 @@ +{ + "title": "保存为模板", + "templateName": "模板名称", + "includes": "项目中应包含哪些内容到模板中?", + "includesOptions": { + "statuses": "状态", + "phases": "阶段", + "labels": "标签" + }, + "taskIncludes": "任务中应包含哪些内容到模板中?", + "taskIncludesOptions": { + "statuses": "状态", + "phases": "阶段", + "labels": "标签", + "name": "名称", + "priority": "优先级", + "status": "状态", + "phase": "阶段", + "label": "标签", + "timeEstimate": "预计用时", + "description": "描述", + "subTasks": "子任务" + }, + "cancel": "取消", + "save": "保存", + "templateNamePlaceholder": "输入模板名称" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/reporting-members-drawer.json b/worklenz-frontend/public/locales/zh/reporting-members-drawer.json new file mode 100644 index 00000000..db42a74b --- /dev/null +++ b/worklenz-frontend/public/locales/zh/reporting-members-drawer.json @@ -0,0 +1,76 @@ +{ + "exportButton": "导出", + "timeLogsButton": "时间日志", + "activityLogsButton": "活动日志", + "tasksButton": "任务", + "searchByNameInputPlaceholder": "按名称搜索", + "overviewTab": "概览", + "timeLogsTab": "时间日志", + "activityLogsTab": "活动日志", + "tasksTab": "任务", + "projectsText": "项目", + "totalTasksText": "任务总数", + "assignedTasksText": "已分配任务", + "completedTasksText": "已完成任务", + "ongoingTasksText": "进行中任务", + "overdueTasksText": "逾期任务", + "loggedHoursText": "记录小时数", + "tasksText": "任务", + "allText": "全部", + "tasksByProjectsText": "按项目分类任务", + "tasksByStatusText": "按状态分类任务", + "tasksByPriorityText": "按优先级分类任务", + "todoText": "待办", + "doingText": "进行中", + "doneText": "已完成", + "lowText": "低", + "mediumText": "中", + "highText": "高", + "billableButton": "可计费", + "billableText": "可计费", + "nonBillableText": "不可计费", + "timeLogsEmptyPlaceholder": "没有时间日志可显示", + "loggedText": "记录", + "forText": "为", + "inText": "在", + "updatedText": "更新", + "fromText": "从", + "toText": "到", + "withinText": "在...之内", + "activityLogsEmptyPlaceholder": "没有活动日志可显示", + "filterByText": "筛选依据:", + "selectProjectPlaceholder": "选择项目", + "taskColumn": "任务", + "nameColumn": "名称", + "projectColumn": "项目", + "statusColumn": "状态", + "priorityColumn": "优先级", + "dueDateColumn": "截止日期", + "completedDateColumn": "完成日期", + "estimatedTimeColumn": "预计用时", + "loggedTimeColumn": "记录时间", + "overloggedTimeColumn": "超额记录时间", + "daysLeftColumn": "剩余天数/逾期", + "startDateColumn": "开始日期", + "endDateColumn": "结束日期", + "actualTimeColumn": "实际时间", + "projectHealthColumn": "项目健康状况", + "categoryColumn": "类别", + "projectManagerColumn": "项目经理", + "tasksStatsOverviewDrawerTitle": "的任务", + "projectsStatsOverviewDrawerTitle": "的项目", + "cancelledText": "已取消", + "blockedText": "已阻塞", + "onHoldText": "暂停", + "proposedText": "提议", + "inPlanningText": "规划中", + "inProgressText": "进行中", + "completedText": "已完成", + "continuousText": "持续", + "daysLeftText": "天剩余", + "daysOverdueText": "天逾期", + "notSetText": "未设置", + "needsAttentionText": "需要关注", + "atRiskText": "有风险", + "goodText": "良好" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/reporting-members.json b/worklenz-frontend/public/locales/zh/reporting-members.json new file mode 100644 index 00000000..de4c23bb --- /dev/null +++ b/worklenz-frontend/public/locales/zh/reporting-members.json @@ -0,0 +1,31 @@ +{ + "yesterdayText": "昨天", + "lastSevenDaysText": "过去7天", + "lastWeekText": "上周", + "lastThirtyDaysText": "过去30天", + "lastMonthText": "上个月", + "lastThreeMonthsText": "过去3个月", + "allTimeText": "所有时间", + "customRangeText": "自定义范围", + "startDateInputPlaceholder": "开始日期", + "EndDateInputPlaceholder": "结束日期", + "filterButton": "筛选", + "membersTitle": "成员", + "includeArchivedButton": "包含已归档项目", + "exportButton": "导出", + "excelButton": "Excel", + "searchByNameInputPlaceholder": "按名称搜索", + "memberColumn": "成员", + "tasksProgressColumn": "任务进度", + "tasksAssignedColumn": "分配任务", + "completedTasksColumn": "已完成任务", + "overdueTasksColumn": "逾期任务", + "ongoingTasksColumn": "进行中任务", + "tasksAssignedColumnTooltip": "在选定日期范围内分配的任务", + "overdueTasksColumnTooltip": "在选定日期范围结束时逾期的任务", + "completedTasksColumnTooltip": "在选定日期范围内完成的任务", + "ongoingTasksColumnTooltip": "已开始但尚未完成的任务", + "todoText": "待办", + "doingText": "进行中", + "doneText": "已完成" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/reporting-overview-drawer.json b/worklenz-frontend/public/locales/zh/reporting-overview-drawer.json new file mode 100644 index 00000000..a02b318f --- /dev/null +++ b/worklenz-frontend/public/locales/zh/reporting-overview-drawer.json @@ -0,0 +1,33 @@ +{ + "exportButton": "导出", + "projectsButton": "项目", + "membersButton": "成员", + "searchByNameInputPlaceholder": "按名称搜索", + "overviewTab": "概览", + "projectsTab": "项目", + "membersTab": "成员", + "projectsByStatusText": "按状态分类项目", + "projectsByCategoryText": "按类别分类项目", + "projectsByHealthText": "按健康状况分类项目", + "projectsText": "项目", + "allText": "全部", + "cancelledText": "已取消", + "blockedText": "已阻塞", + "onHoldText": "暂停", + "proposedText": "提议", + "inPlanningText": "规划中", + "inProgressText": "进行中", + "completedText": "已完成", + "continuousText": "持续", + "notSetText": "未设置", + "needsAttentionText": "需要关注", + "atRiskText": "有风险", + "goodText": "良好", + "nameColumn": "名称", + "emailColumn": "电子邮件", + "projectsColumn": "项目", + "tasksColumn": "任务", + "overdueTasksColumn": "逾期任务", + "completedTasksColumn": "已完成任务", + "ongoingTasksColumn": "进行中任务" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/reporting-overview.json b/worklenz-frontend/public/locales/zh/reporting-overview.json new file mode 100644 index 00000000..fb172817 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/reporting-overview.json @@ -0,0 +1,22 @@ +{ + "overviewTitle": "概览", + "includeArchivedButton": "包含已归档项目", + "teamCount": "团队", + "teamCountPlural": "团队", + "projectCount": "项目", + "projectCountPlural": "项目", + "memberCount": "成员", + "memberCountPlural": "成员", + "activeProjectCount": "活跃项目", + "activeProjectCountPlural": "活跃项目", + "overdueProjectCount": "逾期项目", + "overdueProjectCountPlural": "逾期项目", + "unassignedMemberCount": "未分配成员", + "unassignedMemberCountPlural": "未分配成员", + "memberWithOverdueTaskCount": "有逾期任务的成员", + "memberWithOverdueTaskCountPlural": "有逾期任务的成员", + "teamsText": "团队", + "nameColumn": "名称", + "projectsColumn": "项目", + "membersColumn": "成员" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/reporting-projects-drawer.json b/worklenz-frontend/public/locales/zh/reporting-projects-drawer.json new file mode 100644 index 00000000..d2f2f6ef --- /dev/null +++ b/worklenz-frontend/public/locales/zh/reporting-projects-drawer.json @@ -0,0 +1,52 @@ +{ + "exportButton": "导出", + "membersButton": "成员", + "tasksButton": "任务", + "searchByNameInputPlaceholder": "按名称搜索", + "overviewTab": "概览", + "membersTab": "成员", + "tasksTab": "任务", + "completedTasksText": "已完成任务", + "incompleteTasksText": "未完成任务", + "overdueTasksText": "逾期任务", + "allocatedHoursText": "已分配小时数", + "loggedHoursText": "已记录小时数", + "tasksText": "任务", + "allText": "全部", + "tasksByStatusText": "按状态分类任务", + "tasksByPriorityText": "按优先级分类任务", + "tasksByDueDateText": "按截止日期分类任务", + "todoText": "待办", + "doingText": "进行中", + "doneText": "已完成", + "lowText": "低", + "mediumText": "中", + "highText": "高", + "completedText": "已完成", + "upcomingText": "即将到来", + "overdueText": "逾期", + "noDueDateText": "无截止日期", + "nameColumn": "名称", + "tasksCountColumn": "任务计数", + "completedTasksColumn": "已完成任务", + "incompleteTasksColumn": "未完成任务", + "overdueTasksColumn": "逾期任务", + "contributionColumn": "贡献", + "progressColumn": "进度", + "loggedTimeColumn": "记录时间", + "taskColumn": "任务", + "projectColumn": "项目", + "statusColumn": "状态", + "priorityColumn": "优先级", + "phaseColumn": "阶段", + "dueDateColumn": "截止日期", + "completedDateColumn": "完成日期", + "estimatedTimeColumn": "预计用时", + "overloggedTimeColumn": "超额记录时间", + "completedOnColumn": "完成于", + "daysOverdueColumn": "逾期天数", + "groupByText": "分组依据:", + "statusText": "状态", + "priorityText": "优先级", + "phaseText": "阶段" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/reporting-projects-filters.json b/worklenz-frontend/public/locales/zh/reporting-projects-filters.json new file mode 100644 index 00000000..ddfbe104 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/reporting-projects-filters.json @@ -0,0 +1,31 @@ +{ + "searchByNamePlaceholder": "按名称搜索", + "searchByCategoryPlaceholder": "按类别搜索", + "statusText": "状态", + "healthText": "健康状况", + "categoryText": "类别", + "projectManagerText": "项目经理", + "showFieldsText": "显示字段", + "cancelledText": "已取消", + "blockedText": "已阻塞", + "onHoldText": "暂停", + "proposedText": "提议", + "inPlanningText": "规划中", + "inProgressText": "进行中", + "completedText": "已完成", + "continuousText": "持续", + "notSetText": "未设置", + "needsAttentionText": "需要关注", + "atRiskText": "有风险", + "goodText": "良好", + "nameText": "项目", + "estimatedVsActualText": "预计用时 vs 实际用时", + "tasksProgressText": "任务进度", + "lastActivityText": "最后活动", + "datesText": "开始/结束日期", + "daysLeftText": "剩余天数/逾期", + "projectHealthText": "项目健康状况", + "projectUpdateText": "项目更新", + "clientText": "客户", + "teamText": "团队" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/reporting-projects.json b/worklenz-frontend/public/locales/zh/reporting-projects.json new file mode 100644 index 00000000..0ff7d415 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/reporting-projects.json @@ -0,0 +1,44 @@ +{ + "projectCount": "项目", + "projectCountPlural": "项目", + "includeArchivedButton": "包含已归档项目", + "exportButton": "导出", + "excelButton": "Excel", + "projectColumn": "项目", + "estimatedVsActualColumn": "预计用时 vs 实际用时", + "tasksProgressColumn": "任务进度", + "lastActivityColumn": "最后活动", + "statusColumn": "状态", + "datesColumn": "开始/结束日期", + "daysLeftColumn": "剩余天数/逾期", + "projectHealthColumn": "项目健康状况", + "categoryColumn": "类别", + "projectUpdateColumn": "项目更新", + "clientColumn": "客户", + "teamColumn": "团队", + "projectManagerColumn": "项目经理", + "openButton": "打开", + "estimatedText": "预计", + "actualText": "实际", + "todoText": "待办", + "doingText": "进行中", + "doneText": "已完成", + "cancelledText": "已取消", + "blockedText": "已阻塞", + "onHoldText": "暂停", + "proposedText": "提议", + "inPlanningText": "规划中", + "inProgressText": "进行中", + "completedText": "已完成", + "continuousText": "持续", + "daysLeftText": "天剩余", + "dayLeftText": "天剩余", + "daysOverdueText": "天逾期", + "notSetText": "未设置", + "needsAttentionText": "需要关注", + "atRiskText": "有风险", + "goodText": "良好", + "setCategoryText": "设置类别", + "searchByNameInputPlaceholder": "按名称搜索", + "todayText": "今天" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/reporting-sidebar.json b/worklenz-frontend/public/locales/zh/reporting-sidebar.json new file mode 100644 index 00000000..8a8206fb --- /dev/null +++ b/worklenz-frontend/public/locales/zh/reporting-sidebar.json @@ -0,0 +1,8 @@ +{ + "overview": "概览", + "projects": "项目", + "members": "成员", + "timeReports": "用时报告", + "estimateVsActual": "预计用时 vs 实际用时", + "currentOrganizationTooltip": "当前的组织" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/schedule.json b/worklenz-frontend/public/locales/zh/schedule.json new file mode 100644 index 00000000..53fa8a97 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/schedule.json @@ -0,0 +1,34 @@ +{ + "today": "今天", + "week": "周", + "month": "月", + "settings": "设置", + "workingDays": "工作日", + "monday": "星期一", + "tuesday": "星期二", + "wednesday": "星期三", + "thursday": "星期四", + "friday": "星期五", + "saturday": "星期六", + "sunday": "星期日", + "workingHours": "工作时间", + "hours": "小时", + "saveButton": "保存", + "totalAllocation": "总分配", + "timeLogged": "记录时间", + "remainingTime": "剩余时间", + "total": "总计", + "perDay": "每天", + "tasks": "任务", + "startDate": "开始日期", + "endDate": "结束日期", + "hoursPerDay": "每天小时数", + "totalHours": "总小时数", + "deleteButton": "删除", + "cancelButton": "取消", + "tabTitle": "没有开始和结束日期的任务", + "allocatedTime": "分配时间", + "totalLogged": "总记录", + "loggedBillable": "已记录可计费", + "loggedNonBillable": "已记录不可计费" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/settings/categories.json b/worklenz-frontend/public/locales/zh/settings/categories.json new file mode 100644 index 00000000..00027081 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/settings/categories.json @@ -0,0 +1,10 @@ +{ + "categoryColumn": "类别", + "deleteConfirmationTitle": "您确定吗?", + "deleteConfirmationOk": "是", + "deleteConfirmationCancel": "取消", + "associatedTaskColumn": "关联项目", + "searchPlaceholder": "按名称搜索", + "emptyText": "在更新或创建项目时可以创建类别。", + "colorChangeTooltip": "点击更改颜色" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/settings/change-password.json b/worklenz-frontend/public/locales/zh/settings/change-password.json new file mode 100644 index 00000000..30cec581 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/settings/change-password.json @@ -0,0 +1,15 @@ +{ + "title": "更改密码", + "currentPassword": "当前密码", + "newPassword": "新密码", + "confirmPassword": "确认密码", + "currentPasswordPlaceholder": "输入您的当前密码", + "newPasswordPlaceholder": "新密码", + "confirmPasswordPlaceholder": "确认密码", + "currentPasswordRequired": "请输入您的当前密码!", + "newPasswordRequired": "请输入您的新密码!", + "passwordValidationError": "密码必须至少包含8个字符,包括一个大写字母、一个数字和一个符号。", + "passwordMismatch": "密码不匹配!", + "passwordRequirements": "新密码应至少包含8个字符,包括一个大写字母、一个数字和一个符号。", + "updateButton": "更新密码" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/settings/clients.json b/worklenz-frontend/public/locales/zh/settings/clients.json new file mode 100644 index 00000000..c06b1adc --- /dev/null +++ b/worklenz-frontend/public/locales/zh/settings/clients.json @@ -0,0 +1,22 @@ +{ + "nameColumn": "名称", + "projectColumn": "项目", + "noProjectsAvailable": "没有可用的项目", + "deleteConfirmationTitle": "您确定吗?", + "deleteConfirmationOk": "是", + "deleteConfirmationCancel": "取消", + "searchPlaceholder": "按名称搜索", + "createClient": "创建客户", + "pinTooltip": "点击将其固定到主菜单", + "createClientDrawerTitle": "创建客户", + "updateClientDrawerTitle": "更新客户", + "nameLabel": "名称", + "namePlaceholder": "名称", + "nameRequiredError": "请输入名称", + "createButton": "创建", + "updateButton": "更新", + "createClientSuccessMessage": "客户创建成功!", + "createClientErrorMessage": "客户创建失败!", + "updateClientSuccessMessage": "客户更新成功!", + "updateClientErrorMessage": "客户更新失败!" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/settings/job-titles.json b/worklenz-frontend/public/locales/zh/settings/job-titles.json new file mode 100644 index 00000000..c0458bb6 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/settings/job-titles.json @@ -0,0 +1,20 @@ +{ + "nameColumn": "名称", + "deleteConfirmationTitle": "您确定吗?", + "deleteConfirmationOk": "是", + "deleteConfirmationCancel": "取消", + "searchPlaceholder": "按名称搜索", + "createJobTitleButton": "创建职位", + "pinTooltip": "点击将其固定到主菜单", + "createJobTitleDrawerTitle": "创建职位", + "updateJobTitleDrawerTitle": "更新职位", + "nameLabel": "名称", + "namePlaceholder": "名称", + "nameRequiredError": "请输入名称", + "createButton": "创建", + "updateButton": "更新", + "createJobTitleSuccessMessage": "职位创建成功!", + "createJobTitleErrorMessage": "职位创建失败!", + "updateJobTitleSuccessMessage": "职位更新成功!", + "updateJobTitleErrorMessage": "职位更新失败!" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/settings/labels.json b/worklenz-frontend/public/locales/zh/settings/labels.json new file mode 100644 index 00000000..ab0d01cd --- /dev/null +++ b/worklenz-frontend/public/locales/zh/settings/labels.json @@ -0,0 +1,11 @@ +{ + "labelColumn": "标签", + "deleteConfirmationTitle": "您确定吗?", + "deleteConfirmationOk": "是", + "deleteConfirmationCancel": "取消", + "associatedTaskColumn": "关联任务计数", + "searchPlaceholder": "按名称搜索", + "emptyText": "标签可以在更新或创建任务时创建。", + "pinTooltip": "点击将其固定到主菜单", + "colorChangeTooltip": "点击更改颜色" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/settings/language.json b/worklenz-frontend/public/locales/zh/settings/language.json new file mode 100644 index 00000000..631eac11 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/settings/language.json @@ -0,0 +1,7 @@ +{ + "language": "语言", + "language_required": "语言是必需的", + "time_zone": "时区", + "time_zone_required": "时区是必需的", + "save_changes": "保存更改" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/settings/notifications.json b/worklenz-frontend/public/locales/zh/settings/notifications.json new file mode 100644 index 00000000..f15784bf --- /dev/null +++ b/worklenz-frontend/public/locales/zh/settings/notifications.json @@ -0,0 +1,11 @@ +{ + "title": "通知设置", + "emailTitle": "向我发送电子邮件通知", + "emailDescription": "包括新的任务分配", + "dailyDigestTitle": "向我发送每日摘要", + "dailyDigestDescription": "每天晚上,您将收到任务中最近活动的摘要。", + "popupTitle": "当Worklenz打开时,在我的电脑上弹出通知", + "popupDescription": "弹出通知可能会被您的浏览器禁用。更改您的浏览器设置以允许它们。", + "unreadItemsTitle": "显示未读项目的数量", + "unreadItemsDescription": "您将看到每个通知的计数。" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/settings/profile.json b/worklenz-frontend/public/locales/zh/settings/profile.json new file mode 100644 index 00000000..cfafeb12 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/settings/profile.json @@ -0,0 +1,14 @@ +{ + "uploadError": "您只能上传JPG/PNG文件!", + "uploadSizeError": "图片必须小于2MB!", + "upload": "上传", + "nameLabel": "名称", + "nameRequiredError": "名称是必需的", + "emailLabel": "电子邮件", + "emailRequiredError": "电子邮件是必需的", + "saveChanges": "保存更改", + "profileJoinedText": "一个月前加入", + "profileLastUpdatedText": "一个月前更新", + "avatarTooltip": "点击上传头像", + "title": "个人资料设置" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/settings/project-templates.json b/worklenz-frontend/public/locales/zh/settings/project-templates.json new file mode 100644 index 00000000..5dcc866c --- /dev/null +++ b/worklenz-frontend/public/locales/zh/settings/project-templates.json @@ -0,0 +1,8 @@ +{ + "nameColumn": "名称", + "editToolTip": "编辑", + "deleteToolTip": "删除", + "confirmText": "您确定吗?", + "okText": "是", + "cancelText": "取消" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/settings/sidebar.json b/worklenz-frontend/public/locales/zh/settings/sidebar.json new file mode 100644 index 00000000..b9f74709 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/settings/sidebar.json @@ -0,0 +1,15 @@ +{ + "profile": "个人资料", + "appearance": "外观", + "notifications": "通知", + "clients": "客户", + "job-titles": "职位", + "labels": "标签", + "categories": "类别", + "project-templates": "项目模板", + "task-templates": "任务模板", + "team-members": "团队成员", + "teams": "团队", + "change-password": "更改密码", + "language-and-region": "语言和地区" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/settings/task-templates.json b/worklenz-frontend/public/locales/zh/settings/task-templates.json new file mode 100644 index 00000000..3fd9124a --- /dev/null +++ b/worklenz-frontend/public/locales/zh/settings/task-templates.json @@ -0,0 +1,9 @@ +{ + "nameColumn": "名称", + "createdColumn": "创建时间", + "editToolTip": "编辑", + "deleteToolTip": "删除", + "confirmText": "您确定吗?", + "okText": "是", + "cancelText": "取消" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/settings/team-members.json b/worklenz-frontend/public/locales/zh/settings/team-members.json new file mode 100644 index 00000000..8b39483c --- /dev/null +++ b/worklenz-frontend/public/locales/zh/settings/team-members.json @@ -0,0 +1,47 @@ +{ + "title": "团队成员", + "nameColumn": "名称", + "projectsColumn": "项目", + "emailColumn": "电子邮件", + "teamAccessColumn": "团队访问", + "memberCount": "成员", + "membersCountPlural": "成员", + "searchPlaceholder": "按名称搜索成员", + "pinTooltip": "刷新成员列表", + "addMemberButton": "添加新成员", + "editTooltip": "编辑成员", + "deactivateTooltip": "停用成员", + "activateTooltip": "激活成员", + "deleteTooltip": "删除成员", + "confirmDeleteTitle": "您确定要删除此成员吗?", + "confirmActivateTitle": "您确定要更改此成员的状态吗?", + "okText": "是,继续", + "cancelText": "否,取消", + "deactivatedText": "(当前已停用)", + "pendingInvitationText": "(邀请待处理)", + "addMemberDrawerTitle": "添加新团队成员", + "updateMemberDrawerTitle": "更新团队成员", + "addMemberEmailHint": "无论是否接受邀请,成员都将被添加到团队中", + "memberEmailLabel": "电子邮件", + "memberEmailPlaceholder": "输入团队成员的电子邮件地址", + "memberEmailRequiredError": "请输入有效的电子邮件地址", + "jobTitleLabel": "职位", + "jobTitlePlaceholder": "选择或搜索职位(可选)", + "memberAccessLabel": "访问级别", + "addToTeamButton": "将成员添加到团队", + "updateButton": "保存更改", + "resendInvitationButton": "重新发送邀请邮件", + "invitationSentSuccessMessage": "团队邀请已成功发送!", + "createMemberSuccessMessage": "新团队成员已成功添加!", + "createMemberErrorMessage": "添加团队成员失败。请重试。", + "updateMemberSuccessMessage": "团队成员已成功更新!", + "updateMemberErrorMessage": "更新团队成员失败。请重试。", + "memberText": "成员", + "adminText": "管理员", + "ownerText": "团队所有者", + "addedText": "已添加", + "updatedText": "已更新", + "noResultFound": "输入电子邮件地址并按回车键...", + "jobTitlesFetchError": "获取职位失败", + "invitationResent": "邀请重新发送成功!" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/settings/teams.json b/worklenz-frontend/public/locales/zh/settings/teams.json new file mode 100644 index 00000000..af2064ae --- /dev/null +++ b/worklenz-frontend/public/locales/zh/settings/teams.json @@ -0,0 +1,16 @@ +{ + "title": "团队", + "team": "团队", + "teams": "团队", + "name": "名称", + "created": "创建时间", + "ownsBy": "所有者", + "edit": "编辑", + "editTeam": "编辑团队", + "pinTooltip": "点击将此项固定到主菜单", + "editTeamName": "编辑团队名称", + "updateName": "更新名称", + "namePlaceholder": "名称", + "nameRequired": "请输入名称", + "updateFailed": "团队名称更改失败!" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/task-drawer/task-drawer-info-tab.json b/worklenz-frontend/public/locales/zh/task-drawer/task-drawer-info-tab.json new file mode 100644 index 00000000..b0b36689 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/task-drawer/task-drawer-info-tab.json @@ -0,0 +1,29 @@ +{ + "details": { + "task-key": "任务ID", + "phase": "阶段", + "assignees": "受托人", + "due-date": "截止日期", + "time-estimation": "估计时间", + "priority": "优先级", + "labels": "标签", + "billable": "可计费", + "notify": "通知", + "when-done-notify": "完成时通知", + "start-date": "开始日期", + "end-date": "结束日期", + "hide-start-date": "隐藏开始日期", + "show-start-date": "显示开始日期", + "hours": "小时", + "minutes": "分钟" + }, + "description": { + "title": "描述", + "placeholder": "添加更详细的描述..." + }, + "subTasks": { + "title": "子任务", + "add-sub-task": "+ 添加子任务", + "refresh-sub-tasks": "刷新子任务" + } +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/task-drawer/task-drawer.json b/worklenz-frontend/public/locales/zh/task-drawer/task-drawer.json new file mode 100644 index 00000000..dfe304fe --- /dev/null +++ b/worklenz-frontend/public/locales/zh/task-drawer/task-drawer.json @@ -0,0 +1,123 @@ +{ + "taskHeader": { + "taskNamePlaceholder": "输入您的任务", + "deleteTask": "删除任务" + }, + "taskInfoTab": { + "title": "信息", + "details": { + "title": "详情", + "task-key": "任务键", + "phase": "阶段", + "assignees": "受让人", + "due-date": "截止日期", + "time-estimation": "时间估算", + "priority": "优先级", + "labels": "标签", + "billable": "可计费", + "notify": "通知", + "when-done-notify": "完成时,通知", + "start-date": "开始日期", + "end-date": "结束日期", + "hide-start-date": "隐藏开始日期", + "show-start-date": "显示开始日期", + "hours": "小时", + "minutes": "分钟", + "progressValue": "进度值", + "progressValueTooltip": "设置进度百分比(0-100%)", + "progressValueRequired": "请输入进度值", + "progressValueRange": "进度必须在0到100之间", + "taskWeight": "任务权重", + "taskWeightTooltip": "设置此子任务的权重(百分比)", + "taskWeightRequired": "请输入任务权重", + "taskWeightRange": "权重必须在0到100之间", + "recurring": "重复" + }, + "labels": { + "labelInputPlaceholder": "搜索或创建", + "labelsSelectorInputTip": "按回车创建" + }, + "description": { + "title": "描述", + "placeholder": "添加更详细的描述..." + }, + "subTasks": { + "title": "子任务", + "addSubTask": "添加子任务", + "addSubTaskInputPlaceholder": "输入您的任务并按回车", + "refreshSubTasks": "刷新子任务", + "edit": "编辑", + "delete": "删除", + "confirmDeleteSubTask": "您确定要删除此子任务吗?", + "deleteSubTask": "删除子任务" + }, + "dependencies": { + "title": "依赖关系", + "addDependency": "+ 添加新依赖", + "blockedBy": "被阻止", + "searchTask": "输入搜索任务", + "noTasksFound": "未找到任务", + "confirmDeleteDependency": "您确定要删除吗?" + }, + "attachments": { + "title": "附件", + "chooseOrDropFileToUpload": "选择或拖放文件上传", + "uploading": "上传中..." + }, + "comments": { + "title": "评论", + "addComment": "+ 添加新评论", + "noComments": "还没有评论。成为第一个评论的人!", + "delete": "删除", + "confirmDeleteComment": "您确定要删除此评论吗?", + "addCommentPlaceholder": "添加评论...", + "cancel": "取消", + "commentButton": "评论", + "attachFiles": "附加文件", + "addMoreFiles": "添加更多文件", + "selectedFiles": "已选择的文件(最多25MB,最大{count}个)", + "maxFilesError": "您最多只能上传{count}个文件", + "processFilesError": "处理文件失败", + "addCommentError": "请添加评论或附加文件", + "createdBy": "{{time}}由{{user}}创建", + "updatedTime": "更新于{{time}}" + }, + "searchInputPlaceholder": "按名称搜索", + "pendingInvitation": "待处理邀请" + }, + "taskTimeLogTab": { + "title": "时间日志", + "addTimeLog": "添加新时间日志", + "totalLogged": "总记录时间", + "exportToExcel": "导出到Excel", + "noTimeLogsFound": "未找到时间日志", + "timeLogForm": { + "date": "日期", + "startTime": "开始时间", + "endTime": "结束时间", + "workDescription": "工作描述", + "descriptionPlaceholder": "添加描述", + "logTime": "记录时间", + "updateTime": "更新时间", + "cancel": "取消", + "selectDateError": "请选择日期", + "selectStartTimeError": "请选择开始时间", + "selectEndTimeError": "请选择结束时间", + "endTimeAfterStartError": "结束时间必须在开始时间之后" + } + }, + "taskActivityLogTab": { + "title": "活动日志", + "add": "添加", + "remove": "移除", + "none": "无", + "weight": "权重", + "createdTask": "创建了任务。" + }, + "taskProgress": { + "markAsDoneTitle": "将任务标记为完成?", + "confirmMarkAsDone": "是的,标记为完成", + "cancelMarkAsDone": "不,保持当前状态", + "markAsDoneDescription": "您已将进度设置为100%。您想将任务状态更新为\"完成\"吗?" + } +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/task-list-filters.json b/worklenz-frontend/public/locales/zh/task-list-filters.json new file mode 100644 index 00000000..4d1d6b43 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/task-list-filters.json @@ -0,0 +1,83 @@ +{ + "searchButton": "搜索", + "resetButton": "重置", + "searchInputPlaceholder": "按名称搜索", + "sortText": "排序", + "statusText": "状态", + "phaseText": "阶段", + "memberText": "成员", + "assigneesText": "受托人", + "priorityText": "优先级", + "labelsText": "标签", + "membersText": "成员", + "groupByText": "分组依据", + "showArchivedText": "显示已归档的任务", + "showFieldsText": "显示字段", + "keyText": "ID", + "taskText": "任务", + "descriptionText": "描述", + "phasesText": "阶段", + "listText": "列表", + "progressText": "进度", + "timeTrackingText": "时间跟踪", + "timetrackingText": "时间跟踪", + "estimationText": "估计", + "startDateText": "开始日期", + "startdateText": "开始日期", + "endDateText": "结束日期", + "dueDateText": "截止日期", + "duedateText": "截止日期", + "completedDateText": "完成日期", + "completeddateText": "完成日期", + "createdDateText": "创建日期", + "createddateText": "创建日期", + "lastUpdatedText": "最后更新", + "lastupdatedText": "最后更新", + "reporterText": "报告人", + "dueTimeText": "截止时间", + "duetimeText": "截止时间", + "lowText": "低", + "mediumText": "中", + "highText": "高", + "createStatusButtonTooltip": "状态设置", + "configPhaseButtonTooltip": "阶段设置", + "noLabelsFound": "未找到标签", + "addStatusButton": "添加状态", + "addPhaseButton": "添加阶段", + "createStatus": "创建状态", + "name": "名称", + "category": "类别", + "selectCategory": "选择类别", + "pleaseEnterAName": "请输入名称", + "pleaseSelectACategory": "请选择类别", + "create": "创建", + "searchTasks": "搜索任务...", + "searchPlaceholder": "搜索...", + "fieldsText": "字段", + "loadingFilters": "加载筛选器...", + "noOptionsFound": "未找到选项", + "filtersActive": "个筛选器已激活", + "filterActive": "个筛选器已激活", + "clearAll": "清除全部", + "clearing": "清除中...", + "cancel": "取消", + "search": "搜索", + "groupedBy": "分组方式", + "manage": "管理", + "manageStatuses": "管理状态", + "managePhases": "管理阶段", + "dragToReorderStatuses": "拖拽状态以重新排序。每个状态可以有不同的类别。", + "enterNewStatusName": "输入新状态名称...", + "addStatus": "添加状态", + "noStatusesFound": "未找到状态。请在上面创建您的第一个状态。", + "deleteStatus": "删除状态", + "deleteStatusConfirm": "您确定要删除此状态吗?此操作无法撤销。", + "rename": "重命名", + "delete": "删除", + "enterStatusName": "输入状态名称", + "selectCategory": "选择类别", + "close": "关闭", + "cannotMoveStatus": "无法移动状态", + "cannotMoveStatusMessage": "无法移动此状态,因为这会使\"{{categoryName}}\"类别为空。每个类别必须至少有一个状态。", + "ok": "确定" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/task-list-table.json b/worklenz-frontend/public/locales/zh/task-list-table.json new file mode 100644 index 00000000..f3ec040f --- /dev/null +++ b/worklenz-frontend/public/locales/zh/task-list-table.json @@ -0,0 +1,129 @@ +{ + "keyColumn": "ID", + "taskColumn": "任务", + "descriptionColumn": "描述", + "progressColumn": "进度", + "membersColumn": "成员", + "assigneesColumn": "受托人", + "labelsColumn": "标签", + "phasesColumn": "阶段", + "phaseColumn": "阶段", + "statusColumn": "状态", + "priorityColumn": "优先级", + "timeTrackingColumn": "时间追踪", + "timetrackingColumn": "时间追踪", + "estimationColumn": "估算", + "startDateColumn": "开始日期", + "startdateColumn": "开始日期", + "dueDateColumn": "截止日期", + "duedateColumn": "截止日期", + "completedDateColumn": "完成日期", + "completeddateColumn": "完成日期", + "createdDateColumn": "创建日期", + "createddateColumn": "创建日期", + "lastUpdatedColumn": "最后更新", + "lastupdatedColumn": "最后更新", + "reporterColumn": "报告人", + "dueTimeColumn": "截止时间", + "todoSelectorText": "待办", + "doingSelectorText": "进行中", + "doneSelectorText": "已完成", + "lowSelectorText": "低", + "mediumSelectorText": "中", + "highSelectorText": "高", + "selectText": "选择", + "labelsSelectorInputTip": "按回车键创建!", + "addTaskText": "+ 添加任务", + "addSubTaskText": "+ 添加子任务", + "addTaskInputPlaceholder": "输入任务并按回车键", + "noTasksInGroup": "此组中没有任务", + "openButton": "打开", + "okButton": "确定", + "noLabelsFound": "未找到标签", + "searchInputPlaceholder": "搜索或创建", + "assigneeSelectorInviteButton": "通过电子邮件邀请新成员", + "labelInputPlaceholder": "搜索或创建", + "searchLabelsPlaceholder": "搜索标签...", + "createLabelButton": "创建 \"{{name}}\"", + "manageLabelsPath": "设置 → 标签", + "pendingInvitation": "待处理邀请", + "contextMenu": { + "assignToMe": "分配给我", + "moveTo": "移动到", + "unarchive": "取消归档", + "archive": "归档", + "convertToSubTask": "转换为子任务", + "convertToTask": "转换为任务", + "delete": "删除", + "searchByNameInputPlaceholder": "按名称搜索" + }, + "setDueDate": "设置截止日期", + "setStartDate": "设置开始日期", + "clearDueDate": "清除截止日期", + "clearStartDate": "清除开始日期", + "dueDatePlaceholder": "截止日期", + "startDatePlaceholder": "开始日期", + + "emptyStates": { + "noTaskGroups": "未找到任务组", + "noTaskGroupsDescription": "创建任务或应用筛选器后,任务将显示在此处。", + "errorPrefix": "错误:", + "dragTaskFallback": "任务" + }, + + "customColumns": { + "addCustomColumn": "添加自定义列", + "customColumnHeader": "自定义列", + "customColumnSettings": "自定义列设置", + "noCustomValue": "无值", + "peopleField": "人员字段", + "noDate": "无日期", + "unsupportedField": "不支持的字段类型", + + "modal": { + "addFieldTitle": "添加字段", + "editFieldTitle": "编辑字段", + "fieldTitle": "字段标题", + "fieldTitleRequired": "字段标题为必填项", + "columnTitlePlaceholder": "列标题", + "type": "类型", + "deleteConfirmTitle": "确定要删除此自定义列吗?", + "deleteConfirmDescription": "此操作无法撤销。与此列关联的所有数据将被永久删除。", + "deleteButton": "删除", + "cancelButton": "取消", + "createButton": "创建", + "updateButton": "更新", + "createSuccessMessage": "自定义列创建成功", + "updateSuccessMessage": "自定义列更新成功", + "deleteSuccessMessage": "自定义列删除成功", + "deleteErrorMessage": "删除自定义列失败", + "createErrorMessage": "创建自定义列失败", + "updateErrorMessage": "更新自定义列失败" + }, + + "fieldTypes": { + "people": "人员", + "number": "数字", + "date": "日期", + "selection": "选择", + "checkbox": "复选框", + "labels": "标签", + "key": "键", + "formula": "公式" + } + }, + + "indicators": { + "tooltips": { + "subtasks": "{{count}} 个子任务", + "subtasks_plural": "{{count}} 个子任务", + "comments": "{{count}} 条评论", + "comments_plural": "{{count}} 条评论", + "attachments": "{{count}} 个附件", + "attachments_plural": "{{count}} 个附件", + "subscribers": "任务有订阅者", + "dependencies": "任务有依赖项", + "recurring": "重复任务" + } + } +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/task-management.json b/worklenz-frontend/public/locales/zh/task-management.json new file mode 100644 index 00000000..b2589ecf --- /dev/null +++ b/worklenz-frontend/public/locales/zh/task-management.json @@ -0,0 +1,39 @@ +{ + "noTasksInGroup": "此组中没有任务", + "noTasksInGroupDescription": "添加任务开始使用", + "addFirstTask": "添加你的第一个任务", + "openTask": "打开", + "subtask": "子任务", + "subtasks": "子任务", + "comment": "评论", + "comments": "评论", + "attachment": "附件", + "attachments": "附件", + "enterSubtaskName": "输入子任务名称...", + "add": "添加", + "cancel": "取消", + "renameGroup": "重命名组", + "renameStatus": "重命名状态", + "renamePhase": "重命名阶段", + "changeCategory": "更改类别", + "clickToEditGroupName": "点击编辑组名称", + "enterGroupName": "输入组名称", + "todo": "待办", + "inProgress": "进行中", + "done": "已完成", + "defaultTaskName": "无标题任务", + + "indicators": { + "tooltips": { + "subtasks": "{{count}} 个子任务", + "subtasks_plural": "{{count}} 个子任务", + "comments": "{{count}} 条评论", + "comments_plural": "{{count}} 条评论", + "attachments": "{{count}} 个附件", + "attachments_plural": "{{count}} 个附件", + "subscribers": "任务有订阅者", + "dependencies": "任务有依赖项", + "recurring": "重复任务" + } + } +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/task-template-drawer.json b/worklenz-frontend/public/locales/zh/task-template-drawer.json new file mode 100644 index 00000000..53e99119 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/task-template-drawer.json @@ -0,0 +1,11 @@ +{ + "createTaskTemplate": "创建任务模板", + "editTaskTemplate": "编辑任务模板", + "cancelText": "取消", + "saveText": "保存", + "templateNameText": "模板名称", + "selectedTasks": "已选任务", + "removeTask": "移除", + "cancelButton": "取消", + "saveButton": "保存" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/tasks/task-table-bulk-actions.json b/worklenz-frontend/public/locales/zh/tasks/task-table-bulk-actions.json new file mode 100644 index 00000000..2a4c89d6 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/tasks/task-table-bulk-actions.json @@ -0,0 +1,24 @@ +{ + "taskSelected": "任务已选择", + "tasksSelected": "任务已选择", + "changeStatus": "更改状态/优先级/阶段", + "changeLabel": "更改标签", + "assignToMe": "分配给我", + "changeAssignees": "更改受托人", + "archive": "归档", + "unarchive": "取消归档", + "delete": "删除", + "moreOptions": "更多选项", + "deselectAll": "取消全选", + "status": "状态", + "priority": "优先级", + "phase": "阶段", + "member": "成员", + "createTaskTemplate": "创建任务模板", + "apply": "应用", + "createLabel": "+ 创建标签", + "hitEnterToCreate": "按回车键创建", + "pendingInvitation": "待处理邀请", + "noMatchingLabels": "没有匹配的标签", + "noLabels": "没有标签" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/template-drawer.json b/worklenz-frontend/public/locales/zh/template-drawer.json new file mode 100644 index 00000000..64fd242f --- /dev/null +++ b/worklenz-frontend/public/locales/zh/template-drawer.json @@ -0,0 +1,19 @@ +{ + "title": "编辑任务模板", + "cancelText": "取消", + "saveText": "保存", + "templateNameText": "模板名称", + "selectedTasks": "已选任务", + "removeTask": "移除", + "description": "描述", + "phase": "阶段", + "statuses": "状态", + "priorities": "优先级", + "labels": "标签", + "tasks": "任务", + "noTemplateSelected": "未选择模板", + "noDescription": "无描述", + "worklenzTemplates": "Worklenz模板", + "yourTemplatesLibrary": "您的模板库", + "searchTemplates": "搜索模板" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/templateDrawer.json b/worklenz-frontend/public/locales/zh/templateDrawer.json new file mode 100644 index 00000000..8405f8ab --- /dev/null +++ b/worklenz-frontend/public/locales/zh/templateDrawer.json @@ -0,0 +1,23 @@ +{ + "bugTracking": "错误跟踪", + "construction": "建筑与施工", + "designCreative": "设计与创意", + "education": "教育", + "finance": "金融", + "hrRecruiting": "人力资源与招聘", + "informationTechnology": "信息技术", + "legal": "法律", + "manufacturing": "制造业", + "marketing": "市场营销", + "nonprofit": "非营利", + "personalUse": "个人使用", + "salesCRM": "销售与客户关系管理", + "serviceConsulting": "服务与咨询", + "softwareDevelopment": "软件开发", + "description": "描述", + "phase": "阶段", + "statuses": "状态", + "priorities": "优先级", + "labels": "标签", + "tasks": "任务" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/time-report.json b/worklenz-frontend/public/locales/zh/time-report.json new file mode 100644 index 00000000..c376954a --- /dev/null +++ b/worklenz-frontend/public/locales/zh/time-report.json @@ -0,0 +1,33 @@ +{ + "includeArchivedProjects": "包含已归档项目", + "export": "导出", + "timeSheet": "时间表", + "searchByName": "按名称搜索", + "selectAll": "全选", + "teams": "团队", + "searchByProject": "按项目名称搜索", + "projects": "项目", + "searchByCategory": "按类别名称搜索", + "categories": "类别", + "billable": "可计费", + "nonBillable": "不可计费", + "total": "总计", + "projectsTimeSheet": "项目时间表", + "loggedTime": "已记录时间(小时)", + "exportToExcel": "导出到Excel", + "logged": "已记录", + "for": "为", + "membersTimeSheet": "成员时间表", + "member": "成员", + "estimatedVsActual": "预计用时 vs 实际用时", + "workingDays": "工作日", + "manDays": "人天", + "days": "天", + "estimatedDays": "预计天数", + "actualDays": "实际天数", + "noCategories": "未找到类别", + "noCategory": "无类别", + "noProjects": "未找到项目", + "noTeams": "未找到团队", + "noData": "未找到数据" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/zh/unauthorized.json b/worklenz-frontend/public/locales/zh/unauthorized.json new file mode 100644 index 00000000..985b1d08 --- /dev/null +++ b/worklenz-frontend/public/locales/zh/unauthorized.json @@ -0,0 +1,5 @@ +{ + "title": "未授权!", + "subtitle": "您无权访问此页面", + "button": "返回首页" +} \ No newline at end of file diff --git a/worklenz-frontend/public/manifest.json b/worklenz-frontend/public/manifest.json new file mode 100644 index 00000000..08214a45 --- /dev/null +++ b/worklenz-frontend/public/manifest.json @@ -0,0 +1,78 @@ +{ + "name": "Worklenz - Project Management", + "short_name": "Worklenz", + "description": "A comprehensive project management application for teams", + "start_url": "/", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#2b2b2b", + "orientation": "portrait-primary", + "categories": ["productivity", "business"], + "lang": "en", + "scope": "/", + "icons": [ + { + "src": "/favicon.ico", + "sizes": "16x16 32x32 48x48", + "type": "image/x-icon", + "purpose": "any maskable" + }, + { + "src": "/assets/icons/icon-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "/assets/icons/icon-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + } + ], + "shortcuts": [ + { + "name": "Dashboard", + "short_name": "Dashboard", + "description": "View your project dashboard", + "url": "/worklenz", + "icons": [ + { + "src": "/favicon.ico", + "sizes": "16x16 32x32 48x48" + } + ] + }, + { + "name": "Tasks", + "short_name": "Tasks", + "description": "Manage your tasks", + "url": "/worklenz/tasks", + "icons": [ + { + "src": "/favicon.ico", + "sizes": "16x16 32x32 48x48" + } + ] + } + ], + "screenshots": [ + { + "src": "/assets/images/screenshot-desktop.png", + "sizes": "1280x720", + "type": "image/png", + "form_factor": "wide" + }, + { + "src": "/assets/images/screenshot-mobile.png", + "sizes": "480x854", + "type": "image/png", + "form_factor": "narrow" + } + ], + "prefer_related_applications": false, + "related_applications": [], + "launch_handler": { + "client_mode": "focus-existing" + } +} \ No newline at end of file diff --git a/worklenz-frontend/public/sw.js b/worklenz-frontend/public/sw.js new file mode 100644 index 00000000..526541b0 --- /dev/null +++ b/worklenz-frontend/public/sw.js @@ -0,0 +1,345 @@ +// Worklenz Service Worker +// Provides offline functionality, caching, and performance improvements + +const CACHE_VERSION = 'v1.0.0'; +const CACHE_NAMES = { + STATIC: `worklenz-static-${CACHE_VERSION}`, + DYNAMIC: `worklenz-dynamic-${CACHE_VERSION}`, + API: `worklenz-api-${CACHE_VERSION}`, + IMAGES: `worklenz-images-${CACHE_VERSION}` +}; + +// Resources to cache immediately on install +const STATIC_CACHE_URLS = [ + '/', + '/index.html', + '/favicon.ico', + '/env-config.js', + '/manifest.json', + // Ant Design and other critical CSS/JS will be cached as they're requested +]; + +// API endpoints that can be cached +const CACHEABLE_API_PATTERNS = [ + /\/api\/project-categories/, + /\/api\/project-statuses/, + /\/api\/task-priorities/, + /\/api\/task-statuses/, + /\/api\/job-titles/, + /\/api\/teams\/\d+\/members/, + /\/api\/auth\/user/, // Cache user info for offline access +]; + +// Resources that should never be cached +const NEVER_CACHE_PATTERNS = [ + /\/api\/auth\/login/, + /\/api\/auth\/logout/, + /\/api\/notifications/, + /\/socket\.io/, + /\.hot-update\./, + /sw\.js$/, + /chrome-extension/, + /moz-extension/, +]; + +// Install event - Cache static resources +self.addEventListener('install', event => { + console.log('Service Worker: Installing...'); + + event.waitUntil( + (async () => { + try { + const cache = await caches.open(CACHE_NAMES.STATIC); + await cache.addAll(STATIC_CACHE_URLS); + console.log('Service Worker: Static resources cached'); + + // Skip waiting to activate immediately + await self.skipWaiting(); + } catch (error) { + console.error('Service Worker: Installation failed', error); + } + })() + ); +}); + +// Activate event - Clean up old caches +self.addEventListener('activate', event => { + console.log('Service Worker: Activating...'); + + event.waitUntil( + (async () => { + try { + // Clean up old caches + const cacheNames = await caches.keys(); + const oldCaches = cacheNames.filter(name => + Object.values(CACHE_NAMES).every(currentCache => currentCache !== name) + ); + + await Promise.all( + oldCaches.map(cacheName => caches.delete(cacheName)) + ); + + console.log('Service Worker: Old caches cleaned up'); + + // Take control of all pages + await self.clients.claim(); + } catch (error) { + console.error('Service Worker: Activation failed', error); + } + })() + ); +}); + +// Fetch event - Handle all network requests +self.addEventListener('fetch', event => { + const { request } = event; + const url = new URL(request.url); + + // Skip non-GET requests and browser extensions + if (request.method !== 'GET' || NEVER_CACHE_PATTERNS.some(pattern => pattern.test(url.href))) { + return; + } + + event.respondWith(handleFetchRequest(request)); +}); + +// Main fetch handler with different strategies based on resource type +async function handleFetchRequest(request) { + const url = new URL(request.url); + + try { + // Static assets - Cache First strategy + if (isStaticAsset(url)) { + return await cacheFirstStrategy(request, CACHE_NAMES.STATIC); + } + + // Images - Cache First with long-term storage + if (isImageRequest(url)) { + return await cacheFirstStrategy(request, CACHE_NAMES.IMAGES); + } + + // API requests - Network First with fallback + if (isAPIRequest(url)) { + return await networkFirstStrategy(request, CACHE_NAMES.API); + } + + // HTML pages - Stale While Revalidate + if (isHTMLRequest(request)) { + return await staleWhileRevalidateStrategy(request, CACHE_NAMES.DYNAMIC); + } + + // Everything else - Network First + return await networkFirstStrategy(request, CACHE_NAMES.DYNAMIC); + + } catch (error) { + console.error('Service Worker: Fetch failed', error); + return createOfflineResponse(request); + } +} + +// Cache First Strategy - Try cache first, fallback to network +async function cacheFirstStrategy(request, cacheName) { + const cache = await caches.open(cacheName); + const cachedResponse = await cache.match(request); + + if (cachedResponse) { + return cachedResponse; + } + + try { + const networkResponse = await fetch(request); + if (networkResponse.status === 200) { + // Clone before caching as response can only be used once + const responseClone = networkResponse.clone(); + await cache.put(request, responseClone); + } + return networkResponse; + } catch (error) { + console.error('Cache First: Network failed', error); + throw error; + } +} + +// Network First Strategy - Try network first, fallback to cache +async function networkFirstStrategy(request, cacheName) { + const cache = await caches.open(cacheName); + + try { + const networkResponse = await fetch(request); + + if (networkResponse.status === 200) { + // Cache successful responses + const responseClone = networkResponse.clone(); + await cache.put(request, responseClone); + } + + return networkResponse; + } catch (error) { + console.warn('Network First: Network failed, trying cache', error); + const cachedResponse = await cache.match(request); + + if (cachedResponse) { + return cachedResponse; + } + + throw error; + } +} + +// Stale While Revalidate Strategy - Return cached version while updating in background +async function staleWhileRevalidateStrategy(request, cacheName) { + const cache = await caches.open(cacheName); + const cachedResponse = await cache.match(request); + + // Fetch from network in background + const networkResponsePromise = fetch(request).then(async networkResponse => { + if (networkResponse.status === 200) { + const responseClone = networkResponse.clone(); + await cache.put(request, responseClone); + } + return networkResponse; + }).catch(error => { + console.warn('Stale While Revalidate: Background update failed', error); + }); + + // Return cached version immediately if available + if (cachedResponse) { + return cachedResponse; + } + + // If no cached version, wait for network + return await networkResponsePromise; +} + +// Helper functions to identify resource types +function isStaticAsset(url) { + return /\.(js|css|woff2?|ttf|eot)$/.test(url.pathname) || + url.pathname.includes('/assets/') || + url.pathname === '/' || + url.pathname === '/index.html' || + url.pathname === '/favicon.ico' || + url.pathname === '/env-config.js'; +} + +function isImageRequest(url) { + return /\.(png|jpg|jpeg|gif|svg|webp|ico)$/.test(url.pathname) || + url.pathname.includes('/file-types/'); +} + +function isAPIRequest(url) { + return url.pathname.startsWith('/api/') || + CACHEABLE_API_PATTERNS.some(pattern => pattern.test(url.pathname)); +} + +function isHTMLRequest(request) { + return request.headers.get('accept')?.includes('text/html'); +} + +// Create offline fallback response +function createOfflineResponse(request) { + if (isImageRequest(new URL(request.url))) { + // Return a simple SVG placeholder for images + const svg = ` + + + Offline + + `; + + return new Response(svg, { + headers: { 'Content-Type': 'image/svg+xml' } + }); + } + + if (isAPIRequest(new URL(request.url))) { + // Return empty array or error for API requests + return new Response(JSON.stringify({ + error: 'Offline', + message: 'This feature requires an internet connection' + }), { + status: 503, + headers: { 'Content-Type': 'application/json' } + }); + } + + // For HTML requests, try to return cached index.html + return caches.match('/') || new Response('Offline', { status: 503 }); +} + +// Handle background sync events (for future implementation) +self.addEventListener('sync', event => { + console.log('Service Worker: Background sync', event.tag); + + if (event.tag === 'background-sync') { + event.waitUntil(handleBackgroundSync()); + } +}); + +async function handleBackgroundSync() { + // This is where you would handle queued actions when coming back online + console.log('Service Worker: Handling background sync'); + + // Example: Send queued task updates, sync offline changes, etc. + // Implementation would depend on your app's specific needs +} + +// Handle push notification events (for future implementation) +self.addEventListener('push', event => { + if (!event.data) return; + + const options = { + body: event.data.text(), + icon: '/favicon.ico', + badge: '/favicon.ico', + vibrate: [200, 100, 200], + data: { + dateOfArrival: Date.now(), + primaryKey: 1 + } + }; + + event.waitUntil( + self.registration.showNotification('Worklenz', options) + ); +}); + +// Handle notification click events +self.addEventListener('notificationclick', event => { + event.notification.close(); + + event.waitUntil( + self.clients.openWindow('/') + ); +}); + +// Message handling for communication with main thread +self.addEventListener('message', event => { + const { type, payload } = event.data; + + switch (type) { + case 'SKIP_WAITING': + self.skipWaiting(); + break; + + case 'GET_VERSION': + event.ports[0].postMessage({ version: CACHE_VERSION }); + break; + + case 'CLEAR_CACHE': + clearAllCaches().then(() => { + event.ports[0].postMessage({ success: true }); + }); + break; + + default: + console.log('Service Worker: Unknown message type', type); + } +}); + +async function clearAllCaches() { + const cacheNames = await caches.keys(); + await Promise.all(cacheNames.map(name => caches.delete(name))); + console.log('Service Worker: All caches cleared'); +} + +console.log('Service Worker: Loaded successfully'); \ No newline at end of file diff --git a/worklenz-frontend/scripts/copy-tinymce.js b/worklenz-frontend/scripts/copy-tinymce.js index 8f801c46..00a27dd7 100644 --- a/worklenz-frontend/scripts/copy-tinymce.js +++ b/worklenz-frontend/scripts/copy-tinymce.js @@ -16,7 +16,7 @@ copyFolderRecursiveSync(sourceDir, path.join(__dirname, '..', 'public')); function copyFolderRecursiveSync(source, target) { const targetFolder = path.join(target, path.basename(source)); - + // Create target folder if it doesn't exist if (!fs.existsSync(targetFolder)) { fs.mkdirSync(targetFolder); @@ -25,7 +25,7 @@ function copyFolderRecursiveSync(source, target) { // Copy files if (fs.lstatSync(source).isDirectory()) { const files = fs.readdirSync(source); - files.forEach(function(file) { + files.forEach(function (file) { const curSource = path.join(source, file); if (fs.lstatSync(curSource).isDirectory()) { copyFolderRecursiveSync(curSource, targetFolder); @@ -36,4 +36,4 @@ function copyFolderRecursiveSync(source, target) { } } -console.log('TinyMCE files copied successfully!'); \ No newline at end of file +console.log('TinyMCE files copied successfully!'); diff --git a/worklenz-frontend/src/App.tsx b/worklenz-frontend/src/App.tsx index 13abc6f8..aa20e0ed 100644 --- a/worklenz-frontend/src/App.tsx +++ b/worklenz-frontend/src/App.tsx @@ -1,11 +1,10 @@ // Core dependencies -import React, { Suspense, useEffect } from 'react'; +import React, { Suspense, useEffect, memo, useMemo, useCallback } from 'react'; import { RouterProvider } from 'react-router-dom'; import i18next from 'i18next'; // Components import ThemeWrapper from './features/theme/ThemeWrapper'; -import PreferenceSelector from './components/PreferenceSelector'; // Routes import router from './app/routes'; @@ -13,36 +12,155 @@ import router from './app/routes'; // Hooks & Utils import { useAppSelector } from './hooks/useAppSelector'; import { initMixpanel } from './utils/mixpanelInit'; +import { initializeCsrfToken } from './api/api-client'; // Types & Constants import { Language } from './features/i18n/localesSlice'; import logger from './utils/errorLogger'; import { SuspenseFallback } from './components/suspense-fallback/suspense-fallback'; -const App: React.FC<{ children: React.ReactNode }> = ({ children }) => { +// Performance optimizations +import { CSSPerformanceMonitor, LayoutStabilizer, CriticalCSSManager } from './utils/css-optimizations'; + +// Service Worker +import { registerSW } from './utils/serviceWorkerRegistration'; + +/** + * Main App Component - Performance Optimized + * + * Performance optimizations applied: + * 1. React.memo() - Prevents unnecessary re-renders + * 2. useMemo() - Memoizes expensive computations + * 3. useCallback() - Memoizes event handlers + * 4. Lazy loading - All route components loaded on demand + * 5. Suspense boundaries - Better loading states + * 6. Optimized guard components with memoization + * 7. Deferred initialization - Non-critical operations moved to background + */ +const App: React.FC = memo(() => { const themeMode = useAppSelector(state => state.themeReducer.mode); const language = useAppSelector(state => state.localesReducer.lng); - initMixpanel(import.meta.env.VITE_MIXPANEL_TOKEN as string); + // Memoize mixpanel initialization to prevent re-initialization + const mixpanelToken = useMemo(() => import.meta.env.VITE_MIXPANEL_TOKEN as string, []); + // Defer mixpanel initialization to not block initial render + useEffect(() => { + const initializeMixpanel = () => { + try { + initMixpanel(mixpanelToken); + } catch (error) { + logger.error('Failed to initialize Mixpanel:', error); + } + }; + + // Use requestIdleCallback to defer mixpanel initialization + if ('requestIdleCallback' in window) { + requestIdleCallback(initializeMixpanel, { timeout: 2000 }); + } else { + setTimeout(initializeMixpanel, 1000); + } + }, [mixpanelToken]); + + // Memoize language change handler + const handleLanguageChange = useCallback((lng: string) => { + i18next.changeLanguage(lng, err => { + if (err) return logger.error('Error changing language', err); + }); + }, []); + + // Apply theme immediately to prevent flash useEffect(() => { document.documentElement.setAttribute('data-theme', themeMode); }, [themeMode]); + // Handle language changes useEffect(() => { - i18next.changeLanguage(language || Language.EN, err => { - if (err) return logger.error('Error changing language', err); + handleLanguageChange(language || Language.EN); + }, [language, handleLanguageChange]); + + // Initialize critical app functionality + useEffect(() => { + let isMounted = true; + + const initializeCriticalApp = async () => { + try { + // Initialize CSRF token immediately as it's needed for API calls + await initializeCsrfToken(); + + // Start CSS performance monitoring + CSSPerformanceMonitor.monitorLayoutShifts(); + CSSPerformanceMonitor.monitorRenderBlocking(); + + // Preload critical fonts to prevent layout shifts + LayoutStabilizer.preloadFonts([ + { family: 'Inter', weight: '400' }, + { family: 'Inter', weight: '500' }, + { family: 'Inter', weight: '600' }, + ]); + } catch (error) { + if (isMounted) { + logger.error('Failed to initialize critical app functionality:', error); + } + } + }; + + // Initialize critical functionality immediately + initializeCriticalApp(); + + return () => { + isMounted = false; + }; + }, []); + + // Register service worker + useEffect(() => { + registerSW({ + onSuccess: (registration) => { + console.log('Service Worker registered successfully', registration); + }, + onUpdate: (registration) => { + console.log('New content is available and will be used when all tabs for this page are closed.'); + // You could show a toast notification here for user to refresh + }, + onOfflineReady: () => { + console.log('This web app has been cached for offline use.'); + }, + onError: (error) => { + logger.error('Service Worker registration failed:', error); + } }); - }, [language]); + }, []); + + // Defer non-critical initialization + useEffect(() => { + const initializeNonCriticalApp = () => { + // Any non-critical initialization can go here + // For example: analytics, feature flags, etc. + }; + + // Defer non-critical initialization to not block initial render + if ('requestIdleCallback' in window) { + requestIdleCallback(initializeNonCriticalApp, { timeout: 3000 }); + } else { + setTimeout(initializeNonCriticalApp, 1500); + } + }, []); return ( }> - - + ); -}; +}); + +App.displayName = 'App'; export default App; diff --git a/worklenz-frontend/src/api/admin-center/admin-center.api.service.ts b/worklenz-frontend/src/api/admin-center/admin-center.api.service.ts index 857efb91..60269917 100644 --- a/worklenz-frontend/src/api/admin-center/admin-center.api.service.ts +++ b/worklenz-frontend/src/api/admin-center/admin-center.api.service.ts @@ -112,11 +112,11 @@ export const adminCenterApiService = { async updateTeam( team_id: string, - team_members: IOrganizationUser[] + body: { name: string; teamMembers: IOrganizationUser[] } ): Promise> { const response = await apiClient.put>( `${rootUrl}/organization/team/${team_id}`, - team_members + body ); return response.data; }, @@ -152,7 +152,6 @@ export const adminCenterApiService = { return response.data; }, - // Billing - Configuration async getCountries(): Promise> { const response = await apiClient.get>( @@ -168,7 +167,9 @@ export const adminCenterApiService = { return response.data; }, - async updateBillingConfiguration(body: IBillingConfiguration): Promise> { + async updateBillingConfiguration( + body: IBillingConfiguration + ): Promise> { const response = await apiClient.put>( `${rootUrl}/billing/configuration`, body @@ -178,42 +179,58 @@ export const adminCenterApiService = { // Billing - Current Bill async getCharges(): Promise> { - const response = await apiClient.get>(`${rootUrl}/billing/charges`); + const response = await apiClient.get>( + `${rootUrl}/billing/charges` + ); return response.data; }, async getTransactions(): Promise> { - const response = await apiClient.get>(`${rootUrl}/billing/transactions`); + const response = await apiClient.get>( + `${rootUrl}/billing/transactions` + ); return response.data; }, async getBillingAccountInfo(): Promise> { - const response = await apiClient.get>(`${rootUrl}/billing/info`); + const response = await apiClient.get>( + `${rootUrl}/billing/info` + ); return response.data; }, async getFreePlanSettings(): Promise> { - const response = await apiClient.get>(`${rootUrl}/billing/free-plan`); + const response = await apiClient.get>( + `${rootUrl}/billing/free-plan` + ); return response.data; }, async upgradePlan(plan: string): Promise> { - const response = await apiClient.get>(`${rootUrl}/billing/upgrade-plan${toQueryString({plan})}`); + const response = await apiClient.get>( + `${rootUrl}/billing/upgrade-plan${toQueryString({ plan })}` + ); return response.data; }, async changePlan(plan: string): Promise> { - const response = await apiClient.get>(`${rootUrl}/billing/change-plan${toQueryString({plan})}`); + const response = await apiClient.get>( + `${rootUrl}/billing/change-plan${toQueryString({ plan })}` + ); return response.data; }, async getPlans(): Promise> { - const response = await apiClient.get>(`${rootUrl}/billing/plans`); + const response = await apiClient.get>( + `${rootUrl}/billing/plans` + ); return response.data; }, async getStorageInfo(): Promise> { - const response = await apiClient.get>(`${rootUrl}/billing/storage`); + const response = await apiClient.get>( + `${rootUrl}/billing/storage` + ); return response.data; }, @@ -225,7 +242,7 @@ export const adminCenterApiService = { async resumeSubscription(): Promise> { const response = await apiClient.get>(`${rootUrl}/billing/resume-plan`); return response.data; - }, + }, async cancelSubscription(): Promise> { const response = await apiClient.get>(`${rootUrl}/billing/cancel-plan`); @@ -233,26 +250,34 @@ export const adminCenterApiService = { }, async addMoreSeats(totalSeats: number): Promise> { - const response = await apiClient.post>(`${rootUrl}/billing/purchase-more-seats`, {seatCount: totalSeats}); + const response = await apiClient.post>( + `${rootUrl}/billing/purchase-more-seats`, + { seatCount: totalSeats } + ); return response.data; }, async redeemCode(code: string): Promise> { - const response = await apiClient.post>(`${rootUrl}/billing/redeem`, { - code, - }); + const response = await apiClient.post>( + `${rootUrl}/billing/redeem`, + { + code, + } + ); return response.data; }, async getAccountStorage(): Promise> { - const response = await apiClient.get>(`${rootUrl}/billing/account-storage`); + const response = await apiClient.get>( + `${rootUrl}/billing/account-storage` + ); return response.data; }, async switchToFreePlan(teamId: string): Promise> { - const response = await apiClient.get>(`${rootUrl}/billing/switch-to-free-plan/${teamId}`); + const response = await apiClient.get>( + `${rootUrl}/billing/switch-to-free-plan/${teamId}` + ); return response.data; }, - }; - diff --git a/worklenz-frontend/src/api/admin-center/billing.api.service.ts b/worklenz-frontend/src/api/admin-center/billing.api.service.ts index 3d51cb3b..42c91b64 100644 --- a/worklenz-frontend/src/api/admin-center/billing.api.service.ts +++ b/worklenz-frontend/src/api/admin-center/billing.api.service.ts @@ -6,7 +6,10 @@ import { IUpgradeSubscriptionPlanResponse } from '@/types/admin-center/admin-cen const rootUrl = `${API_BASE_URL}/billing`; export const billingApiService = { - async upgradeToPaidPlan(plan: string, seatCount: number): Promise> { + async upgradeToPaidPlan( + plan: string, + seatCount: number + ): Promise> { const q = toQueryString({ plan, seatCount }); const response = await apiClient.get>( `${rootUrl}/upgrade-to-paid-plan${q}` @@ -14,7 +17,9 @@ export const billingApiService = { return response.data; }, - async purchaseMoreSeats(seatCount: number): Promise> { + async purchaseMoreSeats( + seatCount: number + ): Promise> { const response = await apiClient.post>( `${rootUrl}/purchase-more-seats`, { seatCount } @@ -27,9 +32,5 @@ export const billingApiService = { `${rootUrl}/contact-us${toQueryString({ contactNo })}` ); return response.data; - } - - - - + }, }; diff --git a/worklenz-frontend/src/api/api-client.ts b/worklenz-frontend/src/api/api-client.ts index ec43f7a5..79404c74 100644 --- a/worklenz-frontend/src/api/api-client.ts +++ b/worklenz-frontend/src/api/api-client.ts @@ -4,45 +4,79 @@ import alertService from '@/services/alerts/alertService'; import logger from '@/utils/errorLogger'; import config from '@/config/env'; -export const getCsrfToken = (): string | null => { - const match = document.cookie.split('; ').find(cookie => cookie.startsWith('XSRF-TOKEN=')); +// Store CSRF token in memory (since csrf-sync uses session-based tokens) +let csrfToken: string | null = null; - if (!match) { - return null; - } - return decodeURIComponent(match.split('=')[1]); +export const getCsrfToken = (): string | null => { + return csrfToken; }; -// Function to refresh CSRF token if needed +// Function to refresh CSRF token from server export const refreshCsrfToken = async (): Promise => { try { - // Make a GET request to the server to get a fresh CSRF token - await axios.get(`${config.apiUrl}/csrf-token`, { withCredentials: true }); - return getCsrfToken(); - } catch (error) { - console.error('Failed to refresh CSRF token:', error); + const tokenStart = performance.now(); + console.log('[CSRF] Starting CSRF token refresh...'); + + // Make a GET request to the server to get a fresh CSRF token with timeout + const response = await axios.get(`${config.apiUrl}/csrf-token`, { + withCredentials: true, + timeout: 10000, // 10 second timeout for CSRF token requests + }); + + const tokenEnd = performance.now(); + console.log(`[CSRF] CSRF token refresh completed in ${(tokenEnd - tokenStart).toFixed(2)}ms`); + + if (response.data && response.data.token) { + csrfToken = response.data.token; + console.log('[CSRF] CSRF token successfully refreshed'); + return csrfToken; + } else { + console.warn('[CSRF] No token in response:', response.data); + } return null; + } catch (error) { + console.error('[CSRF] Failed to refresh CSRF token:', error); + return null; + } +}; + +// Initialize CSRF token on app load +export const initializeCsrfToken = async (): Promise => { + if (!csrfToken) { + await refreshCsrfToken(); } }; const apiClient = axios.create({ baseURL: config.apiUrl, withCredentials: true, + timeout: 30000, // 30 second timeout to prevent hanging requests headers: { 'Content-Type': 'application/json', Accept: 'application/json', }, }); -// Request interceptor +// Request interceptor with performance optimization apiClient.interceptors.request.use( - config => { - const token = getCsrfToken(); - if (token) { - config.headers['X-CSRF-Token'] = token; - } else { - console.warn('No CSRF token found'); + async config => { + const requestStart = performance.now(); + + // Ensure we have a CSRF token before making requests + if (!csrfToken) { + const tokenStart = performance.now(); + await refreshCsrfToken(); + const tokenEnd = performance.now(); } + + if (csrfToken) { + config.headers['X-CSRF-Token'] = csrfToken; + } else { + console.warn('No CSRF token available after refresh attempt'); + } + + const requestEnd = performance.now(); + return config; }, error => Promise.reject(error) @@ -80,21 +114,24 @@ apiClient.interceptors.response.use( const errorResponse = error.response; // Handle CSRF token errors - if (errorResponse?.status === 403 && - (typeof errorResponse.data === 'object' && - errorResponse.data !== null && - 'message' in errorResponse.data && - errorResponse.data.message === 'Invalid CSRF token' || - (error as any).code === 'EBADCSRFTOKEN')) { + if ( + errorResponse?.status === 403 && + ((typeof errorResponse.data === 'object' && + errorResponse.data !== null && + 'message' in errorResponse.data && + (errorResponse.data.message === 'invalid csrf token' || + errorResponse.data.message === 'Invalid CSRF token')) || + (error as any).code === 'EBADCSRFTOKEN') + ) { alertService.error('Security Error', 'Invalid security token. Refreshing your session...'); - + // Try to refresh the CSRF token and retry the request const newToken = await refreshCsrfToken(); if (newToken && error.config) { // Update the token in the failed request error.config.headers['X-CSRF-Token'] = newToken; // Retry the original request with the new token - return axios(error.config); + return apiClient(error.config); } else { // If token refresh failed, redirect to login window.location.href = '/auth/login'; diff --git a/worklenz-frontend/src/api/attachments/attachments.api.service.ts b/worklenz-frontend/src/api/attachments/attachments.api.service.ts index e6a3c7c8..5aaf6fc1 100644 --- a/worklenz-frontend/src/api/attachments/attachments.api.service.ts +++ b/worklenz-frontend/src/api/attachments/attachments.api.service.ts @@ -1,25 +1,37 @@ -import { IServerResponse } from "@/types/common.types"; -import { IProjectAttachmentsViewModel } from "@/types/tasks/task-attachment-view-model"; -import apiClient from "../api-client"; -import { API_BASE_URL } from "@/shared/constants"; -import { toQueryString } from "@/utils/toQueryString"; +import { IServerResponse } from '@/types/common.types'; +import { IProjectAttachmentsViewModel } from '@/types/tasks/task-attachment-view-model'; +import apiClient from '../api-client'; +import { API_BASE_URL } from '@/shared/constants'; +import { toQueryString } from '@/utils/toQueryString'; const rootUrl = `${API_BASE_URL}/attachments`; export const attachmentsApiService = { - getTaskAttachments: async (taskId: string): Promise> => { - const response = await apiClient.get>(`${rootUrl}/tasks/${taskId}`); + getTaskAttachments: async ( + taskId: string + ): Promise> => { + const response = await apiClient.get>( + `${rootUrl}/tasks/${taskId}` + ); return response.data; }, - getProjectAttachments: async (projectId: string, index: number, size: number): Promise> => { + getProjectAttachments: async ( + projectId: string, + index: number, + size: number + ): Promise> => { const q = toQueryString({ index, size }); - const response = await apiClient.get>(`${rootUrl}/project/${projectId}${q}`); + const response = await apiClient.get>( + `${rootUrl}/project/${projectId}${q}` + ); return response.data; }, downloadAttachment: async (id: string, filename: string): Promise> => { - const response = await apiClient.get>(`${rootUrl}/download?id=${id}&file=${filename}`); + const response = await apiClient.get>( + `${rootUrl}/download?id=${id}&file=${filename}` + ); return response.data; }, @@ -27,7 +39,4 @@ export const attachmentsApiService = { const response = await apiClient.delete>(`${rootUrl}/tasks/${id}`); return response.data; }, - }; - - diff --git a/worklenz-frontend/src/api/home-page/home-page.api.service.ts b/worklenz-frontend/src/api/home-page/home-page.api.service.ts index 74f5615a..8e0c5881 100644 --- a/worklenz-frontend/src/api/home-page/home-page.api.service.ts +++ b/worklenz-frontend/src/api/home-page/home-page.api.service.ts @@ -5,7 +5,7 @@ import { toQueryString } from '@/utils/toQueryString'; import { IHomeTasksModel, IHomeTasksConfig } from '@/types/home/home-page.types'; import { IMyTask } from '@/types/home/my-tasks.types'; import { IProject } from '@/types/project/project.types'; -import { getCsrfToken } from '../api-client'; +import { getCsrfToken, refreshCsrfToken } from '../api-client'; import config from '@/config/env'; const rootUrl = '/home'; @@ -14,9 +14,18 @@ const api = createApi({ reducerPath: 'homePageApi', baseQuery: fetchBaseQuery({ baseUrl: `${config.apiUrl}${API_BASE_URL}`, - prepareHeaders: headers => { - headers.set('X-CSRF-Token', getCsrfToken() || ''); + prepareHeaders: async headers => { + // Get CSRF token, refresh if needed + let token = getCsrfToken(); + if (!token) { + token = await refreshCsrfToken(); + } + + if (token) { + headers.set('X-CSRF-Token', token); + } headers.set('Content-Type', 'application/json'); + return headers; }, credentials: 'include', }), diff --git a/worklenz-frontend/src/api/project-members/project-members.api.service.ts b/worklenz-frontend/src/api/project-members/project-members.api.service.ts index 3eeac3da..926d4550 100644 --- a/worklenz-frontend/src/api/project-members/project-members.api.service.ts +++ b/worklenz-frontend/src/api/project-members/project-members.api.service.ts @@ -10,7 +10,7 @@ export const projectMembersApiService = { createProjectMember: async ( body: IProjectMemberViewModel ): Promise> => { - const q = toQueryString({current_project_id: body.project_id}); + const q = toQueryString({ current_project_id: body.project_id }); const response = await apiClient.post>( `${rootUrl}${q}`, diff --git a/worklenz-frontend/src/api/project-templates/project-templates.api.service.ts b/worklenz-frontend/src/api/project-templates/project-templates.api.service.ts index 9dcc8ba8..542d27e6 100644 --- a/worklenz-frontend/src/api/project-templates/project-templates.api.service.ts +++ b/worklenz-frontend/src/api/project-templates/project-templates.api.service.ts @@ -34,7 +34,9 @@ export const projectTemplatesApiService = { return response.data; }, - createCustomTemplate: async (body: { template_id: string }): Promise> => { + createCustomTemplate: async (body: { + template_id: string; + }): Promise> => { const response = await apiClient.post(`${rootUrl}/custom-template`, body); return response.data; }, @@ -44,15 +46,17 @@ export const projectTemplatesApiService = { return response.data; }, - createFromWorklenzTemplate: async (body: { template_id: string }): Promise> => { + createFromWorklenzTemplate: async (body: { + template_id: string; + }): Promise> => { const response = await apiClient.post(`${rootUrl}/import-template`, body); return response.data; - }, - - createFromCustomTemplate: async (body: { template_id: string }): Promise> => { - const response = await apiClient.post(`${rootUrl}/import-custom-template`, body); - return response.data; }, + createFromCustomTemplate: async (body: { + template_id: string; + }): Promise> => { + const response = await apiClient.post(`${rootUrl}/import-custom-template`, body); + return response.data; + }, }; - diff --git a/worklenz-frontend/src/api/projects/projects.api.service.ts b/worklenz-frontend/src/api/projects/projects.api.service.ts index a817e76e..b18aa038 100644 --- a/worklenz-frontend/src/api/projects/projects.api.service.ts +++ b/worklenz-frontend/src/api/projects/projects.api.service.ts @@ -7,9 +7,15 @@ import { IProjectViewModel } from '@/types/project/projectViewModel.types'; import { ITeamMemberOverviewGetResponse } from '@/types/project/project-insights.types'; import { IProjectMembersViewModel } from '@/types/projectMember.types'; import { IProjectManager } from '@/types/project/projectManager.types'; +import { IGroupedProjectsViewModel } from '@/types/project/groupedProjectsViewModel.types'; const rootUrl = `${API_BASE_URL}/projects`; +interface UpdateProjectPayload { + id: string; + [key: string]: any; +} + export const projectsApiService = { getProjects: async ( index: number, @@ -27,6 +33,23 @@ export const projectsApiService = { return response.data; }, + getGroupedProjects: async ( + index: number, + size: number, + field: string | null, + order: string | null, + search: string | null, + groupBy: string, + filter: number | null = null, + statuses: string | null = null, + categories: string | null = null + ): Promise> => { + const s = encodeURIComponent(search || ''); + const url = `${rootUrl}/grouped${toQueryString({ index, size, field, order, search: s, groupBy, filter, statuses, categories })}`; + const response = await apiClient.get>(`${url}`); + return response.data; + }, + getProject: async (id: string): Promise> => { const url = `${rootUrl}/${id}`; const response = await apiClient.get>(`${url}`); @@ -79,12 +102,12 @@ export const projectsApiService = { }, updateProject: async ( - id: string, - project: IProjectViewModel + payload: UpdateProjectPayload ): Promise> => { + const { id, ...data } = payload; const q = toQueryString({ current_project_id: id }); - const url = `${rootUrl}/${id}${q}`; - const response = await apiClient.put>(`${url}`, project); + const url = `${API_BASE_URL}/projects/${id}${q}`; + const response = await apiClient.patch>(url, data); return response.data; }, @@ -106,7 +129,10 @@ export const projectsApiService = { return response.data; }, - updateDefaultTab: async (body: { project_id: string; default_view: string }): Promise> => { + updateDefaultTab: async (body: { + project_id: string; + default_view: string; + }): Promise> => { const url = `${rootUrl}/update-pinned-view`; const response = await apiClient.put>(`${url}`, body); return response.data; @@ -118,4 +144,3 @@ export const projectsApiService = { return response.data; }, }; - diff --git a/worklenz-frontend/src/api/projects/projects.v1.api.service.ts b/worklenz-frontend/src/api/projects/projects.v1.api.service.ts index 1fe279d5..f4ec6ea5 100644 --- a/worklenz-frontend/src/api/projects/projects.v1.api.service.ts +++ b/worklenz-frontend/src/api/projects/projects.v1.api.service.ts @@ -5,7 +5,7 @@ import { IProjectCategory } from '@/types/project/projectCategory.types'; import { IProjectsViewModel } from '@/types/project/projectsViewModel.types'; import { IServerResponse } from '@/types/common.types'; import { IProjectMembersViewModel } from '@/types/projectMember.types'; -import { getCsrfToken } from '../api-client'; +import { getCsrfToken, refreshCsrfToken } from '../api-client'; import config from '@/config/env'; const rootUrl = '/projects'; @@ -14,9 +14,18 @@ export const projectsApi = createApi({ reducerPath: 'projectsApi', baseQuery: fetchBaseQuery({ baseUrl: `${config.apiUrl}${API_BASE_URL}`, - prepareHeaders: headers => { - headers.set('X-CSRF-Token', getCsrfToken() || ''); + prepareHeaders: async headers => { + // Get CSRF token, refresh if needed + let token = getCsrfToken(); + if (!token) { + token = await refreshCsrfToken(); + } + + if (token) { + headers.set('X-CSRF-Token', token); + } headers.set('Content-Type', 'application/json'); + return headers; }, credentials: 'include', }), diff --git a/worklenz-frontend/src/api/reporting/reporting-members.api.service.ts b/worklenz-frontend/src/api/reporting/reporting-members.api.service.ts index d8658465..d4f26f28 100644 --- a/worklenz-frontend/src/api/reporting/reporting-members.api.service.ts +++ b/worklenz-frontend/src/api/reporting/reporting-members.api.service.ts @@ -1,5 +1,10 @@ import { IServerResponse } from '@/types/common.types'; -import { IGetProjectsRequestBody, IRPTMembersViewModel, IRPTOverviewProjectMember, IRPTProjectsViewModel } from '@/types/reporting/reporting.types'; +import { + IGetProjectsRequestBody, + IRPTMembersViewModel, + IRPTOverviewProjectMember, + IRPTProjectsViewModel, +} from '@/types/reporting/reporting.types'; import apiClient from '../api-client'; import { API_BASE_URL } from '@/shared/constants'; import { toQueryString } from '@/utils/toQueryString'; @@ -7,9 +12,7 @@ import { toQueryString } from '@/utils/toQueryString'; const rootUrl = `${API_BASE_URL}/reporting/members`; export const reportingMembersApiService = { - getMembers: async ( - body: any - ): Promise> => { + getMembers: async (body: any): Promise> => { const q = toQueryString(body); const url = `${rootUrl}${q}`; const response = await apiClient.get>(url); diff --git a/worklenz-frontend/src/api/reporting/reporting-projects.api.service.ts b/worklenz-frontend/src/api/reporting/reporting-projects.api.service.ts index 8b5e51ec..2d57e153 100644 --- a/worklenz-frontend/src/api/reporting/reporting-projects.api.service.ts +++ b/worklenz-frontend/src/api/reporting/reporting-projects.api.service.ts @@ -1,5 +1,10 @@ import { IServerResponse } from '@/types/common.types'; -import { IGetProjectsRequestBody, IRPTOverviewProjectInfo, IRPTOverviewProjectMember, IRPTProjectsViewModel } from '@/types/reporting/reporting.types'; +import { + IGetProjectsRequestBody, + IRPTOverviewProjectInfo, + IRPTOverviewProjectMember, + IRPTProjectsViewModel, +} from '@/types/reporting/reporting.types'; import apiClient from '../api-client'; import { API_BASE_URL } from '@/shared/constants'; import { toQueryString } from '@/utils/toQueryString'; @@ -33,8 +38,11 @@ export const reportingProjectsApiService = { return response.data; }, - getTasks: async (projectId: string, groupBy: string): Promise> => { - const q = toQueryString({group: groupBy}) + getTasks: async ( + projectId: string, + groupBy: string + ): Promise> => { + const q = toQueryString({ group: groupBy }); const url = `${API_BASE_URL}/reporting/overview/project/tasks/${projectId}${q}`; const response = await apiClient.get>(url); diff --git a/worklenz-frontend/src/api/reporting/reporting.timesheet.api.service.ts b/worklenz-frontend/src/api/reporting/reporting.timesheet.api.service.ts index 1529d46b..22342527 100644 --- a/worklenz-frontend/src/api/reporting/reporting.timesheet.api.service.ts +++ b/worklenz-frontend/src/api/reporting/reporting.timesheet.api.service.ts @@ -3,12 +3,20 @@ import { toQueryString } from '@/utils/toQueryString'; import apiClient from '../api-client'; import { IServerResponse } from '@/types/common.types'; import { IAllocationViewModel } from '@/types/reporting/reporting-allocation.types'; -import { IProjectLogsBreakdown, IRPTTimeMember, IRPTTimeProject, ITimeLogBreakdownReq } from '@/types/reporting/reporting.types'; +import { + IProjectLogsBreakdown, + IRPTTimeMember, + IRPTTimeProject, + ITimeLogBreakdownReq, +} from '@/types/reporting/reporting.types'; const rootUrl = `${API_BASE_URL}/reporting`; export const reportingTimesheetApiService = { - getTimeSheetData: async (body = {}, archived = false): Promise> => { + getTimeSheetData: async ( + body = {}, + archived = false + ): Promise> => { const q = toQueryString({ archived }); const response = await apiClient.post(`${rootUrl}/allocation/${q}`, body); return response.data; @@ -19,24 +27,35 @@ export const reportingTimesheetApiService = { return response.data; }, - getProjectTimeSheets: async (body = {}, archived = false): Promise> => { + getProjectTimeSheets: async ( + body = {}, + archived = false + ): Promise> => { const q = toQueryString({ archived }); const response = await apiClient.post(`${rootUrl}/time-reports/projects/${q}`, body); return response.data; }, - getMemberTimeSheets: async (body = {}, archived = false): Promise> => { + getMemberTimeSheets: async ( + body = {}, + archived = false + ): Promise> => { const q = toQueryString({ archived }); const response = await apiClient.post(`${rootUrl}/time-reports/members/${q}`, body); return response.data; }, - getProjectTimeLogs: async (body: ITimeLogBreakdownReq): Promise> => { + getProjectTimeLogs: async ( + body: ITimeLogBreakdownReq + ): Promise> => { const response = await apiClient.post(`${rootUrl}/project-timelogs`, body); return response.data; }, - getProjectEstimatedVsActual: async (body = {}, archived = false): Promise> => { + getProjectEstimatedVsActual: async ( + body = {}, + archived = false + ): Promise> => { const q = toQueryString({ archived }); const response = await apiClient.post(`${rootUrl}/time-reports/estimated-vs-actual${q}`, body); return response.data; diff --git a/worklenz-frontend/src/api/schedule/schedule.api.service.ts b/worklenz-frontend/src/api/schedule/schedule.api.service.ts index 1d54ab1a..47f16b56 100644 --- a/worklenz-frontend/src/api/schedule/schedule.api.service.ts +++ b/worklenz-frontend/src/api/schedule/schedule.api.service.ts @@ -2,7 +2,13 @@ import { API_BASE_URL } from '@/shared/constants'; import apiClient from '../api-client'; import { IServerResponse } from '@/types/common.types'; import { ITeamMemberViewModel } from '@/types/teamMembers/teamMembersGetResponse.types'; -import { DateList, Member, Project, ScheduleData, Settings } from '@/types/schedule/schedule-v2.types'; +import { + DateList, + Member, + Project, + ScheduleData, + Settings, +} from '@/types/schedule/schedule-v2.types'; const rootUrl = `${API_BASE_URL}/schedule-gannt-v2`; @@ -45,16 +51,18 @@ export const scheduleAPIService = { }, fetchMemberProjects: async ({ id }: { id: string }): Promise> => { - const response = await apiClient.get>(`${rootUrl}/members/projects/${id}`); + const response = await apiClient.get>( + `${rootUrl}/members/projects/${id}` + ); return response.data; }, submitScheduleData: async ({ - schedule + schedule, }: { - schedule: ScheduleData + schedule: ScheduleData; }): Promise> => { const response = await apiClient.post>(`${rootUrl}/schedule`, schedule); return response.data; - } + }, }; diff --git a/worklenz-frontend/src/api/settings/profile/profile-settings.api.service.ts b/worklenz-frontend/src/api/settings/profile/profile-settings.api.service.ts index 9e269c59..10d61a68 100644 --- a/worklenz-frontend/src/api/settings/profile/profile-settings.api.service.ts +++ b/worklenz-frontend/src/api/settings/profile/profile-settings.api.service.ts @@ -52,8 +52,11 @@ export const profileSettingsApiService = { return response.data; }, - updateTeamName: async (id: string, body: ITeam): Promise> => { - const response = await apiClient.put>(`${rootUrl}/team-name/${id}`, body); + updateTeamName: async (id: string, body: ITeam): Promise> => { + const response = await apiClient.put>( + `${rootUrl}/team-name/${id}`, + body + ); return response.data; }, diff --git a/worklenz-frontend/src/api/task-templates/task-templates.api.service.ts b/worklenz-frontend/src/api/task-templates/task-templates.api.service.ts index 2be6b5ff..98f6904e 100644 --- a/worklenz-frontend/src/api/task-templates/task-templates.api.service.ts +++ b/worklenz-frontend/src/api/task-templates/task-templates.api.service.ts @@ -21,12 +21,18 @@ export const taskTemplatesApiService = { const response = await apiClient.get>(`${url}`); return response.data; }, - createTemplate: async (body: { name: string, tasks: IProjectTask[] }): Promise> => { + createTemplate: async (body: { + name: string; + tasks: IProjectTask[]; + }): Promise> => { const url = `${rootUrl}`; const response = await apiClient.post>(`${url}`, body); return response.data; }, - updateTemplate: async (id: string, body: { name: string, tasks: IProjectTask[] }): Promise> => { + updateTemplate: async ( + id: string, + body: { name: string; tasks: IProjectTask[] } + ): Promise> => { const url = `${rootUrl}/${id}`; const response = await apiClient.put>(`${url}`, body); return response.data; diff --git a/worklenz-frontend/src/api/taskAttributes/phases/phases.api.service.ts b/worklenz-frontend/src/api/taskAttributes/phases/phases.api.service.ts index c12831ad..fc47544a 100644 --- a/worklenz-frontend/src/api/taskAttributes/phases/phases.api.service.ts +++ b/worklenz-frontend/src/api/taskAttributes/phases/phases.api.service.ts @@ -1,12 +1,12 @@ -import apiClient from '@/api/api-client'; -import { API_BASE_URL } from '@/shared/constants'; import { IServerResponse } from '@/types/common.types'; +import apiClient from '@api/api-client'; +import { API_BASE_URL } from '@/shared/constants'; import { ITaskPhase } from '@/types/tasks/taskPhase.types'; import { toQueryString } from '@/utils/toQueryString'; const rootUrl = `${API_BASE_URL}/task-phases`; -interface UpdateSortOrderBody { +export interface UpdateSortOrderBody { from_index: number; to_index: number; phases: ITaskPhase[]; @@ -14,9 +14,10 @@ interface UpdateSortOrderBody { } export const phasesApiService = { - addPhaseOption: async (projectId: string) => { + addPhaseOption: async (projectId: string, name?: string) => { const q = toQueryString({ id: projectId, current_project_id: projectId }); - const response = await apiClient.post>(`${rootUrl}${q}`); + const body = name ? { name } : {}; + const response = await apiClient.post>(`${rootUrl}${q}`, body); return response.data; }, @@ -43,7 +44,7 @@ export const phasesApiService = { return response.data; }, - updateNameOfPhase: async (phaseId: string, body: ITaskPhase, projectId: string,) => { + updateNameOfPhase: async (phaseId: string, body: ITaskPhase, projectId: string) => { const q = toQueryString({ id: projectId, current_project_id: projectId }); const response = await apiClient.put>( `${rootUrl}/${phaseId}${q}`, diff --git a/worklenz-frontend/src/api/taskAttributes/status/status.api.service.ts b/worklenz-frontend/src/api/taskAttributes/status/status.api.service.ts index 282505e2..376fed69 100644 --- a/worklenz-frontend/src/api/taskAttributes/status/status.api.service.ts +++ b/worklenz-frontend/src/api/taskAttributes/status/status.api.service.ts @@ -60,6 +60,20 @@ export const statusApiService = { return response.data; }, + updateStatusCategory: async ( + statusId: string, + categoryId: string, + currentProjectId: string + ): Promise> => { + const q = toQueryString({ current_project_id: currentProjectId }); + + const response = await apiClient.put>( + `${rootUrl}/category/${statusId}${q}`, + { category_id: categoryId } + ); + return response.data; + }, + updateStatusOrder: async ( body: ITaskStatusCreateRequest, currentProjectId: string @@ -69,8 +83,16 @@ export const statusApiService = { return response.data; }, - deleteStatus: async (statusId: string, projectId: string, replacingStatusId: string): Promise> => { - const q = toQueryString({ project: projectId, current_project_id: projectId, replace: replacingStatusId || null }); + deleteStatus: async ( + statusId: string, + projectId: string, + replacingStatusId: string + ): Promise> => { + const q = toQueryString({ + project: projectId, + current_project_id: projectId, + replace: replacingStatusId || null, + }); const response = await apiClient.delete>(`${rootUrl}/${statusId}${q}`); return response.data; }, diff --git a/worklenz-frontend/src/api/tasks/subtasks.api.service.ts b/worklenz-frontend/src/api/tasks/subtasks.api.service.ts index db028a59..f9c59685 100644 --- a/worklenz-frontend/src/api/tasks/subtasks.api.service.ts +++ b/worklenz-frontend/src/api/tasks/subtasks.api.service.ts @@ -1,7 +1,7 @@ -import { API_BASE_URL } from "@/shared/constants"; -import apiClient from "../api-client"; -import { IServerResponse } from "@/types/common.types"; -import { ISubTask } from "@/types/tasks/subTask.types"; +import { API_BASE_URL } from '@/shared/constants'; +import apiClient from '../api-client'; +import { IServerResponse } from '@/types/common.types'; +import { ISubTask } from '@/types/tasks/subTask.types'; const root = `${API_BASE_URL}/sub-tasks`; @@ -10,7 +10,4 @@ export const subTasksApiService = { const response = await apiClient.get(`${root}/${parentTaskId}`); return response.data; }, - - }; - diff --git a/worklenz-frontend/src/api/tasks/task-attachments.api.service.ts b/worklenz-frontend/src/api/tasks/task-attachments.api.service.ts index 774771bf..03167bc9 100644 --- a/worklenz-frontend/src/api/tasks/task-attachments.api.service.ts +++ b/worklenz-frontend/src/api/tasks/task-attachments.api.service.ts @@ -1,5 +1,9 @@ import { IServerResponse } from '@/types/common.types'; -import { IProjectAttachmentsViewModel, ITaskAttachment, ITaskAttachmentViewModel } from '@/types/tasks/task-attachment-view-model'; +import { + IProjectAttachmentsViewModel, + ITaskAttachment, + ITaskAttachmentViewModel, +} from '@/types/tasks/task-attachment-view-model'; import apiClient from '../api-client'; import { API_BASE_URL } from '@/shared/constants'; import { IAvatarAttachment } from '@/types/avatarAttachment.types'; @@ -8,7 +12,6 @@ import { toQueryString } from '@/utils/toQueryString'; const rootUrl = `${API_BASE_URL}/attachments`; const taskAttachmentsApiService = { - createTaskAttachment: async ( body: ITaskAttachment ): Promise> => { @@ -16,18 +19,26 @@ const taskAttachmentsApiService = { return response.data; }, - createAvatarAttachment: async (body: IAvatarAttachment): Promise> => { + createAvatarAttachment: async ( + body: IAvatarAttachment + ): Promise> => { const response = await apiClient.post(`${rootUrl}/avatar`, body); return response.data; }, - getTaskAttachments: async (taskId: string): Promise> => { + getTaskAttachments: async ( + taskId: string + ): Promise> => { const response = await apiClient.get(`${rootUrl}/tasks/${taskId}`); return response.data; }, - getProjectAttachments: async (projectId: string, index: number, size: number ): Promise> => { - const q = toQueryString({ index, size }); + getProjectAttachments: async ( + projectId: string, + index: number, + size: number + ): Promise> => { + const q = toQueryString({ index, size }); const response = await apiClient.get(`${rootUrl}/project/${projectId}${q}`); return response.data; }, diff --git a/worklenz-frontend/src/api/tasks/task-comments.api.service.ts b/worklenz-frontend/src/api/tasks/task-comments.api.service.ts index 711fc50e..478588c7 100644 --- a/worklenz-frontend/src/api/tasks/task-comments.api.service.ts +++ b/worklenz-frontend/src/api/tasks/task-comments.api.service.ts @@ -2,10 +2,16 @@ import apiClient from '@api/api-client'; import { API_BASE_URL } from '@/shared/constants'; import { IServerResponse } from '@/types/common.types'; import { toQueryString } from '@/utils/toQueryString'; -import { ITaskComment, ITaskCommentsCreateRequest, ITaskCommentViewModel } from '@/types/tasks/task-comments.types'; +import { + ITaskComment, + ITaskCommentsCreateRequest, + ITaskCommentViewModel, +} from '@/types/tasks/task-comments.types'; const taskCommentsApiService = { - create: async (data: ITaskCommentsCreateRequest): Promise> => { + create: async ( + data: ITaskCommentsCreateRequest + ): Promise> => { const response = await apiClient.post(`${API_BASE_URL}/task-comments`, data); return response.data; }, @@ -21,12 +27,16 @@ const taskCommentsApiService = { }, deleteAttachment: async (id: string, taskId: string): Promise> => { - const response = await apiClient.delete(`${API_BASE_URL}/task-comments/attachment/${id}/${taskId}`); + const response = await apiClient.delete( + `${API_BASE_URL}/task-comments/attachment/${id}/${taskId}` + ); return response.data; }, download: async (id: string, filename: string): Promise> => { - const response = await apiClient.get(`${API_BASE_URL}/task-comments/download?id=${id}&file=${filename}`); + const response = await apiClient.get( + `${API_BASE_URL}/task-comments/download?id=${id}&file=${filename}` + ); return response.data; }, @@ -35,8 +45,13 @@ const taskCommentsApiService = { return response.data; }, - updateReaction: async (id: string, body: {reaction_type: string, task_id: string}): Promise> => { - const response = await apiClient.put(`${API_BASE_URL}/task-comments/reaction/${id}${toQueryString(body)}`); + updateReaction: async ( + id: string, + body: { reaction_type: string; task_id: string } + ): Promise> => { + const response = await apiClient.put( + `${API_BASE_URL}/task-comments/reaction/${id}${toQueryString(body)}` + ); return response.data; }, }; diff --git a/worklenz-frontend/src/api/tasks/task-dependencies.api.service.ts b/worklenz-frontend/src/api/tasks/task-dependencies.api.service.ts index 89faee1e..59def2cf 100644 --- a/worklenz-frontend/src/api/tasks/task-dependencies.api.service.ts +++ b/worklenz-frontend/src/api/tasks/task-dependencies.api.service.ts @@ -1,7 +1,7 @@ -import { API_BASE_URL } from "@/shared/constants"; -import apiClient from "../api-client"; -import { ITaskDependency } from "@/types/tasks/task-dependency.types"; -import { IServerResponse } from "@/types/common.types"; +import { API_BASE_URL } from '@/shared/constants'; +import apiClient from '../api-client'; +import { ITaskDependency } from '@/types/tasks/task-dependency.types'; +import { IServerResponse } from '@/types/common.types'; const rootUrl = `${API_BASE_URL}/task-dependencies`; @@ -10,7 +10,9 @@ export const taskDependenciesApiService = { const response = await apiClient.get(`${rootUrl}/${taskId}`); return response.data; }, - createTaskDependency: async (body: ITaskDependency): Promise> => { + createTaskDependency: async ( + body: ITaskDependency + ): Promise> => { const response = await apiClient.post(`${rootUrl}`, body); return response.data; }, @@ -18,4 +20,4 @@ export const taskDependenciesApiService = { const response = await apiClient.delete(`${rootUrl}/${dependencyId}`); return response.data; }, -}; \ No newline at end of file +}; diff --git a/worklenz-frontend/src/api/tasks/task-recurring.api.service.ts b/worklenz-frontend/src/api/tasks/task-recurring.api.service.ts new file mode 100644 index 00000000..fb19d0c4 --- /dev/null +++ b/worklenz-frontend/src/api/tasks/task-recurring.api.service.ts @@ -0,0 +1,21 @@ +import { API_BASE_URL } from '@/shared/constants'; +import { IServerResponse } from '@/types/common.types'; +import { ITaskRecurringSchedule } from '@/types/tasks/task-recurring-schedule'; +import apiClient from '../api-client'; + +const rootUrl = `${API_BASE_URL}/task-recurring`; + +export const taskRecurringApiService = { + getTaskRecurringData: async ( + schedule_id: string + ): Promise> => { + const response = await apiClient.get(`${rootUrl}/${schedule_id}`); + return response.data; + }, + updateTaskRecurringData: async ( + schedule_id: string, + body: any + ): Promise> => { + return apiClient.put(`${rootUrl}/${schedule_id}`, body); + }, +}; diff --git a/worklenz-frontend/src/api/tasks/task-time-logs.api.service.ts b/worklenz-frontend/src/api/tasks/task-time-logs.api.service.ts index f4565837..1a9191ee 100644 --- a/worklenz-frontend/src/api/tasks/task-time-logs.api.service.ts +++ b/worklenz-frontend/src/api/tasks/task-time-logs.api.service.ts @@ -1,17 +1,27 @@ -import { API_BASE_URL } from "@/shared/constants"; -import apiClient from "../api-client"; -import { IServerResponse } from "@/types/common.types"; -import { ITaskLogViewModel } from "@/types/tasks/task-log-view.types"; +import { API_BASE_URL } from '@/shared/constants'; +import apiClient from '../api-client'; +import { IServerResponse } from '@/types/common.types'; +import { ITaskLogViewModel } from '@/types/tasks/task-log-view.types'; const rootUrl = `${API_BASE_URL}/task-time-log`; +export interface IRunningTimer { + task_id: string; + start_time: string; + task_name: string; + project_id: string; + project_name: string; + parent_task_id?: string; + parent_task_name?: string; +} + export const taskTimeLogsApiService = { - getByTask: async (id: string) : Promise> => { + getByTask: async (id: string): Promise> => { const response = await apiClient.get(`${rootUrl}/task/${id}`); return response.data; }, - delete: async (id: string, taskId: string) : Promise> => { + delete: async (id: string, taskId: string): Promise> => { const response = await apiClient.delete(`${rootUrl}/${id}?task=${taskId}`); return response.data; }, @@ -26,6 +36,11 @@ export const taskTimeLogsApiService = { return response.data; }, + getRunningTimers: async (): Promise> => { + const response = await apiClient.get(`${rootUrl}/running-timers`); + return response.data; + }, + exportToExcel(taskId: string) { window.location.href = `${import.meta.env.VITE_API_URL}${API_BASE_URL}/task-time-log/export/${taskId}`; }, diff --git a/worklenz-frontend/src/api/tasks/tasks-custom-columns.service.ts b/worklenz-frontend/src/api/tasks/tasks-custom-columns.service.ts index 187a27e2..117f1f25 100644 --- a/worklenz-frontend/src/api/tasks/tasks-custom-columns.service.ts +++ b/worklenz-frontend/src/api/tasks/tasks-custom-columns.service.ts @@ -1,23 +1,23 @@ -import { ITaskListColumn } from "@/types/tasks/taskList.types"; -import apiClient from "../api-client"; -import { IServerResponse } from "@/types/common.types"; +import { ITaskListColumn } from '@/types/tasks/taskList.types'; +import apiClient from '../api-client'; +import { IServerResponse } from '@/types/common.types'; export const tasksCustomColumnsService = { getCustomColumns: async (projectId: string): Promise> => { const response = await apiClient.get(`/api/v1/custom-columns/project/${projectId}/columns`); return response.data; }, - + updateTaskCustomColumnValue: async ( - taskId: string, - columnKey: string, + taskId: string, + columnKey: string, value: string | number | boolean, projectId: string ): Promise> => { const response = await apiClient.put(`/api/v1/tasks/${taskId}/custom-column`, { column_key: columnKey, value: value, - project_id: projectId + project_id: projectId, }); return response.data; }, @@ -35,7 +35,7 @@ export const tasksCustomColumnsService = { ): Promise> => { const response = await apiClient.post('/api/v1/custom-columns', { project_id: projectId, - ...columnData + ...columnData, }); return response.data; }, @@ -63,7 +63,10 @@ export const tasksCustomColumnsService = { projectId: string, item: ITaskListColumn ): Promise> => { - const response = await apiClient.put(`/api/v1/custom-columns/project/${projectId}/columns`, item); + const response = await apiClient.put( + `/api/v1/custom-columns/project/${projectId}/columns`, + item + ); return response.data; - } + }, }; diff --git a/worklenz-frontend/src/api/tasks/tasks.api.service.ts b/worklenz-frontend/src/api/tasks/tasks.api.service.ts index cd3d80dd..1b18c0f3 100644 --- a/worklenz-frontend/src/api/tasks/tasks.api.service.ts +++ b/worklenz-frontend/src/api/tasks/tasks.api.service.ts @@ -28,6 +28,24 @@ export interface ITaskListConfigV2 { parent_task?: string; group?: string; isSubtasksInclude: boolean; + include_empty?: string; // Include empty groups in response + customColumns?: boolean; // Include custom column values in response +} + +export interface ITaskListV3Response { + groups: Array<{ + id: string; + title: string; + groupType: 'status' | 'priority' | 'phase'; + groupValue: string; + collapsed: boolean; + tasks: any[]; + taskIds: string[]; + color: string; + }>; + allTasks: any[]; + grouping: string; + totalTasks: number; } export const tasksApiService = { @@ -114,9 +132,70 @@ export const tasksApiService = { return response.data; }, - getTaskDependencyStatus: async (taskId: string, statusId: string): Promise> => { - const q = toQueryString({taskId, statusId}); + getTaskDependencyStatus: async ( + taskId: string, + statusId: string + ): Promise> => { + const q = toQueryString({ taskId, statusId }); const response = await apiClient.get(`${rootUrl}/dependency-status${q}`); return response.data; }, + + getTaskListV3: async ( + config: ITaskListConfigV2 + ): Promise> => { + const q = toQueryString({ ...config, include_empty: 'true' }); + const response = await apiClient.get(`${rootUrl}/list/v3/${config.id}${q}`); + return response.data; + }, + + refreshTaskProgress: async (projectId: string): Promise> => { + const response = await apiClient.post(`${rootUrl}/refresh-progress/${projectId}`); + return response.data; + }, + + getTaskProgressStatus: async ( + projectId: string + ): Promise< + IServerResponse<{ + projectId: string; + totalTasks: number; + completedTasks: number; + avgProgress: number; + lastUpdated: string; + completionPercentage: number; + }> + > => { + const response = await apiClient.get(`${rootUrl}/progress-status/${projectId}`); + return response.data; + }, + + // API method to reorder tasks + reorderTasks: async (params: { + taskIds: string[]; + newOrder: number[]; + projectId: string; + }): Promise> => { + const response = await apiClient.post(`${rootUrl}/reorder`, { + task_ids: params.taskIds, + new_order: params.newOrder, + project_id: params.projectId, + }); + return response.data; + }, + + // API method to update task group (status, priority, phase) + updateTaskGroup: async (params: { + taskId: string; + groupType: 'status' | 'priority' | 'phase'; + groupValue: string; + projectId: string; + }): Promise> => { + const response = await apiClient.put(`${rootUrl}/${params.taskId}/group`, { + group_type: params.groupType, + group_value: params.groupValue, + project_id: params.projectId, + }); + return response.data; + }, }; diff --git a/worklenz-frontend/src/api/team-members/teamMembers.api.service.ts b/worklenz-frontend/src/api/team-members/teamMembers.api.service.ts index f96de294..45ab9eaa 100644 --- a/worklenz-frontend/src/api/team-members/teamMembers.api.service.ts +++ b/worklenz-frontend/src/api/team-members/teamMembers.api.service.ts @@ -44,7 +44,9 @@ export const teamMembersApiService = { return response.data; }, - getAll: async (projectId: string | null = null): Promise> => { + getAll: async ( + projectId: string | null = null + ): Promise> => { const params = new URLSearchParams(projectId ? { project: projectId } : {}); const response = await apiClient.get>( `${rootUrl}/all${params.toString() ? '?' + params.toString() : ''}` diff --git a/worklenz-frontend/src/api/teams/teams.api.service.ts b/worklenz-frontend/src/api/teams/teams.api.service.ts index 4fb56b00..626e7e0d 100644 --- a/worklenz-frontend/src/api/teams/teams.api.service.ts +++ b/worklenz-frontend/src/api/teams/teams.api.service.ts @@ -14,11 +14,8 @@ const rootUrl = `${API_BASE_URL}/teams`; export const teamsApiService = { getTeams: async (): Promise> => { - const response = await apiClient.get>( - `${rootUrl}` - ); + const response = await apiClient.get>(`${rootUrl}`); return response.data; - }, setActiveTeam: async (teamId: string): Promise> => { @@ -29,23 +26,18 @@ export const teamsApiService = { return response.data; }, - createTeam: async (team: IOrganizationTeam): Promise> => { const response = await apiClient.post>(`${rootUrl}`, team); return response.data; }, - getInvitations: async (): Promise> => { - const response = await apiClient.get>( - `${rootUrl}/invites` - ); + const response = await apiClient.get>(`${rootUrl}/invites`); return response.data; }, acceptInvitation: async (body: IAcceptTeamInvite): Promise> => { const response = await apiClient.put>(`${rootUrl}`, body); return response.data; - } + }, }; - diff --git a/worklenz-frontend/src/app/performance-monitor.ts b/worklenz-frontend/src/app/performance-monitor.ts new file mode 100644 index 00000000..66599e6e --- /dev/null +++ b/worklenz-frontend/src/app/performance-monitor.ts @@ -0,0 +1,123 @@ +import { Middleware } from '@reduxjs/toolkit'; + +// Performance monitoring for Redux store +export interface PerformanceMetrics { + actionType: string; + duration: number; + timestamp: number; + stateSize: number; +} + +class ReduxPerformanceMonitor { + private metrics: PerformanceMetrics[] = []; + private maxMetrics = 100; // Keep last 100 metrics + private slowActionThreshold = 50; // Log actions taking more than 50ms + + logMetric(metric: PerformanceMetrics) { + this.metrics.push(metric); + + // Keep only recent metrics + if (this.metrics.length > this.maxMetrics) { + this.metrics = this.metrics.slice(-this.maxMetrics); + } + + // Log slow actions in development + if (process.env.NODE_ENV === 'development' && metric.duration > this.slowActionThreshold) { + console.warn(`Slow Redux action detected: ${metric.actionType} took ${metric.duration}ms`); + } + } + + getMetrics() { + return [...this.metrics]; + } + + getSlowActions(threshold = this.slowActionThreshold) { + return this.metrics.filter(m => m.duration > threshold); + } + + getAverageActionTime() { + if (this.metrics.length === 0) return 0; + const total = this.metrics.reduce((sum, m) => sum + m.duration, 0); + return total / this.metrics.length; + } + + reset() { + this.metrics = []; + } +} + +export const performanceMonitor = new ReduxPerformanceMonitor(); + +// Redux middleware for performance monitoring +export const performanceMiddleware: Middleware = store => next => (action: any) => { + const start = performance.now(); + + const result = next(action); + + const end = performance.now(); + const duration = end - start; + + // Calculate approximate state size (in development only) + let stateSize = 0; + if (process.env.NODE_ENV === 'development') { + try { + stateSize = JSON.stringify(store.getState()).length; + } catch (e) { + stateSize = -1; // Indicates serialization error + } + } + + performanceMonitor.logMetric({ + actionType: action.type || 'unknown', + duration, + timestamp: Date.now(), + stateSize, + }); + + return result; +}; + +// Hook to access performance metrics in components +export function useReduxPerformance() { + return { + metrics: performanceMonitor.getMetrics(), + slowActions: performanceMonitor.getSlowActions(), + averageTime: performanceMonitor.getAverageActionTime(), + reset: () => performanceMonitor.reset(), + }; +} + +// Utility to detect potential performance issues +export function analyzeReduxPerformance() { + const metrics = performanceMonitor.getMetrics(); + const analysis = { + totalActions: metrics.length, + slowActions: performanceMonitor.getSlowActions().length, + averageActionTime: performanceMonitor.getAverageActionTime(), + largestStateSize: Math.max(...metrics.map(m => m.stateSize)), + mostFrequentActions: {} as Record, + recommendations: [] as string[], + }; + + // Count action frequencies + metrics.forEach(m => { + analysis.mostFrequentActions[m.actionType] = + (analysis.mostFrequentActions[m.actionType] || 0) + 1; + }); + + // Generate recommendations + if (analysis.slowActions > analysis.totalActions * 0.1) { + analysis.recommendations.push('Consider optimizing selectors with createSelector'); + } + + if (analysis.largestStateSize > 1000000) { + // 1MB + analysis.recommendations.push('State size is large - consider normalizing data'); + } + + if (analysis.averageActionTime > 20) { + analysis.recommendations.push('Average action time is high - check for expensive reducers'); + } + + return analysis; +} diff --git a/worklenz-frontend/src/app/routes/account-setup-routes.tsx b/worklenz-frontend/src/app/routes/account-setup-routes.tsx index b7f2fcaf..6df71a4d 100644 --- a/worklenz-frontend/src/app/routes/account-setup-routes.tsx +++ b/worklenz-frontend/src/app/routes/account-setup-routes.tsx @@ -1,9 +1,15 @@ import { RouteObject } from 'react-router-dom'; -import AccountSetup from '@/pages/account-setup/account-setup'; +import { lazy, Suspense } from 'react'; +import { SuspenseFallback } from '@/components/suspense-fallback/suspense-fallback'; +const AccountSetup = lazy(() => import('@/pages/account-setup/account-setup')); const accountSetupRoute: RouteObject = { path: '/worklenz/setup', - element: , + element: ( + }> + + + ), }; export default accountSetupRoute; diff --git a/worklenz-frontend/src/app/routes/admin-center-routes.tsx b/worklenz-frontend/src/app/routes/admin-center-routes.tsx index 2e670a7f..6d41ae6c 100644 --- a/worklenz-frontend/src/app/routes/admin-center-routes.tsx +++ b/worklenz-frontend/src/app/routes/admin-center-routes.tsx @@ -1,8 +1,10 @@ import { RouteObject } from 'react-router-dom'; -import AdminCenterLayout from '@/layouts/admin-center-layout'; +import { Suspense } from 'react'; import { adminCenterItems } from '@/pages/admin-center/admin-center-constants'; import { Navigate } from 'react-router-dom'; import { useAuthService } from '@/hooks/useAuth'; +import { SuspenseFallback } from '@/components/suspense-fallback/suspense-fallback'; +import AdminCenterLayout from '@/layouts/AdminCenterLayout'; const AdminCenterGuard = ({ children }: { children: React.ReactNode }) => { const isOwnerOrAdmin = useAuthService().isOwnerOrAdmin(); @@ -24,7 +26,11 @@ const adminCenterRoutes: RouteObject[] = [ ), children: adminCenterItems.map(item => ({ path: item.endpoint, - element: item.element, + element: ( + }> + {item.element} + + ), })), }, ]; diff --git a/worklenz-frontend/src/app/routes/auth-routes.tsx b/worklenz-frontend/src/app/routes/auth-routes.tsx index 2eca96a9..5cddb925 100644 --- a/worklenz-frontend/src/app/routes/auth-routes.tsx +++ b/worklenz-frontend/src/app/routes/auth-routes.tsx @@ -1,20 +1,20 @@ +import { lazy, Suspense } from 'react'; import AuthLayout from '@/layouts/AuthLayout'; -import LoginPage from '@/pages/auth/login-page'; -import SignupPage from '@/pages/auth/signup-page'; -import ForgotPasswordPage from '@/pages/auth/forgot-password-page'; -import LoggingOutPage from '@/pages/auth/logging-out'; -import AuthenticatingPage from '@/pages/auth/authenticating'; import { Navigate } from 'react-router-dom'; -import VerifyResetEmailPage from '@/pages/auth/verify-reset-email'; -import { Suspense } from 'react'; import { SuspenseFallback } from '@/components/suspense-fallback/suspense-fallback'; +// Lazy load auth page components for better code splitting +const LoginPage = lazy(() => import('@/pages/auth/login-page')); +const SignupPage = lazy(() => import('@/pages/auth/signup-page')); +const ForgotPasswordPage = lazy(() => import('@/pages/auth/forgot-password-page')); +const LoggingOutPage = lazy(() => import('@/pages/auth/logging-out')); +const AuthenticatingPage = lazy(() => import('@/pages/auth/authenticating')); +const VerifyResetEmailPage = lazy(() => import('@/pages/auth/verify-reset-email')); + const authRoutes = [ { path: '/auth', - element: ( - - ), + element: , children: [ { path: '', @@ -22,27 +22,51 @@ const authRoutes = [ }, { path: 'login', - element: , + element: ( + }> + + + ), }, { path: 'signup', - element: , + element: ( + }> + + + ), }, { path: 'forgot-password', - element: , + element: ( + }> + + + ), }, { path: 'logging-out', - element: , + element: ( + }> + + + ), }, { path: 'authenticating', - element: , + element: ( + }> + + + ), }, { path: 'verify-reset-email/:user/:hash', - element: , + element: ( + }> + + + ), }, ], }, diff --git a/worklenz-frontend/src/app/routes/index.tsx b/worklenz-frontend/src/app/routes/index.tsx index 7d6d6826..a7f75760 100644 --- a/worklenz-frontend/src/app/routes/index.tsx +++ b/worklenz-frontend/src/app/routes/index.tsx @@ -1,4 +1,5 @@ import { createBrowserRouter, Navigate, RouteObject, useLocation } from 'react-router-dom'; +import { lazy, Suspense, memo, useMemo } from 'react'; import rootRoutes from './root-routes'; import authRoutes from './auth-routes'; import mainRoutes, { licenseExpiredRoute } from './main-routes'; @@ -8,116 +9,88 @@ import reportingRoutes from './reporting-routes'; import { useAuthService } from '@/hooks/useAuth'; import { AuthenticatedLayout } from '@/layouts/AuthenticatedLayout'; import ErrorBoundary from '@/components/ErrorBoundary'; -import NotFoundPage from '@/pages/404-page/404-page'; +import { SuspenseFallback } from '@/components/suspense-fallback/suspense-fallback'; import { ISUBSCRIPTION_TYPE } from '@/shared/constants'; -import LicenseExpired from '@/pages/license-expired/license-expired'; + +// Lazy load the NotFoundPage component for better code splitting +const NotFoundPage = lazy(() => import('@/pages/404-page/404-page')); interface GuardProps { children: React.ReactNode; } -export const AuthGuard = ({ children }: GuardProps) => { - const isAuthenticated = useAuthService().isAuthenticated(); - const location = useLocation(); +// Route-based code splitting utility +const withCodeSplitting = (Component: React.LazyExoticComponent>) => { + return memo(() => ( + }> + + + )); +}; + +// Memoized guard components with defensive programming +import { useAuthStatus } from '@/hooks/useAuthStatus'; + +export const AuthGuard = memo(({ children }: GuardProps) => { + const { isAuthenticated, location } = useAuthStatus(); if (!isAuthenticated) { return ; } return <>{children}; -}; +}); -export const AdminGuard = ({ children }: GuardProps) => { - const isAuthenticated = useAuthService().isAuthenticated(); - const isOwnerOrAdmin = useAuthService().isOwnerOrAdmin(); - const currentSession = useAuthService().getCurrentSession(); - const isFreePlan = currentSession?.subscription_type === ISUBSCRIPTION_TYPE.FREE; - const location = useLocation(); +AuthGuard.displayName = 'AuthGuard'; + +export const AdminGuard = memo(({ children }: GuardProps) => { + const { isAuthenticated, isAdmin, location } = useAuthStatus(); if (!isAuthenticated) { return ; } - if (!isOwnerOrAdmin || isFreePlan) { - return ; + if (!isAdmin) { + return ; } return <>{children}; -}; +}); + +AdminGuard.displayName = 'AdminGuard'; + +export const LicenseExpiryGuard = memo(({ children }: GuardProps) => { + const { isLicenseExpired, location } = useAuthStatus(); -export const LicenseExpiryGuard = ({ children }: GuardProps) => { - const isAuthenticated = useAuthService().isAuthenticated(); - const currentSession = useAuthService().getCurrentSession(); - const location = useLocation(); const isAdminCenterRoute = location.pathname.includes('/worklenz/admin-center'); const isLicenseExpiredRoute = location.pathname === '/worklenz/license-expired'; - // Don't check or redirect if we're already on the license-expired page - if (isLicenseExpiredRoute) { - return <>{children}; - } - - // Check if trial is expired more than 7 days or if is_expired flag is set - const isLicenseExpiredMoreThan7Days = () => { - // Quick bail if no session data is available - if (!currentSession) { - return false; - } - - // Check is_expired flag first - if (currentSession.is_expired) { - // If no trial_expire_date exists but is_expired is true, defer to backend check - if (!currentSession.trial_expire_date) { - return true; - } - - // If there is a trial_expire_date, check if it's more than 7 days past - const today = new Date(); - const expiryDate = new Date(currentSession.trial_expire_date); - const diffTime = today.getTime() - expiryDate.getTime(); - const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); - - // Redirect if more than 7 days past expiration - return diffDays > 7; - } - - // If not marked as expired but has trial_expire_date, do a date check - if (currentSession.subscription_type === ISUBSCRIPTION_TYPE.TRIAL && currentSession.trial_expire_date) { - const today = new Date(); - const expiryDate = new Date(currentSession.trial_expire_date); - - const diffTime = today.getTime() - expiryDate.getTime(); - const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); - - // If expired more than 7 days, redirect - return diffDays > 7; - } - - // No expiration data found - return false; - }; - - // Add this explicit check and log the result - const shouldRedirect = isAuthenticated && isLicenseExpiredMoreThan7Days() && !isAdminCenterRoute; - if (shouldRedirect) { + if (isLicenseExpired && !isAdminCenterRoute && !isLicenseExpiredRoute) { return ; } return <>{children}; -}; +}); -export const SetupGuard = ({ children }: GuardProps) => { - const isAuthenticated = useAuthService().isAuthenticated(); - const location = useLocation(); +LicenseExpiryGuard.displayName = 'LicenseExpiryGuard'; + +export const SetupGuard = memo(({ children }: GuardProps) => { + const { isAuthenticated, isSetupComplete, location } = useAuthStatus(); if (!isAuthenticated) { return ; } - return <>{children}; -}; + if (!isSetupComplete) { + return ; + } -// Helper to wrap routes with guards + return <>{children}; +}); + +SetupGuard.displayName = 'SetupGuard'; + +// Optimized route wrapping function with Suspense boundaries const wrapRoutes = ( routes: RouteObject[], Guard: React.ComponentType<{ children: React.ReactNode }> @@ -125,7 +98,11 @@ const wrapRoutes = ( return routes.map(route => { const wrappedRoute = { ...route, - element: {route.element}, + element: ( + }> + {route.element} + + ), }; if (route.children) { @@ -140,34 +117,37 @@ const wrapRoutes = ( }); }; -// Static license expired component that doesn't rely on translations or authentication -const StaticLicenseExpired = () => { - +// Optimized static license expired component +const StaticLicenseExpired = memo(() => { return ( -
-
+
+

Your Worklenz trial has expired!

Please upgrade now to continue using Worklenz.

-
); -}; +}); + +StaticLicenseExpired.displayName = 'StaticLicenseExpired'; + +// Create route arrays (moved outside of useMemo to avoid hook violations) +const publicRoutes = [...rootRoutes, ...authRoutes, notFoundRoute]; -const publicRoutes = [ - ...rootRoutes, - ...authRoutes, - notFoundRoute -]; const protectedMainRoutes = wrapRoutes(mainRoutes, AuthGuard); const adminRoutes = wrapRoutes(reportingRoutes, AdminGuard); const setupRoutes = wrapRoutes([accountSetupRoute], SetupGuard); -// Apply LicenseExpiryGuard to all protected routes +// License expiry check function const withLicenseExpiryCheck = (routes: RouteObject[]): RouteObject[] => { return routes.map(route => { const wrappedRoute = { ...route, - element: {route.element}, + element: ( + }> + {route.element} + + ), }; if (route.children) { @@ -213,18 +197,36 @@ const withLicenseExpiryCheck = (routes: RouteObject[]): RouteObject[] => { const licenseCheckedMainRoutes = withLicenseExpiryCheck(protectedMainRoutes); -const router = createBrowserRouter([ +// Create optimized router with future flags for better performance +const router = createBrowserRouter( + [ + { + element: ( + + + + ), + errorElement: ( + + }> + + + + ), + children: [...licenseCheckedMainRoutes, ...adminRoutes, ...setupRoutes, licenseExpiredRoute], + }, + ...publicRoutes, + ], { - element: , - errorElement: , - children: [ - ...licenseCheckedMainRoutes, - ...adminRoutes, - ...setupRoutes, - licenseExpiredRoute, - ], - }, - ...publicRoutes, -]); + // Enable React Router future features for better performance + future: { + v7_relativeSplatPath: true, + v7_fetcherPersist: true, + v7_normalizeFormMethod: true, + v7_partialHydration: true, + v7_skipActionErrorRevalidation: true, + }, + } +); export default router; diff --git a/worklenz-frontend/src/app/routes/main-routes.tsx b/worklenz-frontend/src/app/routes/main-routes.tsx index 52826bf3..8ec8cb9a 100644 --- a/worklenz-frontend/src/app/routes/main-routes.tsx +++ b/worklenz-frontend/src/app/routes/main-routes.tsx @@ -1,32 +1,53 @@ import { RouteObject } from 'react-router-dom'; +import { lazy, Suspense } from 'react'; import MainLayout from '@/layouts/MainLayout'; -import HomePage from '@/pages/home/home-page'; -import ProjectList from '@/pages/projects/project-list'; import settingsRoutes from './settings-routes'; import adminCenterRoutes from './admin-center-routes'; -import Schedule from '@/pages/schedule/schedule'; -import ProjectTemplateEditView from '@/pages/settings/project-templates/projectTemplateEditView/ProjectTemplateEditView'; -import LicenseExpired from '@/pages/license-expired/license-expired'; -import ProjectView from '@/pages/projects/projectView/project-view'; -import Unauthorized from '@/pages/unauthorized/unauthorized'; import { useAuthService } from '@/hooks/useAuth'; import { Navigate, useLocation } from 'react-router-dom'; +import { SuspenseFallback } from '@/components/suspense-fallback/suspense-fallback'; -// Define AdminGuard component first +// Lazy load page components for better code splitting +const HomePage = lazy(() => import('@/pages/home/home-page')); +const ProjectList = lazy(() => import('@/pages/projects/project-list')); +const Schedule = lazy(() => import('@/pages/schedule/schedule')); +const ProjectTemplateEditView = lazy( + () => import('@/pages/settings/project-templates/projectTemplateEditView/ProjectTemplateEditView') +); +const LicenseExpired = lazy(() => import('@/pages/license-expired/license-expired')); +const ProjectView = lazy(() => import('@/pages/projects/projectView/project-view')); +const Unauthorized = lazy(() => import('@/pages/unauthorized/unauthorized')); + +// Define AdminGuard component with defensive programming const AdminGuard = ({ children }: { children: React.ReactNode }) => { - const isAuthenticated = useAuthService().isAuthenticated(); - const isOwnerOrAdmin = useAuthService().isOwnerOrAdmin(); + const authService = useAuthService(); const location = useLocation(); - if (!isAuthenticated) { - return ; - } + try { + // Defensive checks to ensure authService and its methods exist + if ( + !authService || + typeof authService.isAuthenticated !== 'function' || + typeof authService.isOwnerOrAdmin !== 'function' + ) { + // If auth service is not ready, render children (don't block) + return <>{children}; + } - if (!isOwnerOrAdmin) { - return ; - } + if (!authService.isAuthenticated()) { + return ; + } - return <>{children}; + if (!authService.isOwnerOrAdmin()) { + return ; + } + + return <>{children}; + } catch (error) { + console.error('Error in AdminGuard (main-routes):', error); + // On error, render children to prevent complete blocking + return <>{children}; + } }; const mainRoutes: RouteObject[] = [ @@ -34,18 +55,57 @@ const mainRoutes: RouteObject[] = [ path: '/worklenz', element: , children: [ - { path: 'home', element: }, - { path: 'projects', element: }, + { index: true, element: }, + { + path: 'home', + element: ( + }> + + + ), + }, + { + path: 'projects', + element: ( + }> + + + ), + }, { path: 'schedule', - element: + element: ( + }> + + + + + ), + }, + { + path: `projects/:projectId`, + element: ( + }> + + + ), }, - { path: `projects/:projectId`, element: }, { path: `settings/project-templates/edit/:templateId/:templateName`, - element: , + element: ( + }> + + + ), + }, + { + path: 'unauthorized', + element: ( + }> + + + ), }, - { path: 'unauthorized', element: }, ...settingsRoutes, ...adminCenterRoutes, ], @@ -57,8 +117,15 @@ export const licenseExpiredRoute: RouteObject = { path: '/worklenz', element: , children: [ - { path: 'license-expired', element: } - ] + { + path: 'license-expired', + element: ( + }> + + + ), + }, + ], }; export default mainRoutes; diff --git a/worklenz-frontend/src/app/routes/not-found-route.tsx b/worklenz-frontend/src/app/routes/not-found-route.tsx index 4059609f..3c25e979 100644 --- a/worklenz-frontend/src/app/routes/not-found-route.tsx +++ b/worklenz-frontend/src/app/routes/not-found-route.tsx @@ -1,10 +1,16 @@ -import React from 'react'; +import React, { lazy, Suspense } from 'react'; import { RouteObject } from 'react-router-dom'; -import NotFoundPage from '@/pages/404-page/404-page'; +import { SuspenseFallback } from '@/components/suspense-fallback/suspense-fallback'; + +const NotFoundPage = lazy(() => import('@/pages/404-page/404-page')); const notFoundRoute: RouteObject = { path: '*', - element: , + element: ( + }> + + + ), }; export default notFoundRoute; diff --git a/worklenz-frontend/src/app/routes/reporting-routes.tsx b/worklenz-frontend/src/app/routes/reporting-routes.tsx index 01f83e9b..2e62de18 100644 --- a/worklenz-frontend/src/app/routes/reporting-routes.tsx +++ b/worklenz-frontend/src/app/routes/reporting-routes.tsx @@ -1,6 +1,8 @@ import { RouteObject } from 'react-router-dom'; +import { Suspense } from 'react'; import ReportingLayout from '@/layouts/ReportingLayout'; import { ReportingMenuItems, reportingsItems } from '@/lib/reporting/reporting-constants'; +import { SuspenseFallback } from '@/components/suspense-fallback/suspense-fallback'; // function to flatten nested menu items const flattenItems = (items: ReportingMenuItems[]): ReportingMenuItems[] => { @@ -20,7 +22,11 @@ const reportingRoutes: RouteObject[] = [ element: , children: flattenedItems.map(item => ({ path: item.endpoint, - element: item.element, + element: ( + }> + {item.element} + + ), })), }, ]; diff --git a/worklenz-frontend/src/app/routes/settings-routes.tsx b/worklenz-frontend/src/app/routes/settings-routes.tsx index 39468efb..78f59f88 100644 --- a/worklenz-frontend/src/app/routes/settings-routes.tsx +++ b/worklenz-frontend/src/app/routes/settings-routes.tsx @@ -1,10 +1,18 @@ import { RouteObject } from 'react-router-dom'; import { Navigate } from 'react-router-dom'; +import { Suspense } from 'react'; import SettingsLayout from '@/layouts/SettingsLayout'; import { settingsItems } from '@/lib/settings/settings-constants'; import { useAuthService } from '@/hooks/useAuth'; +import { SuspenseFallback } from '@/components/suspense-fallback/suspense-fallback'; -const SettingsGuard = ({ children, adminRequired }: { children: React.ReactNode; adminRequired: boolean }) => { +const SettingsGuard = ({ + children, + adminRequired, +}: { + children: React.ReactNode; + adminRequired: boolean; +}) => { const isOwnerOrAdmin = useAuthService().isOwnerOrAdmin(); if (adminRequired && !isOwnerOrAdmin) { @@ -21,9 +29,9 @@ const settingsRoutes: RouteObject[] = [ children: settingsItems.map(item => ({ path: item.endpoint, element: ( - - {item.element} - + }> + {item.element} + ), })), }, diff --git a/worklenz-frontend/src/app/routes/utils.ts b/worklenz-frontend/src/app/routes/utils.ts new file mode 100644 index 00000000..ca1d81d6 --- /dev/null +++ b/worklenz-frontend/src/app/routes/utils.ts @@ -0,0 +1,17 @@ +import { redirect } from 'react-router-dom'; +import { store } from '../store'; +import { verifyAuthentication } from '@/features/auth/authSlice'; + +export const authLoader = async () => { + const session = await store.dispatch(verifyAuthentication()).unwrap(); + + if (!session.user) { + return redirect('/auth/login'); + } + + if (session.user.is_expired) { + return redirect('/worklenz/license-expired'); + } + + return session; +}; diff --git a/worklenz-frontend/src/app/selectors.ts b/worklenz-frontend/src/app/selectors.ts new file mode 100644 index 00000000..7796fb5d --- /dev/null +++ b/worklenz-frontend/src/app/selectors.ts @@ -0,0 +1,75 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { RootState } from './store'; + +// Memoized selectors for better performance +// These prevent unnecessary re-renders when state hasn't actually changed + +// Auth selectors +export const selectAuth = (state: RootState) => state.auth; +export const selectUser = (state: RootState) => state.userReducer; +export const selectIsAuthenticated = createSelector([selectAuth], auth => !!auth.user); + +// Project selectors +export const selectProjects = (state: RootState) => state.projectsReducer; +export const selectCurrentProject = (state: RootState) => state.projectReducer; +export const selectProjectMembers = (state: RootState) => state.projectMemberReducer; + +// Task selectors +export const selectTasks = (state: RootState) => state.taskReducer; +export const selectTaskManagement = (state: RootState) => state.taskManagement; +export const selectTaskSelection = (state: RootState) => state.taskManagementSelection; + +// UI State selectors +export const selectTheme = (state: RootState) => state.themeReducer; +export const selectLocale = (state: RootState) => state.localesReducer; +export const selectAlerts = (state: RootState) => state.alertsReducer; + +// Board and Project View selectors +export const selectBoard = (state: RootState) => state.boardReducer; +export const selectProjectView = (state: RootState) => state.projectViewReducer; +export const selectProjectDrawer = (state: RootState) => state.projectDrawerReducer; + +// Task attributes selectors +export const selectTaskPriorities = (state: RootState) => state.priorityReducer; +export const selectTaskLabels = (state: RootState) => state.taskLabelsReducer; +export const selectTaskStatuses = (state: RootState) => state.taskStatusReducer; +export const selectTaskDrawer = (state: RootState) => state.taskDrawerReducer; + +// Settings selectors +export const selectMembers = (state: RootState) => state.memberReducer; +export const selectClients = (state: RootState) => state.clientReducer; +export const selectJobs = (state: RootState) => state.jobReducer; +export const selectTeams = (state: RootState) => state.teamReducer; +export const selectCategories = (state: RootState) => state.categoriesReducer; +export const selectLabels = (state: RootState) => state.labelReducer; + +// Reporting selectors +export const selectReporting = (state: RootState) => state.reportingReducer; +export const selectProjectReports = (state: RootState) => state.projectReportsReducer; +export const selectMemberReports = (state: RootState) => state.membersReportsReducer; +export const selectTimeReports = (state: RootState) => state.timeReportsOverviewReducer; + +// Admin and billing selectors +export const selectAdminCenter = (state: RootState) => state.adminCenterReducer; +export const selectBilling = (state: RootState) => state.billingReducer; + +// Schedule and date selectors +export const selectSchedule = (state: RootState) => state.scheduleReducer; +export const selectDate = (state: RootState) => state.dateReducer; + +// Feature-specific selectors +export const selectHomePage = (state: RootState) => state.homePageReducer; +export const selectAccountSetup = (state: RootState) => state.accountSetupReducer; +export const selectRoadmap = (state: RootState) => state.roadmapReducer; +export const selectGroupByFilter = (state: RootState) => state.groupByFilterDropdownReducer; + +// Memoized computed selectors for common use cases +export const selectHasActiveProject = createSelector( + [selectCurrentProject], + project => !!project && Object.keys(project).length > 0 +); + +export const selectIsLoading = createSelector([selectTasks, selectProjects], (tasks, projects) => { + // Check if any major feature is loading + return (tasks as any)?.loading || (projects as any)?.loading; +}); diff --git a/worklenz-frontend/src/app/store.ts b/worklenz-frontend/src/app/store.ts index 6bf7adcf..63c738a0 100644 --- a/worklenz-frontend/src/app/store.ts +++ b/worklenz-frontend/src/app/store.ts @@ -1,4 +1,5 @@ import { configureStore } from '@reduxjs/toolkit'; +import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; // Auth & User import authReducer from '@features/auth/authSlice'; @@ -42,6 +43,7 @@ import priorityReducer from '@features/taskAttributes/taskPrioritySlice'; import taskLabelsReducer from '@features/taskAttributes/taskLabelSlice'; import taskStatusReducer, { deleteStatus } from '@features/taskAttributes/taskStatusSlice'; import taskDrawerReducer from '@features/task-drawer/task-drawer.slice'; +import enhancedKanbanReducer from '@features/enhanced-kanban/enhanced-kanban.slice'; // Settings & Management import memberReducer from '@features/settings/member/memberSlice'; @@ -73,9 +75,17 @@ import timeReportsOverviewReducer from '@features/reporting/time-reports/time-re import roadmapReducer from '../features/roadmap/roadmap-slice'; import teamMembersReducer from '@features/team-members/team-members.slice'; import groupByFilterDropdownReducer from '../features/group-by-filter-dropdown/group-by-filter-dropdown-slice'; + +// Task Management System +import taskManagementReducer from '@/features/task-management/task-management.slice'; +import groupingReducer from '@/features/task-management/grouping.slice'; +import selectionReducer from '@/features/task-management/selection.slice'; import homePageApiService from '@/api/home-page/home-page.api.service'; import { projectsApi } from '@/api/projects/projects.v1.api.service'; +import projectViewReducer from '@features/project/project-view-slice'; +import taskManagementFieldsReducer from '@features/task-management/taskListFields.slice'; + export const store = configureStore({ middleware: getDefaultMiddleware => getDefaultMiddleware({ @@ -114,6 +124,8 @@ export const store = configureStore({ boardReducer: boardReducer, projectDrawerReducer: projectDrawerReducer, + projectViewReducer: projectViewReducer, + // Project Lookups projectCategoriesReducer: projectCategoriesReducer, projectStatusesReducer: projectStatusesReducer, @@ -126,6 +138,7 @@ export const store = configureStore({ taskLabelsReducer: taskLabelsReducer, taskStatusReducer: taskStatusReducer, taskDrawerReducer: taskDrawerReducer, + enhancedKanbanReducer: enhancedKanbanReducer, // Settings & Management memberReducer: memberReducer, @@ -155,8 +168,18 @@ export const store = configureStore({ roadmapReducer: roadmapReducer, groupByFilterDropdownReducer: groupByFilterDropdownReducer, timeReportsOverviewReducer: timeReportsOverviewReducer, + + // Task Management System + taskManagement: taskManagementReducer, + grouping: groupingReducer, + taskManagementSelection: selectionReducer, + taskManagementFields: taskManagementFieldsReducer, }, }); export type RootState = ReturnType; + export type AppDispatch = typeof store.dispatch; + +export const useAppDispatch = () => useDispatch(); +export const useAppSelector: TypedUseSelectorHook = useSelector; diff --git a/worklenz-frontend/src/assets/images/empty-box.webp b/worklenz-frontend/src/assets/images/empty-box.webp new file mode 100644 index 00000000..a23c97bd Binary files /dev/null and b/worklenz-frontend/src/assets/images/empty-box.webp differ diff --git a/worklenz-frontend/src/assets/images/worklenz-dark-mode.png b/worklenz-frontend/src/assets/images/worklenz-dark-mode.png new file mode 100644 index 00000000..3a22e238 Binary files /dev/null and b/worklenz-frontend/src/assets/images/worklenz-dark-mode.png differ diff --git a/worklenz-frontend/src/assets/images/worklenz-light-mode.png b/worklenz-frontend/src/assets/images/worklenz-light-mode.png new file mode 100644 index 00000000..cf0d3251 Binary files /dev/null and b/worklenz-frontend/src/assets/images/worklenz-light-mode.png differ diff --git a/worklenz-frontend/src/components/AssigneeSelector.tsx b/worklenz-frontend/src/components/AssigneeSelector.tsx new file mode 100644 index 00000000..f9d8fd1f --- /dev/null +++ b/worklenz-frontend/src/components/AssigneeSelector.tsx @@ -0,0 +1,365 @@ +import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react'; +import { createPortal } from 'react-dom'; +import { useSelector } from 'react-redux'; +import { PlusOutlined, UserAddOutlined } from '@ant-design/icons'; +import { RootState } from '@/app/store'; +import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; +import { ITeamMembersViewModel } from '@/types/teamMembers/teamMembersViewModel.types'; +import { useSocket } from '@/socket/socketContext'; +import { SocketEvents } from '@/shared/socket-events'; +import { useAuthService } from '@/hooks/useAuth'; +import { Avatar, Button, Checkbox } from '@/components'; +import { sortTeamMembers } from '@/utils/sort-team-members'; +import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { toggleProjectMemberDrawer } from '@/features/projects/singleProject/members/projectMembersSlice'; +import { updateEnhancedKanbanTaskAssignees } from '@/features/enhanced-kanban/enhanced-kanban.slice'; + +interface AssigneeSelectorProps { + task: IProjectTask; + groupId?: string | null; + isDarkMode?: boolean; + kanbanMode?: boolean; +} + +const AssigneeSelector: React.FC = ({ + task, + groupId = null, + isDarkMode = false, + kanbanMode = false +}) => { + const [isOpen, setIsOpen] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); + const [teamMembers, setTeamMembers] = useState({ data: [], total: 0 }); + const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0 }); + const [optimisticAssignees, setOptimisticAssignees] = useState([]); // For optimistic updates + const [pendingChanges, setPendingChanges] = useState>(new Set()); // Track pending member changes + const dropdownRef = useRef(null); + const buttonRef = useRef(null); + const searchInputRef = useRef(null); + + const { projectId } = useSelector((state: RootState) => state.projectReducer); + const members = useSelector((state: RootState) => state.teamMembersReducer.teamMembers); + const currentSession = useAuthService().getCurrentSession(); + const { socket } = useSocket(); + const dispatch = useAppDispatch(); + + const filteredMembers = useMemo(() => { + return teamMembers?.data?.filter(member => + member.name?.toLowerCase().includes(searchQuery.toLowerCase()) + ); + }, [teamMembers, searchQuery]); + + // Update dropdown position + const updateDropdownPosition = useCallback(() => { + if (buttonRef.current) { + const rect = buttonRef.current.getBoundingClientRect(); + setDropdownPosition({ + top: rect.bottom + window.scrollY + 2, + left: rect.left + window.scrollX, + }); + } + }, []); + + // Close dropdown when clicking outside and handle scroll + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node) && + buttonRef.current && !buttonRef.current.contains(event.target as Node)) { + setIsOpen(false); + } + }; + + const handleScroll = () => { + if (isOpen) { + // Check if the button is still visible in the viewport + if (buttonRef.current) { + const rect = buttonRef.current.getBoundingClientRect(); + const isVisible = rect.top >= 0 && rect.left >= 0 && + rect.bottom <= window.innerHeight && + rect.right <= window.innerWidth; + + if (isVisible) { + updateDropdownPosition(); + } else { + // Hide dropdown if button is not visible + setIsOpen(false); + } + } + } + }; + + const handleResize = () => { + if (isOpen) { + updateDropdownPosition(); + } + }; + + if (isOpen) { + document.addEventListener('mousedown', handleClickOutside); + window.addEventListener('scroll', handleScroll, true); + window.addEventListener('resize', handleResize); + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + window.removeEventListener('scroll', handleScroll, true); + window.removeEventListener('resize', handleResize); + }; + } else { + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + } + }, [isOpen, updateDropdownPosition]); + + const handleDropdownToggle = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + + if (!isOpen) { + updateDropdownPosition(); + + // Prepare team members data when opening + const assignees = task?.assignees?.map(assignee => assignee.team_member_id); + const membersData = (members?.data || []).map(member => ({ + ...member, + selected: assignees?.includes(member.id), + })); + const sortedMembers = sortTeamMembers(membersData); + setTeamMembers({ data: sortedMembers }); + + setIsOpen(true); + // Focus search input after opening + setTimeout(() => { + searchInputRef.current?.focus(); + }, 0); + } else { + setIsOpen(false); + } + }; + + const handleMemberToggle = (memberId: string, checked: boolean) => { + if (!memberId || !projectId || !task?.id || !currentSession?.id) return; + + // Add to pending changes for visual feedback + setPendingChanges(prev => new Set(prev).add(memberId)); + + // OPTIMISTIC UPDATE: Update local state immediately for instant UI feedback + const currentAssignees = task?.assignees?.map(a => a.team_member_id) || []; + let newAssigneeIds: string[]; + + if (checked) { + // Adding assignee + newAssigneeIds = [...currentAssignees, memberId]; + } else { + // Removing assignee + newAssigneeIds = currentAssignees.filter(id => id !== memberId); + } + + // Update optimistic state for immediate UI feedback in dropdown + setOptimisticAssignees(newAssigneeIds); + + // Update local team members state for dropdown UI + setTeamMembers(prev => ({ + ...prev, + data: (prev.data || []).map(member => + member.id === memberId + ? { ...member, selected: checked } + : member + ) + })); + + const body = { + team_member_id: memberId, + project_id: projectId, + task_id: task.id, + reporter_id: currentSession.id, + mode: checked ? 0 : 1, + parent_task: task.parent_task_id, + }; + + // Emit socket event - the socket handler will update Redux with proper types + socket?.emit(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), JSON.stringify(body)); + socket?.once( + SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), + (data: any) => { + dispatch(updateEnhancedKanbanTaskAssignees(data)); + } + ); + + // Remove from pending changes after a short delay (optimistic) + setTimeout(() => { + setPendingChanges(prev => { + const newSet = new Set(prev); + newSet.delete(memberId); + return newSet; + }); + }, 500); // Remove pending state after 500ms + }; + + const checkMemberSelected = (memberId: string) => { + if (!memberId) return false; + // Use optimistic assignees if available, otherwise fall back to task assignees + const assignees = optimisticAssignees.length > 0 + ? optimisticAssignees + : task?.assignees?.map(assignee => assignee.team_member_id) || []; + return assignees.includes(memberId); + }; + + const handleInviteProjectMemberDrawer = () => { + setIsOpen(false); // Close the assignee dropdown first + dispatch(toggleProjectMemberDrawer()); // Then open the invite drawer + }; + + return ( + <> + + + {isOpen && createPortal( +
e.stopPropagation()} + className={` + fixed z-[99999] w-72 rounded-md shadow-lg border + ${isDarkMode + ? 'bg-gray-800 border-gray-600' + : 'bg-white border-gray-200' + } + `} + style={{ + top: dropdownPosition.top, + left: dropdownPosition.left, + }} + > + {/* Header */} +
+ setSearchQuery(e.target.value)} + placeholder="Search members..." + className={` + w-full px-2 py-1 text-xs rounded border + ${isDarkMode + ? 'bg-gray-700 border-gray-600 text-gray-100 placeholder-gray-400 focus:border-blue-500' + : 'bg-white border-gray-300 text-gray-900 placeholder-gray-500 focus:border-blue-500' + } + focus:outline-none focus:ring-1 focus:ring-blue-500 + `} + /> +
+ + {/* Members List */} +
+ {filteredMembers && filteredMembers.length > 0 ? ( + filteredMembers.map((member) => ( +
{ + if (!member.pending_invitation) { + const isSelected = checkMemberSelected(member.id || ''); + handleMemberToggle(member.id || '', !isSelected); + } + }} + style={{ + // Add visual feedback for immediate response + transition: 'all 0.15s ease-in-out', + }} + > +
+ e.stopPropagation()}> + handleMemberToggle(member.id || '', checked)} + disabled={member.pending_invitation || pendingChanges.has(member.id || '')} + isDarkMode={isDarkMode} + /> + + {pendingChanges.has(member.id || '') && ( +
+
+
+ )} +
+ + + +
+
+ {member.name} +
+
+ {member.email} + {member.pending_invitation && ( + (Pending) + )} +
+
+
+ )) + ) : ( +
+
No members found
+
+ )} +
+ + {/* Footer */} +
+ +
+
, + document.body + )} + + ); +}; + +export default AssigneeSelector; \ No newline at end of file diff --git a/worklenz-frontend/src/components/AuthPageHeader.tsx b/worklenz-frontend/src/components/AuthPageHeader.tsx index a94d5fa5..e48b8ce8 100644 --- a/worklenz-frontend/src/components/AuthPageHeader.tsx +++ b/worklenz-frontend/src/components/AuthPageHeader.tsx @@ -1,6 +1,6 @@ import { Flex, Typography } from 'antd'; -import logo from '../assets/images/logo.png'; -import logoDark from '@/assets/images/logo-dark-mode.png'; +import logo from '@/assets/images/worklenz-light-mode.png'; +import logoDark from '@/assets/images/worklenz-dark-mode.png'; import { useAppSelector } from '@/hooks/useAppSelector'; type AuthPageHeaderProp = { diff --git a/worklenz-frontend/src/components/Avatar.tsx b/worklenz-frontend/src/components/Avatar.tsx new file mode 100644 index 00000000..59da1650 --- /dev/null +++ b/worklenz-frontend/src/components/Avatar.tsx @@ -0,0 +1,105 @@ +import React from 'react'; + +interface AvatarProps { + name?: string; + size?: number | 'small' | 'default' | 'large'; + isDarkMode?: boolean; + className?: string; + src?: string; + backgroundColor?: string; + onClick?: (e: React.MouseEvent) => void; + style?: React.CSSProperties; +} + +const Avatar: React.FC = ({ + name = '', + size = 'default', + isDarkMode = false, + className = '', + src, + backgroundColor, + onClick, + style = {}, +}) => { + // Handle both numeric and string sizes + const getSize = () => { + if (typeof size === 'number') { + return { width: size, height: size, fontSize: `${size * 0.4}px` }; + } + + const sizeMap = { + small: { width: 24, height: 24, fontSize: '10px' }, + default: { width: 32, height: 32, fontSize: '14px' }, + large: { width: 48, height: 48, fontSize: '18px' }, + }; + + return sizeMap[size]; + }; + + const sizeStyle = getSize(); + + const lightColors = [ + '#f56565', + '#4299e1', + '#48bb78', + '#ed8936', + '#9f7aea', + '#ed64a6', + '#667eea', + '#38b2ac', + '#f6ad55', + '#4fd1c7', + ]; + + const darkColors = [ + '#e53e3e', + '#3182ce', + '#38a169', + '#dd6b20', + '#805ad5', + '#d53f8c', + '#5a67d8', + '#319795', + '#d69e2e', + '#319795', + ]; + + const colors = isDarkMode ? darkColors : lightColors; + const colorIndex = name.charCodeAt(0) % colors.length; + const defaultBgColor = backgroundColor || colors[colorIndex]; + + const handleClick = (e: React.MouseEvent) => { + e.stopPropagation(); + onClick?.(e); + }; + + const avatarStyle = { + ...sizeStyle, + backgroundColor: defaultBgColor, + ...style, + }; + + if (src) { + return ( + {name} + ); + } + + return ( +
+ {name.charAt(0)?.toUpperCase() || '?'} +
+ ); +}; + +export default Avatar; diff --git a/worklenz-frontend/src/components/AvatarGroup.tsx b/worklenz-frontend/src/components/AvatarGroup.tsx new file mode 100644 index 00000000..04e4b57a --- /dev/null +++ b/worklenz-frontend/src/components/AvatarGroup.tsx @@ -0,0 +1,113 @@ +import React, { useCallback, useMemo } from 'react'; +import { Avatar, Tooltip } from './index'; + +interface Member { + id?: string; + team_member_id?: string; + name?: string; + names?: string[]; + avatar_url?: string; + color_code?: string; + end?: boolean; +} + +interface AvatarGroupProps { + members: Member[]; + maxCount?: number; + size?: number | 'small' | 'default' | 'large'; + isDarkMode?: boolean; + className?: string; + onClick?: (e: React.MouseEvent) => void; +} + +const AvatarGroup: React.FC = ({ + members, + maxCount, + size = 28, + isDarkMode = false, + className = '', + onClick, +}) => { + const stopPropagation = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation(); + onClick?.(e); + }, + [onClick] + ); + + const renderAvatar = useCallback( + (member: Member, index: number) => { + const memberName = member.end && member.names ? member.names.join(', ') : member.name || ''; + const displayName = + member.end && member.names ? member.name : member.name?.charAt(0).toUpperCase(); + + return ( + + + + ); + }, + [stopPropagation, size, isDarkMode] + ); + + const visibleMembers = useMemo(() => { + return maxCount ? members.slice(0, maxCount) : members; + }, [members, maxCount]); + + const remainingCount = useMemo(() => { + return maxCount ? Math.max(0, members.length - maxCount) : 0; + }, [members.length, maxCount]); + + const avatarElements = useMemo(() => { + return visibleMembers.map((member, index) => renderAvatar(member, index)); + }, [visibleMembers, renderAvatar]); + + const getSizeStyle = () => { + if (typeof size === 'number') { + return { width: size, height: size, fontSize: `${size * 0.4}px` }; + } + + const sizeMap = { + small: { width: 24, height: 24, fontSize: '10px' }, + default: { width: 32, height: 32, fontSize: '14px' }, + large: { width: 48, height: 48, fontSize: '18px' }, + }; + + return sizeMap[size]; + }; + + return ( +
+ {avatarElements} + {remainingCount > 0 && ( + +
+ +{remainingCount} +
+
+ )} +
+ ); +}; + +export default AvatarGroup; diff --git a/worklenz-frontend/src/components/Button.tsx b/worklenz-frontend/src/components/Button.tsx new file mode 100644 index 00000000..8d9ce9d1 --- /dev/null +++ b/worklenz-frontend/src/components/Button.tsx @@ -0,0 +1,64 @@ +import React from 'react'; + +interface ButtonProps { + children?: React.ReactNode; + onClick?: () => void; + variant?: 'text' | 'default' | 'primary' | 'danger'; + size?: 'small' | 'default' | 'large'; + className?: string; + icon?: React.ReactNode; + isDarkMode?: boolean; + disabled?: boolean; + type?: 'button' | 'submit' | 'reset'; +} + +const Button: React.FC> = ({ + children, + onClick, + variant = 'default', + size = 'default', + className = '', + icon, + isDarkMode = false, + disabled = false, + type = 'button', + ...props +}) => { + const baseClasses = `inline-flex items-center justify-center font-medium transition-colors duration-200 focus:outline-none focus:ring-2 ${isDarkMode ? 'focus:ring-blue-400' : 'focus:ring-blue-500'} focus:ring-offset-2 ${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}`; + + const variantClasses = { + text: isDarkMode + ? 'text-gray-400 hover:text-gray-200 hover:bg-gray-700/50' + : 'text-gray-600 hover:text-gray-800 hover:bg-gray-100', + default: isDarkMode + ? 'bg-gray-800 border border-gray-600 text-gray-200 hover:bg-gray-700' + : 'bg-white border border-gray-300 text-gray-700 hover:bg-gray-50', + primary: isDarkMode + ? 'bg-blue-600 text-white hover:bg-blue-700' + : 'bg-blue-500 text-white hover:bg-blue-600', + danger: isDarkMode + ? 'bg-red-600 text-white hover:bg-red-700' + : 'bg-red-500 text-white hover:bg-red-600', + }; + + const sizeClasses = { + small: 'px-2 py-1 text-xs rounded-sm', + default: 'px-3 py-2 text-sm rounded-md', + large: 'px-4 py-3 text-base rounded-lg', + }; + + return ( + + ); +}; + +export default Button; diff --git a/worklenz-frontend/src/components/Checkbox.tsx b/worklenz-frontend/src/components/Checkbox.tsx new file mode 100644 index 00000000..f663c3f7 --- /dev/null +++ b/worklenz-frontend/src/components/Checkbox.tsx @@ -0,0 +1,61 @@ +import React from 'react'; + +interface CheckboxProps { + checked: boolean; + onChange: (checked: boolean) => void; + isDarkMode?: boolean; + className?: string; + disabled?: boolean; + indeterminate?: boolean; +} + +const Checkbox: React.FC = ({ + checked, + onChange, + isDarkMode = false, + className = '', + disabled = false, + indeterminate = false, +}) => { + return ( + + ); +}; + +export default Checkbox; diff --git a/worklenz-frontend/src/components/CustomAvatar.tsx b/worklenz-frontend/src/components/CustomAvatar.tsx index 309e2cb7..ef63448e 100644 --- a/worklenz-frontend/src/components/CustomAvatar.tsx +++ b/worklenz-frontend/src/components/CustomAvatar.tsx @@ -1,25 +1,37 @@ +import React from 'react'; import Tooltip from 'antd/es/tooltip'; import Avatar from 'antd/es/avatar'; import { AvatarNamesMap } from '../shared/constants'; -const CustomAvatar = ({ avatarName, size = 32 }: { avatarName: string; size?: number }) => { - const avatarCharacter = avatarName[0].toUpperCase(); +interface CustomAvatarProps { + avatarName: string; + size?: number; +} - return ( - - - {avatarCharacter} - - - ); -}; +const CustomAvatar = React.forwardRef( + ({ avatarName, size = 32 }, ref) => { + const avatarCharacter = avatarName[0].toUpperCase(); + + return ( + +
+ + {avatarCharacter} + +
+
+ ); + } +); + +CustomAvatar.displayName = 'CustomAvatar'; export default CustomAvatar; diff --git a/worklenz-frontend/src/components/CustomColordLabel.tsx b/worklenz-frontend/src/components/CustomColordLabel.tsx new file mode 100644 index 00000000..068907f0 --- /dev/null +++ b/worklenz-frontend/src/components/CustomColordLabel.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { Tooltip } from 'antd'; +import { Label } from '@/types/task-management.types'; +import { ITaskLabel } from '@/types/tasks/taskLabel.types'; + +interface CustomColordLabelProps { + label: Label | ITaskLabel; + isDarkMode?: boolean; +} + +const CustomColordLabel = React.forwardRef( + ({ label, isDarkMode = false }, ref) => { + const truncatedName = + label.name && label.name.length > 10 ? `${label.name.substring(0, 10)}...` : label.name; + + // Handle different color property names for different types + const backgroundColor = (label as Label).color || (label as ITaskLabel).color_code || '#6b7280'; // Default to gray-500 if no color + + // Function to determine if we should use white or black text based on background color + const getTextColor = (bgColor: string): string => { + // Remove # if present + const color = bgColor.replace('#', ''); + + // Convert to RGB + const r = parseInt(color.substr(0, 2), 16); + const g = parseInt(color.substr(2, 2), 16); + const b = parseInt(color.substr(4, 2), 16); + + // Calculate luminance + const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255; + + // Return white for dark backgrounds, black for light backgrounds + return luminance > 0.5 ? '#000000' : '#ffffff'; + }; + + const textColor = getTextColor(backgroundColor); + + return ( + + + {truncatedName} + + + ); + } +); + +CustomColordLabel.displayName = 'CustomColordLabel'; + +export default CustomColordLabel; diff --git a/worklenz-frontend/src/components/CustomNumberLabel.tsx b/worklenz-frontend/src/components/CustomNumberLabel.tsx new file mode 100644 index 00000000..89c2d740 --- /dev/null +++ b/worklenz-frontend/src/components/CustomNumberLabel.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { Tooltip } from 'antd'; +import { NumbersColorMap } from '@/shared/constants'; + +interface CustomNumberLabelProps { + labelList: string[]; + namesString: string; + isDarkMode?: boolean; + color?: string; // Add color prop for label color +} + +const CustomNumberLabel = React.forwardRef( + ({ labelList, namesString, isDarkMode = false, color }, ref) => { + // Use provided color, or fall back to NumbersColorMap based on first digit + const backgroundColor = color || (() => { + const firstDigit = namesString.match(/\d/)?.[0] || '0'; + return NumbersColorMap[firstDigit] || NumbersColorMap['0']; + })(); + + return ( + + + {namesString} + + + ); + } +); + +CustomNumberLabel.displayName = 'CustomNumberLabel'; + +export default CustomNumberLabel; diff --git a/worklenz-frontend/src/components/EmptyListPlaceholder.tsx b/worklenz-frontend/src/components/EmptyListPlaceholder.tsx index 4953f202..dfe1aa76 100644 --- a/worklenz-frontend/src/components/EmptyListPlaceholder.tsx +++ b/worklenz-frontend/src/components/EmptyListPlaceholder.tsx @@ -8,7 +8,7 @@ type EmptyListPlaceholderProps = { }; const EmptyListPlaceholder = ({ - imageSrc = '/assets/images/empty-box.webp', + imageSrc = 'https://s3.us-west-2.amazonaws.com/worklenz.com/assets/empty-box.webp', imageHeight = 60, text, }: EmptyListPlaceholderProps) => { diff --git a/worklenz-frontend/src/components/ErrorBoundary.tsx b/worklenz-frontend/src/components/ErrorBoundary.tsx index f89bdaad..f624214d 100644 --- a/worklenz-frontend/src/components/ErrorBoundary.tsx +++ b/worklenz-frontend/src/components/ErrorBoundary.tsx @@ -27,7 +27,7 @@ class ErrorBoundary extends React.Component { logger.error('Error caught by ErrorBoundary:', { error: error.message, stack: error.stack, - componentStack: errorInfo.componentStack + componentStack: errorInfo.componentStack, }); console.error('Error caught by ErrorBoundary:', error); } @@ -70,4 +70,4 @@ const ErrorFallback: React.FC<{ error?: Error }> = ({ error }) => { ); }; -export default ErrorBoundary; \ No newline at end of file +export default ErrorBoundary; diff --git a/worklenz-frontend/src/components/HubSpot.tsx b/worklenz-frontend/src/components/HubSpot.tsx new file mode 100644 index 00000000..9c6f5a7a --- /dev/null +++ b/worklenz-frontend/src/components/HubSpot.tsx @@ -0,0 +1,24 @@ +import { useEffect } from 'react'; + +const HubSpot = () => { + useEffect(() => { + const script = document.createElement('script'); + script.type = 'text/javascript'; + script.id = 'hs-script-loader'; + script.async = true; + script.defer = true; + script.src = '//js.hs-scripts.com/22348300.js'; + document.body.appendChild(script); + + return () => { + const existingScript = document.getElementById('hs-script-loader'); + if (existingScript) { + existingScript.remove(); + } + }; + }, []); + + return null; +}; + +export default HubSpot; diff --git a/worklenz-frontend/src/components/LabelsSelector.tsx b/worklenz-frontend/src/components/LabelsSelector.tsx new file mode 100644 index 00000000..350e5aad --- /dev/null +++ b/worklenz-frontend/src/components/LabelsSelector.tsx @@ -0,0 +1,296 @@ +import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react'; +import { createPortal } from 'react-dom'; +import { useSelector } from 'react-redux'; +import { PlusOutlined, TagOutlined } from '@ant-design/icons'; +import { useTranslation } from 'react-i18next'; +import { RootState } from '@/app/store'; +import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; +import { ITaskLabel } from '@/types/tasks/taskLabel.types'; +import { useSocket } from '@/socket/socketContext'; +import { SocketEvents } from '@/shared/socket-events'; +import { useAuthService } from '@/hooks/useAuth'; +import { Button, Checkbox, Tag } from '@/components'; + +interface LabelsSelectorProps { + task: IProjectTask; + isDarkMode?: boolean; +} + +const LabelsSelector: React.FC = ({ task, isDarkMode = false }) => { + const [isOpen, setIsOpen] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); + const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0 }); + const dropdownRef = useRef(null); + const buttonRef = useRef(null); + const searchInputRef = useRef(null); + + const { labels } = useSelector((state: RootState) => state.taskLabelsReducer); + const currentSession = useAuthService().getCurrentSession(); + const { socket } = useSocket(); + const { t } = useTranslation('task-list-table'); + + const filteredLabels = useMemo(() => { + return ( + (labels as ITaskLabel[])?.filter(label => + label.name?.toLowerCase().includes(searchQuery.toLowerCase()) + ) || [] + ); + }, [labels, searchQuery]); + + // Update dropdown position + const updateDropdownPosition = useCallback(() => { + if (buttonRef.current) { + const rect = buttonRef.current.getBoundingClientRect(); + const dropdownHeight = 300; // Approximate height of dropdown (max-height + padding) + const spaceBelow = window.innerHeight - rect.bottom; + const spaceAbove = rect.top; + + // Position dropdown above button if there's not enough space below + const shouldPositionAbove = spaceBelow < dropdownHeight && spaceAbove > dropdownHeight; + + if (shouldPositionAbove) { + setDropdownPosition({ + top: rect.top + window.scrollY - dropdownHeight - 2, + left: rect.left + window.scrollX, + }); + } else { + setDropdownPosition({ + top: rect.bottom + window.scrollY + 2, + left: rect.left + window.scrollX, + }); + } + } + }, []); + + // Close dropdown when clicking outside and handle scroll + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) && + buttonRef.current && + !buttonRef.current.contains(event.target as Node) + ) { + setIsOpen(false); + } + }; + + const handleScroll = (event: Event) => { + if (isOpen) { + // Only close dropdown if scrolling happens outside the dropdown + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setIsOpen(false); + } + } + }; + + const handleResize = () => { + if (isOpen) { + updateDropdownPosition(); + } + }; + + if (isOpen) { + document.addEventListener('mousedown', handleClickOutside); + window.addEventListener('scroll', handleScroll, true); + window.addEventListener('resize', handleResize); + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + window.removeEventListener('scroll', handleScroll, true); + window.removeEventListener('resize', handleResize); + }; + } else { + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + } + }, [isOpen, updateDropdownPosition]); + + const handleDropdownToggle = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + console.log('Labels dropdown toggle clicked, current state:', isOpen); + + if (!isOpen) { + updateDropdownPosition(); + setIsOpen(true); + // Focus search input after opening + setTimeout(() => { + searchInputRef.current?.focus(); + }, 0); + } else { + setIsOpen(false); + } + }; + + const handleLabelToggle = (label: ITaskLabel) => { + const labelData = { + task_id: task.id, + label_id: label.id, + parent_task: task.parent_task_id, + team_id: currentSession?.team_id, + }; + + socket?.emit(SocketEvents.TASK_LABELS_CHANGE.toString(), JSON.stringify(labelData)); + }; + + const handleCreateLabel = () => { + if (!searchQuery.trim()) return; + + const labelData = { + task_id: task.id, + label: searchQuery.trim(), + parent_task: task.parent_task_id, + team_id: currentSession?.team_id, + }; + + socket?.emit(SocketEvents.CREATE_LABEL.toString(), JSON.stringify(labelData)); + setSearchQuery(''); + }; + + const checkLabelSelected = (labelId: string) => { + return task?.all_labels?.some(existingLabel => existingLabel.id === labelId) || false; + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + const existingLabel = filteredLabels.find( + label => label.name?.toLowerCase() === searchQuery.toLowerCase() + ); + + if (!existingLabel && e.key === 'Enter') { + handleCreateLabel(); + } + }; + + return ( + <> + + + {isOpen && + createPortal( +
+ {/* Header */} +
+ setSearchQuery(e.target.value)} + onKeyDown={handleKeyDown} + placeholder={t('searchLabelsPlaceholder')} + className={` + w-full px-2 py-1 text-xs rounded border + ${ + isDarkMode + ? 'bg-gray-700 border-gray-600 text-gray-100 placeholder-gray-400 focus:border-blue-500' + : 'bg-white border-gray-300 text-gray-900 placeholder-gray-500 focus:border-blue-500' + } + focus:outline-none focus:ring-1 focus:ring-blue-500 + `} + /> +
+ + {/* Labels List */} +
+ {filteredLabels && filteredLabels.length > 0 ? ( + filteredLabels.map(label => ( +
{ + e.stopPropagation(); + handleLabelToggle(label); + }} + > +
+ {}} // Empty handler since we handle click on the div + isDarkMode={isDarkMode} + /> +
+ +
+ +
+
+ {label.name} +
+
+
+ )) + ) : ( +
+
{t('noLabelsFound')}
+ {searchQuery.trim() && ( + + )} +
+ )} +
+ + {/* Footer */} +
+
+ + {t('manageLabelsPath')} +
+
+
, + document.body + )} + + ); +}; + +export default LabelsSelector; diff --git a/worklenz-frontend/src/components/PinRouteToNavbarButton.tsx b/worklenz-frontend/src/components/PinRouteToNavbarButton.tsx index c7e99c1d..c2e415a9 100644 --- a/worklenz-frontend/src/components/PinRouteToNavbarButton.tsx +++ b/worklenz-frontend/src/components/PinRouteToNavbarButton.tsx @@ -5,8 +5,15 @@ import { PushpinFilled, PushpinOutlined } from '@ant-design/icons'; import { colors } from '../styles/colors'; import { navRoutes, NavRoutesType } from '../features/navbar/navRoutes'; +// Props type for the component +type PinRouteToNavbarButtonProps = { + name: string; + path: string; + adminOnly?: boolean; +}; + // this component pin the given path to navbar -const PinRouteToNavbarButton = ({ name, path }: NavRoutesType) => { +const PinRouteToNavbarButton = ({ name, path, adminOnly = false }: PinRouteToNavbarButtonProps) => { const navRoutesList: NavRoutesType[] = getJSONFromLocalStorage('navRoutes') || navRoutes; const [isPinned, setIsPinned] = useState( @@ -18,7 +25,7 @@ const PinRouteToNavbarButton = ({ name, path }: NavRoutesType) => { const handlePinToNavbar = (name: string, path: string) => { let newNavRoutesList; - const route: NavRoutesType = { name, path }; + const route: NavRoutesType = { name, path, adminOnly }; if (isPinned) { newNavRoutesList = navRoutesList.filter(item => item.name !== route.name); diff --git a/worklenz-frontend/src/components/PreferenceSelector.tsx b/worklenz-frontend/src/components/PreferenceSelector.tsx index 9bf3c324..b908c247 100644 --- a/worklenz-frontend/src/components/PreferenceSelector.tsx +++ b/worklenz-frontend/src/components/PreferenceSelector.tsx @@ -1,7 +1,7 @@ import { FloatButton, Space, Tooltip } from 'antd'; import { FormatPainterOutlined } from '@ant-design/icons'; -import LanguageSelector from '../features/i18n/language-selector'; -import ThemeSelector from '../features/theme/ThemeSelector'; +// import LanguageSelector from '../features/i18n/language-selector'; +// import ThemeSelector from '../features/theme/ThemeSelector'; const PreferenceSelector = () => { return ( @@ -17,7 +17,7 @@ const PreferenceSelector = () => { justifyContent: 'center', }} > - + {/* */}
diff --git a/worklenz-frontend/src/components/Progress.tsx b/worklenz-frontend/src/components/Progress.tsx new file mode 100644 index 00000000..94ccf95a --- /dev/null +++ b/worklenz-frontend/src/components/Progress.tsx @@ -0,0 +1,88 @@ +import React from 'react'; + +interface ProgressProps { + percent: number; + type?: 'line' | 'circle'; + size?: number; + strokeColor?: string; + strokeWidth?: number; + showInfo?: boolean; + isDarkMode?: boolean; + className?: string; +} + +const Progress: React.FC = ({ + percent, + type = 'line', + size = 24, + strokeColor = '#1890ff', + strokeWidth = 2, + showInfo = true, + isDarkMode = false, + className = '', +}) => { + // Ensure percent is between 0 and 100 + const normalizedPercent = Math.min(Math.max(percent, 0), 100); + + if (type === 'circle') { + const radius = (size - strokeWidth) / 2; + const circumference = radius * 2 * Math.PI; + const strokeDasharray = circumference; + const strokeDashoffset = circumference - (normalizedPercent / 100) * circumference; + + return ( +
+ + + + + {showInfo && ( + + {normalizedPercent}% + + )} +
+ ); + } + + return ( +
+
+ {showInfo && ( +
+ {normalizedPercent}% +
+ )} +
+ ); +}; + +export default Progress; diff --git a/worklenz-frontend/src/components/Tag.tsx b/worklenz-frontend/src/components/Tag.tsx new file mode 100644 index 00000000..e6e7e966 --- /dev/null +++ b/worklenz-frontend/src/components/Tag.tsx @@ -0,0 +1,51 @@ +import React from 'react'; + +interface TagProps { + children: React.ReactNode; + color?: string; + backgroundColor?: string; + className?: string; + size?: 'small' | 'default'; + variant?: 'default' | 'outlined'; + isDarkMode?: boolean; +} + +const Tag: React.FC = ({ + children, + color = 'white', + backgroundColor = '#1890ff', + className = '', + size = 'default', + variant = 'default', + isDarkMode = false, +}) => { + const sizeClasses = { + small: 'px-1 py-0.5 text-xs', + default: 'px-2 py-1 text-xs', + }; + + const baseClasses = `inline-flex items-center font-medium rounded-sm ${sizeClasses[size]}`; + + if (variant === 'outlined') { + return ( + + {children} + + ); + } + + return ( + + {children} + + ); +}; + +export default Tag; diff --git a/worklenz-frontend/src/components/TawkTo.tsx b/worklenz-frontend/src/components/TawkTo.tsx index d07b5a71..c447a050 100644 --- a/worklenz-frontend/src/components/TawkTo.tsx +++ b/worklenz-frontend/src/components/TawkTo.tsx @@ -20,7 +20,7 @@ const TawkTo: React.FC = ({ propertyId, widgetId }) => { s1.async = true; s1.src = `https://embed.tawk.to/${propertyId}/${widgetId}`; s1.setAttribute('crossorigin', '*'); - + const s0 = document.getElementsByTagName('script')[0]; s0.parentNode?.insertBefore(s1, s0); @@ -31,13 +31,13 @@ const TawkTo: React.FC = ({ propertyId, widgetId }) => { if (tawkScript && tawkScript.parentNode) { tawkScript.parentNode.removeChild(tawkScript); } - + // Remove the tawk.to iframe const tawkIframe = document.getElementById('tawk-iframe'); if (tawkIframe) { tawkIframe.remove(); } - + // Reset Tawk globals delete window.Tawk_API; delete window.Tawk_LoadStart; @@ -47,4 +47,4 @@ const TawkTo: React.FC = ({ propertyId, widgetId }) => { return null; }; -export default TawkTo; \ No newline at end of file +export default TawkTo; diff --git a/worklenz-frontend/src/components/Tooltip.tsx b/worklenz-frontend/src/components/Tooltip.tsx new file mode 100644 index 00000000..e8f71a8a --- /dev/null +++ b/worklenz-frontend/src/components/Tooltip.tsx @@ -0,0 +1,37 @@ +import React from 'react'; + +interface TooltipProps { + title: string | React.ReactNode; + children: React.ReactNode; + isDarkMode?: boolean; + placement?: 'top' | 'bottom' | 'left' | 'right'; + className?: string; +} + +const Tooltip: React.FC = ({ + title, + children, + isDarkMode = false, + placement = 'top', + className = '', +}) => { + const placementClasses = { + top: 'bottom-full left-1/2 transform -translate-x-1/2 mb-2', + bottom: 'top-full left-1/2 transform -translate-x-1/2 mt-2', + left: 'right-full top-1/2 transform -translate-y-1/2 mr-2', + right: 'left-full top-1/2 transform -translate-y-1/2 ml-2', + }; + + return ( +
+ {children} +
+ {title} +
+
+ ); +}; + +export default Tooltip; diff --git a/worklenz-frontend/src/components/account-setup/tasks-step.tsx b/worklenz-frontend/src/components/account-setup/tasks-step.tsx index 472654fc..129e4409 100644 --- a/worklenz-frontend/src/components/account-setup/tasks-step.tsx +++ b/worklenz-frontend/src/components/account-setup/tasks-step.tsx @@ -39,7 +39,9 @@ export const TasksStep: React.FC = ({ onEnter, styles, isDarkMode }) => { const updateTask = (id: number, value: string) => { const sanitizedValue = sanitizeInput(value); - dispatch(setTasks(tasks.map(task => (task.id === id ? { ...task, value: sanitizedValue } : task)))); + dispatch( + setTasks(tasks.map(task => (task.id === id ? { ...task, value: sanitizedValue } : task))) + ); }; const handleKeyPress = (e: React.KeyboardEvent) => { diff --git a/worklenz-frontend/src/components/admin-center/billing/account-storage/account-storage.tsx b/worklenz-frontend/src/components/admin-center/billing/account-storage/account-storage.tsx index facd237d..11a3282e 100644 --- a/worklenz-frontend/src/components/admin-center/billing/account-storage/account-storage.tsx +++ b/worklenz-frontend/src/components/admin-center/billing/account-storage/account-storage.tsx @@ -18,7 +18,9 @@ const AccountStorage = ({ themeMode }: IAccountStorageProps) => { const dispatch = useAppDispatch(); const [subscriptionType, setSubscriptionType] = useState(SUBSCRIPTION_STATUS.TRIALING); - const { loadingBillingInfo, billingInfo, storageInfo } = useAppSelector(state => state.adminCenterReducer); + const { loadingBillingInfo, billingInfo, storageInfo } = useAppSelector( + state => state.adminCenterReducer + ); const formatBytes = useMemo( () => @@ -68,7 +70,7 @@ const AccountStorage = ({ themeMode }: IAccountStorageProps) => {
{percent}% Used} /> diff --git a/worklenz-frontend/src/components/admin-center/billing/current-bill.tsx b/worklenz-frontend/src/components/admin-center/billing/current-bill.tsx index 7eacbae2..86252375 100644 --- a/worklenz-frontend/src/components/admin-center/billing/current-bill.tsx +++ b/worklenz-frontend/src/components/admin-center/billing/current-bill.tsx @@ -1,21 +1,20 @@ -import { Button, Card, Col, Modal, Row, Tooltip, Typography } from 'antd'; +import { Card, Col, Row, Tooltip, Typography } from 'antd'; import React, { useEffect } from 'react'; import './current-bill.css'; import { InfoCircleTwoTone } from '@ant-design/icons'; import ChargesTable from './billing-tables/charges-table'; import InvoicesTable from './billing-tables/invoices-table'; -import UpgradePlansLKR from './drawers/upgrade-plans-lkr/upgrade-plans-lkr'; -import UpgradePlans from './drawers/upgrade-plans/upgrade-plans'; + import { useAppSelector } from '@/hooks/useAppSelector'; import { useAppDispatch } from '@/hooks/useAppDispatch'; import { useMediaQuery } from 'react-responsive'; import { useTranslation } from 'react-i18next'; + import { - toggleDrawer, - toggleUpgradeModal, -} from '@/features/admin-center/billing/billing.slice'; -import { fetchBillingInfo, fetchFreePlanSettings } from '@/features/admin-center/admin-center.slice'; -import RedeemCodeDrawer from './drawers/redeem-code-drawer/redeem-code-drawer'; + fetchBillingInfo, + fetchFreePlanSettings, +} from '@/features/admin-center/admin-center.slice'; + import CurrentPlanDetails from './current-plan-details/current-plan-details'; import AccountStorage from './account-storage/account-storage'; import { useAuthService } from '@/hooks/useAuth'; @@ -25,9 +24,7 @@ const CurrentBill: React.FC = () => { const dispatch = useAppDispatch(); const { t } = useTranslation('admin-center/current-bill'); const themeMode = useAppSelector(state => state.themeReducer.mode); - const { isUpgradeModalOpen } = useAppSelector(state => state.adminCenterReducer); const isTablet = useMediaQuery({ query: '(min-width: 1025px)' }); - const browserTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; const currentSession = useAuthService().getCurrentSession(); useEffect(() => { @@ -46,42 +43,7 @@ const CurrentBill: React.FC = () => { const renderMobileView = () => (
- {t('currentPlanDetails')}} - extra={ -
- - dispatch(toggleUpgradeModal())} - width={1000} - centered - okButtonProps={{ hidden: true }} - cancelButtonProps={{ hidden: true }} - > - {browserTimeZone === 'Asia/Colombo' ? : } - -
- } - > -
-
- {t('cardBodyText01')} - {t('cardBodyText02')} -
- - -
-
+ @@ -109,10 +71,7 @@ const CurrentBill: React.FC = () => {
- {t('invoices')}} - style={{ marginTop: '16px' }} - > + {t('invoices')}} style={{ marginTop: '16px' }}>
@@ -133,7 +92,8 @@ const CurrentBill: React.FC = () => { ) : ( renderMobileView() )} - {currentSession?.subscription_type === ISUBSCRIPTION_TYPE.PADDLE && renderChargesAndInvoices()} + {currentSession?.subscription_type === ISUBSCRIPTION_TYPE.PADDLE && + renderChargesAndInvoices()}
); }; diff --git a/worklenz-frontend/src/components/admin-center/billing/current-plan-details/current-plan-details.tsx b/worklenz-frontend/src/components/admin-center/billing/current-plan-details/current-plan-details.tsx index 42e1a449..77de144e 100644 --- a/worklenz-frontend/src/components/admin-center/billing/current-plan-details/current-plan-details.tsx +++ b/worklenz-frontend/src/components/admin-center/billing/current-plan-details/current-plan-details.tsx @@ -7,7 +7,20 @@ import { } from '@/shared/worklenz-analytics-events'; import { useMixpanelTracking } from '@/hooks/useMixpanelTracking'; import logger from '@/utils/errorLogger'; -import { Button, Card, Flex, Modal, Space, Tooltip, Typography, Statistic, Select, Form, Row, Col } from 'antd/es'; +import { + Button, + Card, + Flex, + Modal, + Space, + Tooltip, + Typography, + Statistic, + Select, + Form, + Row, + Col, +} from 'antd/es'; import RedeemCodeDrawer from '../drawers/redeem-code-drawer/redeem-code-drawer'; import { fetchBillingInfo, @@ -44,8 +57,9 @@ const CurrentPlanDetails = () => { const browserTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; type SeatOption = { label: string; value: number | string }; - const seatCountOptions: SeatOption[] = [1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90] - .map(value => ({ label: value.toString(), value })); + const seatCountOptions: SeatOption[] = [ + 1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, + ].map(value => ({ label: value.toString(), value })); seatCountOptions.push({ label: '100+', value: '100+' }); const handleSubscriptionAction = async (action: 'pause' | 'resume') => { @@ -127,8 +141,10 @@ const CurrentPlanDetails = () => { const shouldShowAddSeats = () => { if (!billingInfo) return false; - return billingInfo.subscription_type === ISUBSCRIPTION_TYPE.PADDLE && - billingInfo.status === SUBSCRIPTION_STATUS.ACTIVE; + return ( + billingInfo.subscription_type === ISUBSCRIPTION_TYPE.PADDLE && + billingInfo.status === SUBSCRIPTION_STATUS.ACTIVE + ); }; const renderExtra = () => { @@ -199,13 +215,13 @@ const CurrentPlanDetails = () => { const getExpirationMessage = (expireDate: string) => { const today = new Date(); today.setHours(0, 0, 0, 0); // Set to start of day for comparison - + const tomorrow = new Date(today); tomorrow.setDate(tomorrow.getDate() + 1); - + const expDate = new Date(expireDate); expDate.setHours(0, 0, 0, 0); // Set to start of day for comparison - + if (expDate.getTime() === today.getTime()) { return t('expirestoday', 'today'); } else if (expDate.getTime() === tomorrow.getTime()) { @@ -230,14 +246,13 @@ const CurrentPlanDetails = () => { - {isExpired + {isExpired ? t('trialExpired', { - trial_expire_string: getExpirationMessage(trialExpireDate) + trial_expire_string: getExpirationMessage(trialExpireDate), }) : t('trialInProgress', { - trial_expire_string: getExpirationMessage(trialExpireDate) - }) - } + trial_expire_string: getExpirationMessage(trialExpireDate), + })} @@ -246,7 +261,7 @@ const CurrentPlanDetails = () => { const renderFreePlan = () => ( - Free Plan + {t('freePlan')}
-{' '} {freePlanSettings?.team_member_limit === 0 @@ -268,25 +283,24 @@ const CurrentPlanDetails = () => { {billingInfo?.billing_type === 'year' ? billingInfo.unit_price_per_month : billingInfo?.unit_price} -  {t('perMonthPerUser')}
- + {shouldShowAddSeats() && billingInfo?.total_seats && (
- - - @@ -308,17 +322,25 @@ const CurrentPlanDetails = () => { }; const renderCreditSubscriptionInfo = () => { - return - Credit Plan - - }; + return ( + + {t('creditPlan', 'Credit Plan')} + + ); + }; const renderCustomSubscriptionInfo = () => { - return - Custom Plan - Your plan is valid till {billingInfo?.valid_till_date} - - }; + return ( + + {t('customPlan', 'Custom Plan')} + + {t('planValidTill', 'Your plan is valid till {{date}}', { + date: billingInfo?.valid_till_date, + })} + + + ); + }; return ( { title={ { >
- {billingInfo?.subscription_type === ISUBSCRIPTION_TYPE.LIFE_TIME_DEAL && renderLtdDetails()} + {billingInfo?.subscription_type === ISUBSCRIPTION_TYPE.LIFE_TIME_DEAL && + renderLtdDetails()} {billingInfo?.subscription_type === ISUBSCRIPTION_TYPE.TRIAL && renderTrialDetails()} {billingInfo?.subscription_type === ISUBSCRIPTION_TYPE.FREE && renderFreePlan()} - {billingInfo?.subscription_type === ISUBSCRIPTION_TYPE.PADDLE && renderPaddleSubscriptionInfo()} - {billingInfo?.subscription_type === ISUBSCRIPTION_TYPE.CREDIT && renderCreditSubscriptionInfo()} - {billingInfo?.subscription_type === ISUBSCRIPTION_TYPE.CUSTOM && renderCustomSubscriptionInfo()} + {billingInfo?.subscription_type === ISUBSCRIPTION_TYPE.PADDLE && + renderPaddleSubscriptionInfo()} + {billingInfo?.subscription_type === ISUBSCRIPTION_TYPE.CREDIT && + renderCreditSubscriptionInfo()} + {billingInfo?.subscription_type === ISUBSCRIPTION_TYPE.CUSTOM && + renderCustomSubscriptionInfo()}
{shouldShowRedeemButton() && ( @@ -370,7 +395,7 @@ const CurrentPlanDetails = () => { > {browserTimeZone === 'Asia/Colombo' ? : } - + { centered > - - To continue, you'll need to purchase additional seats. + + {t('purchaseSeatsText', "To continue, you'll need to purchase additional seats.")} - + - You currently have {billingInfo?.total_seats} seats available. + {t('currentSeatsText', 'You currently have {{seats}} seats available.', { + seats: billingInfo?.total_seats, + })} - + - Please select the number of additional seats to purchase. + {t('selectSeatsText', 'Please select the number of additional seats to purchase.')} - +
* Seats: @@ -402,28 +431,25 @@ const CurrentPlanDetails = () => { style={{ width: '300px' }} />
- + {selectedSeatCount.toString() !== '100+' ? ( - ) : ( - )} diff --git a/worklenz-frontend/src/components/admin-center/billing/drawers/upgrade-plans/upgrade-plans.tsx b/worklenz-frontend/src/components/admin-center/billing/drawers/upgrade-plans/upgrade-plans.tsx index f390896a..8cccc24c 100644 --- a/worklenz-frontend/src/components/admin-center/billing/drawers/upgrade-plans/upgrade-plans.tsx +++ b/worklenz-frontend/src/components/admin-center/billing/drawers/upgrade-plans/upgrade-plans.tsx @@ -1,5 +1,17 @@ import { useEffect, useState } from 'react'; -import { Button, Card, Col, Flex, Form, Row, Select, Tag, Tooltip, Typography, message } from 'antd/es'; +import { + Button, + Card, + Col, + Flex, + Form, + Row, + Select, + Tag, + Tooltip, + Typography, + message, +} from 'antd/es'; import { useTranslation } from 'react-i18next'; import { adminCenterApiService } from '@/api/admin-center/admin-center.api.service'; @@ -106,7 +118,7 @@ const UpgradePlans = () => { const handlePaddleCallback = (data: any) => { console.log('Paddle event:', data); - + switch (data.event) { case 'Checkout.Loaded': setSwitchingToPaddlePlan(false); @@ -144,13 +156,13 @@ const UpgradePlans = () => { const initializePaddle = (data: IUpgradeSubscriptionPlanResponse) => { setPaddleLoading(true); setPaddleError(null); - + // Check if Paddle is already loaded if (window.Paddle) { configurePaddle(data); return; } - + const script = document.createElement('script'); script.src = 'https://cdn.paddle.com/paddle/paddle.js'; script.type = 'text/javascript'; @@ -159,7 +171,7 @@ const UpgradePlans = () => { script.onload = () => { configurePaddle(data); }; - + script.onerror = () => { setPaddleLoading(false); setPaddleError('Failed to load Paddle checkout'); @@ -169,7 +181,7 @@ const UpgradePlans = () => { document.getElementsByTagName('head')[0].appendChild(script); }; - + const configurePaddle = (data: IUpgradeSubscriptionPlanResponse) => { try { if (data.sandbox) Paddle.Environment.set('sandbox'); @@ -193,7 +205,7 @@ const UpgradePlans = () => { setSwitchingToPaddlePlan(true); setPaddleLoading(true); setPaddleError(null); - + if (billingInfo?.trial_in_progress && billingInfo.status === SUBSCRIPTION_STATUS.TRIALING) { const res = await billingApiService.upgradeToPaidPlan(planId, selectedSeatCount); if (res.done) { @@ -264,7 +276,6 @@ const UpgradePlans = () => { const isSelected = (cardIndex: IPaddlePlans) => selectedPlan === cardIndex ? { border: '2px solid #1890ff' } : {}; - const cardStyles = { title: { color: themeMode === 'dark' ? '#ffffffd9' : '#000000d9', @@ -363,7 +374,6 @@ const UpgradePlans = () => { title={{t('freePlan')}} onClick={() => setSelectedCard(paddlePlans.FREE)} > -
$ 0.00 @@ -389,7 +399,6 @@ const UpgradePlans = () => { {t('annualPlan')}{' '} @@ -401,7 +410,6 @@ const UpgradePlans = () => { onClick={() => setSelectedCard(paddlePlans.ANNUAL)} >
- $ {plans.annual_price} seat / month @@ -442,7 +450,6 @@ const UpgradePlans = () => { hoverable title={{t('monthlyPlan')}} onClick={() => setSelectedCard(paddlePlans.MONTHLY)} - >
@@ -501,7 +508,9 @@ const UpgradePlans = () => { onClick={continueWithPaddlePlan} disabled={billingInfo?.plan_id === plans.annual_plan_id} > - {billingInfo?.status === SUBSCRIPTION_STATUS.ACTIVE ? t('changeToPlan', {plan: t('annualPlan')}) : t('continueWith', {plan: t('annualPlan')})} + {billingInfo?.status === SUBSCRIPTION_STATUS.ACTIVE + ? t('changeToPlan', { plan: t('annualPlan') }) + : t('continueWith', { plan: t('annualPlan') })} )} {selectedPlan === paddlePlans.MONTHLY && ( @@ -512,7 +521,9 @@ const UpgradePlans = () => { onClick={continueWithPaddlePlan} disabled={billingInfo?.plan_id === plans.monthly_plan_id} > - {billingInfo?.status === SUBSCRIPTION_STATUS.ACTIVE ? t('changeToPlan', {plan: t('monthlyPlan')}) : t('continueWith', {plan: t('monthlyPlan')})} + {billingInfo?.status === SUBSCRIPTION_STATUS.ACTIVE + ? t('changeToPlan', { plan: t('monthlyPlan') }) + : t('continueWith', { plan: t('monthlyPlan') })} )} diff --git a/worklenz-frontend/src/components/admin-center/configuration/configuration.tsx b/worklenz-frontend/src/components/admin-center/configuration/configuration.tsx index a9a24e24..afa5b51a 100644 --- a/worklenz-frontend/src/components/admin-center/configuration/configuration.tsx +++ b/worklenz-frontend/src/components/admin-center/configuration/configuration.tsx @@ -39,7 +39,7 @@ const Configuration: React.FC = () => { }, []); const handleSave = async (values: any) => { - try { + try { setLoading(true); const res = await adminCenterApiService.updateBillingConfiguration(values); if (res.done) { @@ -75,11 +75,7 @@ const Configuration: React.FC = () => { } style={{ marginTop: '16px' }} > -
+ { showSearch placeholder="Country" optionFilterProp="label" - allowClear options={countryOptions} /> diff --git a/worklenz-frontend/src/components/admin-center/teams/settings-drawer/settings-drawer.tsx b/worklenz-frontend/src/components/admin-center/teams/settings-drawer/settings-drawer.tsx index 8a1efc35..ff8e8bc0 100644 --- a/worklenz-frontend/src/components/admin-center/teams/settings-drawer/settings-drawer.tsx +++ b/worklenz-frontend/src/components/admin-center/teams/settings-drawer/settings-drawer.tsx @@ -10,22 +10,17 @@ import { Table, TableProps, Typography, + Tooltip, } from 'antd'; import React, { useState } from 'react'; import { useAppDispatch } from '@/hooks/useAppDispatch'; -import { toggleSettingDrawer, updateTeam } from '@/features/teams/teamSlice'; -import { TeamsType } from '@/types/admin-center/team.types'; import './settings-drawer.css'; -import CustomAvatar from '@/components/CustomAvatar'; -import { teamsApiService } from '@/api/teams/teams.api.service'; import logger from '@/utils/errorLogger'; import { adminCenterApiService } from '@/api/admin-center/admin-center.api.service'; import { IOrganizationTeam, IOrganizationTeamMember, } from '@/types/admin-center/admin-center.types'; -import Avatars from '@/components/avatars/avatars'; -import { AvatarNamesMap } from '@/shared/constants'; import SingleAvatar from '@/components/common/single-avatar/single-avatar'; import { useTranslation } from 'react-i18next'; @@ -68,26 +63,30 @@ const SettingTeamDrawer: React.FC = ({ }; const handleFormSubmit = async (values: any) => { - console.log(values); - // const newTeam: TeamsType = { - // teamId: teamId, - // teamName: values.name, - // membersCount: team?.membersCount || 1, - // members: team?.members || ['Raveesha Dilanka'], - // owner: values.name, - // created: team?.created || new Date(), - // isActive: false, - // }; - // dispatch(updateTeam(newTeam)); - // dispatch(toggleSettingDrawer()); - // form.resetFields(); - // message.success('Team updated!'); + try { + setUpdatingTeam(true); + + const body = { + name: values.name, + teamMembers: teamData?.team_members || [], + }; + + const response = await adminCenterApiService.updateTeam(teamId, body); + + if (response.done) { + setIsSettingDrawerOpen(false); + } + } catch (error) { + logger.error('Error updating team', error); + } finally { + setUpdatingTeam(false); + } }; const roleOptions = [ - { value: 'Admin', label: t('admin') }, - { value: 'Member', label: t('member') }, - { value: 'Owner', label: t('owner') }, + { key: 'Admin', value: 'Admin', label: t('admin') }, + { key: 'Member', value: 'Member', label: t('member') }, + { key: 'Owner', value: 'Owner', label: t('owner'), disabled: true }, ]; const columns: TableProps['columns'] = [ @@ -104,16 +103,56 @@ const SettingTeamDrawer: React.FC = ({ { title: t('role'), key: 'role', - render: (_, record: IOrganizationTeamMember) => ( -
+ render: (_, record: IOrganizationTeamMember) => { + const handleRoleChange = (value: string) => { + if (value === 'Owner') { + return; + } + + // Update the team member's role in teamData + if (teamData && teamData.team_members) { + const updatedMembers = teamData.team_members.map(member => { + if (member.id === record.id) { + return { ...member, role_name: value }; + } + return member; + }); + + setTeamData({ + ...teamData, + team_members: updatedMembers, + }); + } + }; + + const isDisabled = record.role_name === 'Owner' || record.pending_invitation; + const tooltipTitle = + record.role_name === 'Owner' + ? t('cannotChangeOwnerRole') + : record.pending_invitation + ? t('pendingInvitation') + : ''; + + const selectComponent = ( setSearchQuery(e.target.value)} + placeholder="Search members..." + className={` + w-full px-2 py-1 text-xs rounded border + ${ + isDarkMode + ? 'bg-gray-700 border-gray-600 text-gray-100 placeholder-gray-400 focus:border-blue-500' + : 'bg-white border-gray-300 text-gray-900 placeholder-gray-500 focus:border-blue-500' + } + focus:outline-none focus:ring-1 focus:ring-blue-500 + `} + /> +
+ + {/* Members List */} +
+ {filteredMembers && filteredMembers.length > 0 ? ( + filteredMembers.map(member => ( +
{ + if (!member.pending_invitation) { + const isSelected = checkMemberSelected(member.id || ''); + handleMemberToggle(member.id || '', !isSelected); + } + }} + style={{ + // Add visual feedback for immediate response + transition: 'all 0.15s ease-in-out', + }} + > +
+ e.stopPropagation()}> + handleMemberToggle(member.id || '', checked)} + disabled={ + member.pending_invitation || pendingChanges.has(member.id || '') + } + isDarkMode={isDarkMode} + /> + + {pendingChanges.has(member.id || '') && ( +
+
+
+ )} +
+ + + +
+
+ {member.name} +
+
+ {member.email} + {member.pending_invitation && ( + (Pending) + )} +
+
+
+ )) + ) : ( +
+
+ {isLoading ? 'Loading members...' : 'No members found'} +
+
+ )} +
+ + {/* Footer */} +
+ +
+
, + document.body + )} + + ); +}; + +export default PeopleDropdown; \ No newline at end of file diff --git a/worklenz-frontend/src/components/common/project-status-icon/project-status-icon.tsx b/worklenz-frontend/src/components/common/project-status-icon/project-status-icon.tsx index 0be59f72..09974db2 100644 --- a/worklenz-frontend/src/components/common/project-status-icon/project-status-icon.tsx +++ b/worklenz-frontend/src/components/common/project-status-icon/project-status-icon.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import Icon, { CheckCircleOutlined, ClockCircleOutlined, @@ -12,10 +13,23 @@ const iconMap = { 'check-circle': CheckCircleOutlined, }; -const ProjectStatusIcon = ({ iconName, color }: { iconName: string; color: string }) => { - const IconComponent = iconMap[iconName as keyof typeof iconMap]; - if (!IconComponent) return null; - return ; -}; +interface ProjectStatusIconProps { + iconName: string; + color: string; +} + +const ProjectStatusIcon = React.forwardRef( + ({ iconName, color }, ref) => { + const IconComponent = iconMap[iconName as keyof typeof iconMap]; + if (!IconComponent) return null; + return ( + + + + ); + } +); + +ProjectStatusIcon.displayName = 'ProjectStatusIcon'; export default ProjectStatusIcon; diff --git a/worklenz-frontend/src/components/common/template-drawer/template-drawer.css b/worklenz-frontend/src/components/common/template-drawer/template-drawer.css index 7783762c..f8be1fe5 100644 --- a/worklenz-frontend/src/components/common/template-drawer/template-drawer.css +++ b/worklenz-frontend/src/components/common/template-drawer/template-drawer.css @@ -91,4 +91,3 @@ .custom-template-list .selected-custom-template:hover { background-color: var(--color-paleBlue); } - diff --git a/worklenz-frontend/src/components/common/tooltip-wrapper/tooltip-wrapper.tsx b/worklenz-frontend/src/components/common/tooltip-wrapper/tooltip-wrapper.tsx new file mode 100644 index 00000000..ab9d1504 --- /dev/null +++ b/worklenz-frontend/src/components/common/tooltip-wrapper/tooltip-wrapper.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Tooltip, TooltipProps } from 'antd'; + +interface TooltipWrapperProps extends Omit { + children: React.ReactElement; +} + +/** + * TooltipWrapper - A wrapper component that helps avoid findDOMNode warnings in React StrictMode + * + * This component ensures that the child element can properly receive refs from Ant Design's Tooltip + * by wrapping it in a div with a ref when necessary. + */ +const TooltipWrapper = React.forwardRef( + ({ children, ...tooltipProps }, ref) => { + return ( + +
+ {children} +
+
+ ); + } +); + +TooltipWrapper.displayName = 'TooltipWrapper'; + +export default TooltipWrapper; \ No newline at end of file diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoard.css b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoard.css new file mode 100644 index 00000000..5a023bce --- /dev/null +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoard.css @@ -0,0 +1,43 @@ +.enhanced-kanban-board { + width: 100%; + height: 100%; + overflow-x: auto; + overflow-y: hidden; + padding: 16px; + background: var(--ant-color-bg-container); + color: var(--ant-color-text); +} + +.kanban-groups-container { + display: flex; + gap: 16px; + min-height: calc(100vh - 350px); + padding-bottom: 16px; +} + +/* Ensure groups have proper spacing for drop indicators */ +.enhanced-kanban-group { + flex-shrink: 0; +} + +/* Smooth transitions for all drag and drop interactions */ +.enhanced-kanban-board * { + transition: all 0.2s ease; +} + +/* Loading state */ +.enhanced-kanban-board .ant-spin { + display: flex; + justify-content: center; + align-items: center; + min-height: 200px; +} + +/* Empty state */ +.enhanced-kanban-board .ant-empty { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + min-height: 200px; +} diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoard.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoard.tsx new file mode 100644 index 00000000..b048744e --- /dev/null +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoard.tsx @@ -0,0 +1,511 @@ +import React, { useEffect, useState, useMemo } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { Card, Spin, Empty } from 'antd'; +import { + DndContext, + DragOverlay, + DragStartEvent, + DragEndEvent, + DragOverEvent, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, + UniqueIdentifier, + getFirstCollision, + pointerWithin, + rectIntersection, +} from '@dnd-kit/core'; +import { SortableContext, horizontalListSortingStrategy } from '@dnd-kit/sortable'; +import { RootState } from '@/app/store'; +import { + fetchEnhancedKanbanGroups, + reorderEnhancedKanbanTasks, + reorderEnhancedKanbanGroups, + setDragState, + reorderTasks, + reorderGroups, + fetchEnhancedKanbanTaskAssignees, + fetchEnhancedKanbanLabels, +} from '@/features/enhanced-kanban/enhanced-kanban.slice'; +import EnhancedKanbanGroup from './EnhancedKanbanGroup'; +import './EnhancedKanbanBoard.css'; +import { useSocket } from '@/socket/socketContext'; +import { useAppSelector } from '@/hooks/useAppSelector'; +import { SocketEvents } from '@/shared/socket-events'; +import logger from '@/utils/errorLogger'; +import { statusApiService } from '@/api/taskAttributes/status/status.api.service'; +import { ITaskStatusCreateRequest } from '@/types/tasks/task-status-create-request'; +import alertService from '@/services/alerts/alertService'; +import { IGroupBy } from '@/features/enhanced-kanban/enhanced-kanban.slice'; +import EnhancedKanbanCreateSection from './EnhancedKanbanCreateSection'; +import ImprovedTaskFilters from '../task-management/improved-task-filters'; +import { fetchStatusesCategories } from '@/features/taskAttributes/taskStatusSlice'; +import { useFilterDataLoader } from '@/hooks/useFilterDataLoader'; +import { useTaskSocketHandlers } from '@/hooks/useTaskSocketHandlers'; +import { useAuthService } from '@/hooks/useAuth'; + +// Import the TaskListFilters component +const TaskListFilters = React.lazy( + () => import('@/pages/projects/projectView/taskList/task-list-filters/task-list-filters') +); +interface EnhancedKanbanBoardProps { + projectId: string; + className?: string; +} + +const EnhancedKanbanBoard: React.FC = ({ projectId, className = '' }) => { + const dispatch = useDispatch(); + const { taskGroups, loadingGroups, error, dragState, performanceMetrics } = useSelector( + (state: RootState) => state.enhancedKanbanReducer + ); + const { socket } = useSocket(); + const authService = useAuthService(); + const teamId = authService.getCurrentSession()?.team_id; + const groupBy = useSelector((state: RootState) => state.enhancedKanbanReducer.groupBy); + const project = useAppSelector((state: RootState) => state.projectReducer.project); + const { statusCategories, status: existingStatuses } = useAppSelector( + state => state.taskStatusReducer + ); + const themeMode = useAppSelector(state => state.themeReducer.mode); + + // Load filter data + useFilterDataLoader(); + + // Set up socket event handlers for real-time updates + useTaskSocketHandlers(); + + // Local state for drag overlay + const [activeTask, setActiveTask] = useState(null); + const [activeGroup, setActiveGroup] = useState(null); + const [overId, setOverId] = useState(null); + + // Sensors for drag and drop + const sensors = useSensors( + useSensor(PointerSensor, { + activationConstraint: { + distance: 8, + }, + }), + useSensor(KeyboardSensor) + ); + + useEffect(() => { + if (projectId) { + dispatch(fetchEnhancedKanbanGroups(projectId) as any); + // Load filter data for enhanced kanban + dispatch(fetchEnhancedKanbanTaskAssignees(projectId) as any); + dispatch(fetchEnhancedKanbanLabels(projectId) as any); + } + if (!statusCategories.length) { + dispatch(fetchStatusesCategories() as any); + } + }, [dispatch, projectId]); + + // Get all task IDs for sortable context + const allTaskIds = useMemo( + () => taskGroups.flatMap(group => group.tasks.map(task => task.id!)), + [taskGroups] + ); + const allGroupIds = useMemo(() => taskGroups.map(group => group.id), [taskGroups]); + + // Enhanced collision detection + const collisionDetectionStrategy = (args: any) => { + // First, let's see if we're colliding with any droppable areas + const pointerIntersections = pointerWithin(args); + const intersections = + pointerIntersections.length > 0 ? pointerIntersections : rectIntersection(args); + + let overId = getFirstCollision(intersections, 'id'); + + if (overId) { + // Check if we're over a task or a group + const overGroup = taskGroups.find(g => g.id === overId); + + if (overGroup) { + // We're over a group, check if there are tasks in it + if (overGroup.tasks.length > 0) { + // Find the closest task within this group + const taskIntersections = pointerWithin({ + ...args, + droppableContainers: args.droppableContainers.filter( + (container: any) => container.data.current?.type === 'task' + ), + }); + + if (taskIntersections.length > 0) { + overId = taskIntersections[0].id; + } + } + } + } + + return overId ? [{ id: overId }] : []; + }; + + const handleDragStart = (event: DragStartEvent) => { + const { active } = event; + const activeId = active.id as string; + const activeData = active.data.current; + + // Check if dragging a group or a task + if (activeData?.type === 'group') { + // Dragging a group + const foundGroup = taskGroups.find(g => g.id === activeId); + setActiveGroup(foundGroup); + setActiveTask(null); + + dispatch( + setDragState({ + activeTaskId: null, + activeGroupId: activeId, + isDragging: true, + }) + ); + } else { + // Dragging a task + let foundTask = null; + let foundGroup = null; + + for (const group of taskGroups) { + const task = group.tasks.find(t => t.id === activeId); + if (task) { + foundTask = task; + foundGroup = group; + break; + } + } + + setActiveTask(foundTask); + setActiveGroup(null); + + dispatch( + setDragState({ + activeTaskId: activeId, + activeGroupId: foundGroup?.id || null, + isDragging: true, + }) + ); + } + }; + + const handleDragOver = (event: DragOverEvent) => { + const { active, over } = event; + + if (!over) { + setOverId(null); + dispatch(setDragState({ overId: null })); + return; + } + + const activeId = active.id as string; + const overId = over.id as string; + + setOverId(overId); + + // Update over ID in Redux + dispatch(setDragState({ overId })); + }; + + const handleDragEnd = async (event: DragEndEvent) => { + const { active, over } = event; + const activeData = active.data.current; + + // Reset local state + setActiveTask(null); + setActiveGroup(null); + setOverId(null); + + // Reset Redux drag state + dispatch( + setDragState({ + activeTaskId: null, + activeGroupId: null, + overId: null, + isDragging: false, + }) + ); + + if (!over) return; + + const activeId = active.id as string; + const overId = over.id as string; + + // Handle group (column) reordering + if (activeData?.type === 'group') { + // Don't allow reordering if groupBy is phases + if (groupBy === IGroupBy.PHASE) { + return; + } + + const fromIndex = taskGroups.findIndex(g => g.id === activeId); + const toIndex = taskGroups.findIndex(g => g.id === overId); + + if (fromIndex !== -1 && toIndex !== -1 && fromIndex !== toIndex) { + // Create new array with reordered groups + const reorderedGroups = [...taskGroups]; + const [movedGroup] = reorderedGroups.splice(fromIndex, 1); + reorderedGroups.splice(toIndex, 0, movedGroup); + + // Synchronous UI update for immediate feedback + dispatch(reorderGroups({ fromIndex, toIndex, reorderedGroups })); + dispatch(reorderEnhancedKanbanGroups({ fromIndex, toIndex, reorderedGroups }) as any); + + // Prepare column order for API + const columnOrder = reorderedGroups.map(group => group.id); + + // Call API to update status order + try { + const requestBody: ITaskStatusCreateRequest = { + status_order: columnOrder, + }; + + const response = await statusApiService.updateStatusOrder(requestBody, projectId); + if (!response.done) { + // Revert the change if API call fails + const revertedGroups = [...reorderedGroups]; + const [movedBackGroup] = revertedGroups.splice(toIndex, 1); + revertedGroups.splice(fromIndex, 0, movedBackGroup); + dispatch( + reorderGroups({ + fromIndex: toIndex, + toIndex: fromIndex, + reorderedGroups: revertedGroups, + }) + ); + alertService.error('Failed to update column order', 'Please try again'); + } + } catch (error) { + // Revert the change if API call fails + const revertedGroups = [...reorderedGroups]; + const [movedBackGroup] = revertedGroups.splice(toIndex, 1); + revertedGroups.splice(fromIndex, 0, movedBackGroup); + dispatch( + reorderGroups({ + fromIndex: toIndex, + toIndex: fromIndex, + reorderedGroups: revertedGroups, + }) + ); + alertService.error('Failed to update column order', 'Please try again'); + logger.error('Failed to update column order', error); + } + } + return; + } + + // Handle task reordering (within or between groups) + let sourceGroup = null; + let targetGroup = null; + let sourceIndex = -1; + let targetIndex = -1; + + // Find source group and index + for (const group of taskGroups) { + const taskIndex = group.tasks.findIndex(t => t.id === activeId); + if (taskIndex !== -1) { + sourceGroup = group; + sourceIndex = taskIndex; + break; + } + } + + // Find target group and index + for (const group of taskGroups) { + const taskIndex = group.tasks.findIndex(t => t.id === overId); + if (taskIndex !== -1) { + targetGroup = group; + targetIndex = taskIndex; + break; + } + } + + // If dropping on a group (not a task) + if (!targetGroup) { + targetGroup = taskGroups.find(g => g.id === overId); + if (targetGroup) { + targetIndex = targetGroup.tasks.length; // Add to end of group + } + } + + if (!sourceGroup || !targetGroup || sourceIndex === -1) return; + + // Don't do anything if dropping in the same position + if (sourceGroup.id === targetGroup.id && sourceIndex === targetIndex) return; + + // Create updated task arrays + const updatedSourceTasks = [...sourceGroup.tasks]; + const [movedTask] = updatedSourceTasks.splice(sourceIndex, 1); + + let updatedTargetTasks: any[]; + if (sourceGroup.id === targetGroup.id) { + // Moving within the same group + updatedTargetTasks = updatedSourceTasks; + updatedTargetTasks.splice(targetIndex, 0, movedTask); + } else { + // Moving between different groups + updatedTargetTasks = [...targetGroup.tasks]; + updatedTargetTasks.splice(targetIndex, 0, movedTask); + } + + // Synchronous UI update + dispatch( + reorderTasks({ + activeGroupId: sourceGroup.id, + overGroupId: targetGroup.id, + fromIndex: sourceIndex, + toIndex: targetIndex, + task: movedTask, + updatedSourceTasks, + updatedTargetTasks, + }) + ); + dispatch( + reorderEnhancedKanbanTasks({ + activeGroupId: sourceGroup.id, + overGroupId: targetGroup.id, + fromIndex: sourceIndex, + toIndex: targetIndex, + task: movedTask, + updatedSourceTasks, + updatedTargetTasks, + }) as any + ); + + // --- Socket emit for task sort order --- + if (socket && projectId && movedTask) { + // Find sort_order for from and to + const fromSortOrder = movedTask.sort_order; + let toSortOrder = -1; + let toLastIndex = false; + if (targetIndex === targetGroup.tasks.length) { + // Dropping at the end + toSortOrder = -1; + toLastIndex = true; + } else if (targetGroup.tasks[targetIndex]) { + toSortOrder = + typeof targetGroup.tasks[targetIndex].sort_order === 'number' + ? targetGroup.tasks[targetIndex].sort_order! + : -1; + toLastIndex = false; + } else if (targetGroup.tasks.length > 0) { + const lastSortOrder = targetGroup.tasks[targetGroup.tasks.length - 1].sort_order; + toSortOrder = typeof lastSortOrder === 'number' ? lastSortOrder! : -1; + toLastIndex = false; + } + const body = { + project_id: projectId, + from_index: fromSortOrder, + to_index: toSortOrder, + to_last_index: toLastIndex, + from_group: sourceGroup.id, + to_group: targetGroup.id, + group_by: groupBy || 'status', + task: movedTask, + team_id: teamId || project?.team_id || '', + }; + socket.emit(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), body); + } + }; + + if (error) { + return ( + + + + ); + } + + return ( + <> + {/* Task Filters */} +
+ Loading filters...
}> + + +
+
+ {/* Performance Monitor - only show for large datasets */} + {/* {performanceMetrics.totalTasks > 100 && } */} + + {loadingGroups ? ( + +
+ +
+
+ ) : taskGroups.length === 0 ? ( + + + + ) : ( + + +
+ {taskGroups.map(group => ( + + ))} + +
+
+ + + {activeTask && ( +
+ {activeTask.name} +
+ )} + {activeGroup && ( +
+

{activeGroup.name}

+ ({activeGroup.tasks.length}) +
+ )} +
+
+ )} +
+ + ); +}; + +export default EnhancedKanbanBoard; diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx new file mode 100644 index 00000000..e1d7248a --- /dev/null +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx @@ -0,0 +1,331 @@ +import React, { useState, useEffect } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { RootState } from '@/app/store'; +import '../EnhancedKanbanBoard.css'; +import '../EnhancedKanbanGroup.css'; +import '../EnhancedKanbanTaskCard.css'; +import ImprovedTaskFilters from '../../task-management/improved-task-filters'; +import Card from 'antd/es/card'; +import Spin from 'antd/es/spin'; +import Empty from 'antd/es/empty'; +import { reorderGroups, reorderEnhancedKanbanGroups, reorderTasks, reorderEnhancedKanbanTasks, fetchEnhancedKanbanLabels, fetchEnhancedKanbanGroups, fetchEnhancedKanbanTaskAssignees } from '@/features/enhanced-kanban/enhanced-kanban.slice'; +import { fetchStatusesCategories } from '@/features/taskAttributes/taskStatusSlice'; +import { useAppSelector } from '@/hooks/useAppSelector'; +import KanbanGroup from './KanbanGroup'; +import EnhancedKanbanCreateSection from '../EnhancedKanbanCreateSection'; +import { useSocket } from '@/socket/socketContext'; +import { SocketEvents } from '@/shared/socket-events'; +import { useAuthService } from '@/hooks/useAuth'; +import { statusApiService } from '@/api/taskAttributes/status/status.api.service'; +import alertService from '@/services/alerts/alertService'; +import logger from '@/utils/errorLogger'; +import Skeleton from 'antd/es/skeleton/Skeleton'; +import { checkTaskDependencyStatus } from '@/utils/check-task-dependency-status'; +import { useTaskSocketHandlers } from '@/hooks/useTaskSocketHandlers'; + +const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ projectId }) => { + const dispatch = useDispatch(); + const authService = useAuthService(); + const { socket } = useSocket(); + const project = useAppSelector((state: RootState) => state.projectReducer.project); + const groupBy = useSelector((state: RootState) => state.enhancedKanbanReducer.groupBy); + const teamId = authService.getCurrentSession()?.team_id; + const { + taskGroups, + loadingGroups, + error, + } = useSelector((state: RootState) => state.enhancedKanbanReducer); + const [draggedGroupId, setDraggedGroupId] = useState(null); + const [draggedTaskId, setDraggedTaskId] = useState(null); + const [draggedTaskGroupId, setDraggedTaskGroupId] = useState(null); + const [hoveredGroupId, setHoveredGroupId] = useState(null); + const [hoveredTaskIdx, setHoveredTaskIdx] = useState(null); + const [dragType, setDragType] = useState<'group' | 'task' | null>(null); + const { statusCategories, status: existingStatuses } = useAppSelector((state) => state.taskStatusReducer); + + // Set up socket event handlers for real-time updates + useTaskSocketHandlers(); + + useEffect(() => { + if (projectId) { + dispatch(fetchEnhancedKanbanGroups(projectId) as any); + // Load filter data for enhanced kanban + dispatch(fetchEnhancedKanbanTaskAssignees(projectId) as any); + dispatch(fetchEnhancedKanbanLabels(projectId) as any); + } + + if (!statusCategories.length) { + dispatch(fetchStatusesCategories() as any); + } + }, [dispatch, projectId]); + // Reset drag state if taskGroups changes (e.g., real-time update) + useEffect(() => { + setDraggedGroupId(null); + setDraggedTaskId(null); + setDraggedTaskGroupId(null); + setHoveredGroupId(null); + setHoveredTaskIdx(null); + setDragType(null); + }, [taskGroups]); + + // Group drag handlers + const handleGroupDragStart = (e: React.DragEvent, groupId: string) => { + setDraggedGroupId(groupId); + setDragType('group'); + e.dataTransfer.effectAllowed = 'move'; + }; + const handleGroupDragOver = (e: React.DragEvent) => { + if (dragType !== 'group') return; + e.preventDefault(); + }; + const handleGroupDrop = async (e: React.DragEvent, targetGroupId: string) => { + if (dragType !== 'group') return; + e.preventDefault(); + if (!draggedGroupId || draggedGroupId === targetGroupId) return; + // Calculate new order and dispatch + const fromIdx = taskGroups.findIndex(g => g.id === draggedGroupId); + const toIdx = taskGroups.findIndex(g => g.id === targetGroupId); + if (fromIdx === -1 || toIdx === -1) return; + const reorderedGroups = [...taskGroups]; + const [moved] = reorderedGroups.splice(fromIdx, 1); + reorderedGroups.splice(toIdx, 0, moved); + dispatch(reorderGroups({ fromIndex: fromIdx, toIndex: toIdx, reorderedGroups })); + dispatch(reorderEnhancedKanbanGroups({ fromIndex: fromIdx, toIndex: toIdx, reorderedGroups }) as any); + + // API call for group order + try { + const columnOrder = reorderedGroups.map(group => group.id); + const requestBody = { status_order: columnOrder }; + const response = await statusApiService.updateStatusOrder(requestBody, projectId); + if (!response.done) { + // Revert the change if API call fails + const revertedGroups = [...reorderedGroups]; + const [movedBackGroup] = revertedGroups.splice(toIdx, 1); + revertedGroups.splice(fromIdx, 0, movedBackGroup); + dispatch(reorderGroups({ fromIndex: toIdx, toIndex: fromIdx, reorderedGroups: revertedGroups })); + alertService.error('Failed to update column order', 'Please try again'); + } + } catch (error) { + // Revert the change if API call fails + const revertedGroups = [...reorderedGroups]; + const [movedBackGroup] = revertedGroups.splice(toIdx, 1); + revertedGroups.splice(fromIdx, 0, movedBackGroup); + dispatch(reorderGroups({ fromIndex: toIdx, toIndex: fromIdx, reorderedGroups: revertedGroups })); + alertService.error('Failed to update column order', 'Please try again'); + logger.error('Failed to update column order', error); + } + + setDraggedGroupId(null); + setDragType(null); + }; + + // Task drag handlers + const handleTaskDragStart = (e: React.DragEvent, taskId: string, groupId: string) => { + setDraggedTaskId(taskId); + setDraggedTaskGroupId(groupId); + setDragType('task'); + e.dataTransfer.effectAllowed = 'move'; + }; + const handleTaskDragOver = (e: React.DragEvent, groupId: string, taskIdx: number | null) => { + if (dragType !== 'task') return; + e.preventDefault(); + if (draggedTaskId) { + setHoveredGroupId(groupId); + } + if (taskIdx === null) { + setHoveredTaskIdx(0); + } else { + setHoveredTaskIdx(taskIdx); + }; + }; + const handleTaskDrop = async (e: React.DragEvent, targetGroupId: string, targetTaskIdx: number | null) => { + if (dragType !== 'task') return; + e.preventDefault(); + if (!draggedTaskId || !draggedTaskGroupId || hoveredGroupId === null || hoveredTaskIdx === null) return; + + // Calculate new order and dispatch + const sourceGroup = taskGroups.find(g => g.id === draggedTaskGroupId); + const targetGroup = taskGroups.find(g => g.id === targetGroupId); + if (!sourceGroup || !targetGroup) return; + + + const taskIdx = sourceGroup.tasks.findIndex(t => t.id === draggedTaskId); + if (taskIdx === -1) return; + + const movedTask = sourceGroup.tasks[taskIdx]; + if (groupBy === 'status' && movedTask.id) { + if (sourceGroup.id !== targetGroup.id) { + const canContinue = await checkTaskDependencyStatus(movedTask.id, targetGroupId); + if (!canContinue) { + alertService.error( + 'Task is not completed', + 'Please complete the task dependencies before proceeding' + ); + return; + } + } + } + let insertIdx = hoveredTaskIdx; + + // Handle same group reordering + if (sourceGroup.id === targetGroup.id) { + // Create a single updated array for the same group + const updatedTasks = [...sourceGroup.tasks]; + updatedTasks.splice(taskIdx, 1); // Remove from original position + + // Adjust insert index if moving forward in the same array + if (taskIdx < insertIdx) { + insertIdx--; + } + + if (insertIdx < 0) insertIdx = 0; + if (insertIdx > updatedTasks.length) insertIdx = updatedTasks.length; + + updatedTasks.splice(insertIdx, 0, movedTask); // Insert at new position + + dispatch(reorderTasks({ + activeGroupId: sourceGroup.id, + overGroupId: targetGroup.id, + fromIndex: taskIdx, + toIndex: insertIdx, + task: movedTask, + updatedSourceTasks: updatedTasks, + updatedTargetTasks: updatedTasks, + })); + dispatch(reorderEnhancedKanbanTasks({ + activeGroupId: sourceGroup.id, + overGroupId: targetGroup.id, + fromIndex: taskIdx, + toIndex: insertIdx, + task: movedTask, + updatedSourceTasks: updatedTasks, + updatedTargetTasks: updatedTasks, + }) as any); + } else { + // Handle cross-group reordering + const updatedSourceTasks = [...sourceGroup.tasks]; + updatedSourceTasks.splice(taskIdx, 1); + + const updatedTargetTasks = [...targetGroup.tasks]; + if (insertIdx < 0) insertIdx = 0; + if (insertIdx > updatedTargetTasks.length) insertIdx = updatedTargetTasks.length; + updatedTargetTasks.splice(insertIdx, 0, movedTask); + + dispatch(reorderTasks({ + activeGroupId: sourceGroup.id, + overGroupId: targetGroup.id, + fromIndex: taskIdx, + toIndex: insertIdx, + task: movedTask, + updatedSourceTasks, + updatedTargetTasks, + })); + dispatch(reorderEnhancedKanbanTasks({ + activeGroupId: sourceGroup.id, + overGroupId: targetGroup.id, + fromIndex: taskIdx, + toIndex: insertIdx, + task: movedTask, + updatedSourceTasks, + updatedTargetTasks, + }) as any); + } + + // Socket emit for task order + if (socket && projectId && teamId && movedTask) { + let toSortOrder = -1; + let toLastIndex = false; + if (insertIdx === targetGroup.tasks.length) { + toSortOrder = -1; + toLastIndex = true; + } else if (targetGroup.tasks[insertIdx]) { + const sortOrder = targetGroup.tasks[insertIdx].sort_order; + toSortOrder = typeof sortOrder === 'number' ? sortOrder : 0; + toLastIndex = false; + } else if (targetGroup.tasks.length > 0) { + const lastSortOrder = targetGroup.tasks[targetGroup.tasks.length - 1].sort_order; + toSortOrder = typeof lastSortOrder === 'number' ? lastSortOrder : 0; + toLastIndex = false; + } + socket.emit(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), { + project_id: projectId, + from_index: movedTask.sort_order ?? 0, + to_index: toSortOrder, + to_last_index: toLastIndex, + from_group: sourceGroup.id, + to_group: targetGroup.id, + group_by: groupBy || 'status', + task: movedTask, + team_id: teamId, + }); + + } + + setDraggedTaskId(null); + setDraggedTaskGroupId(null); + setHoveredGroupId(null); + setHoveredTaskIdx(null); + setDragType(null); + }; + + const handleDragEnd = () => { + setHoveredGroupId(null); + setHoveredTaskIdx(null); + }; + + // Note: Socket event handlers are now managed by useTaskSocketHandlers hook + // This includes TASK_NAME_CHANGE, QUICK_TASK, and other real-time updates + + if (error) { + return ( + + + + ); + } + + return ( + <> +
+ Loading filters...
}> + + +
+
+ {loadingGroups ? ( +
+
+
+
+
+
+ ) : taskGroups.length === 0 ? ( + + + + ) : ( +
+ {taskGroups.map(group => ( + + ))} + +
+ )} +
+ + ); +}; + +export default EnhancedKanbanBoardNativeDnD; \ No newline at end of file diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/KanbanGroup.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/KanbanGroup.tsx new file mode 100644 index 00000000..f5e443d2 --- /dev/null +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/KanbanGroup.tsx @@ -0,0 +1,608 @@ +import React, { memo, useMemo, useState, useRef, useEffect } from 'react'; +import { useAppSelector } from '@/hooks/useAppSelector'; +import { ITaskListGroup } from '@/types/tasks/taskList.types'; +import TaskCard from './TaskCard'; +import { themeWiseColor } from '@/utils/themeWiseColor'; +import EnhancedKanbanCreateTaskCard from '../EnhancedKanbanCreateTaskCard'; +import { useTranslation } from 'react-i18next'; +import { useAuthService } from '@/hooks/useAuth'; +import useIsProjectManager from '@/hooks/useIsProjectManager'; +import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { ITaskStatusUpdateModel } from '@/types/tasks/task-status-update-model.types'; +import { statusApiService } from '@/api/taskAttributes/status/status.api.service'; +import { fetchStatuses } from '@/features/taskAttributes/taskStatusSlice'; +import logger from '@/utils/errorLogger'; +import { evt_project_board_column_setting_click } from '@/shared/worklenz-analytics-events'; +import { phasesApiService } from '@/api/taskAttributes/phases/phases.api.service'; +import { ITaskPhase } from '@/types/tasks/taskPhase.types'; +import { useMixpanelTracking } from '@/hooks/useMixpanelTracking'; +import { + deleteStatusToggleDrawer, + seletedStatusCategory, +} from '@/features/projects/status/DeleteStatusSlice'; +import { + fetchEnhancedKanbanGroups, + IGroupBy, +} from '@/features/enhanced-kanban/enhanced-kanban.slice'; +import { createPortal } from 'react-dom'; + +// Simple Portal component +const Portal: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const portalRoot = document.getElementById('portal-root') || document.body; + return createPortal(children, portalRoot); +}; + +interface KanbanGroupProps { + group: ITaskListGroup; + onGroupDragStart: (e: React.DragEvent, groupId: string) => void; + onGroupDragOver: (e: React.DragEvent) => void; + onGroupDrop: (e: React.DragEvent, groupId: string) => void; + onTaskDragStart: (e: React.DragEvent, taskId: string, groupId: string) => void; + onTaskDragOver: (e: React.DragEvent, groupId: string, taskIdx: number | null) => void; + onTaskDrop: (e: React.DragEvent, groupId: string, taskIdx: number | null) => void; + onDragEnd: (e: React.DragEvent) => void; + hoveredTaskIdx: number | null; + hoveredGroupId: string | null; +} + +const KanbanGroup: React.FC = memo(({ + group, + onGroupDragStart, + onGroupDragOver, + onGroupDrop, + onTaskDragStart, + onTaskDragOver, + onTaskDrop, + onDragEnd, + hoveredTaskIdx, + hoveredGroupId +}) => { + const [isHover, setIsHover] = useState(false); + const isOwnerOrAdmin = useAuthService().isOwnerOrAdmin(); + const [isEditable, setIsEditable] = useState(false); + const isProjectManager = useIsProjectManager(); + const [isLoading, setIsLoading] = useState(false); + const [name, setName] = useState(group.name); + const inputRef = useRef(null); + const [editName, setEdit] = useState(group.name); + const [isEllipsisActive, setIsEllipsisActive] = useState(false); + const [showDropdown, setShowDropdown] = useState(false); + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); + const dropdownRef = useRef(null); + const themeMode = useAppSelector(state => state.themeReducer.mode); + const dispatch = useAppDispatch(); + const { projectId } = useAppSelector(state => state.projectReducer); + const { groupBy } = useAppSelector(state => state.enhancedKanbanReducer); + const { statusCategories, status } = useAppSelector(state => state.taskStatusReducer); + const { trackMixpanelEvent } = useMixpanelTracking(); + const [showNewCardTop, setShowNewCardTop] = useState(false); + const [showNewCardBottom, setShowNewCardBottom] = useState(false); + const { t } = useTranslation('kanban-board'); + + const headerBackgroundColor = useMemo(() => { + if (themeMode === 'dark') { + return group.color_code_dark || group.color_code || '#1e1e1e'; + } + return group.color_code || '#f5f5f5'; + }, [themeMode, group.color_code, group.color_code_dark]); + + const getUniqueSectionName = (baseName: string): string => { + // Check if the base name already exists + const existingNames = status.map(status => status.name?.toLowerCase()); + + if (!existingNames.includes(baseName.toLowerCase())) { + return baseName; + } + + // If the base name exists, add a number suffix + let counter = 1; + let newName = `${baseName.trim()} (${counter})`; + + while (existingNames.includes(newName.toLowerCase())) { + counter++; + newName = `${baseName.trim()} (${counter})`; + } + + return newName; + }; + + const updateStatus = async (category = group.category_id ?? null) => { + if (!category || !projectId || !group.id) return; + // const sectionName = getUniqueSectionName(name); + const body: ITaskStatusUpdateModel = { + name: name.trim(), + project_id: projectId, + category_id: category, + }; + const res = await statusApiService.updateStatus(group.id, body, projectId); + if (res.done) { + dispatch(fetchEnhancedKanbanGroups(projectId)); + dispatch(fetchStatuses(projectId)); + setName(name.trim()); + } else { + setName(editName); + logger.error('Error updating status', res.message); + } + }; + + const handleChange = async (e: React.ChangeEvent) => { + const taskName = e.target.value; + setName(taskName); + }; + + const handleBlur = async () => { + setIsEditable(false); + if (name === editName) return; + if (name === t('untitledSection')) { + dispatch(fetchEnhancedKanbanGroups(projectId ?? '')); + } + + if (!projectId || !group.id) return; + + if (groupBy === IGroupBy.STATUS) { + await updateStatus(); + } + + if (groupBy === IGroupBy.PHASE) { + const body = { + id: group.id, + name: name, + }; + + const res = await phasesApiService.updateNameOfPhase( + group.id, + body as ITaskPhase, + projectId + ); + if (res.done) { + trackMixpanelEvent(evt_project_board_column_setting_click, { Rename: 'Phase' }); + dispatch(fetchEnhancedKanbanGroups(projectId)); + } + } + }; + + const handlePressEnter = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + setShowNewCardTop(true); + setShowNewCardBottom(false); + handleBlur(); + } + }; + + const handleDeleteSection = async () => { + if (!projectId || !group.id) return; + + try { + if (groupBy === IGroupBy.STATUS) { + const replacingStatusId = ''; + const res = await statusApiService.deleteStatus(group.id, projectId, replacingStatusId); + if (res.message === 'At least one status should exists under each category.') return; + if (res.done) { + dispatch(fetchEnhancedKanbanGroups(projectId)); + } else { + dispatch( + seletedStatusCategory({ + id: group.id, + name: name, + category_id: group.category_id ?? '', + message: res.message ?? '', + }) + ); + dispatch(deleteStatusToggleDrawer()); + } + } else if (groupBy === IGroupBy.PHASE) { + const res = await phasesApiService.deletePhaseOption(group.id, projectId); + if (res.done) { + dispatch(fetchEnhancedKanbanGroups(projectId)); + } + } + } catch (error) { + logger.error('Error deleting section', error); + } + }; + + const handleRename = () => { + setIsEditable(true); + setShowDropdown(false); + setTimeout(() => { + if (inputRef.current) { + inputRef.current.focus(); + inputRef.current.select(); // Select all text on focus + } + }, 100); + }; + + const handleCategoryChange = (categoryId: string) => { + updateStatus(categoryId); + setShowDropdown(false); + }; + + const handleDelete = () => { + setShowDeleteConfirm(true); + setShowDropdown(false); + }; + + // Close dropdown when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setShowDropdown(false); + } + }; + + if (showDropdown) { + document.addEventListener('mousedown', handleClickOutside); + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [showDropdown]); + + return ( +
+ {/* Background layer - z-index 0 */} +
{ e.preventDefault(); onTaskDragOver(e, group.id, null); }} + onDrop={e => { e.preventDefault(); onTaskDrop(e, group.id, null); }} + /> + + {/* Content layer - z-index 1 */} +
+
onGroupDragStart(e, group.id)} + onDragOver={onGroupDragOver} + onDrop={e => onGroupDrop(e, group.id)} + onDragEnd={onDragEnd} + > +
setIsHover(true)} + onMouseLeave={() => setIsHover(false)} + > +
{ + e.stopPropagation(); + if ((isProjectManager || isOwnerOrAdmin) && group.name !== t('unmapped')) + setIsEditable(true); + }} + onMouseDown={e => { + e.stopPropagation(); + }} + > + {isLoading && ( +
+ )} + {isEditable ? ( + { + e.stopPropagation(); + }} + onClick={e => { + e.stopPropagation(); + }} + /> + ) : ( +
{ + e.stopPropagation(); + e.preventDefault(); + }} + onMouseUp={e => { + e.stopPropagation(); + }} + onClick={e => { + e.stopPropagation(); + }} + > + {name} ({group.tasks.length}) +
+ )} +
+ +
+ + + {(isOwnerOrAdmin || isProjectManager) && name !== t('unmapped') && ( +
+ + + {showDropdown && ( +
+
+ + + {groupBy === IGroupBy.STATUS && statusCategories && ( +
+
+ {t('changeCategory')} +
+ {statusCategories.map(status => ( + + ))} +
+ )} + + {groupBy !== IGroupBy.PRIORITY && ( +
+ +
+ )} +
+
+ )} +
+ )} +
+
+
+ + {/* Simple Delete Confirmation */} + {showDeleteConfirm && ( + +
setShowDeleteConfirm(false)} + > +
e.stopPropagation()} + > +
+
+
+ + + +
+
+

+ {t('deleteConfirmationTitle')} +

+
+
+
+ + +
+
+
+
+
+ )} +
+ {/* Create card at top */} + {showNewCardTop && ( + + )} + + {/* If group is empty, render a drop zone */} + {group.tasks.length === 0 && !showNewCardTop && !showNewCardBottom && hoveredGroupId !== group.id && ( +
{ e.preventDefault(); onTaskDragOver(e, group.id, 0); }} + onDrop={e => { e.preventDefault(); onTaskDrop(e, group.id, 0); }} + > + {(isOwnerOrAdmin || isProjectManager) && !showNewCardTop && !showNewCardBottom && ( + + )} +
+ ) + } + + + {/* Drop indicator at the top of the group */} + {hoveredGroupId === group.id && hoveredTaskIdx === 0 && ( +
+
+
+ )} + + {group.tasks.map((task, idx) => ( + + {/* Drop indicator before this card */} + {hoveredGroupId === group.id && hoveredTaskIdx === idx && ( +
onTaskDragOver(e, group.id, idx)} + onDrop={e => onTaskDrop(e, group.id, idx)} + > +
+
+ )} + +
+ ))} + {/* Drop indicator at the end of the group */} + {hoveredGroupId === group.id && hoveredTaskIdx === group.tasks.length && ( +
onTaskDragOver(e, group.id, group.tasks.length)} + onDrop={e => onTaskDrop(e, group.id, group.tasks.length)} + > +
+
+ )} + + {/* Create card at bottom */} + {showNewCardBottom && ( + + )} + + {/* Footer Add Task Button */} + {!showNewCardTop && !showNewCardBottom && group.tasks.length > 0 && ( + + )} +
+
+
+ ); +}); + +KanbanGroup.displayName = 'KanbanGroup'; + +export default KanbanGroup; \ No newline at end of file diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/TaskCard.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/TaskCard.tsx new file mode 100644 index 00000000..4046a8f2 --- /dev/null +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/TaskCard.tsx @@ -0,0 +1,487 @@ +import React, { memo, useCallback, useState, useRef, useEffect } from 'react'; +import { useSelector } from 'react-redux'; +import { RootState } from '@/app/store'; +import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; +import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { setSelectedTaskId, setShowTaskDrawer } from '@/features/task-drawer/task-drawer.slice'; +import { useTranslation } from 'react-i18next'; +import AvatarGroup from '@/components/AvatarGroup'; +import LazyAssigneeSelectorWrapper from '@/components/task-management/lazy-assignee-selector'; +import { format } from 'date-fns'; +import logger from '@/utils/errorLogger'; +import { createPortal } from 'react-dom'; +import { useSocket } from '@/socket/socketContext'; +import { SocketEvents } from '@/shared/socket-events'; +import { getUserSession } from '@/utils/session-helper'; +import { themeWiseColor } from '@/utils/themeWiseColor'; +import { toggleTaskExpansion, fetchBoardSubTasks } from '@/features/enhanced-kanban/enhanced-kanban.slice'; + +// Simple Portal component +const Portal: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const portalRoot = document.getElementById('portal-root') || document.body; + return createPortal(children, portalRoot); +}; + +interface TaskCardProps { + task: IProjectTask; + onTaskDragStart: (e: React.DragEvent, taskId: string, groupId: string) => void; + onTaskDragOver: (e: React.DragEvent, groupId: string, taskIdx: number) => void; + onTaskDrop: (e: React.DragEvent, groupId: string, taskIdx: number) => void; + groupId: string; + idx: number; + onDragEnd: (e: React.DragEvent) => void; // <-- add this +} + +function getDaysInMonth(year: number, month: number) { + return new Date(year, month + 1, 0).getDate(); +} + +function getFirstDayOfWeek(year: number, month: number) { + return new Date(year, month, 1).getDay(); +} + +const TaskCard: React.FC = memo(({ + task, + onTaskDragStart, + onTaskDragOver, + onTaskDrop, + groupId, + idx, + onDragEnd // <-- add this +}) => { + const { socket } = useSocket(); + const themeMode = useSelector((state: RootState) => state.themeReducer.mode); + const { projectId } = useSelector((state: RootState) => state.projectReducer); + const background = themeMode === 'dark' ? '#23272f' : '#fff'; + const color = themeMode === 'dark' ? '#fff' : '#23272f'; + const dispatch = useAppDispatch(); + const { t } = useTranslation('kanban-board'); + + const [showDatePicker, setShowDatePicker] = useState(false); + const [selectedDate, setSelectedDate] = useState( + task.end_date ? new Date(task.end_date) : null + ); + const [isUpdating, setIsUpdating] = useState(false); + const datePickerRef = useRef(null); + const dateButtonRef = useRef(null); + const [dropdownPosition, setDropdownPosition] = useState<{ top: number; left: number } | null>(null); + const [calendarMonth, setCalendarMonth] = useState(() => { + const d = selectedDate || new Date(); + return new Date(d.getFullYear(), d.getMonth(), 1); + }); + const [showSubtasks, setShowSubtasks] = useState(false); + + useEffect(() => { + setSelectedDate(task.end_date ? new Date(task.end_date) : null); + }, [task.end_date]); + + // Close date picker when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (datePickerRef.current && !datePickerRef.current.contains(event.target as Node)) { + setShowDatePicker(false); + } + }; + + if (showDatePicker) { + document.addEventListener('mousedown', handleClickOutside); + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [showDatePicker]); + + useEffect(() => { + if (showDatePicker && dateButtonRef.current) { + const rect = dateButtonRef.current.getBoundingClientRect(); + setDropdownPosition({ + top: rect.bottom + window.scrollY, + left: rect.left + window.scrollX, + }); + } + }, [showDatePicker]); + + const handleCardClick = useCallback((e: React.MouseEvent, id: string) => { + e.stopPropagation(); + dispatch(setSelectedTaskId(id)); + dispatch(setShowTaskDrawer(true)); + }, [dispatch]); + + const handleDateClick = useCallback((e: React.MouseEvent) => { + e.stopPropagation(); + setShowDatePicker(true); + }, []); + + const handleDateChange = useCallback( + (date: Date | null) => { + if (!task.id || !projectId) return; + setIsUpdating(true); + try { + setSelectedDate(date); + socket?.emit( + SocketEvents.TASK_END_DATE_CHANGE.toString(), + JSON.stringify({ + task_id: task.id, + end_date: date, + parent_task: task.parent_task_id, + time_zone: getUserSession()?.timezone_name + ? getUserSession()?.timezone_name + : Intl.DateTimeFormat().resolvedOptions().timeZone, + }) + ); + } catch (error) { + logger.error('Failed to update due date:', error); + } finally { + setIsUpdating(false); + setShowDatePicker(false); + } + }, + [task.id, projectId, socket] + ); + + const handleClearDate = useCallback(() => { + handleDateChange(null); + }, [handleDateChange]); + + const handleToday = useCallback(() => { + handleDateChange(new Date()); + }, [handleDateChange]); + + const handleTomorrow = useCallback(() => { + const tomorrow = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + handleDateChange(tomorrow); + }, [handleDateChange]); + + const handleNextWeek = useCallback(() => { + const nextWeek = new Date(); + nextWeek.setDate(nextWeek.getDate() + 7); + handleDateChange(nextWeek); + }, [handleDateChange]); + + const handleSubTaskExpand = useCallback(() => { + if (task && task.id && projectId) { + if (task.sub_tasks && task.sub_tasks.length > 0 && task.sub_tasks_count && task.sub_tasks_count > 0) { + dispatch(toggleTaskExpansion(task.id)); + } else if (task.sub_tasks_count && task.sub_tasks_count > 0) { + dispatch(toggleTaskExpansion(task.id)); + dispatch(fetchBoardSubTasks({ taskId: task.id, projectId })); + } else { + dispatch(toggleTaskExpansion(task.id)); + } + } + }, [task, projectId, dispatch]); + + const handleSubtaskButtonClick = useCallback((e: React.MouseEvent) => { + e.stopPropagation(); + handleSubTaskExpand(); + }, [handleSubTaskExpand]); + + // Calendar rendering helpers + const year = calendarMonth.getFullYear(); + const month = calendarMonth.getMonth(); + const daysInMonth = getDaysInMonth(year, month); + const firstDayOfWeek = (getFirstDayOfWeek(year, month) + 6) % 7; // Make Monday first + const today = new Date(); + + const weeks: (Date | null)[][] = []; + let week: (Date | null)[] = Array(firstDayOfWeek).fill(null); + for (let day = 1; day <= daysInMonth; day++) { + week.push(new Date(year, month, day)); + if (week.length === 7) { + weeks.push(week); + week = []; + } + } + if (week.length > 0) { + while (week.length < 7) week.push(null); + weeks.push(week); + } + const [isDown, setIsDown] = useState(false); + + return ( + <> +
+
onTaskDragStart(e, task.id!, groupId)} + onDragOver={e => { + e.preventDefault(); + const rect = e.currentTarget.getBoundingClientRect(); + const offsetY = e.clientY - rect.top; + const isDown = offsetY > rect.height / 2; + setIsDown(isDown); + onTaskDragOver(e, groupId, isDown ? idx + 1 : idx); + }} + onDrop={e => onTaskDrop(e, groupId, idx)} + onDragEnd={onDragEnd} // <-- add this + onClick={e => handleCardClick(e, task.id!)} + > +
+
+ {task.labels?.map(label => ( +
+ {label.name} +
+ ))} +
+
+ +
{task.name}
+
+ +
+
+
+ {isUpdating ? ( +
+ ) : ( + selectedDate ? format(selectedDate, 'MMM d, yyyy') : t('noDueDate') + )} +
+ {/* Custom Calendar Popup */} + {showDatePicker && dropdownPosition && ( + +
e.stopPropagation()} + > +
+ + + {calendarMonth.toLocaleString('default', { month: 'long' })} {year} + + +
+
+ {['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].map(d => ( +
{d}
+ ))} + {weeks.map((week, i) => ( + + {week.map((date, j) => { + const isSelected = date && selectedDate && date.toDateString() === selectedDate.toDateString(); + const isToday = date && date.toDateString() === today.toDateString(); + return ( + + ); + })} + + ))} +
+
+ + +
+
+ + +
+
+
+ )} +
+
+ + + {(task.sub_tasks_count ?? 0) > 0 && ( + + )} +
+
+
+
+
+
+ {/* Loading state */} + {task.sub_tasks_loading && ( +
+ )} + {/* Loaded subtasks */} + {!task.sub_tasks_loading && Array.isArray(task.sub_tasks) && task.sub_tasks.length > 0 && ( +
    + {task.sub_tasks.map(sub => ( +
  • handleCardClick(e, sub.id!)} className="flex items-center gap-2 px-2 py-1 rounded hover:bg-gray-50 dark:hover:bg-gray-800"> + {sub.priority_color || sub.priority_color_dark ? ( + + ) : null} + {sub.name} + + {sub.end_date ? format(new Date(sub.end_date), 'MMM d, yyyy') : ''} + + + {sub.names && sub.names.length > 0 && ( + + )} + + +
  • + ))} +
+ )} + {/* Empty state */} + {!task.sub_tasks_loading && (!Array.isArray(task.sub_tasks) || task.sub_tasks.length === 0) && ( +
{t('noSubtasks', 'No subtasks')}
+ )} +
+
+
+ + ); +}); + +TaskCard.displayName = 'TaskCard'; + +export default TaskCard; \ No newline at end of file diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/index.ts b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/index.ts new file mode 100644 index 00000000..1ddc40f1 --- /dev/null +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/index.ts @@ -0,0 +1,3 @@ +export { default } from './EnhancedKanbanBoardNativeDnD'; +export { default as TaskCard } from './TaskCard'; +export { default as KanbanGroup } from './KanbanGroup'; \ No newline at end of file diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanCreateSection.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanCreateSection.tsx new file mode 100644 index 00000000..eb9b87cc --- /dev/null +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanCreateSection.tsx @@ -0,0 +1,292 @@ +import React, { useState, useRef, useEffect, useMemo } from 'react'; +import { Button, Flex } from 'antd'; +import { PlusOutlined } from '@ant-design/icons'; +import { useTranslation } from 'react-i18next'; +import { nanoid } from '@reduxjs/toolkit'; +import { DownOutlined } from '@ant-design/icons'; + +import { useAppSelector } from '@/hooks/useAppSelector'; +import { themeWiseColor } from '@/utils/themeWiseColor'; +import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { + IGroupBy, + fetchEnhancedKanbanGroups, +} from '@/features/enhanced-kanban/enhanced-kanban.slice'; +import { statusApiService } from '@/api/taskAttributes/status/status.api.service'; +import { createStatus, fetchStatuses } from '@/features/taskAttributes/taskStatusSlice'; +import { ALPHA_CHANNEL } from '@/shared/constants'; +import logger from '@/utils/errorLogger'; +import { phasesApiService } from '@/api/taskAttributes/phases/phases.api.service'; +import { useAuthService } from '@/hooks/useAuth'; +import useIsProjectManager from '@/hooks/useIsProjectManager'; + +const EnhancedKanbanCreateSection: React.FC = () => { + const { t } = useTranslation('kanban-board'); + + const themeMode = useAppSelector(state => state.themeReducer.mode); + const { projectId } = useAppSelector(state => state.projectReducer); + const groupBy = useAppSelector(state => state.enhancedKanbanReducer.groupBy); + const { statusCategories, status: existingStatuses } = useAppSelector( + state => state.taskStatusReducer + ); + + const dispatch = useAppDispatch(); + const isOwnerorAdmin = useAuthService().isOwnerOrAdmin(); + const isProjectManager = useIsProjectManager(); + + const [isAdding, setIsAdding] = useState(false); + const [sectionName, setSectionName] = useState(''); + const [selectedCategoryId, setSelectedCategoryId] = useState(''); + const [showCategoryDropdown, setShowCategoryDropdown] = useState(false); + const inputRef = useRef(null); + const dropdownRef = useRef(null); + const categoryDropdownRef = useRef(null); + + // Find selected category object + const selectedCategory = statusCategories?.find(cat => cat.id === selectedCategoryId); + + // Compute header background color + const headerBackgroundColor = React.useMemo(() => { + if (!selectedCategory) return themeWiseColor('#f5f5f5', '#1e1e1e', themeMode); + return selectedCategory.color_code || (themeMode === 'dark' ? '#1e1e1e' : '#f5f5f5'); + }, [themeMode, selectedCategory]); + + // Focus input when adding + useEffect(() => { + if (isAdding && inputRef.current) { + inputRef.current.focus(); + inputRef.current.select(); + } + }, [isAdding]); + + // Close on outside click (for both input and category dropdown) + useEffect(() => { + if (!isAdding && !showCategoryDropdown) return; + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) && + inputRef.current && + !inputRef.current.contains(event.target as Node) && + (!categoryDropdownRef.current || !categoryDropdownRef.current.contains(event.target as Node)) + ) { + setIsAdding(false); + setSectionName(''); + setSelectedCategoryId(''); + setShowCategoryDropdown(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, [isAdding, showCategoryDropdown]); + + // Don't show for priority grouping or if user doesn't have permissions + if (groupBy === IGroupBy.PRIORITY || (!isOwnerorAdmin && !isProjectManager)) { + return null; + } + + const getUniqueSectionName = (baseName: string): string => { + // Check if the base name already exists + const existingNames = existingStatuses.map(status => status.name?.toLowerCase()); + + if (!existingNames.includes(baseName.toLowerCase())) { + return baseName; + } + + // If the base name exists, add a number suffix + let counter = 1; + let newName = `${baseName.trim()} (${counter})`; + + while (existingNames.includes(newName.toLowerCase())) { + counter++; + newName = `${baseName.trim()} (${counter})`; + } + + return newName; + }; + + const handleAddSection = async () => { + setIsAdding(true); + setSectionName(''); + // Default to first category if available + if (statusCategories && statusCategories.length > 0 && typeof statusCategories[0].id === 'string') { + setSelectedCategoryId(statusCategories[0].id); + } else { + setSelectedCategoryId(''); + } + }; + + const handleCreateSection = async () => { + if (!sectionName.trim() || !projectId) return; + const name = getUniqueSectionName(sectionName.trim()); + if (groupBy === IGroupBy.STATUS && selectedCategoryId) { + const body = { + name, + project_id: projectId, + category_id: selectedCategoryId, + }; + try { + const response = await dispatch( + createStatus({ body, currentProjectId: projectId }) + ).unwrap(); + if (response.done && response.body) { + dispatch(fetchEnhancedKanbanGroups(projectId)); + dispatch(fetchStatuses(projectId)); + } + } catch (error) { + logger.error('Failed to create status:', error); + } + } + if (groupBy === IGroupBy.PHASE) { + try { + const response = await phasesApiService.addPhaseOption(projectId, name); + if (response.done && response.body) { + dispatch(fetchEnhancedKanbanGroups(projectId)); + } + } catch (error) { + logger.error('Failed to create phase:', error); + } + } + setIsAdding(false); + setSectionName(''); + setSelectedCategoryId(''); + }; + + const handleInputKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + handleCreateSection(); + } else if (e.key === 'Escape') { + setIsAdding(false); + setSectionName(''); + setSelectedCategoryId(''); + } + }; + + return ( + +
+ {isAdding ? ( +
+ {/* Header-like area */} +
+ {/* Borderless input */} + setSectionName(e.target.value)} + onKeyDown={handleInputKeyDown} + className={`bg-transparent border-none outline-none text-sm font-semibold capitalize min-w-[120px] flex-1 ${themeMode === 'dark' ? 'text-gray-800 placeholder-gray-800' : 'text-gray-800 placeholder-gray-600'}`} + placeholder={t('untitledSection')} + style={{ marginBottom: 0 }} + /> + {/* Category selector dropdown */} + {groupBy === IGroupBy.STATUS && statusCategories && statusCategories.length > 0 && ( +
+ + {showCategoryDropdown && ( +
+
+ {statusCategories.filter(cat => typeof cat.id === 'string').map(cat => ( + + ))} +
+
+ )} +
+ )} +
+
+ + +
+
+ ) : ( + + )} +
+
+ ); +}; + +export default React.memo(EnhancedKanbanCreateSection); diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanCreateSubtaskCard.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanCreateSubtaskCard.tsx new file mode 100644 index 00000000..e15044b9 --- /dev/null +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanCreateSubtaskCard.tsx @@ -0,0 +1,178 @@ +import { Flex, Input, InputRef } from 'antd'; +import React, { useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { updateEnhancedKanbanTaskProgress } from '@/features/enhanced-kanban/enhanced-kanban.slice'; +import { themeWiseColor } from '@/utils/themeWiseColor'; +import { useAppSelector } from '@/hooks/useAppSelector'; +import { getCurrentGroup } from '@/features/enhanced-kanban/enhanced-kanban.slice'; +import { useAuthService } from '@/hooks/useAuth'; +import { ITaskCreateRequest } from '@/types/tasks/task-create-request.types'; +import { useParams } from 'react-router-dom'; +import { useSocket } from '@/socket/socketContext'; +import { SocketEvents } from '@/shared/socket-events'; +import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; +import logger from '@/utils/errorLogger'; + +type EnhancedKanbanCreateSubtaskCardProps = { + sectionId: string; + parentTaskId: string; + setShowNewSubtaskCard: (x: boolean) => void; +}; + +const EnhancedKanbanCreateSubtaskCard = ({ + sectionId, + parentTaskId, + setShowNewSubtaskCard, +}: EnhancedKanbanCreateSubtaskCardProps) => { + const { socket, connected } = useSocket(); + const dispatch = useAppDispatch(); + + const [creatingTask, setCreatingTask] = useState(false); + const [newSubtaskName, setNewSubtaskName] = useState(''); + const [isEnterKeyPressed, setIsEnterKeyPressed] = useState(false); + + const cardRef = useRef(null); + const inputRef = useRef(null); + + const { t } = useTranslation('kanban-board'); + + const themeMode = useAppSelector(state => state.themeReducer.mode); + const { projectId } = useParams(); + const currentSession = useAuthService().getCurrentSession(); + + const createRequestBody = (): ITaskCreateRequest | null => { + if (!projectId || !currentSession) return null; + const body: ITaskCreateRequest = { + project_id: projectId, + name: newSubtaskName, + reporter_id: currentSession.id, + team_id: currentSession.team_id, + }; + + const groupBy = getCurrentGroup(); + if (groupBy === 'status') { + body.status_id = sectionId || undefined; + } else if (groupBy === 'priority') { + body.priority_id = sectionId || undefined; + } else if (groupBy === 'phase') { + body.phase_id = sectionId || undefined; + } + + if (parentTaskId) { + body.parent_task_id = parentTaskId; + } + return body; + }; + + const handleAddSubtask = () => { + if (creatingTask || !projectId || !currentSession || newSubtaskName.trim() === '' || !connected) + return; + + try { + setCreatingTask(true); + const body = createRequestBody(); + if (!body) return; + + socket?.emit(SocketEvents.QUICK_TASK.toString(), JSON.stringify(body)); + socket?.once(SocketEvents.QUICK_TASK.toString(), (task: IProjectTask) => { + if (!task) return; + setCreatingTask(false); + setNewSubtaskName(''); + setTimeout(() => { + inputRef.current?.focus(); + }, 0); + if (task.parent_task_id) { + socket?.emit(SocketEvents.GET_TASK_PROGRESS.toString(), task.parent_task_id); + socket?.once( + SocketEvents.GET_TASK_PROGRESS.toString(), + (data: { + id: string; + complete_ratio: number; + completed_count: number; + total_tasks_count: number; + parent_task: string; + }) => { + if (!data.parent_task) data.parent_task = task.parent_task_id || ''; + dispatch( + updateEnhancedKanbanTaskProgress({ + id: task.id || '', + complete_ratio: data.complete_ratio, + completed_count: data.completed_count, + total_tasks_count: data.total_tasks_count, + parent_task: data.parent_task, + }) + ); + } + ); + } + }); + } catch (error) { + logger.error('Error adding task:', error); + setCreatingTask(false); + } + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + setIsEnterKeyPressed(true); + handleAddSubtask(); + } + }; + + const handleInputBlur = () => { + if (!isEnterKeyPressed && newSubtaskName.length > 0) { + handleAddSubtask(); + } + setIsEnterKeyPressed(false); + }; + + const handleCancelNewCard = (e: React.FocusEvent) => { + if (cardRef.current && !cardRef.current.contains(e.relatedTarget)) { + setNewSubtaskName(''); + setShowNewSubtaskCard(false); + } + }; + + return ( + + e.stopPropagation()} + onChange={e => setNewSubtaskName(e.target.value)} + onKeyDown={e => { + e.stopPropagation(); + if (e.key === 'Enter') { + setIsEnterKeyPressed(true); + handleAddSubtask(); + } + }} + onKeyUp={e => e.stopPropagation()} + onKeyPress={e => e.stopPropagation()} + onBlur={handleInputBlur} + placeholder={t('newSubtaskNamePlaceholder')} + className={`enhanced-kanban-create-subtask-input ${themeMode === 'dark' ? 'dark' : ''}`} + disabled={creatingTask} + autoFocus + /> + + ); +}; + +export default EnhancedKanbanCreateSubtaskCard; diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanCreateTaskCard.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanCreateTaskCard.tsx new file mode 100644 index 00000000..f677c4bc --- /dev/null +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanCreateTaskCard.tsx @@ -0,0 +1,162 @@ +import React, { useRef, useState, useEffect } from 'react'; +import { Button, Flex, Input, InputRef } from 'antd'; +import { useTranslation } from 'react-i18next'; +import { nanoid } from '@reduxjs/toolkit'; +import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { useAppSelector } from '@/hooks/useAppSelector'; +import { themeWiseColor } from '@/utils/themeWiseColor'; +import { useSocket } from '@/socket/socketContext'; +import { SocketEvents } from '@/shared/socket-events'; +import { useAuthService } from '@/hooks/useAuth'; +import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; +import { addTaskToGroup } from '@/features/enhanced-kanban/enhanced-kanban.slice'; +import { ITaskCreateRequest } from '@/types/tasks/task-create-request.types'; + +interface EnhancedKanbanCreateTaskCardProps { + sectionId: string; + setShowNewCard: (x: boolean) => void; + position?: 'top' | 'bottom'; +} + +const EnhancedKanbanCreateTaskCard: React.FC = ({ + sectionId, + setShowNewCard, + position = 'bottom', +}) => { + const { t } = useTranslation('kanban-board'); + const dispatch = useAppDispatch(); + const { socket } = useSocket(); + const currentSession = useAuthService().getCurrentSession(); + + const [newTaskName, setNewTaskName] = useState(''); + const [creatingTask, setCreatingTask] = useState(false); + const cardRef = useRef(null); + const inputRef = useRef(null); + + const themeMode = useAppSelector(state => state.themeReducer.mode); + const projectId = useAppSelector(state => state.projectReducer.projectId); + const groupBy = useAppSelector(state => state.enhancedKanbanReducer.groupBy); + + useEffect(() => { + const timer = setTimeout(() => { + inputRef.current?.focus(); + }, 100); + + return () => clearTimeout(timer); + }, []); + + const createRequestBody = (): ITaskCreateRequest | null => { + if (!projectId || !currentSession) return null; + const body: ITaskCreateRequest = { + project_id: projectId, + name: newTaskName.trim(), + reporter_id: currentSession.id, + team_id: currentSession.team_id, + }; + if (groupBy === 'status') body.status_id = sectionId; + else if (groupBy === 'priority') body.priority_id = sectionId; + else if (groupBy === 'phase') body.phase_id = sectionId; + return body; + }; + + const resetForm = () => { + setNewTaskName(''); + setCreatingTask(false); + setShowNewCard(false); + setTimeout(() => { + inputRef.current?.focus(); + }, 100); + }; + + const resetForNextTask = () => { + setNewTaskName(''); + setCreatingTask(false); + // Keep the card visible for creating the next task + setTimeout(() => { + inputRef.current?.focus(); + }, 100); + }; + + const handleAddTask = async () => { + if (creatingTask || !projectId || !currentSession || newTaskName.trim() === '') return; + + const body = createRequestBody(); + if (!body) { + setCreatingTask(true); + setShowNewCard(true); + return; + } + + // Real-time socket event handler + const eventHandler = (task: IProjectTask) => { + // Only reset the form - the global handler will add the task to Redux + socket?.off(SocketEvents.QUICK_TASK.toString(), eventHandler); + resetForNextTask(); + }; + socket?.once(SocketEvents.QUICK_TASK.toString(), eventHandler); + socket?.emit(SocketEvents.QUICK_TASK.toString(), JSON.stringify(body)); + }; + + const handleCancel = () => { + setNewTaskName(''); + setShowNewCard(false); + setCreatingTask(false); + }; + + const handleBlur = () => { + if (newTaskName.trim() === '') { + setCreatingTask(false); + setShowNewCard(false); + } + }; + + return ( + + setNewTaskName(e.target.value)} + onPressEnter={handleAddTask} + onBlur={handleBlur} + placeholder={t('newTaskNamePlaceholder')} + style={{ + width: '100%', + borderRadius: 6, + padding: 8, + }} + disabled={creatingTask} + /> + {newTaskName.trim() && ( + + + + + )} + + ); +}; + +export default EnhancedKanbanCreateTaskCard; diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanGroup.css b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanGroup.css new file mode 100644 index 00000000..c7ed1d3f --- /dev/null +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanGroup.css @@ -0,0 +1,243 @@ +.enhanced-kanban-group { + width: 300px; + min-width: 300px; + max-width: 300px; + background: var(--ant-color-bg-elevated); + border-radius: 8px; + padding: 12px; + border: 1px solid var(--ant-color-border); + box-shadow: 0 1px 2px var(--ant-color-shadow); + transition: all 0.2s ease; + display: flex; + flex-direction: column; +} + +.enhanced-kanban-group.drag-over { + border-color: var(--ant-color-primary); + box-shadow: 0 0 0 2px var(--ant-color-primary-border); +} + +.enhanced-kanban-group.group-dragging { + opacity: 0.5; + z-index: 1000; + box-shadow: 0 8px 24px var(--ant-color-shadow); +} + +.enhanced-kanban-group.group-dragging .enhanced-kanban-group-tasks { + background: var(--ant-color-bg-elevated); +} + +.enhanced-kanban-group-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + padding-bottom: 8px; + border-bottom: 1px solid var(--ant-color-border); + cursor: grab; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + transition: all 0.2s ease; + border-radius: 6px; + padding: 8px 12px; + margin: -8px -8px 4px -8px; +} + +.enhanced-kanban-group-header:active { + cursor: grabbing; +} + +.enhanced-kanban-group-header h3 { + margin: 0; + font-size: 16px; + font-weight: 600; + color: inherit; + text-shadow: 0 1px 2px var(--ant-color-shadow); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 180px; + display: inline-block; + vertical-align: middle; +} + +.task-count { + background: var(--ant-color-bg-container); + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + color: var(--ant-color-text); + font-weight: 500; + border: 1px solid var(--ant-color-border); + opacity: 0.8; +} + +.virtualization-indicator { + background: var(--ant-color-warning); + color: var(--ant-color-warning-text); + padding: 2px 6px; + border-radius: 8px; + font-size: 10px; + font-weight: 600; + cursor: help; + transition: all 0.2s ease; +} + +.virtualization-indicator:hover { + background: var(--ant-color-warning-hover); + transform: scale(1.1); +} + +.enhanced-kanban-group-tasks { + display: flex; + flex-direction: column; + gap: 8px; + min-height: 200px; + max-height: 600px; + transition: all 0.2s ease; + overflow-y: auto; + overflow-x: hidden; + background: transparent; +} + +/* Performance optimizations for large lists */ +.enhanced-kanban-group-tasks.large-list { + contain: layout style paint; + will-change: transform; +} + +/* Drop preview indicators */ +.drop-preview-indicator { + height: 4px; + margin: 4px 0; + display: flex; + align-items: center; + justify-content: center; +} + +.drop-line { + height: 2px; + background: var(--ant-color-primary); + border-radius: 1px; + width: 100%; + box-shadow: 0 0 4px var(--ant-color-primary); + animation: dropPulse 1.5s ease-in-out infinite; +} + +@keyframes dropPulse { + 0%, + 100% { + opacity: 0.6; + transform: scaleX(0.8); + } + 50% { + opacity: 1; + transform: scaleX(1); + } +} + +/* Empty state drop zone */ +.drop-preview-empty { + min-height: 80px; + display: flex; + align-items: center; + justify-content: center; + border: 2px dashed var(--ant-color-border); + border-radius: 6px; + background: var(--ant-color-bg-container); + transition: all 0.2s ease; +} + +.enhanced-kanban-group.drag-over .drop-preview-empty { + border-color: var(--ant-color-primary); +} + +.drop-indicator { + color: var(--ant-color-text-secondary); + font-size: 14px; + font-weight: 500; +} + +.enhanced-kanban-group.drag-over .drop-indicator { + color: var(--ant-color-primary); +} + +/* Group drag overlay */ +.group-drag-overlay { + background: var(--ant-color-bg-elevated); + border: 1px solid var(--ant-color-border); + border-radius: 8px; + padding: 12px; + box-shadow: 0 8px 24px var(--ant-color-shadow); + min-width: 280px; + max-width: 320px; + opacity: 0.9; + pointer-events: none; + z-index: 1000; +} + +.group-drag-overlay .group-header-content { + display: flex; + align-items: center; + gap: 8px; +} + +.group-drag-overlay h3 { + margin: 0; + font-size: 16px; + font-weight: 600; + color: var(--ant-color-text); +} + +.group-drag-overlay .task-count { + background: var(--ant-color-bg-container); + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + color: var(--ant-color-text); + border: 1px solid var(--ant-color-border); +} + +/* Responsive design for different screen sizes */ +@media (max-width: 768px) { + .enhanced-kanban-group { + min-width: 240px; + max-width: 280px; + } + + .enhanced-kanban-group-tasks { + max-height: 400px; + } +} + +@media (max-width: 480px) { + .enhanced-kanban-group { + min-width: 200px; + max-width: 240px; + } + + .enhanced-kanban-group-tasks { + max-height: 300px; + } +} + +.enhanced-kanban-task-card { + width: 100%; + box-sizing: border-box; +} + +.task-title { + font-weight: 500; + color: var(--ant-color-text); + margin-bottom: 4px; + line-height: 1.4; + word-break: break-word; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 220px; + display: inline-block; + vertical-align: middle; +} diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanGroup.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanGroup.tsx new file mode 100644 index 00000000..86b98521 --- /dev/null +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanGroup.tsx @@ -0,0 +1,571 @@ +import React, { useMemo, useRef, useEffect, useState } from 'react'; +import { useDroppable } from '@dnd-kit/core'; +import { + SortableContext, + verticalListSortingStrategy, + useSortable, + defaultAnimateLayoutChanges, +} from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import { ITaskListGroup } from '@/types/tasks/taskList.types'; +import EnhancedKanbanTaskCard from './EnhancedKanbanTaskCard'; +import VirtualizedTaskList from './VirtualizedTaskList'; +import { useAppSelector } from '@/hooks/useAppSelector'; +import './EnhancedKanbanGroup.css'; +import { Badge, Flex, InputRef, MenuProps, Popconfirm } from 'antd'; +import { themeWiseColor } from '@/utils/themeWiseColor'; +import useIsProjectManager from '@/hooks/useIsProjectManager'; +import { useAuthService } from '@/hooks/useAuth'; +import { + DeleteOutlined, + ExclamationCircleFilled, + EditOutlined, + LoadingOutlined, + RetweetOutlined, + MoreOutlined, +} from '@ant-design/icons/lib/icons'; +import { colors } from '@/styles/colors'; +import { Input } from 'antd'; +import { Tooltip } from 'antd'; +import { Typography } from 'antd'; +import { Dropdown } from 'antd'; +import { Button } from 'antd'; +import { PlusOutlined } from '@ant-design/icons/lib/icons'; +import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { useTranslation } from 'react-i18next'; +import { ITaskStatusUpdateModel } from '@/types/tasks/task-status-update-model.types'; +import { statusApiService } from '@/api/taskAttributes/status/status.api.service'; +import { fetchStatuses } from '@/features/taskAttributes/taskStatusSlice'; +import logger from '@/utils/errorLogger'; +import { evt_project_board_column_setting_click } from '@/shared/worklenz-analytics-events'; +import { phasesApiService } from '@/api/taskAttributes/phases/phases.api.service'; +import { ITaskPhase } from '@/types/tasks/taskPhase.types'; +import { useMixpanelTracking } from '@/hooks/useMixpanelTracking'; +import { + deleteStatusToggleDrawer, + seletedStatusCategory, +} from '@/features/projects/status/DeleteStatusSlice'; +import { + fetchEnhancedKanbanGroups, + IGroupBy, +} from '@/features/enhanced-kanban/enhanced-kanban.slice'; +import EnhancedKanbanCreateTaskCard from './EnhancedKanbanCreateTaskCard'; + +interface EnhancedKanbanGroupProps { + group: ITaskListGroup; + activeTaskId?: string | null; + overId?: string | null; +} + +// Performance threshold for virtualization +const VIRTUALIZATION_THRESHOLD = 50; + +const EnhancedKanbanGroup: React.FC = React.memo( + ({ group, activeTaskId, overId }) => { + const [isHover, setIsHover] = useState(false); + const isOwnerOrAdmin = useAuthService().isOwnerOrAdmin(); + const [isEditable, setIsEditable] = useState(false); + const isProjectManager = useIsProjectManager(); + const [isLoading, setIsLoading] = useState(false); + const [name, setName] = useState(group.name); + const inputRef = useRef(null); + const [editName, setEdit] = useState(group.name); + const [isEllipsisActive, setIsEllipsisActive] = useState(false); + const themeMode = useAppSelector(state => state.themeReducer.mode); + const dispatch = useAppDispatch(); + const { projectId } = useAppSelector(state => state.projectReducer); + const { groupBy } = useAppSelector(state => state.enhancedKanbanReducer); + const { statusCategories, status } = useAppSelector(state => state.taskStatusReducer); + const { trackMixpanelEvent } = useMixpanelTracking(); + const [showNewCardTop, setShowNewCardTop] = useState(false); + const [showNewCardBottom, setShowNewCardBottom] = useState(false); + const { t } = useTranslation('kanban-board'); + + const { setNodeRef: setDroppableRef, isOver } = useDroppable({ + id: group.id, + data: { + type: 'group', + group, + }, + }); + + // Add sortable functionality for group header + const { + attributes, + listeners, + setNodeRef: setSortableRef, + transform, + transition, + isDragging: isGroupDragging, + } = useSortable({ + id: group.id, + data: { + type: 'group', + group, + }, + animateLayoutChanges: defaultAnimateLayoutChanges, + }); + + const groupRef = useRef(null); + const [groupHeight, setGroupHeight] = useState(400); + + // Get task IDs for sortable context + const taskIds = group.tasks.map(task => task.id!); + + // Check if this group is the target for dropping + const isTargetGroup = overId === group.id; + const isDraggingOver = isOver || isTargetGroup; + + // Determine if virtualization should be used + const shouldVirtualize = useMemo(() => { + return group.tasks.length > VIRTUALIZATION_THRESHOLD; + }, [group.tasks.length]); + + // Calculate optimal height for virtualization + useEffect(() => { + if (groupRef.current) { + const containerHeight = Math.min( + Math.max(group.tasks.length * 80, 200), // Minimum 200px, scale with tasks + 600 // Maximum 600px + ); + setGroupHeight(containerHeight); + } + }, [group.tasks.length]); + + // Memoize task rendering to prevent unnecessary re-renders + const renderTask = useMemo( + () => (task: any, index: number) => ( + + ), + [activeTaskId, overId] + ); + + // Performance optimization: Only render drop indicators when needed + const shouldShowDropIndicators = isDraggingOver && !shouldVirtualize; + + // Combine refs for the main container + const setRefs = (el: HTMLElement | null) => { + setDroppableRef(el); + setSortableRef(el); + }; + + const style = { + transform: CSS.Transform.toString(transform), + transition, + opacity: isGroupDragging ? 0.5 : 1, + }; + const getUniqueSectionName = (baseName: string): string => { + // Check if the base name already exists + const existingNames = status.map(status => status.name?.toLowerCase()); + + if (!existingNames.includes(baseName.toLowerCase())) { + return baseName; + } + + // If the base name exists, add a number suffix + let counter = 1; + let newName = `${baseName.trim()} (${counter})`; + + while (existingNames.includes(newName.toLowerCase())) { + counter++; + newName = `${baseName.trim()} (${counter})`; + } + + return newName; + }; + const updateStatus = async (category = group.category_id ?? null) => { + if (!category || !projectId || !group.id) return; + const sectionName = getUniqueSectionName(name); + const body: ITaskStatusUpdateModel = { + name: sectionName, + project_id: projectId, + category_id: category, + }; + const res = await statusApiService.updateStatus(group.id, body, projectId); + if (res.done) { + dispatch(fetchEnhancedKanbanGroups(projectId)); + dispatch(fetchStatuses(projectId)); + setName(sectionName); + } else { + setName(editName); + logger.error('Error updating status', res.message); + } + }; + + // Get the appropriate background color based on theme + const headerBackgroundColor = useMemo(() => { + if (themeMode === 'dark') { + return group.color_code_dark || group.color_code || '#1e1e1e'; + } + return group.color_code || '#f5f5f5'; + }, [themeMode, group.color_code, group.color_code_dark]); + + const handleChange = async (e: React.ChangeEvent) => { + const taskName = e.target.value; + setName(taskName); + }; + + const handleBlur = async () => { + if (name === 'Untitled section') { + dispatch(fetchEnhancedKanbanGroups(projectId ?? '')); + } + setIsEditable(false); + + if (!projectId || !group.id) return; + + if (groupBy === IGroupBy.STATUS) { + await updateStatus(); + } + + if (groupBy === IGroupBy.PHASE) { + const body = { + id: group.id, + name: name, + }; + + const res = await phasesApiService.updateNameOfPhase( + group.id, + body as ITaskPhase, + projectId + ); + if (res.done) { + trackMixpanelEvent(evt_project_board_column_setting_click, { Rename: 'Phase' }); + dispatch(fetchEnhancedKanbanGroups(projectId)); + } + } + }; + + const handlePressEnter = () => { + setShowNewCardTop(true); + setShowNewCardBottom(false); + handleBlur(); + }; + const handleDeleteSection = async () => { + if (!projectId || !group.id) return; + + try { + if (groupBy === IGroupBy.STATUS) { + const replacingStatusId = ''; + const res = await statusApiService.deleteStatus(group.id, projectId, replacingStatusId); + if (res.message === 'At least one status should exists under each category.') return; + if (res.done) { + dispatch(fetchEnhancedKanbanGroups(projectId)); + } else { + dispatch( + seletedStatusCategory({ + id: group.id, + name: name, + category_id: group.category_id ?? '', + message: res.message ?? '', + }) + ); + dispatch(deleteStatusToggleDrawer()); + } + } else if (groupBy === IGroupBy.PHASE) { + const res = await phasesApiService.deletePhaseOption(group.id, projectId); + if (res.done) { + dispatch(fetchEnhancedKanbanGroups(projectId)); + } + } + } catch (error) { + logger.error('Error deleting section', error); + } + }; + const items: MenuProps['items'] = [ + { + key: '1', + label: ( +
setIsEditable(true)} + > + {t('rename')} +
+ ), + }, + groupBy === IGroupBy.STATUS && { + key: '2', + icon: , + label: 'Change category', + children: statusCategories?.map(status => ({ + key: status.id, + label: ( + status.id && updateStatus(status.id)} + style={group.category_id === status.id ? { fontWeight: 700 } : {}} + > + + {status.name} + + ), + })), + }, + groupBy !== IGroupBy.PRIORITY && { + key: '3', + label: ( + } + okText={t('deleteConfirmationOk')} + cancelText={t('deleteConfirmationCancel')} + onConfirm={handleDeleteSection} + > + + + {t('delete')} + + + ), + }, + ].filter(Boolean) as MenuProps['items']; + + return ( +
+ {/* section header */} +
+ {/* ({group.tasks.length}) */} + setIsHover(true)} + onMouseLeave={() => setIsHover(false)} + > + { + e.stopPropagation(); + if ((isProjectManager || isOwnerOrAdmin) && group.name !== 'Unmapped') + setIsEditable(true); + }} + onMouseDown={e => { + e.stopPropagation(); + }} + > + {isLoading && } + {isEditable ? ( + { + e.stopPropagation(); + }} + onKeyDown={e => { + e.stopPropagation(); + }} + onClick={e => { + e.stopPropagation(); + }} + /> + ) : ( + + setIsEllipsisActive(ellipsed), + }} + style={{ + minWidth: 185, + textTransform: 'capitalize', + color: themeMode === 'dark' ? '#383838' : '', + display: 'inline-block', + overflow: 'hidden', + userSelect: 'text', + }} + onMouseDown={e => { + e.stopPropagation(); + e.preventDefault(); + }} + onMouseUp={e => { + e.stopPropagation(); + }} + onClick={e => { + e.stopPropagation(); + }} + > + {name} ({group.tasks.length}) + + + )} + + +
+ + + {(isOwnerOrAdmin || isProjectManager) && name !== 'Unmapped' && ( + + + + )} +
+
+ {/*

{group.name}

*/} + + {/* {shouldVirtualize && ( + + ⚡ + + )} */} +
+ +
+ {/* Create card at top */} + {showNewCardTop && (isOwnerOrAdmin || isProjectManager) && ( + + )} + {group.tasks.length === 0 && isDraggingOver && ( +
+
Drop here
+
+ )} + + {shouldVirtualize ? ( + // Use virtualization for large task lists + + + + ) : ( + // Use standard rendering for smaller lists + + {group.tasks.map((task, index) => ( + + {/* Drop indicator before the card if this is the drop target */} + {overId === task.id && ( +
+ )} + + {/* Drop indicator at the end if dropping at the end of the group */} + {index === group.tasks.length - 1 && overId === group.id && ( +
+ )} + + ))} + + )} + {/* Create card at bottom */} + {showNewCardBottom && (isOwnerOrAdmin || isProjectManager) && ( + + )} + {/* Footer Add Task Button */} + {(isOwnerOrAdmin || isProjectManager) && !showNewCardTop && !showNewCardBottom && ( + + )} +
+
+ ); + } +); + +export default EnhancedKanbanGroup; diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanTaskCard.css b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanTaskCard.css new file mode 100644 index 00000000..a8d563dc --- /dev/null +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanTaskCard.css @@ -0,0 +1,127 @@ +.enhanced-kanban-task-card { + background: var(--ant-color-bg-container); + border: 1px solid var(--ant-color-border); + border-radius: 6px; + padding: 12px; + margin-bottom: 8px; + box-shadow: 0 1px 3px var(--ant-color-shadow); + cursor: grab; + transition: all 0.2s ease; + display: flex; + align-items: flex-start; + gap: 8px; + position: relative; +} + +html.light .enhanced-kanban-task-card { + border: 1.5px solid #e1e4e8 !important; /* Asana-like light border */ + box-shadow: 0 1px 4px 0 rgba(60, 64, 67, 0.08), 0 0.5px 1.5px 0 rgba(60, 64, 67, 0.03); + background: #fff !important; +} + +.enhanced-kanban-task-card:hover { + box-shadow: 0 2px 6px var(--ant-color-shadow); + transform: translateY(-1px); +} + +.enhanced-kanban-task-card:active { + cursor: grabbing; +} + +.enhanced-kanban-task-card.dragging { + opacity: 0.5; + box-shadow: 0 4px 12px var(--ant-color-shadow); +} + +.enhanced-kanban-task-card.active { + border-color: var(--ant-color-primary); + box-shadow: 0 0 0 2px var(--ant-color-primary-border); +} + +.enhanced-kanban-task-card.drag-overlay { + cursor: grabbing; + box-shadow: 0 8px 24px var(--ant-color-shadow); + z-index: 1000; +} + +/* Drop target visual feedback */ +.enhanced-kanban-task-card.drop-target { + border-color: var(--ant-color-primary); + background: var(--ant-color-primary-bg); + box-shadow: 0 0 0 2px var(--ant-color-primary-border); + transform: scale(1.02); +} + +.enhanced-kanban-task-card.drop-target::before { + content: ""; + position: absolute; + top: -2px; + left: -2px; + right: -2px; + bottom: -2px; + border: 2px solid var(--ant-color-primary); + border-radius: 8px; + animation: dropTargetPulse 1s ease-in-out infinite; + pointer-events: none; +} + +@keyframes dropTargetPulse { + 0%, + 100% { + opacity: 0.3; + transform: scale(1); + } + 50% { + opacity: 0.6; + transform: scale(1.02); + } +} + +.task-drag-handle { + flex-shrink: 0; + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + cursor: grab; + opacity: 0.6; + transition: opacity 0.2s ease; +} + +.enhanced-kanban-task-card:hover .task-drag-handle { + opacity: 1; +} + +.drag-indicator { + font-size: 12px; + color: var(--ant-color-text-secondary); + line-height: 1; + user-select: none; +} + +.task-content { + flex: 1; + min-width: 0; +} + +.task-title { + font-weight: 500; + color: var(--ant-color-text); + margin-bottom: 4px; + line-height: 1.4; + word-break: break-word; +} + +.task-key { + font-size: 12px; + color: var(--ant-color-text-secondary); + font-family: monospace; + margin-bottom: 4px; +} + +.task-assignees { + font-size: 12px; + color: var(--ant-color-text-tertiary); + margin-top: 4px; +} diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanTaskCard.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanTaskCard.tsx new file mode 100644 index 00000000..c431b321 --- /dev/null +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanTaskCard.tsx @@ -0,0 +1,281 @@ +import React, { useCallback, useMemo, useState } from 'react'; +import { useSortable, defaultAnimateLayoutChanges } from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; +import { useAppSelector } from '@/hooks/useAppSelector'; +import './EnhancedKanbanTaskCard.css'; +import Flex from 'antd/es/flex'; +import Tag from 'antd/es/tag'; +import Tooltip from 'antd/es/tooltip'; +import Progress from 'antd/es/progress'; +import Button from 'antd/es/button'; +import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { setShowTaskDrawer, setSelectedTaskId } from '@/features/task-drawer/task-drawer.slice'; +import PrioritySection from '../board/taskCard/priority-section/priority-section'; +import Typography from 'antd/es/typography'; +import CustomDueDatePicker from '../board/custom-due-date-picker'; +import { themeWiseColor } from '@/utils/themeWiseColor'; +import { ForkOutlined } from '@ant-design/icons'; +import { Dayjs } from 'dayjs'; +import dayjs from 'dayjs'; +import { CaretDownFilled, CaretRightFilled } from '@ant-design/icons'; +import { + fetchBoardSubTasks, + toggleTaskExpansion, +} from '@/features/enhanced-kanban/enhanced-kanban.slice'; +import { Divider } from 'antd'; +import { List } from 'antd'; +import { Skeleton } from 'antd'; +import { PlusOutlined } from '@ant-design/icons'; +import BoardSubTaskCard from '@/pages/projects/projectView/board/board-section/board-sub-task-card/board-sub-task-card'; +import BoardCreateSubtaskCard from '@/pages/projects/projectView/board/board-section/board-sub-task-card/board-create-sub-task-card'; +import { useTranslation } from 'react-i18next'; +import EnhancedKanbanCreateSubtaskCard from './EnhancedKanbanCreateSubtaskCard'; +import LazyAssigneeSelectorWrapper from '@/components/task-management/lazy-assignee-selector'; +import AvatarGroup from '@/components/AvatarGroup'; + +interface EnhancedKanbanTaskCardProps { + task: IProjectTask; + sectionId: string; + isActive?: boolean; + isDragOverlay?: boolean; + isDropTarget?: boolean; +} +// Priority and status colors - moved outside component to avoid recreation +const PRIORITY_COLORS = { + critical: '#ff4d4f', + high: '#ff7a45', + medium: '#faad14', + low: '#52c41a', +} as const; + +const EnhancedKanbanTaskCard: React.FC = React.memo( + ({ task, sectionId, isActive = false, isDragOverlay = false, isDropTarget = false }) => { + const dispatch = useAppDispatch(); + const { t } = useTranslation('kanban-board'); + const themeMode = useAppSelector(state => state.themeReducer.mode); + const [showNewSubtaskCard, setShowNewSubtaskCard] = useState(false); + const [dueDate, setDueDate] = useState( + task?.end_date ? dayjs(task?.end_date) : null + ); + + const projectId = useAppSelector(state => state.projectReducer.projectId); + const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ + id: task.id!, + data: { + type: 'task', + task, + }, + disabled: isDragOverlay, + animateLayoutChanges: defaultAnimateLayoutChanges, + }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + opacity: isDragging ? 0.5 : 1, + backgroundColor: themeMode === 'dark' ? '#292929' : '#fafafa', + }; + + const handleCardClick = useCallback( + (e: React.MouseEvent, id: string) => { + // Prevent the event from propagating to parent elements + e.stopPropagation(); + + // Don't handle click if we're dragging + if (isDragging) return; + dispatch(setSelectedTaskId(id)); + dispatch(setShowTaskDrawer(true)); + }, + [dispatch, isDragging] + ); + + const renderLabels = useMemo(() => { + if (!task?.labels?.length) return null; + + return ( + <> + {task.labels.slice(0, 2).map((label: any) => ( + + + {label.name} + + + ))} + {task.labels.length > 2 && + {task.labels.length - 2}} + + ); + }, [task.labels, themeMode]); + + const handleSubTaskExpand = useCallback(() => { + if (task && task.id && projectId) { + // Check if subtasks are already loaded and we have subtask data + if (task.sub_tasks && task.sub_tasks.length > 0 && task.sub_tasks_count > 0) { + // If subtasks are already loaded, just toggle visibility + dispatch(toggleTaskExpansion(task.id)); + } else if (task.sub_tasks_count > 0) { + // If we have a subtask count but no loaded subtasks, fetch them + dispatch(toggleTaskExpansion(task.id)); + dispatch(fetchBoardSubTasks({ taskId: task.id, projectId })); + } else { + // If no subtasks exist, just toggle visibility (will show empty state) + dispatch(toggleTaskExpansion(task.id)); + } + } + }, [task, projectId, dispatch]); + + const handleSubtaskButtonClick = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation(); + handleSubTaskExpand(); + }, + [handleSubTaskExpand] + ); + + const handleAddSubtaskClick = useCallback((e: React.MouseEvent) => { + e.stopPropagation(); + setShowNewSubtaskCard(true); + }, []); + + return ( +
+
handleCardClick(e, task.id || '')}> + + {renderLabels} + + + = 100 ? 9 : 7} + /> + + + + {/* Action Icons */} +
+ + {task.name} + + + + + + + + + + + {/* Subtask Section - only show if count > 1 */} + {task.sub_tasks_count != null && Number(task.sub_tasks_count) > 1 && ( + + + + )} + + + + {task.show_sub_tasks && ( + + + + {task.sub_tasks_loading && ( + + + + )} + + {!task.sub_tasks_loading && + task?.sub_tasks && + task.sub_tasks.length > 0 && + task.sub_tasks.map((subtask: any) => ( + + ))} + + {!task.sub_tasks_loading && + (!task?.sub_tasks || task.sub_tasks.length === 0) && + task.sub_tasks_count === 0 && ( + +
+ {t('noSubtasks', 'No subtasks')} +
+
+ )} + + {showNewSubtaskCard && ( + + )} +
+ +
+ )} +
+
+
+ ); + } +); + +export default EnhancedKanbanTaskCard; diff --git a/worklenz-frontend/src/components/enhanced-kanban/PerformanceMonitor.css b/worklenz-frontend/src/components/enhanced-kanban/PerformanceMonitor.css new file mode 100644 index 00000000..f8dd177a --- /dev/null +++ b/worklenz-frontend/src/components/enhanced-kanban/PerformanceMonitor.css @@ -0,0 +1,101 @@ +.performance-monitor { + position: fixed; + top: 80px; + right: 16px; + width: 280px; + z-index: 1000; + background: var(--ant-color-bg-elevated); + border: 1px solid var(--ant-color-border); + box-shadow: 0 4px 12px var(--ant-color-shadow); +} + +.performance-monitor-header { + display: flex; + justify-content: space-between; + align-items: center; + font-weight: 600; + color: var(--ant-color-text); +} + +.performance-status { + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.performance-metrics { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; + margin-bottom: 12px; +} + +.performance-metrics .ant-statistic { + text-align: center; +} + +.performance-metrics .ant-statistic-title { + font-size: 12px; + color: var(--ant-color-text-secondary); + margin-bottom: 4px; +} + +.performance-metrics .ant-statistic-content { + font-size: 14px; + color: var(--ant-color-text); +} + +.virtualization-status { + grid-column: 1 / -1; + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 0; + border-top: 1px solid var(--ant-color-border); +} + +.status-label { + font-size: 12px; + color: var(--ant-color-text-secondary); + font-weight: 500; +} + +.performance-tips { + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid var(--ant-color-border); +} + +.performance-tips h4 { + font-size: 12px; + color: var(--ant-color-text); + margin-bottom: 8px; + font-weight: 600; +} + +.performance-tips ul { + margin: 0; + padding-left: 16px; +} + +.performance-tips li { + font-size: 11px; + color: var(--ant-color-text-secondary); + margin-bottom: 4px; + line-height: 1.4; +} + +/* Responsive design */ +@media (max-width: 768px) { + .performance-monitor { + position: static; + width: 100%; + margin-bottom: 16px; + } + + .performance-metrics { + grid-template-columns: 1fr; + gap: 8px; + } +} diff --git a/worklenz-frontend/src/components/enhanced-kanban/PerformanceMonitor.tsx b/worklenz-frontend/src/components/enhanced-kanban/PerformanceMonitor.tsx new file mode 100644 index 00000000..1d203c9c --- /dev/null +++ b/worklenz-frontend/src/components/enhanced-kanban/PerformanceMonitor.tsx @@ -0,0 +1,108 @@ +import React from 'react'; +import { Card, Statistic, Tooltip, Badge } from 'antd'; +import { useSelector } from 'react-redux'; +import { RootState } from '@/app/store'; +import './PerformanceMonitor.css'; + +const PerformanceMonitor: React.FC = () => { + const { performanceMetrics } = useSelector((state: RootState) => state.enhancedKanbanReducer); + + // Only show if there are tasks loaded + if (performanceMetrics.totalTasks === 0) { + return null; + } + + const getPerformanceStatus = () => { + if (performanceMetrics.totalTasks > 1000) return 'critical'; + if (performanceMetrics.totalTasks > 500) return 'warning'; + if (performanceMetrics.totalTasks > 100) return 'good'; + return 'excellent'; + }; + + const getStatusColor = (status: string) => { + switch (status) { + case 'critical': + return 'red'; + case 'warning': + return 'orange'; + case 'good': + return 'blue'; + case 'excellent': + return 'green'; + default: + return 'default'; + } + }; + + const status = getPerformanceStatus(); + const statusColor = getStatusColor(status); + + return ( + + Performance Monitor + +
+ } + > +
+ + + + + + + + + + + + + +
+ Virtualization: + +
+
+
+ + {performanceMetrics.totalTasks > 500 && ( +
+

Performance Tips:

+
    +
  • Use filters to reduce the number of visible tasks
  • +
  • Consider grouping by different criteria
  • +
  • Virtualization is automatically enabled for large groups
  • +
+
+ )} + + ); +}; + +export default React.memo(PerformanceMonitor); diff --git a/worklenz-frontend/src/components/enhanced-kanban/VirtualizedTaskList.css b/worklenz-frontend/src/components/enhanced-kanban/VirtualizedTaskList.css new file mode 100644 index 00000000..478ac4ac --- /dev/null +++ b/worklenz-frontend/src/components/enhanced-kanban/VirtualizedTaskList.css @@ -0,0 +1,60 @@ +.virtualized-task-list { + background: transparent; + border-radius: 6px; + overflow: hidden; +} + +.virtualized-task-row { + padding: 4px 0; + display: flex; + align-items: stretch; +} + +.virtualized-empty-state { + display: flex; + align-items: center; + justify-content: center; + background: var(--ant-color-bg-container); + border-radius: 6px; + border: 2px dashed var(--ant-color-border); +} + +.empty-message { + color: var(--ant-color-text-secondary); + font-size: 14px; + font-weight: 500; +} + +/* Ensure virtualized list works well with drag and drop */ +.virtualized-task-list .react-window__inner { + overflow: visible !important; +} + +/* Performance optimizations */ +.virtualized-task-list * { + will-change: transform; +} + +/* Smooth scrolling */ +.virtualized-task-list { + scroll-behavior: smooth; +} + +/* Custom scrollbar for better UX */ +.virtualized-task-list::-webkit-scrollbar { + width: 6px; +} + +.virtualized-task-list::-webkit-scrollbar-track { + background: var(--ant-color-bg-container); + border-radius: 3px; +} + +.virtualized-task-list::-webkit-scrollbar-thumb { + background: var(--ant-color-border); + border-radius: 3px; +} + +.virtualized-task-list::-webkit-scrollbar-thumb:hover { + background: var(--ant-color-text-tertiary); +} diff --git a/worklenz-frontend/src/components/enhanced-kanban/VirtualizedTaskList.tsx b/worklenz-frontend/src/components/enhanced-kanban/VirtualizedTaskList.tsx new file mode 100644 index 00000000..7269bf2e --- /dev/null +++ b/worklenz-frontend/src/components/enhanced-kanban/VirtualizedTaskList.tsx @@ -0,0 +1,94 @@ +import React, { useMemo, useCallback } from 'react'; +import { FixedSizeList as List } from 'react-window'; +import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; +import EnhancedKanbanTaskCard from './EnhancedKanbanTaskCard'; +import './VirtualizedTaskList.css'; + +interface VirtualizedTaskListProps { + tasks: IProjectTask[]; + height: number; + itemHeight?: number; + activeTaskId?: string | null; + overId?: string | null; + onTaskRender?: (task: IProjectTask, index: number) => void; +} + +const VirtualizedTaskList: React.FC = ({ + tasks, + height, + itemHeight = 80, + activeTaskId, + overId, + onTaskRender, +}) => { + // Memoize task data to prevent unnecessary re-renders + const taskData = useMemo( + () => ({ + tasks, + activeTaskId, + overId, + onTaskRender, + }), + [tasks, activeTaskId, overId, onTaskRender] + ); + + // Row renderer for virtualized list + const Row = useCallback( + ({ index, style }: { index: number; style: React.CSSProperties }) => { + const task = tasks[index]; + if (!task) return null; + + // Call onTaskRender callback if provided + onTaskRender?.(task, index); + + return ( + + ); + }, + [tasks, activeTaskId, overId, onTaskRender] + ); + + // Memoize the list component to prevent unnecessary re-renders + const VirtualizedList = useMemo( + () => ( + + {Row} + + ), + [height, tasks.length, itemHeight, taskData, Row] + ); + + if (tasks.length === 0) { + return ( +
+
+ No tasks in this group +
+
+ ); + } + + return VirtualizedList; +}; + +export default React.memo(VirtualizedTaskList); diff --git a/worklenz-frontend/src/components/home-tasks/statusDropdown/home-tasks-status-dropdown.tsx b/worklenz-frontend/src/components/home-tasks/statusDropdown/home-tasks-status-dropdown.tsx index bc75016d..424d3388 100644 --- a/worklenz-frontend/src/components/home-tasks/statusDropdown/home-tasks-status-dropdown.tsx +++ b/worklenz-frontend/src/components/home-tasks/statusDropdown/home-tasks-status-dropdown.tsx @@ -20,11 +20,9 @@ const HomeTasksStatusDropdown = ({ task, teamId }: HomeTasksStatusDropdownProps) const { t } = useTranslation('task-list-table'); const { socket, connected } = useSocket(); const { homeTasksConfig } = useAppSelector(state => state.homePageReducer); - const { - refetch - } = useGetMyTasksQuery(homeTasksConfig, { - skip: true // Skip automatic queries entirely - }); + const { refetch } = useGetMyTasksQuery(homeTasksConfig, { + skip: false, // Ensure this query runs + }); const [selectedStatus, setSelectedStatus] = useState(undefined); diff --git a/worklenz-frontend/src/components/home-tasks/taskDatePicker/home-tasks-date-picker.tsx b/worklenz-frontend/src/components/home-tasks/taskDatePicker/home-tasks-date-picker.tsx index 604cbce5..37f4d5a3 100644 --- a/worklenz-frontend/src/components/home-tasks/taskDatePicker/home-tasks-date-picker.tsx +++ b/worklenz-frontend/src/components/home-tasks/taskDatePicker/home-tasks-date-picker.tsx @@ -1,110 +1,111 @@ -import { useSocket } from "@/socket/socketContext"; -import { IProjectTask } from "@/types/project/projectTasksViewModel.types"; -import { DatePicker } from "antd"; +import { useSocket } from '@/socket/socketContext'; +import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; +import { DatePicker } from 'antd'; import dayjs from 'dayjs'; import calendar from 'dayjs/plugin/calendar'; import { SocketEvents } from '@/shared/socket-events'; import type { Dayjs } from 'dayjs'; -import { useTranslation } from "react-i18next"; -import { useEffect, useState, useMemo } from "react"; -import { useAppSelector } from "@/hooks/useAppSelector"; -import { useGetMyTasksQuery } from "@/api/home-page/home-page.api.service"; -import { getUserSession } from "@/utils/session-helper"; +import { useTranslation } from 'react-i18next'; +import { useEffect, useState, useMemo } from 'react'; +import { useAppSelector } from '@/hooks/useAppSelector'; +import { useGetMyTasksQuery } from '@/api/home-page/home-page.api.service'; +import { getUserSession } from '@/utils/session-helper'; // Extend dayjs with the calendar plugin dayjs.extend(calendar); type HomeTasksDatePickerProps = { - record: IProjectTask; + record: IProjectTask; }; const HomeTasksDatePicker = ({ record }: HomeTasksDatePickerProps) => { - const { socket, connected } = useSocket(); - const { t } = useTranslation('home'); - const { homeTasksConfig } = useAppSelector(state => state.homePageReducer); - const { refetch } = useGetMyTasksQuery(homeTasksConfig, { - skip: true // Skip automatic queries entirely + const { socket, connected } = useSocket(); + const { t } = useTranslation('home'); + const { homeTasksConfig } = useAppSelector(state => state.homePageReducer); + const { refetch } = useGetMyTasksQuery(homeTasksConfig, { + skip: false, + }); + + // Use useMemo to avoid re-renders when record.end_date is the same + const initialDate = useMemo( + () => (record.end_date ? dayjs(record.end_date) : null), + [record.end_date] + ); + + const [selectedDate, setSelectedDate] = useState(initialDate); + + // Update selected date when record changes + useEffect(() => { + setSelectedDate(initialDate); + }, [initialDate]); + + const handleChangeReceived = (value: any) => { + refetch(); + }; + + useEffect(() => { + socket?.on(SocketEvents.TASK_END_DATE_CHANGE.toString(), handleChangeReceived); + socket?.on(SocketEvents.TASK_STATUS_CHANGE.toString(), handleChangeReceived); + return () => { + socket?.removeListener(SocketEvents.TASK_END_DATE_CHANGE.toString(), handleChangeReceived); + socket?.removeListener(SocketEvents.TASK_STATUS_CHANGE.toString(), handleChangeReceived); + }; + }, [connected]); + + const handleEndDateChanged = (value: Dayjs | null, task: IProjectTask) => { + setSelectedDate(value); + if (!task.id) return; + + const body = { + task_id: task.id, + end_date: value?.format('YYYY-MM-DD'), + parent_task: task.parent_task_id, + time_zone: getUserSession()?.timezone_name + ? getUserSession()?.timezone_name + : Intl.DateTimeFormat().resolvedOptions().timeZone, + }; + socket?.emit(SocketEvents.TASK_END_DATE_CHANGE.toString(), JSON.stringify(body)); + }; + + // Function to dynamically format the date based on the calendar rules + const getFormattedDate = (date: Dayjs | null) => { + if (!date) return ''; + + return date.calendar(null, { + sameDay: '[Today]', + nextDay: '[Tomorrow]', + nextWeek: 'MMM DD', + lastDay: '[Yesterday]', + lastWeek: 'MMM DD', + sameElse: date.year() === dayjs().year() ? 'MMM DD' : 'MMM DD, YYYY', }); - - // Use useMemo to avoid re-renders when record.end_date is the same - const initialDate = useMemo(() => - record.end_date ? dayjs(record.end_date) : null - , [record.end_date]); - - const [selectedDate, setSelectedDate] = useState(initialDate); + }; - // Update selected date when record changes - useEffect(() => { - setSelectedDate(initialDate); - }, [initialDate]); - - const handleChangeReceived = (value: any) => { - refetch(); - }; - - useEffect(() => { - socket?.on(SocketEvents.TASK_END_DATE_CHANGE.toString(), handleChangeReceived); - socket?.on(SocketEvents.TASK_STATUS_CHANGE.toString(), handleChangeReceived); - return () => { - socket?.removeListener(SocketEvents.TASK_END_DATE_CHANGE.toString(), handleChangeReceived); - socket?.removeListener(SocketEvents.TASK_STATUS_CHANGE.toString(), handleChangeReceived); - }; - }, [connected]); - - const handleEndDateChanged = (value: Dayjs | null, task: IProjectTask) => { - setSelectedDate(value); - if (!task.id) return; - - const body = { - task_id: task.id, - end_date: value?.format('YYYY-MM-DD'), - parent_task: task.parent_task_id, - time_zone: getUserSession()?.timezone_name - ? getUserSession()?.timezone_name - : Intl.DateTimeFormat().resolvedOptions().timeZone, - }; - socket?.emit(SocketEvents.TASK_END_DATE_CHANGE.toString(), JSON.stringify(body)); - }; - - // Function to dynamically format the date based on the calendar rules - const getFormattedDate = (date: Dayjs | null) => { - if (!date) return ''; - - return date.calendar(null, { - sameDay: '[Today]', - nextDay: '[Tomorrow]', - nextWeek: 'MMM DD', - lastDay: '[Yesterday]', - lastWeek: 'MMM DD', - sameElse: date.year() === dayjs().year() ? 'MMM DD' : 'MMM DD, YYYY', - }); - }; - - return ( - current.isBefore(dayjs(record.start_date)) : undefined - } - placeholder={t('tasks.dueDatePlaceholder')} - value={selectedDate} - onChange={value => handleEndDateChanged(value || null, record || null)} - format={(value) => getFormattedDate(value)} // Dynamically format the displayed value - style={{ - color: selectedDate - ? selectedDate.isSame(dayjs(), 'day') || selectedDate.isSame(dayjs().add(1, 'day'), 'day') - ? '#52c41a' - : selectedDate.isAfter(dayjs().add(1, 'day'), 'day') - ? undefined - : '#ff4d4f' - : undefined, - width: '125px', // Ensure the input takes full width - }} - inputReadOnly // Prevent manual input to avoid overflow issues - variant={'borderless'} // Make the DatePicker borderless - suffixIcon={null} - /> - ); + return ( + current.isBefore(dayjs(record.start_date)) : undefined + } + placeholder={t('tasks.dueDatePlaceholder')} + value={selectedDate} + onChange={value => handleEndDateChanged(value || null, record || null)} + format={value => getFormattedDate(value)} // Dynamically format the displayed value + style={{ + color: selectedDate + ? selectedDate.isSame(dayjs(), 'day') || selectedDate.isSame(dayjs().add(1, 'day'), 'day') + ? '#52c41a' + : selectedDate.isAfter(dayjs().add(1, 'day'), 'day') + ? undefined + : '#ff4d4f' + : undefined, + width: '125px', // Ensure the input takes full width + }} + inputReadOnly // Prevent manual input to avoid overflow issues + variant={'borderless'} // Make the DatePicker borderless + suffixIcon={null} + /> + ); }; -export default HomeTasksDatePicker; \ No newline at end of file +export default HomeTasksDatePicker; diff --git a/worklenz-frontend/src/components/index.ts b/worklenz-frontend/src/components/index.ts new file mode 100644 index 00000000..57cd8bcd --- /dev/null +++ b/worklenz-frontend/src/components/index.ts @@ -0,0 +1,12 @@ +// Reusable UI Components +export { default as AssigneeSelector } from './AssigneeSelector'; +export { default as Avatar } from './Avatar'; +export { default as AvatarGroup } from './AvatarGroup'; +export { default as Button } from './Button'; +export { default as Checkbox } from './Checkbox'; +export { default as CustomColordLabel } from './CustomColordLabel'; +export { default as CustomNumberLabel } from './CustomNumberLabel'; +export { default as LabelsSelector } from './LabelsSelector'; +export { default as Progress } from './Progress'; +export { default as Tag } from './Tag'; +export { default as Tooltip } from './Tooltip'; diff --git a/worklenz-frontend/src/components/kanban-board-management-v2/SortableKanbanGroup.tsx b/worklenz-frontend/src/components/kanban-board-management-v2/SortableKanbanGroup.tsx new file mode 100644 index 00000000..1fe1c94a --- /dev/null +++ b/worklenz-frontend/src/components/kanban-board-management-v2/SortableKanbanGroup.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { useSortable } from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import KanbanGroup from './kanbanGroup'; +import { ITaskListGroup } from '@/types/tasks/taskList.types'; +import { IGroupBy } from '@/features/tasks/tasks.slice'; + +interface SortableKanbanGroupProps { + group: ITaskListGroup; + projectId: string; + currentGrouping: IGroupBy; + selectedTaskIds: string[]; + onAddTask?: (groupId: string) => void; + onToggleCollapse?: (groupId: string) => void; + onSelectTask?: (taskId: string, selected: boolean) => void; + onToggleSubtasks?: (taskId: string) => void; + activeTaskId?: string | null; +} + +const SortableKanbanGroup: React.FC = props => { + const { group, activeTaskId } = props; + const { setNodeRef, attributes, listeners, transform, transition, isDragging } = useSortable({ + id: group.id, + data: { type: 'group', groupId: group.id }, + }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + opacity: isDragging ? 0.5 : 1, + zIndex: isDragging ? 10 : undefined, + }; + + return ( +
+ +
+ ); +}; + +export default SortableKanbanGroup; diff --git a/worklenz-frontend/src/components/kanban-board-management-v2/kanbanGroup.tsx b/worklenz-frontend/src/components/kanban-board-management-v2/kanbanGroup.tsx new file mode 100644 index 00000000..98f57505 --- /dev/null +++ b/worklenz-frontend/src/components/kanban-board-management-v2/kanbanGroup.tsx @@ -0,0 +1,220 @@ +import React, { useState } from 'react'; +import { useDroppable } from '@dnd-kit/core'; +import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'; +import { Button, Typography } from 'antd'; +import { PlusOutlined, MenuOutlined } from '@ant-design/icons'; +import { ITaskListGroup } from '@/types/tasks/taskList.types'; +import { IGroupBy } from '@/features/tasks/tasks.slice'; +import KanbanTaskCard from './kanbanTaskCard'; + +const { Text } = Typography; + +interface TaskGroupProps { + group: ITaskListGroup; + projectId: string; + currentGrouping: IGroupBy; + selectedTaskIds: string[]; + onAddTask?: (groupId: string) => void; + onToggleCollapse?: (groupId: string) => void; + onSelectTask?: (taskId: string, selected: boolean) => void; + onToggleSubtasks?: (taskId: string) => void; + dragHandleProps?: any; + activeTaskId?: string | null; +} + +const KanbanGroup: React.FC = ({ + group, + projectId, + currentGrouping, + selectedTaskIds, + onAddTask, + onToggleCollapse, + onSelectTask, + onToggleSubtasks, + dragHandleProps, + activeTaskId, +}) => { + const [isCollapsed, setIsCollapsed] = useState(false); + const { setNodeRef, isOver } = useDroppable({ + id: group.id, + data: { + type: 'group', + groupId: group.id, + }, + }); + + // Get task IDs for sortable context + const taskIds = group.tasks.map(task => task.id!); + + // Get group color based on grouping type + const getGroupColor = () => { + if (group.color_code) return group.color_code; + switch (currentGrouping) { + case 'status': + return group.id === 'todo' ? '#faad14' : group.id === 'doing' ? '#1890ff' : '#52c41a'; + case 'priority': + return group.id === 'critical' + ? '#ff4d4f' + : group.id === 'high' + ? '#fa8c16' + : group.id === 'medium' + ? '#faad14' + : '#52c41a'; + case 'phase': + return '#722ed1'; + default: + return '#d9d9d9'; + } + }; + + const handleAddTask = () => { + onAddTask?.(group.id); + }; + + return ( +
+ {/* Group Header */} +
+ {/* Drag handle for column */} +
+ + {/* Tasks as Cards */} + +
+ {group.tasks.length === 0 ? ( +
+ No tasks in this group +
+ ) : ( + group.tasks.map((task, index) => + task.id === activeTaskId ? ( +
+ ) : ( + + ) + ) + )} +
+ + + {/* Add Task Button */} +
+ +
+ + +
+ ); +}; + +export default KanbanGroup; diff --git a/worklenz-frontend/src/components/kanban-board-management-v2/kanbanTaskCard.tsx b/worklenz-frontend/src/components/kanban-board-management-v2/kanbanTaskCard.tsx new file mode 100644 index 00000000..766fbfce --- /dev/null +++ b/worklenz-frontend/src/components/kanban-board-management-v2/kanbanTaskCard.tsx @@ -0,0 +1,420 @@ +import React from 'react'; +import { useSortable } from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import { Avatar, Tag, Progress, Typography, Button, Tooltip, Space } from 'antd'; +import { + HolderOutlined, + MessageOutlined, + PaperClipOutlined, + ClockCircleOutlined, +} from '@ant-design/icons'; +import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; +import { IGroupBy } from '@/features/tasks/tasks.slice'; +import { useTranslation } from 'react-i18next'; + +const { Text } = Typography; + +interface TaskRowProps { + task: IProjectTask; + projectId: string; + groupId: string; + currentGrouping: IGroupBy; + isSelected: boolean; + isDragOverlay?: boolean; + index?: number; + onSelect?: (taskId: string, selected: boolean) => void; + onToggleSubtasks?: (taskId: string) => void; +} + +const KanbanTaskCard: React.FC = ({ + task, + projectId, + groupId, + currentGrouping, + isSelected, + isDragOverlay = false, + index, + onSelect, + onToggleSubtasks, +}) => { + const { t } = useTranslation('task-list-table'); + const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ + id: task.id!, + data: { + type: 'task', + taskId: task.id, + groupId, + }, + disabled: isDragOverlay, + }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + opacity: isDragging ? 0.5 : 1, + }; + + // Format due date + const formatDueDate = (dateString?: string) => { + if (!dateString) return null; + const date = new Date(dateString); + const now = new Date(); + const diffTime = date.getTime() - now.getTime(); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + if (diffDays < 0) { + return { text: `${Math.abs(diffDays)}d overdue`, color: 'error' }; + } else if (diffDays === 0) { + return { text: 'Due today', color: 'warning' }; + } else if (diffDays <= 3) { + return { text: `Due in ${diffDays}d`, color: 'warning' }; + } else { + return { text: `Due ${date.toLocaleDateString()}`, color: 'default' }; + } + }; + const dueDate = formatDueDate(task.end_date); + + return ( +
+
+ + )} +
+
+ + {/* Task Key and Status */} +
+ {task.task_key && ( + + {task.task_key} + + )} + {task.status_name && ( + + {task.status_name} + + )} + {task.priority_name && ( + + {task.priority_name} + + )} +
+ {/* Progress and Due Date */} +
+ {typeof task.complete_ratio === 'number' && ( + + )} + {dueDate && ( + + + {dueDate.text} + + )} +
+ {/* Assignees and Labels */} +
+ {task.assignees && task.assignees.length > 0 && ( + + {task.assignees.map(assignee => ( + + {assignee.name?.charAt(0)?.toUpperCase()} + + ))} + + )} + {task.labels && task.labels.length > 0 && ( +
+ {task.labels.slice(0, 2).map(label => ( + + {label.name} + + ))} + {task.labels.length > 2 && ( + + +{task.labels.length - 2} + + )} +
+ )} +
+ {/* Indicators */} +
+ {task.time_spent_string && ( + + {task.time_spent_string} + + )} + {task.comments_count && task.comments_count > 1 && ( + + + {task.comments_count} + + + )} + {task.attachments_count && task.attachments_count > 1 && ( + + + {task.attachments_count} + + + )} +
+
+
+ {/* Subtasks */} + {task.show_sub_tasks && task.sub_tasks && task.sub_tasks.length > 0 && ( +
+ {task.sub_tasks.map(subtask => ( + + ))} +
+ )} + +
+ ); +}; + +export default KanbanTaskCard; diff --git a/worklenz-frontend/src/components/kanban-board-management-v2/kanbanTaskListBoard.tsx b/worklenz-frontend/src/components/kanban-board-management-v2/kanbanTaskListBoard.tsx new file mode 100644 index 00000000..314f0f72 --- /dev/null +++ b/worklenz-frontend/src/components/kanban-board-management-v2/kanbanTaskListBoard.tsx @@ -0,0 +1,399 @@ +import React, { useEffect, useState, useMemo } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { + DndContext, + DragOverlay, + DragStartEvent, + DragEndEvent, + DragOverEvent, + closestCorners, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, +} from '@dnd-kit/core'; +import { + horizontalListSortingStrategy, + SortableContext, + sortableKeyboardCoordinates, +} from '@dnd-kit/sortable'; +import { Card, Spin, Empty, Flex } from 'antd'; +import { RootState } from '@/app/store'; +import { IGroupBy, setGroup, fetchTaskGroups, reorderTasks } from '@/features/tasks/tasks.slice'; +import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; +import { AppDispatch } from '@/app/store'; +import BoardSectionCard from '@/pages/projects/projectView/board/board-section/board-section-card/board-section-card'; +import BoardCreateSectionCard from '@/pages/projects/projectView/board/board-section/board-section-card/board-create-section-card'; +import { useAuthService } from '@/hooks/useAuth'; +import useIsProjectManager from '@/hooks/useIsProjectManager'; +import BoardViewTaskCard from '@/pages/projects/projectView/board/board-section/board-task-card/board-view-task-card'; +import TaskGroup from '../task-management/TaskGroup'; +import TaskRow from '../task-management/TaskRow'; +import KanbanGroup from './kanbanGroup'; +import KanbanTaskCard from './kanbanTaskCard'; +import SortableKanbanGroup from './SortableKanbanGroup'; + +// Import the TaskListFilters component +const TaskListFilters = React.lazy( + () => import('@/pages/projects/projectView/taskList/task-list-filters/task-list-filters') +); + +interface TaskListBoardProps { + projectId: string; + className?: string; +} + +interface DragState { + activeTask: IProjectTask | null; + activeGroupId: string | null; +} + +const KanbanTaskListBoard: React.FC = ({ projectId, className = '' }) => { + const dispatch = useDispatch(); + const [dragState, setDragState] = useState({ + activeTask: null, + activeGroupId: null, + }); + // New state for active/over ids + const [activeTaskId, setActiveTaskId] = useState(null); + const [overId, setOverId] = useState(null); + + // Redux selectors + + const { taskGroups, groupBy, loadingGroups, error, search, archived } = useSelector( + (state: RootState) => state.boardReducer + ); + + // Selection state + const [selectedTaskIds, setSelectedTaskIds] = useState([]); + + // Drag and Drop sensors + const sensors = useSensors( + useSensor(PointerSensor, { + activationConstraint: { + distance: 8, + }, + }), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }) + ); + const isOwnerorAdmin = useAuthService().isOwnerOrAdmin(); + const isProjectManager = useIsProjectManager(); + + // Fetch task groups when component mounts or dependencies change + useEffect(() => { + if (projectId) { + dispatch(fetchTaskGroups(projectId)); + } + }, [dispatch, projectId, groupBy, search, archived]); + + // Memoized calculations + const allTaskIds = useMemo(() => { + return taskGroups.flatMap(group => group.tasks.map(task => task.id!)); + }, [taskGroups]); + + const totalTasksCount = useMemo(() => { + return taskGroups.reduce((sum, group) => sum + group.tasks.length, 0); + }, [taskGroups]); + + const hasSelection = selectedTaskIds.length > 0; + + // // Handlers + // const handleGroupingChange = (newGroupBy: IGroupBy) => { + // dispatch(setGroup(newGroupBy)); + // }; + + const handleDragStart = (event: DragStartEvent) => { + const { active } = event; + const taskId = active.id as string; + setActiveTaskId(taskId); + setOverId(null); + // Find the task and its group + let activeTask: IProjectTask | null = null; + let activeGroupId: string | null = null; + for (const group of taskGroups) { + const task = group.tasks.find(t => t.id === taskId); + if (task) { + activeTask = task; + activeGroupId = group.id; + break; + } + } + setDragState({ + activeTask, + activeGroupId, + }); + }; + + const handleDragOver = (event: DragOverEvent) => { + setOverId((event.over?.id as string) || null); + }; + + const handleDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + setActiveTaskId(null); + setOverId(null); + setDragState({ + activeTask: null, + activeGroupId: null, + }); + if (!over || !dragState.activeTask || !dragState.activeGroupId) { + return; + } + const activeTaskId = active.id as string; + const overIdVal = over.id as string; + // Find the group and index for drop + let targetGroupId = overIdVal; + let targetIndex = -1; + let isOverTask = false; + // Check if over is a group or a task + const overGroup = taskGroups.find(g => g.id === overIdVal); + if (!overGroup) { + // Dropping on a task, find which group it belongs to + for (const group of taskGroups) { + const taskIndex = group.tasks.findIndex(t => t.id === overIdVal); + if (taskIndex !== -1) { + targetGroupId = group.id; + targetIndex = taskIndex; + isOverTask = true; + break; + } + } + } + const sourceGroup = taskGroups.find(g => g.id === dragState.activeGroupId); + const targetGroup = taskGroups.find(g => g.id === targetGroupId); + if (!sourceGroup || !targetGroup) return; + const sourceIndex = sourceGroup.tasks.findIndex(t => t.id === activeTaskId); + if (sourceIndex === -1) return; + // Calculate new positions + let finalTargetIndex = targetIndex; + if (!isOverTask || finalTargetIndex === -1) { + finalTargetIndex = targetGroup.tasks.length; + } + // If moving within the same group and after itself, adjust index + if (sourceGroup.id === targetGroup.id && sourceIndex < finalTargetIndex) { + finalTargetIndex--; + } + // Create updated task arrays + const updatedSourceTasks = [...sourceGroup.tasks]; + const [movedTask] = updatedSourceTasks.splice(sourceIndex, 1); + let updatedTargetTasks: IProjectTask[]; + if (sourceGroup.id === targetGroup.id) { + updatedTargetTasks = updatedSourceTasks; + updatedTargetTasks.splice(finalTargetIndex, 0, movedTask); + } else { + updatedTargetTasks = [...targetGroup.tasks]; + updatedTargetTasks.splice(finalTargetIndex, 0, movedTask); + } + // Dispatch the reorder action + dispatch( + reorderTasks({ + activeGroupId: sourceGroup.id, + overGroupId: targetGroup.id, + fromIndex: sourceIndex, + toIndex: finalTargetIndex, + task: movedTask, + updatedSourceTasks, + updatedTargetTasks, + }) + ); + }; + + const handleSelectTask = (taskId: string, selected: boolean) => { + setSelectedTaskIds(prev => { + if (selected) { + return [...prev, taskId]; + } else { + return prev.filter(id => id !== taskId); + } + }); + }; + + const handleToggleSubtasks = (taskId: string) => { + // Implementation for toggling subtasks + console.log('Toggle subtasks for task:', taskId); + }; + + if (error) { + return ( + + + + ); + } + + return ( +
+ {/* Task Filters */} + + Loading filters...
}> + + + + + {/* Task Groups Container */} +
+ {loadingGroups ? ( + +
+ +
+
+ ) : taskGroups.length === 0 ? ( + + + + ) : ( + + g.id)} + strategy={horizontalListSortingStrategy} + > +
+ {taskGroups.map(group => ( + + ))} +
+
+ + {dragState.activeTask ? ( + + ) : null} + +
+ )} +
+ + +
+ ); +}; + +export default KanbanTaskListBoard; diff --git a/worklenz-frontend/src/components/navbar/notifications/notifications-drawer/notification/invitation-item.tsx b/worklenz-frontend/src/components/navbar/notifications/notifications-drawer/notification/invitation-item.tsx index ba7e6cbe..894b722f 100644 --- a/worklenz-frontend/src/components/navbar/notifications/notifications-drawer/notification/invitation-item.tsx +++ b/worklenz-frontend/src/components/navbar/notifications/notifications-drawer/notification/invitation-item.tsx @@ -83,7 +83,10 @@ const InvitationItem: React.FC = ({ item, isUnreadNotificat You have been invited to work with {item.team_name}.
{isUnreadNotifications && ( -
+
+ + + + +
+ {/* Project color indicator bar */} +
+ +
+ {/* Project title */} + + {project.name} + + + {/* Client name */} + {project.client_name && ( +
+ + + {project.client_name} + +
+ )} + + {/* Progress section */} +
+
Progress
+ + + {progress}% + +
+ + {/* Meta information grid */} +
+ +
+ +
+ + {completedTasks}/{totalTasks} + + Tasks +
+
+
+ + +
+ +
+ {membersCount} + Members +
+
+
+
+
+ + + ); + }; + + return ( +
+ {groups.map((group, groupIndex) => ( +
+ {/* Enhanced group header */} +
+ + + {group.groupColor && ( +
+ )} +
+ + {group.groupName} + +
+ {group.projects.length} {group.projects.length === 1 ? 'project' : 'projects'} +
+
+ + + + +
+ + {/* Projects grid */} + {group.projects.map(renderProjectCard)} + + {/* Add spacing between groups except for the last one */} + {groupIndex < groups.length - 1 && ( + + )} +
+ ))} +
+ ); +}; + +export default ProjectGroupList; diff --git a/worklenz-frontend/src/components/project-list/project-list-table/project-list-actions/project-list-actions.tsx b/worklenz-frontend/src/components/project-list/project-list-table/project-list-actions/project-list-actions.tsx index 3d2222d0..c447ddeb 100644 --- a/worklenz-frontend/src/components/project-list/project-list-table/project-list-actions/project-list-actions.tsx +++ b/worklenz-frontend/src/components/project-list/project-list-table/project-list-actions/project-list-actions.tsx @@ -1,6 +1,10 @@ import { useGetProjectsQuery } from '@/api/projects/projects.v1.api.service'; import { AppDispatch } from '@/app/store'; -import { fetchProjectData, setProjectId, toggleProjectDrawer } from '@/features/project/project-drawer.slice'; +import { + fetchProjectData, + setProjectId, + toggleProjectDrawer, +} from '@/features/project/project-drawer.slice'; import { toggleArchiveProjectForAll, toggleArchiveProject, @@ -12,7 +16,11 @@ import { IProjectViewModel } from '@/types/project/projectViewModel.types'; import logger from '@/utils/errorLogger'; import { SettingOutlined, InboxOutlined } from '@ant-design/icons'; import { Tooltip, Button, Popconfirm, Space } from 'antd'; -import { evt_projects_archive, evt_projects_archive_all, evt_projects_settings_click } from '@/shared/worklenz-analytics-events'; +import { + evt_projects_archive, + evt_projects_archive_all, + evt_projects_settings_click, +} from '@/shared/worklenz-analytics-events'; import { useMixpanelTracking } from '@/hooks/useMixpanelTracking'; interface ActionButtonsProps { @@ -38,10 +46,25 @@ export const ActionButtons: React.FC = ({ const handleSettingsClick = () => { if (record.id) { + console.log('Opening project drawer for project:', record.id); trackMixpanelEvent(evt_projects_settings_click); + + // Set project ID first dispatch(setProjectId(record.id)); - dispatch(fetchProjectData(record.id)); - dispatch(toggleProjectDrawer()); + + // Then fetch project data + dispatch(fetchProjectData(record.id)) + .unwrap() + .then((projectData) => { + console.log('Project data fetched successfully:', projectData); + // Open drawer after data is fetched + dispatch(toggleProjectDrawer()); + }) + .catch((error) => { + console.error('Failed to fetch project data:', error); + // Still open drawer even if fetch fails, so user can see error state + dispatch(toggleProjectDrawer()); + }); } }; @@ -71,7 +94,9 @@ export const ActionButtons: React.FC = ({ icon={} /> - + = ({ record, t }) => { if (!record.category_name) return '-'; - - const { requestParams } = useAppSelector( - state => state.projectsReducer - ); + + const { requestParams } = useAppSelector(state => state.projectsReducer); const dispatch = useAppDispatch(); const newParams: Partial = {}; const filterByCategory = (categoryId: string | undefined) => { diff --git a/worklenz-frontend/src/components/project-list/project-list-table/project-list-favorite/project-rate-cell.tsx b/worklenz-frontend/src/components/project-list/project-list-table/project-list-favorite/project-rate-cell.tsx index 51858075..c7e9ff89 100644 --- a/worklenz-frontend/src/components/project-list/project-list-table/project-list-favorite/project-rate-cell.tsx +++ b/worklenz-frontend/src/components/project-list/project-list-table/project-list-favorite/project-rate-cell.tsx @@ -37,7 +37,8 @@ export const ProjectRateCell: React.FC<{ ); useEffect(() => { - setIsFavorite(record.favorite);}, [record.favorite]); + setIsFavorite(record.favorite); + }, [record.favorite]); return ( @@ -48,7 +49,7 @@ export const ProjectRateCell: React.FC<{ style={{ backgroundColor: colors.transparent }} shape="circle" icon={} - onClick={(e) => { + onClick={e => { e.stopPropagation(); handleFavorite(); }} @@ -56,4 +57,4 @@ export const ProjectRateCell: React.FC<{ ); -}; \ No newline at end of file +}; diff --git a/worklenz-frontend/src/components/project-task-filters/create-status-button/create-status-button.tsx b/worklenz-frontend/src/components/project-task-filters/create-status-button/create-status-button.tsx index 22924e71..64ac806b 100644 --- a/worklenz-frontend/src/components/project-task-filters/create-status-button/create-status-button.tsx +++ b/worklenz-frontend/src/components/project-task-filters/create-status-button/create-status-button.tsx @@ -5,9 +5,11 @@ import { useAppDispatch } from '@/hooks/useAppDispatch'; import { toggleDrawer } from '../../../features/projects/status/StatusSlice'; import { colors } from '@/styles/colors'; import { useTranslation } from 'react-i18next'; +import { useAppSelector } from '@/hooks/useAppSelector'; const CreateStatusButton = () => { const { t } = useTranslation('task-list-filters'); + const themeMode = useAppSelector(state => state.themeReducer.mode); const dispatch = useAppDispatch(); @@ -19,9 +21,7 @@ const CreateStatusButton = () => { onClick={() => dispatch(toggleDrawer())} icon={ } /> diff --git a/worklenz-frontend/src/components/project-task-filters/create-status-drawer/create-status-drawer.tsx b/worklenz-frontend/src/components/project-task-filters/create-status-drawer/create-status-drawer.tsx index 6945e578..f51925cf 100644 --- a/worklenz-frontend/src/components/project-task-filters/create-status-drawer/create-status-drawer.tsx +++ b/worklenz-frontend/src/components/project-task-filters/create-status-drawer/create-status-drawer.tsx @@ -14,7 +14,7 @@ import { toggleDrawer } from '@/features/projects/status/StatusSlice'; import './create-status-drawer.css'; -import { createStatus, fetchStatusesCategories } from '@/features/taskAttributes/taskStatusSlice'; +import { createStatus, fetchStatusesCategories, fetchStatuses } from '@/features/taskAttributes/taskStatusSlice'; import { ITaskStatusCategory } from '@/types/status.types'; import { useMixpanelTracking } from '@/hooks/useMixpanelTracking'; import useTabSearchParam from '@/hooks/useTabSearchParam'; @@ -56,6 +56,8 @@ const StatusDrawer: React.FC = () => { dispatch(toggleDrawer()); refreshTasks(); dispatch(fetchStatusesCategories()); + // Refetch task statuses to ensure UI reflects the new status + dispatch(fetchStatuses(projectId)); } }; diff --git a/worklenz-frontend/src/components/project-task-filters/delete-status-drawer/delete-status-drawer.tsx b/worklenz-frontend/src/components/project-task-filters/delete-status-drawer/delete-status-drawer.tsx index 48e7ea73..0dd5015c 100644 --- a/worklenz-frontend/src/components/project-task-filters/delete-status-drawer/delete-status-drawer.tsx +++ b/worklenz-frontend/src/components/project-task-filters/delete-status-drawer/delete-status-drawer.tsx @@ -7,140 +7,140 @@ import { fetchStatuses, fetchStatusesCategories } from '@/features/taskAttribute import { useMixpanelTracking } from '@/hooks/useMixpanelTracking'; import useTabSearchParam from '@/hooks/useTabSearchParam'; import { fetchTaskGroups } from '@/features/tasks/tasks.slice'; -import { fetchBoardTaskGroups } from '@/features/board/board-slice'; + import { deleteStatusToggleDrawer } from '@/features/projects/status/DeleteStatusSlice'; import { Drawer, Alert, Card, Select, Button, Typography, Badge } from 'antd'; import { DownOutlined } from '@ant-design/icons'; import { useSelector } from 'react-redux'; import { - deleteSection, - IGroupBy, -} from '@features/board/board-slice'; + deleteSection, + IGroupBy, +} from '@features/enhanced-kanban/enhanced-kanban.slice'; import { statusApiService } from '@/api/taskAttributes/status/status.api.service'; import { phasesApiService } from '@/api/taskAttributes/phases/phases.api.service'; import logger from '@/utils/errorLogger'; +import { fetchEnhancedKanbanGroups } from '@/features/enhanced-kanban/enhanced-kanban.slice'; const { Title, Text } = Typography; const { Option } = Select; const DeleteStatusDrawer: React.FC = () => { - const [currentStatus, setCurrentStatus] = useState(''); - const [deletingStatus, setDeletingStatus] = useState(false); - const dispatch = useAppDispatch(); - const { trackMixpanelEvent } = useMixpanelTracking(); - const { projectView } = useTabSearchParam(); - const [form] = Form.useForm(); - const { t } = useTranslation('task-list-filters'); - const { editableSectionId, groupBy } = useAppSelector(state => state.boardReducer); - const isDelteStatusDrawerOpen = useAppSelector( - state => state.deleteStatusReducer.isDeleteStatusDrawerOpen - ); - const { isDeleteStatusDrawerOpen, status: selectedForDelete } = useAppSelector( - (state) => state.deleteStatusReducer - ); - const { status, statusCategories } = useAppSelector(state => state.taskStatusReducer); - const { projectId } = useAppSelector(state => state.projectReducer); - const themeMode = useAppSelector(state => state.themeReducer.mode); + const [currentStatus, setCurrentStatus] = useState(''); + const [deletingStatus, setDeletingStatus] = useState(false); + const dispatch = useAppDispatch(); + const { trackMixpanelEvent } = useMixpanelTracking(); + const { projectView } = useTabSearchParam(); + const [form] = Form.useForm(); + const { t } = useTranslation('task-list-filters'); + const { editableSectionId, groupBy } = useAppSelector(state => state.enhancedKanbanReducer); + const isDelteStatusDrawerOpen = useAppSelector( + state => state.deleteStatusReducer.isDeleteStatusDrawerOpen + ); + const { isDeleteStatusDrawerOpen, status: selectedForDelete } = useAppSelector( + state => state.deleteStatusReducer + ); + const { status, statusCategories } = useAppSelector(state => state.taskStatusReducer); + const { projectId } = useAppSelector(state => state.projectReducer); + const themeMode = useAppSelector(state => state.themeReducer.mode); - const refreshTasks = useCallback(() => { - if (!projectId) return; - const fetchAction = projectView === 'list' ? fetchTaskGroups : fetchBoardTaskGroups; - dispatch(fetchAction(projectId)); - }, [projectId, projectView, dispatch]); + const refreshTasks = useCallback(() => { + if (!projectId) return; + const fetchAction = projectView === 'list' ? fetchTaskGroups : fetchEnhancedKanbanGroups; + dispatch(fetchAction(projectId) as any); + }, [projectId, projectView, dispatch]); - const handleDrawerOpenChange = () => { - if (status.length === 0) { - dispatch(fetchStatusesCategories()); + const handleDrawerOpenChange = () => { + if (status.length === 0) { + dispatch(fetchStatusesCategories()); + } + }; + + const setReplacingStatus = (value: string) => { + setCurrentStatus(value); + }; + const moveAndDelete = async () => { + const groupId = selectedForDelete?.id; + if (!projectId || !currentStatus || !groupId) return; + setDeletingStatus(true); + try { + if (groupBy === IGroupBy.STATUS) { + const replacingStatusId = currentStatus; + if (!replacingStatusId) return; + const res = await statusApiService.deleteStatus(groupId, projectId, replacingStatusId); + if (res.done) { + dispatch(deleteSection({ sectionId: groupId })); + dispatch(deleteStatusToggleDrawer()); + dispatch(fetchStatuses(projectId)); + refreshTasks(); + dispatch(fetchStatusesCategories()); + } else { + console.error('Error deleting status', res); } - }; - - const setReplacingStatus = (value: string) => { - setCurrentStatus(value); - }; - const moveAndDelete = async () => { - const groupId = selectedForDelete?.id; - if (!projectId || !currentStatus || !groupId) return; - setDeletingStatus(true); - try { - if (groupBy === IGroupBy.STATUS) { - const replacingStatusId = currentStatus; - if (!replacingStatusId) return; - const res = await statusApiService.deleteStatus(groupId, projectId, replacingStatusId); - if (res.done) { - dispatch(deleteSection({ sectionId: groupId })); - dispatch(deleteStatusToggleDrawer()); - dispatch(fetchStatuses(projectId)); - refreshTasks(); - dispatch(fetchStatusesCategories()); - } else{ - console.error('Error deleting status', res); - } - } else if (groupBy === IGroupBy.PHASE) { - const res = await phasesApiService.deletePhaseOption(groupId, projectId); - if (res.done) { - dispatch(deleteSection({ sectionId: groupId })); - } - } - - } catch (error) { - logger.error('Error deleting section', error); - }finally { - setDeletingStatus(false); + } else if (groupBy === IGroupBy.PHASE) { + const res = await phasesApiService.deletePhaseOption(groupId, projectId); + if (res.done) { + dispatch(deleteSection({ sectionId: groupId })); } - }; - useEffect(() => { - setCurrentStatus(status[0]?.id || ''); - }, [isDelteStatusDrawerOpen]); + } + } catch (error) { + logger.error('Error deleting section', error); + } finally { + setDeletingStatus(false); + } + }; + useEffect(() => { + setCurrentStatus(status[0]?.id || ''); + }, [isDelteStatusDrawerOpen]); - return ( - dispatch(deleteStatusToggleDrawer())} - open={isDelteStatusDrawerOpen} - afterOpenChange={handleDrawerOpenChange} + return ( + dispatch(deleteStatusToggleDrawer())} + open={isDelteStatusDrawerOpen} + afterOpenChange={handleDrawerOpenChange} + > + + + + {selectedForDelete?.name} + + <DownOutlined /> + + + ({ - key: item.id, - value: item.id, - name: item.name, - label: ( - - ), - disabled: item.id === selectedForDelete?.id - }))} - /> - - - - - ); + Done + + + + ); }; -export default DeleteStatusDrawer; \ No newline at end of file +export default DeleteStatusDrawer; diff --git a/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/column-configuration-modal.tsx b/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/column-configuration-modal.tsx new file mode 100644 index 00000000..34b317a4 --- /dev/null +++ b/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/column-configuration-modal.tsx @@ -0,0 +1,182 @@ +import React, { useState, useEffect } from 'react'; +import { Modal, Checkbox, Button, Flex, Typography, Space, Divider, message } from 'antd'; +import { SettingOutlined, UpOutlined, DownOutlined } from '@ant-design/icons'; +import { useTranslation } from 'react-i18next'; + +// Configuration interface for column visibility +interface ColumnConfig { + key: string; + label: string; + showInDropdown: boolean; + order: number; + category?: string; +} + +interface ColumnConfigurationModalProps { + open: boolean; + onClose: () => void; + projectId?: string; + onSave: (config: ColumnConfig[]) => void; + currentConfig: ColumnConfig[]; +} + +const ColumnConfigurationModal: React.FC = ({ + open, + onClose, + projectId, + onSave, + currentConfig, +}) => { + const { t } = useTranslation('task-list-filters'); + const [config, setConfig] = useState(currentConfig); + const [hasChanges, setHasChanges] = useState(false); + + useEffect(() => { + setConfig(currentConfig); + setHasChanges(false); + }, [currentConfig, open]); + + const handleToggleColumn = (key: string) => { + const newConfig = config.map(col => + col.key === key ? { ...col, showInDropdown: !col.showInDropdown } : col + ); + setConfig(newConfig); + setHasChanges(true); + }; + + const moveColumn = (key: string, direction: 'up' | 'down') => { + const currentIndex = config.findIndex(col => col.key === key); + if (currentIndex === -1) return; + + const newIndex = direction === 'up' ? currentIndex - 1 : currentIndex + 1; + if (newIndex < 0 || newIndex >= config.length) return; + + const newConfig = [...config]; + [newConfig[currentIndex], newConfig[newIndex]] = [newConfig[newIndex], newConfig[currentIndex]]; + + // Update order numbers + const updatedConfig = newConfig.map((item, index) => ({ + ...item, + order: index + 1, + })); + + setConfig(updatedConfig); + setHasChanges(true); + }; + + const handleSave = () => { + onSave(config); + setHasChanges(false); + message.success('Column configuration saved successfully'); + onClose(); + }; + + const handleReset = () => { + setConfig(currentConfig); + setHasChanges(false); + }; + + const groupedColumns = config.reduce( + (groups, column) => { + const category = column.category || 'other'; + if (!groups[category]) { + groups[category] = []; + } + groups[category].push(column); + return groups; + }, + {} as Record + ); + + const categoryLabels: Record = { + basic: 'Basic Information', + time: 'Time & Estimation', + dates: 'Dates', + other: 'Other', + }; + + return ( + + + Configure Show Fields Dropdown + + } + open={open} + onCancel={onClose} + width={600} + footer={[ + , + , + , + ]} + > +
+ + Configure which columns appear in the "Show Fields" dropdown and their order. Use the + up/down arrows to reorder columns. + +
+ + {Object.entries(groupedColumns).map(([category, columns]) => ( +
+ + {categoryLabels[category] || category} + + + {columns.map((column, index) => ( +
+ handleToggleColumn(column.key)} + style={{ flex: 1 }} + > + {column.label} + + + + Order: {column.order} + + + +
+ ))} +
+ ))} +
+ ); +}; + +export default ColumnConfigurationModal; diff --git a/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/group-by-filter-dropdown.tsx b/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/group-by-filter-dropdown.tsx index 077821db..7d290026 100644 --- a/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/group-by-filter-dropdown.tsx +++ b/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/group-by-filter-dropdown.tsx @@ -45,7 +45,7 @@ const GroupByFilterDropdown = () => { const handleGroupChange = (key: string) => { const group = key as IGroupBy; - + if (projectView === 'list') { setCurrentGroup(group); dispatch(setGroup(group)); @@ -64,7 +64,7 @@ const GroupByFilterDropdown = () => { trigger={['click']} menu={{ items, - onClick: (info) => handleGroupChange(info.key), + onClick: info => handleGroupChange(info.key), selectedKeys: [currentGroup], }} > @@ -72,13 +72,13 @@ const GroupByFilterDropdown = () => { {selectedLabel} - - {(currentGroup === IGroupBy.STATUS || currentGroup === IGroupBy.PHASE) && (isOwnerOrAdmin || isProjectManager) && ( - - {currentGroup === IGroupBy.PHASE && } - {currentGroup === IGroupBy.STATUS && } - - )} + {(currentGroup === IGroupBy.STATUS || currentGroup === IGroupBy.PHASE) && + (isOwnerOrAdmin || isProjectManager) && ( + + {currentGroup === IGroupBy.PHASE && } + {currentGroup === IGroupBy.STATUS && } + + )} ); }; diff --git a/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/labels-filter-dropdown.tsx b/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/labels-filter-dropdown.tsx index 4c2744cc..dc63e9be 100644 --- a/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/labels-filter-dropdown.tsx +++ b/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/labels-filter-dropdown.tsx @@ -11,7 +11,7 @@ import List from 'antd/es/list'; import Space from 'antd/es/space'; import { useSearchParams } from 'react-router-dom'; -import { useMemo, useRef, useState } from 'react'; +import { useMemo, useRef, useState, useEffect } from 'react'; import { useAppSelector } from '@/hooks/useAppSelector'; import { colors } from '@/styles/colors'; import { useTranslation } from 'react-i18next'; @@ -36,6 +36,13 @@ const LabelsFilterDropdown = () => { const tab = searchParams.get('tab'); const projectView = tab === 'tasks-list' ? 'list' : 'kanban'; + // Fetch labels when component mounts or projectId changes + useEffect(() => { + if (projectId) { + dispatch(fetchLabelsByProject(projectId)); + } + }, [dispatch, projectId]); + const filteredLabelData = useMemo(() => { if (projectView === 'list') { return labels.filter(label => label.name?.toLowerCase().includes(searchQuery.toLowerCase())); @@ -81,9 +88,6 @@ const LabelsFilterDropdown = () => { setTimeout(() => { labelInputRef.current?.focus(); }, 0); - if (projectView === 'kanban') { - dispatch(setBoardLabels(labels)); - } } }; diff --git a/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/members-filter-dropdown.tsx b/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/members-filter-dropdown.tsx index c9b2d307..41c920c0 100644 --- a/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/members-filter-dropdown.tsx +++ b/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/members-filter-dropdown.tsx @@ -1,18 +1,18 @@ import { useMemo, useRef, useState, useCallback, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { CaretDownFilled } from '@ant-design/icons'; -import { - Badge, - Button, - Card, - Checkbox, - Dropdown, - Empty, - Flex, - Input, - List, - Space, - Typography +import { + Badge, + Button, + Card, + Checkbox, + Dropdown, + Empty, + Flex, + Input, + List, + Space, + Typography, } from 'antd'; import type { InputRef } from 'antd'; @@ -50,47 +50,49 @@ const MembersFilterDropdown = () => { // Reset task assignees selections const resetTaskMembers = taskAssignees.map(member => ({ ...member, - selected: false + selected: false, })); dispatch(setMembers(resetTaskMembers)); // Reset board assignees selections const resetBoardMembers = boardTaskAssignees.map(member => ({ ...member, - selected: false + selected: false, })); dispatch(setBoardMembers(resetBoardMembers)); } }, [projectId, dispatch]); const selectedCount = useMemo(() => { - return projectView === 'list' ? taskAssignees.filter(member => member.selected).length : boardTaskAssignees.filter(member => member.selected).length; + return projectView === 'list' + ? taskAssignees.filter(member => member.selected).length + : boardTaskAssignees.filter(member => member.selected).length; }, [taskAssignees, boardTaskAssignees, projectView]); const filteredMembersData = useMemo(() => { const members = projectView === 'list' ? taskAssignees : boardTaskAssignees; - return members.filter(member => - member.name?.toLowerCase().includes(searchQuery.toLowerCase()) - ); + return members.filter(member => member.name?.toLowerCase().includes(searchQuery.toLowerCase())); }, [taskAssignees, boardTaskAssignees, searchQuery, projectView]); - const handleSelectedFiltersCount = useCallback(async (memberId: string | undefined, checked: boolean) => { - if (!memberId || !projectId) return; - if (!memberId || !projectId) return; + const handleSelectedFiltersCount = useCallback( + async (memberId: string | undefined, checked: boolean) => { + if (!memberId || !projectId) return; - const updateMembers = async (members: Member[], setAction: any, fetchAction: any) => { - const updatedMembers = members.map(member => - member.id === memberId ? { ...member, selected: checked } : member - ); - await dispatch(setAction(updatedMembers)); - dispatch(fetchAction(projectId)); - }; - if (projectView === 'list') { - await updateMembers(taskAssignees as Member[], setMembers, fetchTaskGroups); - } else { - await updateMembers(boardTaskAssignees as Member[], setBoardMembers, fetchBoardTaskGroups); - } - }, [projectId, projectView, taskAssignees, boardTaskAssignees, dispatch]); + const updateMembers = async (members: Member[], setAction: any, fetchAction: any) => { + const updatedMembers = members.map(member => + member.id === memberId ? { ...member, selected: checked } : member + ); + await dispatch(setAction(updatedMembers)); + dispatch(fetchAction(projectId)); + }; + if (projectView === 'list') { + await updateMembers(taskAssignees as Member[], setMembers, fetchTaskGroups); + } else { + await updateMembers(boardTaskAssignees as Member[], setBoardMembers, fetchBoardTaskGroups); + } + }, + [projectId, projectView, taskAssignees, boardTaskAssignees, dispatch] + ); const renderMemberItem = (member: Member) => ( { onChange={e => handleSelectedFiltersCount(member.id, e.target.checked)} >
- + {member.name} @@ -130,28 +128,36 @@ const MembersFilterDropdown = () => { placeholder={t('searchInputPlaceholder')} /> - {filteredMembersData.length ? - filteredMembersData.map((member, index) => renderMemberItem(member as Member)) : + {filteredMembersData.length ? ( + filteredMembersData.map((member, index) => renderMemberItem(member as Member)) + ) : ( - } + )} ); - const handleMembersDropdownOpen = useCallback((open: boolean) => { - if (open) { - setTimeout(() => membersInputRef.current?.focus(), 0); - if (taskAssignees.length) { - dispatch(setBoardMembers(taskAssignees)); + const handleMembersDropdownOpen = useCallback( + (open: boolean) => { + if (open) { + setTimeout(() => membersInputRef.current?.focus(), 0); + // Only sync the members if board members are empty + if ( + projectView === 'kanban' && + boardTaskAssignees.length === 0 && + taskAssignees.length > 0 + ) { + dispatch(setBoardMembers(taskAssignees)); + } } - } - }, [dispatch, taskAssignees]); + }, + [dispatch, taskAssignees, boardTaskAssignees, projectView] + ); const buttonStyle = { - backgroundColor: selectedCount > 0 - ? themeMode === 'dark' ? '#003a5c' : colors.paleBlue - : colors.transparent, + backgroundColor: + selectedCount > 0 ? (themeMode === 'dark' ? '#003a5c' : colors.paleBlue) : colors.transparent, color: selectedCount > 0 ? (themeMode === 'dark' ? 'white' : colors.darkGray) : 'inherit', }; @@ -162,11 +168,7 @@ const MembersFilterDropdown = () => { dropdownRender={() => membersDropdownContent} onOpenChange={handleMembersDropdownOpen} > - diff --git a/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/search-dropdown.tsx b/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/search-dropdown.tsx index 1737a5f6..e8743f39 100644 --- a/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/search-dropdown.tsx +++ b/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/search-dropdown.tsx @@ -17,7 +17,6 @@ import { SearchOutlined } from '@ant-design/icons'; import { setBoardSearch } from '@/features/board/board-slice'; - const SearchDropdown = () => { const { t } = useTranslation('task-list-filters'); const dispatch = useDispatch(); diff --git a/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/show-fields-filter-dropdown.tsx b/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/show-fields-filter-dropdown.tsx index 4bc338a5..56d1b7d5 100644 --- a/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/show-fields-filter-dropdown.tsx +++ b/worklenz-frontend/src/components/project-task-filters/filter-dropdowns/show-fields-filter-dropdown.tsx @@ -1,34 +1,151 @@ -import { MoreOutlined } from '@ant-design/icons'; +import { MoreOutlined, SettingOutlined } from '@ant-design/icons'; import { useTranslation } from 'react-i18next'; import Button from 'antd/es/button'; import Checkbox from 'antd/es/checkbox'; import Dropdown from 'antd/es/dropdown'; import Space from 'antd/es/space'; +import React, { useState } from 'react'; import { useAppSelector } from '@/hooks/useAppSelector'; import { useAppDispatch } from '@/hooks/useAppDispatch'; -import { - updateColumnVisibility, - updateCustomColumnPinned, -} from '@/features/tasks/tasks.slice'; +import { updateColumnVisibility, updateCustomColumnPinned } from '@/features/tasks/tasks.slice'; import { ITaskListColumn } from '@/types/tasks/taskList.types'; import { useSocket } from '@/socket/socketContext'; import { SocketEvents } from '@/shared/socket-events'; +import ColumnConfigurationModal from './column-configuration-modal'; + +// Configuration interface for column visibility +interface ColumnConfig { + key: string; + label: string; + showInDropdown: boolean; + order: number; + category?: string; +} + +// Default column configuration - this can be customized per project or globally +const DEFAULT_COLUMN_CONFIG: ColumnConfig[] = [ + { key: 'KEY', label: 'Key', showInDropdown: true, order: 1, category: 'basic' }, + { key: 'TASK', label: 'Task', showInDropdown: false, order: 2, category: 'basic' }, // Always visible, not in dropdown + { key: 'DESCRIPTION', label: 'Description', showInDropdown: true, order: 3, category: 'basic' }, + { key: 'PROGRESS', label: 'Progress', showInDropdown: true, order: 4, category: 'basic' }, + { key: 'STATUS', label: 'Status', showInDropdown: true, order: 5, category: 'basic' }, + { key: 'ASSIGNEES', label: 'Assignees', showInDropdown: true, order: 6, category: 'basic' }, + { key: 'LABELS', label: 'Labels', showInDropdown: true, order: 7, category: 'basic' }, + { key: 'PHASE', label: 'Phase', showInDropdown: true, order: 8, category: 'basic' }, + { key: 'PRIORITY', label: 'Priority', showInDropdown: true, order: 9, category: 'basic' }, + { + key: 'TIME_TRACKING', + label: 'Time Tracking', + showInDropdown: true, + order: 10, + category: 'time', + }, + { key: 'ESTIMATION', label: 'Estimation', showInDropdown: true, order: 11, category: 'time' }, + { key: 'START_DATE', label: 'Start Date', showInDropdown: true, order: 12, category: 'dates' }, + { key: 'DUE_DATE', label: 'Due Date', showInDropdown: true, order: 13, category: 'dates' }, + { key: 'DUE_TIME', label: 'Due Time', showInDropdown: true, order: 14, category: 'dates' }, + { + key: 'COMPLETED_DATE', + label: 'Completed Date', + showInDropdown: true, + order: 15, + category: 'dates', + }, + { + key: 'CREATED_DATE', + label: 'Created Date', + showInDropdown: true, + order: 16, + category: 'dates', + }, + { + key: 'LAST_UPDATED', + label: 'Last Updated', + showInDropdown: true, + order: 17, + category: 'dates', + }, + { key: 'REPORTER', label: 'Reporter', showInDropdown: true, order: 18, category: 'basic' }, +]; + +// Hook to get column configuration - can be extended to fetch from API or localStorage +const useColumnConfig = (projectId?: string): ColumnConfig[] => { + // In the future, this could fetch from: + // 1. Project-specific settings from API + // 2. User preferences from localStorage + // 3. Global settings from configuration + // 4. Team-level settings + + // For now, return default configuration + // You can extend this to load from localStorage or API + const storedConfig = localStorage.getItem(`worklenz.column-config.${projectId}`); + + if (storedConfig) { + try { + return JSON.parse(storedConfig); + } catch (error) { + console.warn('Failed to parse stored column config, using default'); + } + } + + return DEFAULT_COLUMN_CONFIG; +}; + +// Hook to save column configuration +const useSaveColumnConfig = () => { + return (projectId: string, config: ColumnConfig[]) => { + localStorage.setItem(`worklenz.column-config.${projectId}`, JSON.stringify(config)); + }; +}; const ShowFieldsFilterDropdown = () => { - const { socket, connected } = useSocket(); - // localization + const { socket } = useSocket(); const { t } = useTranslation('task-list-filters'); - const dispatch = useAppDispatch(); - const columnList = useAppSelector(state => state.taskReducer.columns); const { projectId, project } = useAppSelector(state => state.projectReducer); - - const visibilityChangableColumnList = columnList.filter( - column => column.key !== 'selector' && column.key !== 'TASK' && column.key !== 'customColumn' + const [configModalOpen, setConfigModalOpen] = useState(false); + const [columnConfig, setColumnConfig] = useState( + useColumnConfig(projectId || undefined) ); - + const saveColumnConfig = useSaveColumnConfig(); + + // Update config if projectId changes + React.useEffect(() => { + setColumnConfig(useColumnConfig(projectId || undefined)); + }, [projectId, configModalOpen]); + + // Filter columns based on configuration + const visibilityChangableColumnList = columnList.filter(column => { + // Always exclude selector and TASK columns from dropdown + if (column.key === 'selector' || column.key === 'TASK') { + return false; + } + + // Find configuration for this column + const config = columnConfig.find(c => c.key === column.key); + + // If no config found, show custom columns by default + if (!config) { + return column.custom_column; + } + + // Return based on configuration + return config.showInDropdown; + }); + + // Sort columns based on configuration order + const sortedColumns = visibilityChangableColumnList.sort((a, b) => { + const configA = columnConfig.find(c => c.key === a.key); + const configB = columnConfig.find(c => c.key === b.key); + + const orderA = configA?.order ?? 999; + const orderB = configB?.order ?? 999; + + return orderA - orderB; + }); + const themeMode = useAppSelector(state => state.themeReducer.mode); const handleColumnVisibilityChange = async (col: ITaskListColumn) => { @@ -51,30 +168,68 @@ const ShowFieldsFilterDropdown = () => { } }; - const menuItems = visibilityChangableColumnList.map(col => ({ - key: col.key, - label: ( - - handleColumnVisibilityChange(col)}> - {col.key === 'PHASE' ? project?.phase_label : ''} - {col.key !== 'PHASE' && - (col.custom_column - ? col.name - : t(`${col.key?.replace('_', '').toLowerCase() + 'Text'}`))} - - - ), - })); + const handleConfigSave = (newConfig: ColumnConfig[]) => { + setColumnConfig(newConfig); + if (projectId) saveColumnConfig(projectId, newConfig); + }; + + const menuItems = [ + ...sortedColumns.map(col => ({ + key: col.key || '', + type: 'item' as const, + label: ( + + handleColumnVisibilityChange(col)}> + {col.key === 'PHASE' ? project?.phase_label : ''} + {col.key !== 'PHASE' && + (col.custom_column + ? col.name + : t(`${col.key?.replace('_', '').toLowerCase() + 'Text'}`))} + + + ), + })), + { + type: 'divider' as const, + }, + { + key: 'configure', + type: 'item' as const, + label: ( + + ), + }, + ]; + return ( - - - + <> + + + + setConfigModalOpen(false)} + projectId={projectId || undefined} + onSave={handleConfigSave} + currentConfig={columnConfig} + /> + ); }; diff --git a/worklenz-frontend/src/components/projects/project-create-button/project-create-button.tsx b/worklenz-frontend/src/components/projects/project-create-button/project-create-button.tsx index 5382ef32..5386ce0c 100644 --- a/worklenz-frontend/src/components/projects/project-create-button/project-create-button.tsx +++ b/worklenz-frontend/src/components/projects/project-create-button/project-create-button.tsx @@ -82,14 +82,18 @@ const CreateProjectButton: React.FC = ({ className }) template_id: currentTemplateId, }); if (res.done) { - navigate(`/worklenz/projects/${res.body.project_id}?tab=tasks-list&pinned_tab=tasks-list`); + navigate( + `/worklenz/projects/${res.body.project_id}?tab=tasks-list&pinned_tab=tasks-list` + ); } } else { const res = await projectTemplatesApiService.createFromCustomTemplate({ template_id: currentTemplateId, }); if (res.done) { - navigate(`/worklenz/projects/${res.body.project_id}?tab=tasks-list&pinned_tab=tasks-list`); + navigate( + `/worklenz/projects/${res.body.project_id}?tab=tasks-list&pinned_tab=tasks-list` + ); } } } catch (e) { diff --git a/worklenz-frontend/src/components/projects/project-drawer/project-category-section/project-category-section.tsx b/worklenz-frontend/src/components/projects/project-drawer/project-category-section/project-category-section.tsx index a0c5af1b..984c9b96 100644 --- a/worklenz-frontend/src/components/projects/project-drawer/project-category-section/project-category-section.tsx +++ b/worklenz-frontend/src/components/projects/project-drawer/project-category-section/project-category-section.tsx @@ -140,7 +140,7 @@ const ProjectCategorySection = ({ categories, form, t, disabled }: ProjectCatego onChange={e => setCategoryText(e.currentTarget.value)} allowClear onClear={() => { - setIsAddCategoryInputShow(false) + setIsAddCategoryInputShow(false); }} onPressEnter={() => handleAddCategoryItem(categoryText)} onBlur={() => handleAddCategoryInputBlur(categoryText)} diff --git a/worklenz-frontend/src/components/projects/project-drawer/project-drawer.tsx b/worklenz-frontend/src/components/projects/project-drawer/project-drawer.tsx index 35732ac3..f6519cb9 100644 --- a/worklenz-frontend/src/components/projects/project-drawer/project-drawer.tsx +++ b/worklenz-frontend/src/components/projects/project-drawer/project-drawer.tsx @@ -14,6 +14,7 @@ import { Popconfirm, Skeleton, Space, + Switch, Tooltip, Typography, } from 'antd'; @@ -46,7 +47,11 @@ import { ITeamMemberViewModel } from '@/types/teamMembers/teamMembersGetResponse import { calculateTimeDifference } from '@/utils/calculate-time-difference'; import { formatDateTimeWithLocale } from '@/utils/format-date-time-with-locale'; import logger from '@/utils/errorLogger'; -import { setProjectData, toggleProjectDrawer, setProjectId as setDrawerProjectId } from '@/features/project/project-drawer.slice'; +import { + setProjectData, + toggleProjectDrawer, + setProjectId as setDrawerProjectId, +} from '@/features/project/project-drawer.slice'; import useIsProjectManager from '@/hooks/useIsProjectManager'; import { useAuthService } from '@/hooks/useAuth'; import { evt_projects_create } from '@/shared/worklenz-analytics-events'; @@ -60,13 +65,14 @@ const ProjectDrawer = ({ onClose }: { onClose: () => void }) => { const [form] = Form.useForm(); const [loading, setLoading] = useState(true); const currentSession = useAuthService().getCurrentSession(); - + // State const [editMode, setEditMode] = useState(false); const [selectedProjectManager, setSelectedProjectManager] = useState( null ); const [isFormValid, setIsFormValid] = useState(true); + const [drawerVisible, setDrawerVisible] = useState(false); // Selectors const { clients, loading: loadingClients } = useAppSelector(state => state.clientReducer); @@ -96,6 +102,9 @@ const ProjectDrawer = ({ onClose }: { onClose: () => void }) => { working_days: project?.working_days || 0, man_days: project?.man_days || 0, hours_per_day: project?.hours_per_day || 8, + use_manual_progress: project?.use_manual_progress || false, + use_weighted_progress: project?.use_weighted_progress || false, + use_time_progress: project?.use_time_progress || false, }), [project, projectStatuses, projectHealths] ); @@ -123,6 +132,60 @@ const ProjectDrawer = ({ onClose }: { onClose: () => void }) => { loadInitialData(); }, [dispatch]); + // New effect to handle form population when project data becomes available + useEffect(() => { + if (drawerVisible && projectId && project && !projectLoading) { + console.log('Populating form with project data:', project); + setEditMode(true); + + try { + form.setFieldsValue({ + ...project, + start_date: project.start_date ? dayjs(project.start_date) : null, + end_date: project.end_date ? dayjs(project.end_date) : null, + working_days: project.working_days || 0, + use_manual_progress: project.use_manual_progress || false, + use_weighted_progress: project.use_weighted_progress || false, + use_time_progress: project.use_time_progress || false, + }); + + setSelectedProjectManager(project.project_manager || null); + setLoading(false); + console.log('Form populated successfully with project data'); + } catch (error) { + console.error('Error setting form values:', error); + logger.error('Error setting form values in project drawer', error); + setLoading(false); + } + } else if (drawerVisible && !projectId) { + // Creating new project + console.log('Setting up drawer for new project creation'); + setEditMode(false); + setLoading(false); + } else if (drawerVisible && projectId && !project && !projectLoading) { + // Project data failed to load or is empty + console.warn('Project drawer is visible but no project data available'); + setLoading(false); + } else if (drawerVisible && projectId) { + console.log('Drawer visible, waiting for project data to load...'); + } + }, [drawerVisible, projectId, project, projectLoading, form]); + + // Additional effect to handle loading state when project data is being fetched + useEffect(() => { + if (drawerVisible && projectId && projectLoading) { + console.log('Project data is loading, maintaining loading state'); + setLoading(true); + } + }, [drawerVisible, projectId, projectLoading]); + + // Define resetForm function early to avoid declaration order issues + const resetForm = useCallback(() => { + setEditMode(false); + form.resetFields(); + setSelectedProjectManager(null); + }, [form]); + useEffect(() => { const startDate = form.getFieldValue('start_date'); const endDate = form.getFieldValue('end_date'); @@ -155,6 +218,9 @@ const ProjectDrawer = ({ onClose }: { onClose: () => void }) => { man_days: parseInt(values.man_days), hours_per_day: parseInt(values.hours_per_day), project_manager: selectedProjectManager, + use_manual_progress: values.use_manual_progress || false, + use_weighted_progress: values.use_weighted_progress || false, + use_time_progress: values.use_time_progress || false, }; const action = @@ -169,7 +235,9 @@ const ProjectDrawer = ({ onClose }: { onClose: () => void }) => { dispatch(toggleProjectDrawer()); if (!editMode) { trackMixpanelEvent(evt_projects_create); - navigate(`/worklenz/projects/${response.data.body.id}?tab=tasks-list&pinned_tab=tasks-list`); + navigate( + `/worklenz/projects/${response.data.body.id}?tab=tasks-list&pinned_tab=tasks-list` + ); } refetchProjects(); window.location.reload(); // Refresh the page @@ -184,8 +252,17 @@ const ProjectDrawer = ({ onClose }: { onClose: () => void }) => { logger.error('Error saving project', error); } }; - const calculateWorkingDays = (startDate: dayjs.Dayjs | null, endDate: dayjs.Dayjs | null): number => { - if (!startDate || !endDate || !startDate.isValid() || !endDate.isValid() || startDate.isAfter(endDate)) { + const calculateWorkingDays = ( + startDate: dayjs.Dayjs | null, + endDate: dayjs.Dayjs | null + ): number => { + if ( + !startDate || + !endDate || + !startDate.isValid() || + !endDate.isValid() || + startDate.isAfter(endDate) + ) { return 0; } @@ -204,38 +281,33 @@ const ProjectDrawer = ({ onClose }: { onClose: () => void }) => { return workingDays; }; + // Improved handleVisibilityChange to track drawer state without doing form operations const handleVisibilityChange = useCallback( (visible: boolean) => { - if (visible && projectId) { - setEditMode(true); - if (project) { - form.setFieldsValue({ - ...project, - start_date: project.start_date ? dayjs(project.start_date) : null, - end_date: project.end_date ? dayjs(project.end_date) : null, - working_days: form.getFieldValue('start_date') && form.getFieldValue('end_date') ? calculateWorkingDays(form.getFieldValue('start_date'), form.getFieldValue('end_date')) : project.working_days || 0, - }); - setSelectedProjectManager(project.project_manager || null); - setLoading(false); - } - } else { + console.log('Drawer visibility changed:', visible, 'Project ID:', projectId); + setDrawerVisible(visible); + + if (!visible) { resetForm(); + } else if (visible && !projectId) { + // Creating new project - reset form immediately + console.log('Opening drawer for new project'); + setEditMode(false); + setLoading(false); + } else if (visible && projectId) { + // Editing existing project - loading state will be handled by useEffect + console.log('Opening drawer for existing project:', projectId); + setLoading(true); } }, - [projectId, project] + [projectId, resetForm] ); - const resetForm = useCallback(() => { - setEditMode(false); - form.resetFields(); - setSelectedProjectManager(null); - }, [form]); - const handleDrawerClose = useCallback(() => { setLoading(true); + setDrawerVisible(false); resetForm(); dispatch(setProjectData({} as IProjectViewModel)); - dispatch(setProjectId(null)); dispatch(setDrawerProjectId(null)); dispatch(toggleProjectDrawer()); onClose(); @@ -284,6 +356,49 @@ const ProjectDrawer = ({ onClose }: { onClose: () => void }) => { setIsFormValid(isValid); }; + // Progress calculation method handlers + const handleManualProgressChange = (checked: boolean) => { + if (checked) { + form.setFieldsValue({ + use_manual_progress: true, + use_weighted_progress: false, + use_time_progress: false, + }); + } else { + form.setFieldsValue({ + use_manual_progress: false, + }); + } + }; + + const handleWeightedProgressChange = (checked: boolean) => { + if (checked) { + form.setFieldsValue({ + use_manual_progress: false, + use_weighted_progress: true, + use_time_progress: false, + }); + } else { + form.setFieldsValue({ + use_weighted_progress: false, + }); + } + }; + + const handleTimeProgressChange = (checked: boolean) => { + if (checked) { + form.setFieldsValue({ + use_manual_progress: false, + use_weighted_progress: false, + use_time_progress: true, + }); + } else { + form.setFieldsValue({ + use_time_progress: false, + }); + } + }; + return ( void }) => { } > {!isEditable && ( - + )} - + void }) => { - + { + onChange={date => { const endDate = form.getFieldValue('end_date'); if (date && endDate) { const days = calculateWorkingDays(date, endDate); @@ -411,14 +518,11 @@ const ProjectDrawer = ({ onClose }: { onClose: () => void }) => { }} /> - + { + onChange={date => { const startDate = form.getFieldValue('start_date'); if (startDate && date) { const days = calculateWorkingDays(startDate, date); @@ -429,22 +533,51 @@ const ProjectDrawer = ({ onClose }: { onClose: () => void }) => { - {/* { + if (value === undefined || value >= 0) { + return Promise.resolve(); + } + return Promise.reject(new Error(t('workingDaysValidationMessage', { min: 0 }))); + }, + }, + ]} + > + + + + { + if (value === undefined || value >= 0) { + return Promise.resolve(); + } + return Promise.reject(new Error(t('manDaysValidationMessage', { min: 0 }))); + }, + }, + ]} > { + const value = parseInt(e.target.value, 10); + if (value < 0) { + form.setFieldsValue({ man_days: 0 }); + } + }} /> - */} + - - - - - - void }) => { if (value === undefined || (value >= 0 && value <= 24)) { return Promise.resolve(); } - return Promise.reject(new Error(t('hoursPerDayValidationMessage', { min: 0, max: 24 }))); + return Promise.reject( + new Error(t('hoursPerDayValidationMessage', { min: 0, max: 24 })) + ); }, }, ]} > - + { + const value = parseInt(e.target.value, 10); + if (value < 0) { + form.setFieldsValue({ hours_per_day: 8 }); + } + }} + /> + + + {t('progressSettings')} + + + {t('manualProgress')} + +
)) diff --git a/worklenz-frontend/src/components/schedule/grant-chart/grantt-members-table.tsx b/worklenz-frontend/src/components/schedule/grant-chart/grantt-members-table.tsx index 84aeed4f..6a402b88 100644 --- a/worklenz-frontend/src/components/schedule/grant-chart/grantt-members-table.tsx +++ b/worklenz-frontend/src/components/schedule/grant-chart/grantt-members-table.tsx @@ -2,7 +2,10 @@ import { Badge, Button, Flex, Tooltip } from 'antd'; import React, { useCallback } from 'react'; import { useAppSelector } from '@/hooks/useAppSelector'; import CustomAvatar from '../../CustomAvatar'; -import { fetchMemberProjects, toggleScheduleDrawer } from '../../../features/schedule/scheduleSlice'; +import { + fetchMemberProjects, + toggleScheduleDrawer, +} from '../../../features/schedule/scheduleSlice'; import { CaretDownOutlined, CaretRightFilled } from '@ant-design/icons'; import { useTranslation } from 'react-i18next'; import { useAppDispatch } from '@/hooks/useAppDispatch'; @@ -37,12 +40,10 @@ const GranttMembersTable = React.memo( const handleToggleProject = useCallback( (id: string) => { - if(expandedProject != id) { - + if (expandedProject != id) { dispatch(fetchMemberProjects({ id })); } setExpandedProject(expandedProject === id ? null : id); - }, [expandedProject, setExpandedProject] ); diff --git a/worklenz-frontend/src/components/schedule/grant-chart/project-timeline-bar.tsx b/worklenz-frontend/src/components/schedule/grant-chart/project-timeline-bar.tsx index a84c8345..f51e2556 100644 --- a/worklenz-frontend/src/components/schedule/grant-chart/project-timeline-bar.tsx +++ b/worklenz-frontend/src/components/schedule/grant-chart/project-timeline-bar.tsx @@ -68,7 +68,13 @@ const ProjectTimelineBar = ({ return ( } + content={ + + } trigger={'click'} open={isModalOpen} > @@ -127,7 +133,10 @@ const ProjectTimelineBar = ({ align="center" justify="center" style={{ width: '100%' }} - onClick={() => {setIsModalOpen(true);dispatch(getWorking());}} + onClick={() => { + setIsModalOpen(true); + dispatch(getWorking()); + }} > = ({ + minimal = false, + showControls = false +}) => { + const { isOffline, swManager, clearCache, forceUpdate, getVersion } = useServiceWorker(); + const [swVersion, setSwVersion] = React.useState(''); + const [isClearing, setIsClearing] = React.useState(false); + const [isUpdating, setIsUpdating] = React.useState(false); + + // Get service worker version on mount + React.useEffect(() => { + if (getVersion) { + const versionPromise = getVersion(); + if (versionPromise) { + versionPromise.then(version => { + setSwVersion(version); + }).catch(() => { + // Ignore errors when getting version + }); + } + } + }, [getVersion]); + + const handleClearCache = async () => { + if (!clearCache) return; + + setIsClearing(true); + try { + const success = await clearCache(); + if (success) { + message.success('Cache cleared successfully'); + } else { + message.error('Failed to clear cache'); + } + } catch (error) { + message.error('Error clearing cache'); + } finally { + setIsClearing(false); + } + }; + + const handleForceUpdate = async () => { + if (!forceUpdate) return; + + setIsUpdating(true); + try { + await forceUpdate(); + message.success('Application will reload with updates'); + } catch (error) { + message.error('Failed to update application'); + setIsUpdating(false); + } + }; + + // Minimal version - just show offline status + if (minimal) { + return ( + + + + ); + } + + return ( +
+ + {/* Connection Status */} +
+ {isOffline ? ( + + ) : ( + + )} + + {isOffline ? 'Offline Mode' : 'Online'} + + {swVersion && ( + + v{swVersion} + + )} +
+ + {/* Information */} +
+ {isOffline ? ( + 'App is running from cache. Changes will sync when online.' + ) : ( + 'App is cached for offline use. Ready to work anywhere!' + )} +
+ + {/* Controls */} + {showControls && swManager && ( + + + + + + + + + + )} +
+
+ ); +}; + +export default ServiceWorkerStatus; \ No newline at end of file diff --git a/worklenz-frontend/src/components/settings/edit-team-name-modal.tsx b/worklenz-frontend/src/components/settings/edit-team-name-modal.tsx index 90a29abe..243a6146 100644 --- a/worklenz-frontend/src/components/settings/edit-team-name-modal.tsx +++ b/worklenz-frontend/src/components/settings/edit-team-name-modal.tsx @@ -1,5 +1,6 @@ import { Divider, Form, Input, message, Modal, Typography } from 'antd'; import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useAppDispatch } from '@/hooks/useAppDispatch'; import { editTeamName, fetchTeams } from '@/features/teams/teamSlice'; import { ITeamGetResponse } from '@/types/teams/team.type'; @@ -11,6 +12,7 @@ interface EditTeamNameModalProps { } const EditTeamNameModal = ({ team, isModalOpen, onCancel }: EditTeamNameModalProps) => { + const { t } = useTranslation('settings/teams'); const dispatch = useAppDispatch(); const [form] = Form.useForm(); const [updating, setUpdating] = useState(false); @@ -33,7 +35,7 @@ const EditTeamNameModal = ({ team, isModalOpen, onCancel }: EditTeamNameModalPro } setUpdating(false); } catch (error) { - message.error('Team name change failed!'); + message.error(t('updateFailed')); } finally { setUpdating(false); } @@ -49,13 +51,13 @@ const EditTeamNameModal = ({ team, isModalOpen, onCancel }: EditTeamNameModalPro width: '100%', }} > - Edit Team Name + {t('editTeamName')}
} open={isModalOpen} onOk={form.submit} - okText="Update Name" + okText={t('updateName')} onCancel={() => { onCancel(); setUpdating(false); @@ -67,15 +69,15 @@ const EditTeamNameModal = ({ team, isModalOpen, onCancel }: EditTeamNameModalPro
- +
diff --git a/worklenz-frontend/src/components/settings/update-member-drawer.tsx b/worklenz-frontend/src/components/settings/update-member-drawer.tsx index 5421a7f4..1af06fcc 100644 --- a/worklenz-frontend/src/components/settings/update-member-drawer.tsx +++ b/worklenz-frontend/src/components/settings/update-member-drawer.tsx @@ -65,7 +65,7 @@ const UpdateMemberDrawer = ({ selectedMemberId, onRoleUpdate }: UpdateMemberDraw setJobTitles(res.body.data || []); } } catch (error) { - console.error('Error fetching job titles:', error); + logger.error('Error fetching job titles:', error); message.error(t('jobTitlesFetchError')); } finally { setLoading(false); @@ -269,4 +269,4 @@ const UpdateMemberDrawer = ({ selectedMemberId, onRoleUpdate }: UpdateMemberDraw ); }; -export default UpdateMemberDrawer; \ No newline at end of file +export default UpdateMemberDrawer; diff --git a/worklenz-frontend/src/components/suspense-fallback/suspense-fallback.tsx b/worklenz-frontend/src/components/suspense-fallback/suspense-fallback.tsx index 041fe5a4..377ba150 100644 --- a/worklenz-frontend/src/components/suspense-fallback/suspense-fallback.tsx +++ b/worklenz-frontend/src/components/suspense-fallback/suspense-fallback.tsx @@ -1,46 +1,53 @@ -import { colors } from '@/styles/colors'; -import { getInitialTheme } from '@/utils/get-initial-theme'; -import { ConfigProvider, theme, Layout, Spin } from 'antd'; - -// Loading component with theme awareness -export const SuspenseFallback = () => { - const currentTheme = getInitialTheme(); - const isDark = currentTheme === 'dark'; +import React, { memo } from 'react'; +import { Skeleton } from 'antd'; +// Lightweight loading component with skeleton animation +export const SuspenseFallback = memo(() => { return ( - - - + - - +
+
); -}; +}); + +// Lightweight fallback for internal components that doesn't cover the screen +export const InlineSuspenseFallback = memo(() => { + return ( +
+
+ +
+
+ ); +}); + +SuspenseFallback.displayName = 'SuspenseFallback'; +InlineSuspenseFallback.displayName = 'InlineSuspenseFallback'; diff --git a/worklenz-frontend/src/components/task-drawer/shared/activity-log/task-drawer-activity-log.tsx b/worklenz-frontend/src/components/task-drawer/shared/activity-log/task-drawer-activity-log.tsx index da198294..7b23a596 100644 --- a/worklenz-frontend/src/components/task-drawer/shared/activity-log/task-drawer-activity-log.tsx +++ b/worklenz-frontend/src/components/task-drawer/shared/activity-log/task-drawer-activity-log.tsx @@ -20,11 +20,11 @@ const TaskDrawerActivityLog = () => { const [loading, setLoading] = useState(false); const { selectedTaskId, taskFormViewModel } = useAppSelector(state => state.taskDrawerReducer); const { mode: themeMode } = useAppSelector(state => state.themeReducer); - const { t } = useTranslation(); + const { t } = useTranslation('task-drawer/task-drawer'); - useEffect(()=>{ + useEffect(() => { fetchActivityLogs(); - },[taskFormViewModel]); + }, [taskFormViewModel]); const fetchActivityLogs = async () => { if (!selectedTaskId) return; @@ -59,7 +59,8 @@ const TaskDrawerActivityLog = () => { name={activity.assigned_user?.name} /> {truncateText(activity.assigned_user?.name)} -   + +   {truncateText(activity.log_type?.toUpperCase())} ); @@ -67,21 +68,37 @@ const TaskDrawerActivityLog = () => { case IActivityLogAttributeTypes.LABEL: return ( - {truncateText(activity.label_data?.name)} -   - {activity.log_type === 'create' ? 'ADD' : 'REMOVE'} + + {truncateText(activity.label_data?.name)} + + +   + {activity.log_type === 'create' ? t('taskActivityLogTab.add') : t('taskActivityLogTab.remove')} ); case IActivityLogAttributeTypes.STATUS: return ( - - {truncateText(activity.previous_status?.name) || 'None'} + + {truncateText(activity.previous_status?.name) || t('taskActivityLogTab.none')} -   - - {truncateText(activity.next_status?.name) || 'None'} + +   + + {truncateText(activity.next_status?.name) || t('taskActivityLogTab.none')} ); @@ -89,12 +106,25 @@ const TaskDrawerActivityLog = () => { case IActivityLogAttributeTypes.PRIORITY: return ( - - {truncateText(activity.previous_priority?.name) || 'None'} + + {truncateText(activity.previous_priority?.name) || t('taskActivityLogTab.none')} -   - - {truncateText(activity.next_priority?.name) || 'None'} + +   + + {truncateText(activity.next_priority?.name) || t('taskActivityLogTab.none')} ); @@ -103,21 +133,43 @@ const TaskDrawerActivityLog = () => { return ( - {truncateText(activity.previous_phase?.name) || 'None'} + {truncateText(activity.previous_phase?.name) || t('taskActivityLogTab.none')} -   + +   - {truncateText(activity.next_phase?.name) || 'None'} + {truncateText(activity.next_phase?.name) || t('taskActivityLogTab.none')} ); + case IActivityLogAttributeTypes.PROGRESS: + return ( + + {activity.previous || '0'}% + +   + {activity.current || '0'}% + + ); + + case IActivityLogAttributeTypes.WEIGHT: + return ( + + {t('taskActivityLogTab.weight')}: {activity.previous || '100'} + +   + {t('taskActivityLogTab.weight')}: {activity.current || '100'} + + ); + default: return ( - {truncateText(activity.previous) || 'None'} -   - {truncateText(activity.current) || 'None'} + {truncateText(activity.previous) || t('taskActivityLogTab.none')} + +   + {truncateText(activity.current) || t('taskActivityLogTab.none')} ); } @@ -170,7 +222,7 @@ const TaskDrawerActivityLog = () => { {activityLogs.name} - created the task. + {t('taskActivityLogTab.createdTask')} { const { selectedTaskId } = useAppSelector(state => state.taskDrawerReducer); + const dispatch = useAppDispatch(); const [deleting, setDeleting] = useState(false); const [isVisible, setIsVisible] = useState(false); const [currentFileUrl, setCurrentFileUrl] = useState(null); @@ -32,12 +41,12 @@ const AttachmentsPreview = ({ const [previewedFileName, setPreviewedFileName] = useState(null); const getFileIcon = (type?: string) => { - if (!type) return "search.png"; - return IconsMap[type] || "search.png"; + if (!type) return 'search.png'; + return IconsMap[type] || 'search.png'; }; const isImageFile = (): boolean => { - const imageTypes = ["jpeg", "jpg", "bmp", "gif", "webp", "png", "ico"]; + const imageTypes = ['jpeg', 'jpg', 'bmp', 'gif', 'webp', 'png', 'ico']; const type = attachment?.type; if (type) return imageTypes.includes(type); return false; @@ -53,12 +62,12 @@ const AttachmentsPreview = ({ if (!id || !name) return; try { setDownloading(true); - const api = isCommentAttachment + const api = isCommentAttachment ? attachmentsApiService.downloadAttachment // Assuming this exists, adjust as needed : attachmentsApiService.downloadAttachment; - + const res = await api(id, name); - + if (res && res.done) { const link = document.createElement('a'); link.href = res.body || ''; @@ -82,6 +91,7 @@ const AttachmentsPreview = ({ if (isCommentAttachment) { const res = await taskCommentsApiService.deleteAttachment(id, selectedTaskId); if (res.done) { + // Let the parent component handle the refetch and Redux update document.dispatchEvent(new Event('task-comment-update')); } } else { @@ -90,6 +100,7 @@ const AttachmentsPreview = ({ if (onDelete) { onDelete(id); } + // Parent component will handle the refetch and Redux update } } } catch (e) { @@ -126,7 +137,7 @@ const AttachmentsPreview = ({ if (!extension) return; setIsVisible(true); - + if (isImage(extension)) { setCurrentFileType('image'); } else if (isVideo(extension)) { @@ -149,9 +160,7 @@ const AttachmentsPreview = ({ <>
{attachment && ( -
+
@@ -165,26 +174,26 @@ const AttachmentsPreview = ({ placement="bottom" >
- -
- {!isImageFile() && ( - )} @@ -194,18 +203,18 @@ const AttachmentsPreview = ({ - - - ) + ), ]} > -
+
{currentFileType === 'image' && ( <> )} - + {currentFileType === 'video' && ( <> )} - + {currentFileType === 'audio' && ( <> )} - + {currentFileType === 'document' && ( <> {currentFileUrl && ( -