Fix bug 722 (#737)
* Fix project statuses * Refactor project states * Add translations
This commit is contained in:
parent
e73c354e7e
commit
eee1bbc013
51 changed files with 947 additions and 302 deletions
|
|
@ -133,10 +133,17 @@ const options = {
|
|||
type: 'string',
|
||||
description: 'Project description',
|
||||
},
|
||||
state: {
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['active', 'archived', 'completed'],
|
||||
description: 'Project state',
|
||||
enum: [
|
||||
'not_started',
|
||||
'planned',
|
||||
'in_progress',
|
||||
'waiting',
|
||||
'done',
|
||||
'cancelled',
|
||||
],
|
||||
description: 'Project status',
|
||||
},
|
||||
priority: {
|
||||
type: 'string',
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@
|
|||
* - BearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: state
|
||||
* name: status
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [planned, in_progress, blocked, completed, archived, all]
|
||||
* description: Filter by project state
|
||||
* enum: [not_started, planned, in_progress, waiting, done, cancelled, all, not_completed]
|
||||
* description: Filter by project status
|
||||
* - in: query
|
||||
* name: area_id
|
||||
* schema:
|
||||
|
|
@ -62,10 +62,10 @@
|
|||
* type: string
|
||||
* enum: [low, medium, high]
|
||||
* description: Project priority
|
||||
* state:
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [idea, planned, in_progress, blocked, completed, archived]
|
||||
* description: Project state
|
||||
* enum: [not_started, planned, in_progress, waiting, done, cancelled]
|
||||
* description: Project status
|
||||
* area_id:
|
||||
* type: integer
|
||||
* description: Associated area ID
|
||||
|
|
@ -127,10 +127,10 @@
|
|||
* type: string
|
||||
* enum: [low, medium, high]
|
||||
* description: Project priority
|
||||
* state:
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [idea, planned, in_progress, blocked, completed, archived]
|
||||
* description: Project state
|
||||
* enum: [not_started, planned, in_progress, waiting, done, cancelled]
|
||||
* description: Project status
|
||||
* area_id:
|
||||
* type: integer
|
||||
* description: Associated area ID
|
||||
|
|
|
|||
182
backend/migrations/20251228000001-update-project-state-enum.js
Normal file
182
backend/migrations/20251228000001-update-project-state-enum.js
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Migration to update project state ENUM to use task status values.
|
||||
* This aligns project states with task statuses for consistency.
|
||||
*
|
||||
* Old values: 'idea', 'planned', 'in_progress', 'blocked', 'completed'
|
||||
* New values: 'not_started', 'in_progress', 'done', 'waiting', 'cancelled', 'planned'
|
||||
*
|
||||
* Mapping:
|
||||
* - 'idea' → 'not_started'
|
||||
* - 'planned' → 'planned'
|
||||
* - 'in_progress' → 'in_progress'
|
||||
* - 'blocked' → 'waiting'
|
||||
* - 'completed' → 'done'
|
||||
*/
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
// For SQLite: recreate the column with new values
|
||||
// For PostgreSQL/MySQL: alter the enum type
|
||||
|
||||
const dialect = queryInterface.sequelize.getDialect();
|
||||
|
||||
if (dialect === 'sqlite') {
|
||||
// SQLite doesn't support ENUM, it's stored as TEXT
|
||||
// Just update the data values
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE projects SET state = 'not_started' WHERE state = 'idea'`
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE projects SET state = 'waiting' WHERE state = 'blocked'`
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE projects SET state = 'done' WHERE state = 'completed'`
|
||||
);
|
||||
} else if (dialect === 'postgres') {
|
||||
// PostgreSQL: Create new enum type, migrate data, swap types
|
||||
await queryInterface.sequelize.query(`
|
||||
CREATE TYPE "enum_projects_state_new" AS ENUM(
|
||||
'not_started', 'in_progress', 'done', 'waiting', 'cancelled', 'planned'
|
||||
);
|
||||
`);
|
||||
|
||||
// Update values before changing type
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE projects SET state = 'not_started' WHERE state = 'idea'`
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE projects SET state = 'waiting' WHERE state = 'blocked'`
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE projects SET state = 'done' WHERE state = 'completed'`
|
||||
);
|
||||
|
||||
// Change column type
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE projects
|
||||
ALTER COLUMN state TYPE "enum_projects_state_new"
|
||||
USING state::text::"enum_projects_state_new";
|
||||
`);
|
||||
|
||||
// Drop old enum and rename new one
|
||||
await queryInterface.sequelize.query(
|
||||
`DROP TYPE IF EXISTS "enum_projects_state";`
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`ALTER TYPE "enum_projects_state_new" RENAME TO "enum_projects_state";`
|
||||
);
|
||||
|
||||
// Update default value
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE projects ALTER COLUMN state SET DEFAULT 'not_started';
|
||||
`);
|
||||
} else if (dialect === 'mysql' || dialect === 'mariadb') {
|
||||
// MySQL: Alter column directly
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE projects SET state = 'not_started' WHERE state = 'idea'`
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE projects SET state = 'waiting' WHERE state = 'blocked'`
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE projects SET state = 'done' WHERE state = 'completed'`
|
||||
);
|
||||
|
||||
await queryInterface.changeColumn('projects', 'state', {
|
||||
type: Sequelize.ENUM(
|
||||
'not_started',
|
||||
'in_progress',
|
||||
'done',
|
||||
'waiting',
|
||||
'cancelled',
|
||||
'planned'
|
||||
),
|
||||
allowNull: false,
|
||||
defaultValue: 'not_started',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
const dialect = queryInterface.sequelize.getDialect();
|
||||
|
||||
if (dialect === 'sqlite') {
|
||||
// Reverse the data mapping
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE projects SET state = 'idea' WHERE state = 'not_started'`
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE projects SET state = 'blocked' WHERE state = 'waiting'`
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE projects SET state = 'completed' WHERE state = 'done'`
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE projects SET state = 'idea' WHERE state = 'cancelled'`
|
||||
);
|
||||
} else if (dialect === 'postgres') {
|
||||
await queryInterface.sequelize.query(`
|
||||
CREATE TYPE "enum_projects_state_old" AS ENUM(
|
||||
'idea', 'planned', 'in_progress', 'blocked', 'completed'
|
||||
);
|
||||
`);
|
||||
|
||||
// Reverse data mapping
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE projects SET state = 'idea' WHERE state = 'not_started'`
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE projects SET state = 'blocked' WHERE state = 'waiting'`
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE projects SET state = 'completed' WHERE state = 'done'`
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE projects SET state = 'idea' WHERE state = 'cancelled'`
|
||||
);
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE projects
|
||||
ALTER COLUMN state TYPE "enum_projects_state_old"
|
||||
USING state::text::"enum_projects_state_old";
|
||||
`);
|
||||
|
||||
await queryInterface.sequelize.query(
|
||||
`DROP TYPE IF EXISTS "enum_projects_state";`
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`ALTER TYPE "enum_projects_state_old" RENAME TO "enum_projects_state";`
|
||||
);
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE projects ALTER COLUMN state SET DEFAULT 'idea';
|
||||
`);
|
||||
} else if (dialect === 'mysql' || dialect === 'mariadb') {
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE projects SET state = 'idea' WHERE state = 'not_started'`
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE projects SET state = 'blocked' WHERE state = 'waiting'`
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE projects SET state = 'completed' WHERE state = 'done'`
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE projects SET state = 'idea' WHERE state = 'cancelled'`
|
||||
);
|
||||
|
||||
await queryInterface.changeColumn('projects', 'state', {
|
||||
type: Sequelize.ENUM(
|
||||
'idea',
|
||||
'planned',
|
||||
'in_progress',
|
||||
'blocked',
|
||||
'completed'
|
||||
),
|
||||
allowNull: false,
|
||||
defaultValue: 'idea',
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Migration to rename project 'state' column to 'status' for consistency.
|
||||
*/
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.renameColumn('projects', 'state', 'status');
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.renameColumn('projects', 'status', 'state');
|
||||
},
|
||||
};
|
||||
|
|
@ -71,16 +71,17 @@ module.exports = (sequelize) => {
|
|||
allowNull: true,
|
||||
defaultValue: 'created_at:desc',
|
||||
},
|
||||
state: {
|
||||
status: {
|
||||
type: DataTypes.ENUM(
|
||||
'idea',
|
||||
'planned',
|
||||
'not_started',
|
||||
'in_progress',
|
||||
'blocked',
|
||||
'completed'
|
||||
'done',
|
||||
'waiting',
|
||||
'cancelled',
|
||||
'planned'
|
||||
),
|
||||
allowNull: false,
|
||||
defaultValue: 'idea',
|
||||
defaultValue: 'not_started',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -158,7 +158,10 @@ router.post(
|
|||
|
||||
router.get('/projects', async (req, res) => {
|
||||
try {
|
||||
const { state, active, pin_to_sidebar, area_id, area } = req.query;
|
||||
const { status, state, active, pin_to_sidebar, area_id, area } =
|
||||
req.query;
|
||||
// Support both 'status' (new) and 'state' (legacy) query params
|
||||
const statusFilter = status || state;
|
||||
|
||||
// Base: owned or shared projects
|
||||
const ownedOrShared =
|
||||
|
|
@ -168,22 +171,22 @@ router.get('/projects', async (req, res) => {
|
|||
);
|
||||
let whereClause = ownedOrShared;
|
||||
|
||||
// Filter by state (new primary filter)
|
||||
if (state && state !== 'all') {
|
||||
if (Array.isArray(state)) {
|
||||
whereClause.state = { [Op.in]: state };
|
||||
// Filter by status
|
||||
if (statusFilter && statusFilter !== 'all') {
|
||||
if (Array.isArray(statusFilter)) {
|
||||
whereClause.status = { [Op.in]: statusFilter };
|
||||
} else {
|
||||
whereClause.state = state;
|
||||
whereClause.status = statusFilter;
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy support for active filter - map to states
|
||||
// Legacy support for active filter - map to statuses
|
||||
if (active === 'true') {
|
||||
whereClause.state = {
|
||||
[Op.in]: ['planned', 'in_progress', 'blocked'],
|
||||
whereClause.status = {
|
||||
[Op.in]: ['planned', 'in_progress', 'waiting'],
|
||||
};
|
||||
} else if (active === 'false') {
|
||||
whereClause.state = { [Op.in]: ['idea', 'completed'] };
|
||||
whereClause.status = { [Op.in]: ['not_started', 'done'] };
|
||||
}
|
||||
|
||||
// Filter by pinned status
|
||||
|
|
@ -490,7 +493,8 @@ router.post('/project', async (req, res) => {
|
|||
priority,
|
||||
due_date_at,
|
||||
image_url,
|
||||
state,
|
||||
status,
|
||||
state, // Legacy support
|
||||
tags,
|
||||
Tags,
|
||||
} = req.body;
|
||||
|
|
@ -514,7 +518,7 @@ router.post('/project', async (req, res) => {
|
|||
priority: priority || null,
|
||||
due_date_at: due_date_at || null,
|
||||
image_url: image_url || null,
|
||||
state: state || 'idea',
|
||||
status: status || state || 'not_started',
|
||||
user_id: req.authUserId,
|
||||
};
|
||||
|
||||
|
|
@ -578,7 +582,8 @@ router.patch(
|
|||
priority,
|
||||
due_date_at,
|
||||
image_url,
|
||||
state,
|
||||
status,
|
||||
state, // Legacy support
|
||||
tags,
|
||||
Tags,
|
||||
} = req.body;
|
||||
|
|
@ -595,7 +600,9 @@ router.patch(
|
|||
if (priority !== undefined) updateData.priority = priority;
|
||||
if (due_date_at !== undefined) updateData.due_date_at = due_date_at;
|
||||
if (image_url !== undefined) updateData.image_url = image_url;
|
||||
if (state !== undefined) updateData.state = state;
|
||||
// Support both status (new) and state (legacy)
|
||||
if (status !== undefined) updateData.status = status;
|
||||
else if (state !== undefined) updateData.status = state;
|
||||
|
||||
await project.update(updateData);
|
||||
await updateProjectTags(project, tagsData, req.authUserId);
|
||||
|
|
|
|||
|
|
@ -432,7 +432,7 @@ router.get('/', async (req, res) => {
|
|||
name: project.name,
|
||||
description: project.description,
|
||||
priority: project.priority,
|
||||
status: project.state,
|
||||
status: project.status,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ async function filterTasksByParams(
|
|||
},
|
||||
{
|
||||
model: Project,
|
||||
attributes: ['id', 'name', 'state', 'uid'],
|
||||
attributes: ['id', 'name', 'status', 'uid'],
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
|
|
@ -373,7 +373,7 @@ function getTaskIncludeConfig() {
|
|||
},
|
||||
{
|
||||
model: Project,
|
||||
attributes: ['id', 'name', 'state', 'uid'],
|
||||
attributes: ['id', 'name', 'status', 'uid'],
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -364,7 +364,7 @@ async function importUserData(userId, backupData, options = { merge: true }) {
|
|||
task_show_completed:
|
||||
projectData.task_show_completed,
|
||||
task_sort_order: projectData.task_sort_order,
|
||||
state: projectData.state,
|
||||
status: projectData.status || projectData.state,
|
||||
user_id: userId,
|
||||
area_id: areaId,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ describe('Projects Routes', () => {
|
|||
const projectData = {
|
||||
name: 'Test Project',
|
||||
description: 'Test Description',
|
||||
state: 'planned',
|
||||
status: 'planned',
|
||||
pin_to_sidebar: false,
|
||||
priority: 1,
|
||||
area_id: area.id,
|
||||
|
|
@ -40,7 +40,7 @@ describe('Projects Routes', () => {
|
|||
expect(response.status).toBe(201);
|
||||
expect(response.body.name).toBe(projectData.name);
|
||||
expect(response.body.description).toBe(projectData.description);
|
||||
expect(response.body.state).toBe(projectData.state);
|
||||
expect(response.body.status).toBe(projectData.status);
|
||||
expect(response.body.pin_to_sidebar).toBe(
|
||||
projectData.pin_to_sidebar
|
||||
);
|
||||
|
|
@ -217,7 +217,7 @@ describe('Projects Routes', () => {
|
|||
project = await Project.create({
|
||||
name: 'Test Project',
|
||||
description: 'Test Description',
|
||||
state: 'idea',
|
||||
status: 'not_started',
|
||||
priority: 0,
|
||||
user_id: user.id,
|
||||
});
|
||||
|
|
@ -227,7 +227,7 @@ describe('Projects Routes', () => {
|
|||
const updateData = {
|
||||
name: 'Updated Project',
|
||||
description: 'Updated Description',
|
||||
state: 'in_progress',
|
||||
status: 'in_progress',
|
||||
priority: 2,
|
||||
};
|
||||
|
||||
|
|
@ -238,7 +238,7 @@ describe('Projects Routes', () => {
|
|||
expect(response.status).toBe(200);
|
||||
expect(response.body.name).toBe(updateData.name);
|
||||
expect(response.body.description).toBe(updateData.description);
|
||||
expect(response.body.state).toBe(updateData.state);
|
||||
expect(response.body.status).toBe(updateData.status);
|
||||
expect(response.body.priority).toBe(updateData.priority);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -961,7 +961,7 @@ describe('Universal Search Routes', () => {
|
|||
expect(project.name).toBe('Test project');
|
||||
expect(project.description).toBe('Project description');
|
||||
expect(project.priority).toBe('medium');
|
||||
expect(project.status).toBe('active');
|
||||
expect(project.status).toBe('not_started');
|
||||
});
|
||||
|
||||
it('should return note with correct structure', async () => {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ describe('Project Model', () => {
|
|||
const projectData = {
|
||||
name: 'Test Project',
|
||||
description: 'Test Description',
|
||||
state: 'planned',
|
||||
status: 'planned',
|
||||
pin_to_sidebar: false,
|
||||
priority: 1,
|
||||
user_id: user.id,
|
||||
|
|
@ -32,7 +32,7 @@ describe('Project Model', () => {
|
|||
|
||||
expect(project.name).toBe(projectData.name);
|
||||
expect(project.description).toBe(projectData.description);
|
||||
expect(project.state).toBe(projectData.state);
|
||||
expect(project.status).toBe(projectData.status);
|
||||
expect(project.pin_to_sidebar).toBe(projectData.pin_to_sidebar);
|
||||
expect(project.priority).toBe(projectData.priority);
|
||||
expect(project.user_id).toBe(user.id);
|
||||
|
|
@ -85,7 +85,7 @@ describe('Project Model', () => {
|
|||
user_id: user.id,
|
||||
});
|
||||
|
||||
expect(project.state).toBe('idea');
|
||||
expect(project.status).toBe('not_started');
|
||||
expect(project.pin_to_sidebar).toBe(false);
|
||||
expect(project.task_show_completed).toBe(false);
|
||||
expect(project.task_sort_order).toBe('created_at:desc');
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ const Layout: React.FC<LayoutProps> = ({
|
|||
try {
|
||||
const newProject = await createProject({
|
||||
name,
|
||||
state: 'planned',
|
||||
status: 'planned',
|
||||
});
|
||||
return newProject;
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -418,7 +418,7 @@ const InboxItemDetail: React.FC<InboxItemDetailProps> = ({
|
|||
const newProject: Project = {
|
||||
name: payload.cleanedContent || displayText,
|
||||
description: '',
|
||||
state: 'planned',
|
||||
status: 'planned',
|
||||
tags: payload.tagObjects,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -374,7 +374,7 @@ const InboxItems: React.FC = () => {
|
|||
|
||||
const handleCreateProject = async (name: string): Promise<Project> => {
|
||||
try {
|
||||
const project = await createProject({ name, state: 'planned' });
|
||||
const project = await createProject({ name, status: 'planned' });
|
||||
showSuccessToast(t('project.createSuccess'));
|
||||
return project;
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -1065,7 +1065,7 @@ const QuickCaptureInput = React.forwardRef<
|
|||
try {
|
||||
await createProject({
|
||||
name: projectName,
|
||||
state: 'planned',
|
||||
status: 'planned',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(
|
||||
|
|
@ -1411,7 +1411,7 @@ const QuickCaptureInput = React.forwardRef<
|
|||
const newProject: Project = {
|
||||
name: cleaned,
|
||||
description: '',
|
||||
state: 'planned',
|
||||
status: 'planned',
|
||||
tags: buildTagObjects(
|
||||
composerFooterContext.hashtags
|
||||
),
|
||||
|
|
|
|||
|
|
@ -72,8 +72,8 @@ const ProductivityAssistant: React.FC<ProductivityAssistantProps> = ({
|
|||
// 1. Stalled Projects (no tasks/actions)
|
||||
const stalledProjects = projects.filter(
|
||||
(project) =>
|
||||
(project.state === 'planned' ||
|
||||
project.state === 'in_progress') &&
|
||||
(project.status === 'planned' ||
|
||||
project.status === 'in_progress') &&
|
||||
!activeTasks.some((task) => task.project_id === project.id)
|
||||
);
|
||||
|
||||
|
|
@ -110,8 +110,8 @@ const ProductivityAssistant: React.FC<ProductivityAssistantProps> = ({
|
|||
task.status === 'in_progress')
|
||||
);
|
||||
return (
|
||||
(project.state === 'planned' ||
|
||||
project.state === 'in_progress') &&
|
||||
(project.status === 'planned' ||
|
||||
project.status === 'in_progress') &&
|
||||
hasCompletedTasks &&
|
||||
!hasNextAction
|
||||
);
|
||||
|
|
@ -214,8 +214,8 @@ const ProductivityAssistant: React.FC<ProductivityAssistantProps> = ({
|
|||
const stuckProjects = projects.filter((project) => {
|
||||
if (
|
||||
!(
|
||||
project.state === 'planned' ||
|
||||
project.state === 'in_progress'
|
||||
project.status === 'planned' ||
|
||||
project.status === 'in_progress'
|
||||
)
|
||||
)
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ interface ProjectBannerProps {
|
|||
project: Project;
|
||||
areas: Area[];
|
||||
t: TFunction;
|
||||
getStateIcon: (state: string) => React.ReactNode;
|
||||
getStatusIcon: (status: string) => React.ReactNode;
|
||||
onDeleteClick: () => void;
|
||||
editButtonRef: RefObject<HTMLButtonElement>;
|
||||
onEditBannerClick?: () => void;
|
||||
|
|
@ -32,7 +32,7 @@ const ProjectBanner: React.FC<ProjectBannerProps> = ({
|
|||
project,
|
||||
areas,
|
||||
t,
|
||||
getStateIcon,
|
||||
getStatusIcon,
|
||||
onDeleteClick,
|
||||
editButtonRef,
|
||||
onEditBannerClick,
|
||||
|
|
@ -76,11 +76,11 @@ const ProjectBanner: React.FC<ProjectBannerProps> = ({
|
|||
</div>
|
||||
|
||||
<div className="absolute bottom-2 left-2 right-14 flex items-center flex-wrap gap-2">
|
||||
{project.state && (
|
||||
{project.status && (
|
||||
<BannerBadge>
|
||||
{getStateIcon(project.state)}
|
||||
{getStatusIcon(project.status)}
|
||||
<span className="text-xs text-white/90 font-medium">
|
||||
{t(`projects.states.${project.state}`)}
|
||||
{t(`projectStatus.${project.status}`)}
|
||||
</span>
|
||||
</BannerBadge>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@ import { useParams, useNavigate } from 'react-router-dom';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
MagnifyingGlassIcon,
|
||||
LightBulbIcon,
|
||||
EllipsisHorizontalCircleIcon,
|
||||
ClipboardDocumentListIcon,
|
||||
PlayIcon,
|
||||
ExclamationTriangleIcon,
|
||||
ClockIcon,
|
||||
CheckCircleIcon,
|
||||
XCircleIcon,
|
||||
ChartBarIcon,
|
||||
CheckIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
|
|
@ -700,32 +701,35 @@ const ProjectDetails: React.FC = () => {
|
|||
monthlyCompleted,
|
||||
} = useProjectMetrics(tasks, handleTaskUpdate, t, showSuccessToast);
|
||||
|
||||
const getStateIcon = (state: string) => {
|
||||
switch (state) {
|
||||
case 'idea':
|
||||
const getStatusIcon = (status: string) => {
|
||||
switch (status) {
|
||||
case 'not_started':
|
||||
return (
|
||||
<LightBulbIcon className="h-3 w-3 text-yellow-500 flex-shrink-0 mt-0.5" />
|
||||
<EllipsisHorizontalCircleIcon className="h-3 w-3 text-gray-500 flex-shrink-0 mt-0.5" />
|
||||
);
|
||||
case 'planned':
|
||||
return (
|
||||
<ClipboardDocumentListIcon className="h-3 w-3 text-blue-500 flex-shrink-0 mt-0.5" />
|
||||
);
|
||||
case 'in_progress':
|
||||
case 'active':
|
||||
return (
|
||||
<PlayIcon className="h-3 w-3 text-green-500 flex-shrink-0 mt-0.5" />
|
||||
);
|
||||
case 'blocked':
|
||||
case 'waiting':
|
||||
return (
|
||||
<ExclamationTriangleIcon className="h-3 w-3 text-red-500 flex-shrink-0 mt-0.5" />
|
||||
<ClockIcon className="h-3 w-3 text-yellow-500 flex-shrink-0 mt-0.5" />
|
||||
);
|
||||
case 'completed':
|
||||
case 'done':
|
||||
return (
|
||||
<CheckCircleIcon className="h-3 w-3 text-gray-500 flex-shrink-0 mt-0.5" />
|
||||
<CheckCircleIcon className="h-3 w-3 text-green-600 flex-shrink-0 mt-0.5" />
|
||||
);
|
||||
case 'cancelled':
|
||||
return (
|
||||
<XCircleIcon className="h-3 w-3 text-red-500 flex-shrink-0 mt-0.5" />
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<PlayIcon className="h-3 w-3 text-white/70 flex-shrink-0 mt-0.5" />
|
||||
<EllipsisHorizontalCircleIcon className="h-3 w-3 text-white/70 flex-shrink-0 mt-0.5" />
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
@ -834,7 +838,7 @@ const ProjectDetails: React.FC = () => {
|
|||
project={project}
|
||||
areas={areas}
|
||||
t={t}
|
||||
getStateIcon={getStateIcon}
|
||||
getStatusIcon={getStatusIcon}
|
||||
onDeleteClick={() => {
|
||||
setNoteToDelete(null);
|
||||
setIsConfirmDialogOpen(true);
|
||||
|
|
|
|||
|
|
@ -4,15 +4,16 @@ import { EllipsisVerticalIcon } from '@heroicons/react/24/solid';
|
|||
import {
|
||||
PencilSquareIcon,
|
||||
TrashIcon,
|
||||
LightBulbIcon,
|
||||
DocumentTextIcon,
|
||||
EllipsisHorizontalCircleIcon,
|
||||
ClipboardDocumentListIcon,
|
||||
PlayIcon,
|
||||
StopIcon,
|
||||
ClockIcon,
|
||||
CheckCircleIcon,
|
||||
XCircleIcon,
|
||||
ShareIcon,
|
||||
ExclamationTriangleIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { Project, ProjectState } from '../../entities/Project';
|
||||
import { Project, ProjectStatus } from '../../entities/Project';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useToast } from '../Shared/ToastContext';
|
||||
import { getCurrentUser } from '../../utils/userUtils';
|
||||
|
|
@ -48,38 +49,41 @@ const getProjectInitials = (name: string, maxLetters?: number) => {
|
|||
return maxLetters ? initials.substring(0, maxLetters) : initials;
|
||||
};
|
||||
|
||||
const getStateIcon = (state: ProjectState | undefined) => {
|
||||
switch (state) {
|
||||
case 'idea':
|
||||
return { icon: LightBulbIcon };
|
||||
const getStatusIcon = (status: ProjectStatus | undefined) => {
|
||||
switch (status) {
|
||||
case 'not_started':
|
||||
return { icon: EllipsisHorizontalCircleIcon };
|
||||
case 'planned':
|
||||
return { icon: DocumentTextIcon };
|
||||
return { icon: ClipboardDocumentListIcon };
|
||||
case 'in_progress':
|
||||
return { icon: PlayIcon };
|
||||
case 'blocked':
|
||||
return { icon: StopIcon };
|
||||
case 'completed':
|
||||
case 'waiting':
|
||||
return { icon: ClockIcon };
|
||||
case 'done':
|
||||
return { icon: CheckCircleIcon };
|
||||
case 'cancelled':
|
||||
return { icon: XCircleIcon };
|
||||
default:
|
||||
return { icon: LightBulbIcon };
|
||||
return { icon: EllipsisHorizontalCircleIcon };
|
||||
}
|
||||
};
|
||||
|
||||
const getStateLabel = (state: ProjectState | undefined, t: any): string => {
|
||||
switch (state) {
|
||||
case 'idea':
|
||||
return t('projects.states.idea', 'Idea');
|
||||
const getStatusLabel = (status: ProjectStatus | undefined, t: any): string => {
|
||||
switch (status) {
|
||||
case 'not_started':
|
||||
return t('projectStatus.not_started', 'Not Started');
|
||||
case 'planned':
|
||||
return t('projects.states.planned', 'Planned');
|
||||
return t('projectStatus.planned', 'Planned');
|
||||
case 'in_progress':
|
||||
case 'active':
|
||||
return t('projects.states.in_progress', 'In Progress');
|
||||
case 'blocked':
|
||||
return t('projects.states.blocked', 'Blocked');
|
||||
case 'completed':
|
||||
return t('projects.states.completed', 'Completed');
|
||||
return t('projectStatus.in_progress', 'In Progress');
|
||||
case 'waiting':
|
||||
return t('projectStatus.waiting', 'Waiting');
|
||||
case 'done':
|
||||
return t('projectStatus.done', 'Completed');
|
||||
case 'cancelled':
|
||||
return t('projectStatus.cancelled', 'Cancelled');
|
||||
default:
|
||||
return t('projects.states.idea', 'Idea');
|
||||
return t('projectStatus.not_started', 'Not Started');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -299,14 +303,14 @@ const ProjectItem: React.FC<ProjectItemProps> = ({
|
|||
/>
|
||||
)}
|
||||
{(() => {
|
||||
const { icon: StateIcon } = getStateIcon(
|
||||
project.state
|
||||
const { icon: StatusIcon } = getStatusIcon(
|
||||
project.status
|
||||
);
|
||||
return (
|
||||
<StateIcon
|
||||
<StatusIcon
|
||||
className="h-4 w-4 text-white/80 drop-shadow-sm"
|
||||
title={getStateLabel(
|
||||
project.state,
|
||||
title={getStatusLabel(
|
||||
project.status,
|
||||
t
|
||||
)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ const ProjectModal: React.FC<ProjectModalProps> = ({
|
|||
name: '',
|
||||
description: '',
|
||||
area_id: null,
|
||||
state: 'idea',
|
||||
status: 'not_started',
|
||||
tags: [],
|
||||
priority: null,
|
||||
due_date_at: null,
|
||||
|
|
@ -71,7 +71,7 @@ const ProjectModal: React.FC<ProjectModalProps> = ({
|
|||
|
||||
// Collapsible sections state
|
||||
const [expandedSections, setExpandedSections] = useState({
|
||||
state: false,
|
||||
status: false,
|
||||
tags: false,
|
||||
area: false,
|
||||
priority: false,
|
||||
|
|
@ -128,7 +128,7 @@ const ProjectModal: React.FC<ProjectModalProps> = ({
|
|||
name: '',
|
||||
description: '',
|
||||
area_id: null,
|
||||
state: 'idea',
|
||||
status: 'not_started',
|
||||
tags: [],
|
||||
priority: null,
|
||||
due_date_at: null,
|
||||
|
|
@ -328,7 +328,7 @@ const ProjectModal: React.FC<ProjectModalProps> = ({
|
|||
formData.name.trim() !== '' ||
|
||||
formData.description?.trim() !== '' ||
|
||||
formData.area_id !== null ||
|
||||
formData.state !== 'idea' ||
|
||||
formData.status !== 'not_started' ||
|
||||
tags.length > 0 ||
|
||||
formData.priority !== null ||
|
||||
formData.due_date_at !== null
|
||||
|
|
@ -340,7 +340,7 @@ const ProjectModal: React.FC<ProjectModalProps> = ({
|
|||
formData.name !== project.name ||
|
||||
formData.description !== project.description ||
|
||||
formData.area_id !== project.area_id ||
|
||||
formData.state !== project.state ||
|
||||
formData.status !== project.status ||
|
||||
formData.priority !== project.priority ||
|
||||
formData.due_date_at !== project.due_date_at;
|
||||
|
||||
|
|
@ -511,25 +511,25 @@ const ProjectModal: React.FC<ProjectModalProps> = ({
|
|||
</div>
|
||||
|
||||
{/* Expandable Sections - Only show when expanded */}
|
||||
{/* State Section - First */}
|
||||
{expandedSections.state && (
|
||||
{/* Status Section - First */}
|
||||
{expandedSections.status && (
|
||||
<div className="border-b border-gray-200 dark:border-gray-700 pb-4 mb-4 px-4">
|
||||
<h3 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">
|
||||
{t(
|
||||
'projects.state',
|
||||
'Project State'
|
||||
'projects.status',
|
||||
'Project Status'
|
||||
)}
|
||||
</h3>
|
||||
<ProjectStateDropdown
|
||||
value={
|
||||
formData.state ||
|
||||
'idea'
|
||||
formData.status ||
|
||||
'not_started'
|
||||
}
|
||||
onChange={(state) =>
|
||||
onChange={(status) =>
|
||||
setFormData(
|
||||
(prev) => ({
|
||||
...prev,
|
||||
state,
|
||||
status,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
|
@ -646,25 +646,26 @@ const ProjectModal: React.FC<ProjectModalProps> = ({
|
|||
<div className="flex items-center justify-between">
|
||||
{/* Left side: Section icons */}
|
||||
<div className="flex items-center space-x-1">
|
||||
{/* State Toggle - First */}
|
||||
{/* Status Toggle - First */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
toggleSection('state')
|
||||
toggleSection('status')
|
||||
}
|
||||
className={`relative p-2 rounded-full transition-colors ${
|
||||
expandedSections.state
|
||||
expandedSections.status
|
||||
? 'bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-400'
|
||||
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700'
|
||||
}`}
|
||||
title={t(
|
||||
'projects.state',
|
||||
'Project State'
|
||||
'projects.status',
|
||||
'Project Status'
|
||||
)}
|
||||
>
|
||||
<PlayIcon className="h-5 w-5" />
|
||||
{formData.state &&
|
||||
formData.state !== 'idea' && (
|
||||
{formData.status &&
|
||||
formData.status !==
|
||||
'not_started' && (
|
||||
<span className="absolute -top-1 -right-1 w-3 h-3 bg-green-500 rounded-full"></span>
|
||||
)}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ const Projects: React.FC = () => {
|
|||
const [orderBy, setOrderBy] = useState<string>('created_at:desc');
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const stateFilter = searchParams.get('state') || 'all';
|
||||
const statusFilter = searchParams.get('status') || 'not_completed';
|
||||
|
||||
// Get area UID from URL parameters
|
||||
const getAreaUidFromParams = () => {
|
||||
|
|
@ -94,16 +94,25 @@ const Projects: React.FC = () => {
|
|||
// Filter options for dropdowns
|
||||
const statusOptions: FilterOption[] = [
|
||||
{ value: 'all', label: t('projects.filters.all') },
|
||||
{ value: 'idea', label: t('projects.states.idea', 'Idea') },
|
||||
{ value: 'planned', label: t('projects.states.planned', 'Planned') },
|
||||
{
|
||||
value: 'not_started',
|
||||
label: t('projectStatus.not_started', 'Not Started'),
|
||||
},
|
||||
{ value: 'planned', label: t('projectStatus.planned', 'Planned') },
|
||||
{
|
||||
value: 'in_progress',
|
||||
label: t('projects.states.in_progress', 'In Progress'),
|
||||
label: t('projectStatus.in_progress', 'In Progress'),
|
||||
},
|
||||
{ value: 'blocked', label: t('projects.states.blocked', 'Blocked') },
|
||||
{ value: 'waiting', label: t('projectStatus.waiting', 'Waiting') },
|
||||
{ value: 'done', label: t('projectStatus.done', 'Completed') },
|
||||
{
|
||||
value: 'completed',
|
||||
label: t('projects.states.completed', 'Completed'),
|
||||
value: 'cancelled',
|
||||
label: t('projectStatus.cancelled', 'Cancelled'),
|
||||
},
|
||||
{ value: 'divider', label: '' },
|
||||
{
|
||||
value: 'not_completed',
|
||||
label: t('projects.filters.notCompleted', 'Not Completed'),
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -241,13 +250,13 @@ const Projects: React.FC = () => {
|
|||
return (project as any).completion_percentage || 0;
|
||||
};
|
||||
|
||||
const handleStateFilterChange = (value: string) => {
|
||||
const handleStatusFilterChange = (value: string) => {
|
||||
const params = new URLSearchParams(searchParams);
|
||||
|
||||
if (value === 'all') {
|
||||
params.delete('state');
|
||||
if (value === 'not_completed') {
|
||||
params.delete('status');
|
||||
} else {
|
||||
params.set('state', value);
|
||||
params.set('status', value);
|
||||
}
|
||||
setSearchParams(params);
|
||||
};
|
||||
|
|
@ -273,10 +282,15 @@ const Projects: React.FC = () => {
|
|||
const displayProjects = useMemo(() => {
|
||||
let filteredProjects = [...projects];
|
||||
|
||||
// Apply state filter
|
||||
if (stateFilter !== 'all') {
|
||||
// Apply status filter
|
||||
if (statusFilter === 'not_completed') {
|
||||
filteredProjects = filteredProjects.filter(
|
||||
(project) => project.state === stateFilter
|
||||
(project) =>
|
||||
project.status !== 'done' && project.status !== 'cancelled'
|
||||
);
|
||||
} else if (statusFilter !== 'all') {
|
||||
filteredProjects = filteredProjects.filter(
|
||||
(project) => project.status === statusFilter
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -347,7 +361,7 @@ const Projects: React.FC = () => {
|
|||
});
|
||||
|
||||
return filteredProjects;
|
||||
}, [projects, stateFilter, actualAreaFilter, searchQuery, orderBy]);
|
||||
}, [projects, statusFilter, actualAreaFilter, searchQuery, orderBy]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
|
|
@ -426,8 +440,8 @@ const Projects: React.FC = () => {
|
|||
<div className="w-full md:w-auto mb-4 md:mb-0">
|
||||
<FilterDropdown
|
||||
options={statusOptions}
|
||||
value={stateFilter}
|
||||
onChange={handleStateFilterChange}
|
||||
value={statusFilter}
|
||||
onChange={handleStatusFilterChange}
|
||||
size="desktop"
|
||||
autoWidth={true}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -103,26 +103,33 @@ const FilterDropdown: React.FC<FilterDropdownProps> = ({
|
|||
style={autoWidth ? dynamicStyles : {}}
|
||||
>
|
||||
<div className="p-1">
|
||||
{options.map((option) => (
|
||||
<button
|
||||
key={option.value}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onChange(option.value);
|
||||
setIsOpen(false);
|
||||
}}
|
||||
className={`block ${isMobile ? 'px-4 py-2 text-xs' : 'px-4 py-2 text-sm'} text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-600 w-full text-left`}
|
||||
>
|
||||
<span className="flex items-center justify-between">
|
||||
<span>{option.label}</span>
|
||||
{value === option.value && (
|
||||
<CheckIcon
|
||||
className={`${iconSize} ml-2`}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
{options.map((option, index) =>
|
||||
option.value === 'divider' ? (
|
||||
<div
|
||||
key={`divider-${index}`}
|
||||
className="my-1 -mx-1 border-t border-gray-100 dark:border-gray-700"
|
||||
/>
|
||||
) : (
|
||||
<button
|
||||
key={option.value}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onChange(option.value);
|
||||
setIsOpen(false);
|
||||
}}
|
||||
className={`block ${isMobile ? 'px-4 py-2 text-xs' : 'px-4 py-2 text-sm'} text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-600 w-full text-left`}
|
||||
>
|
||||
<span className="flex items-center justify-between">
|
||||
<span>{option.label}</span>
|
||||
{value === option.value && (
|
||||
<CheckIcon
|
||||
className={`${iconSize} ml-2`}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
LightBulbIcon,
|
||||
ClipboardDocumentListIcon,
|
||||
PlayIcon,
|
||||
ExclamationTriangleIcon,
|
||||
CheckCircleIcon,
|
||||
ClockIcon,
|
||||
XCircleIcon,
|
||||
EllipsisHorizontalCircleIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { ProjectState } from '../../entities/Project';
|
||||
import { ProjectStatus } from '../../entities/Project';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface ProjectStateDropdownProps {
|
||||
value: ProjectState;
|
||||
onChange: (value: ProjectState) => void;
|
||||
value: ProjectStatus;
|
||||
onChange: (value: ProjectStatus) => void;
|
||||
}
|
||||
|
||||
const ProjectStateDropdown: React.FC<ProjectStateDropdownProps> = ({
|
||||
|
|
@ -23,19 +24,21 @@ const ProjectStateDropdown: React.FC<ProjectStateDropdownProps> = ({
|
|||
|
||||
const states = [
|
||||
{
|
||||
value: 'idea' as ProjectState,
|
||||
label: t('projects.states.idea', 'Idea'),
|
||||
value: 'not_started' as ProjectStatus,
|
||||
label: t('projectStatus.not_started', 'Not Started'),
|
||||
description: t(
|
||||
'projects.states.idea_desc',
|
||||
'captured but not planned yet'
|
||||
'projectStatus.not_started_desc',
|
||||
'captured but not started yet'
|
||||
),
|
||||
icon: (
|
||||
<EllipsisHorizontalCircleIcon className="w-5 h-5 text-gray-500" />
|
||||
),
|
||||
icon: <LightBulbIcon className="w-5 h-5 text-yellow-500" />,
|
||||
},
|
||||
{
|
||||
value: 'planned' as ProjectState,
|
||||
label: t('projects.states.planned', 'Planned'),
|
||||
value: 'planned' as ProjectStatus,
|
||||
label: t('projectStatus.planned', 'Planned'),
|
||||
description: t(
|
||||
'projects.states.planned_desc',
|
||||
'projectStatus.planned_desc',
|
||||
'scoped and ready to start'
|
||||
),
|
||||
icon: (
|
||||
|
|
@ -43,31 +46,37 @@ const ProjectStateDropdown: React.FC<ProjectStateDropdownProps> = ({
|
|||
),
|
||||
},
|
||||
{
|
||||
value: 'in_progress' as ProjectState,
|
||||
label: t('projects.states.in_progress', 'In Progress'),
|
||||
value: 'in_progress' as ProjectStatus,
|
||||
label: t('projectStatus.in_progress', 'In Progress'),
|
||||
description: t(
|
||||
'projects.states.in_progress_desc',
|
||||
'projectStatus.in_progress_desc',
|
||||
'active work happening'
|
||||
),
|
||||
icon: <PlayIcon className="w-5 h-5 text-green-500" />,
|
||||
},
|
||||
{
|
||||
value: 'blocked' as ProjectState,
|
||||
label: t('projects.states.blocked', 'Blocked'),
|
||||
value: 'waiting' as ProjectStatus,
|
||||
label: t('projectStatus.waiting', 'Waiting'),
|
||||
description: t(
|
||||
'projects.states.blocked_desc',
|
||||
'temporarily paused or stuck'
|
||||
'projectStatus.waiting_desc',
|
||||
'waiting on external input'
|
||||
),
|
||||
icon: <ExclamationTriangleIcon className="w-5 h-5 text-red-500" />,
|
||||
icon: <ClockIcon className="w-5 h-5 text-yellow-500" />,
|
||||
},
|
||||
{
|
||||
value: 'completed' as ProjectState,
|
||||
label: t('projects.states.completed', 'Completed'),
|
||||
value: 'done' as ProjectStatus,
|
||||
label: t('projectStatus.done', 'Completed'),
|
||||
description: t('projectStatus.done_desc', 'finished and done'),
|
||||
icon: <CheckCircleIcon className="w-5 h-5 text-green-600" />,
|
||||
},
|
||||
{
|
||||
value: 'cancelled' as ProjectStatus,
|
||||
label: t('projectStatus.cancelled', 'Cancelled'),
|
||||
description: t(
|
||||
'projects.states.completed_desc',
|
||||
'finished and done'
|
||||
'projectStatus.cancelled_desc',
|
||||
'will not be completed'
|
||||
),
|
||||
icon: <CheckCircleIcon className="w-5 h-5 text-gray-500" />,
|
||||
icon: <XCircleIcon className="w-5 h-5 text-red-500" />,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -110,8 +119,8 @@ const ProjectStateDropdown: React.FC<ProjectStateDropdownProps> = ({
|
|||
}
|
||||
};
|
||||
|
||||
const handleSelect = (state: ProjectState) => {
|
||||
onChange(state);
|
||||
const handleSelect = (status: ProjectStatus) => {
|
||||
onChange(status);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
|
|
@ -182,7 +191,7 @@ const ProjectStateDropdown: React.FC<ProjectStateDropdownProps> = ({
|
|||
{selectedState ? (
|
||||
selectedState.icon
|
||||
) : (
|
||||
<LightBulbIcon className="w-5 h-5 text-gray-400" />
|
||||
<EllipsisHorizontalCircleIcon className="w-5 h-5 text-gray-400" />
|
||||
)}
|
||||
<span>
|
||||
{selectedState
|
||||
|
|
|
|||
|
|
@ -1196,12 +1196,12 @@ const TasksToday: React.FC = () => {
|
|||
{Array.isArray(localProjects)
|
||||
? localProjects.filter(
|
||||
(project) =>
|
||||
project.state &&
|
||||
project.status &&
|
||||
[
|
||||
'planned',
|
||||
'in_progress',
|
||||
'blocked',
|
||||
].includes(project.state)
|
||||
'waiting',
|
||||
].includes(project.status)
|
||||
).length
|
||||
: 0}
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ import { Tag } from './Tag';
|
|||
import { PriorityType, Task } from './Task';
|
||||
import { Note } from './Note';
|
||||
|
||||
export type ProjectState =
|
||||
| 'idea'
|
||||
export type ProjectStatus =
|
||||
| 'not_started'
|
||||
| 'planned'
|
||||
| 'in_progress'
|
||||
| 'active'
|
||||
| 'blocked'
|
||||
| 'completed';
|
||||
| 'waiting'
|
||||
| 'done'
|
||||
| 'cancelled';
|
||||
|
||||
export interface Project {
|
||||
id?: number;
|
||||
|
|
@ -30,7 +30,7 @@ export interface Project {
|
|||
image_url?: string;
|
||||
task_show_completed?: boolean;
|
||||
task_sort_order?: string;
|
||||
state?: ProjectState;
|
||||
status?: ProjectStatus;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
share_count?: number;
|
||||
|
|
|
|||
|
|
@ -151,7 +151,9 @@
|
|||
"open": "فتح",
|
||||
"completed": "مكتمل",
|
||||
"noCompletedTasksToday": "لا توجد مهام مكتملة اليوم.",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "لم يبدأ بعد",
|
||||
"done": "تم"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "جدول الأنشطة",
|
||||
|
|
@ -820,7 +822,8 @@
|
|||
"active": "نشط",
|
||||
"inactive": "غير نشط",
|
||||
"all": "الكل",
|
||||
"allAreas": "جميع المناطق"
|
||||
"allAreas": "جميع المناطق",
|
||||
"notCompleted": "لم يكتمل"
|
||||
},
|
||||
"selectState": "اختر الحالة",
|
||||
"state": "حالة المشروع",
|
||||
|
|
@ -1342,5 +1345,19 @@
|
|||
"title": "ملاحظة:",
|
||||
"message": "ستكون إشعارات البريد الإلكتروني وإشعارات الدفع متاحة قريبًا. الإشعارات داخل التطبيق وإشعارات تليجرام متاحة حاليًا."
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "لم يبدأ بعد",
|
||||
"not_started_desc": "تم التقاطه ولكن لم يبدأ بعد",
|
||||
"planned": "مخطط",
|
||||
"planned_desc": "تم تحديد نطاقه وجاهز للبدء",
|
||||
"in_progress": "قيد التنفيذ",
|
||||
"in_progress_desc": "عمل نشط جارٍ",
|
||||
"waiting": "انتظار",
|
||||
"waiting_desc": "في انتظار مدخلات خارجية",
|
||||
"done": "مكتمل",
|
||||
"done_desc": "انتهى وتم",
|
||||
"cancelled": "ملغى",
|
||||
"cancelled_desc": "لن يكتمل"
|
||||
}
|
||||
}
|
||||
|
|
@ -151,7 +151,9 @@
|
|||
"open": "Отвори",
|
||||
"completed": "Завършено",
|
||||
"noCompletedTasksToday": "Няма завършени задачи днес.",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "Не е започнато",
|
||||
"done": "Завършено"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "Хронология на активността",
|
||||
|
|
@ -820,7 +822,8 @@
|
|||
"active": "Активен",
|
||||
"inactive": "Неактивен",
|
||||
"all": "Всички",
|
||||
"allAreas": "Всички области"
|
||||
"allAreas": "Всички области",
|
||||
"notCompleted": "Не е завършено"
|
||||
},
|
||||
"selectState": "Изберете състояние",
|
||||
"state": "Състояние на проекта",
|
||||
|
|
@ -1342,5 +1345,19 @@
|
|||
"title": "Забележка:",
|
||||
"message": "Имейл и Push известия ще бъдат налични скоро. В приложението и Telegram известията в момента са налични."
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "Не е започнато",
|
||||
"not_started_desc": "Записано, но все още не е започнато",
|
||||
"planned": "Планирано",
|
||||
"planned_desc": "Определено и готово за започване",
|
||||
"in_progress": "В процес",
|
||||
"in_progress_desc": "Активна работа в ход",
|
||||
"waiting": "Изчакване",
|
||||
"waiting_desc": "Изчакване на външна информация",
|
||||
"done": "Завършено",
|
||||
"done_desc": "Завършено и готово",
|
||||
"cancelled": "Отменено",
|
||||
"cancelled_desc": "Няма да бъде завършено"
|
||||
}
|
||||
}
|
||||
|
|
@ -151,7 +151,9 @@
|
|||
"open": "Åbn",
|
||||
"completed": "Fuldført",
|
||||
"noCompletedTasksToday": "Ingen fuldførte opgaver i dag.",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "Ikke startet",
|
||||
"done": "Færdig"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "Aktivitets Tidslinje",
|
||||
|
|
@ -820,7 +822,8 @@
|
|||
"active": "Aktiv",
|
||||
"inactive": "Inaktiv",
|
||||
"all": "Alle",
|
||||
"allAreas": "Alle områder"
|
||||
"allAreas": "Alle områder",
|
||||
"notCompleted": "Ikke færdiggjort"
|
||||
},
|
||||
"selectState": "Vælg tilstand",
|
||||
"state": "Projektstatus",
|
||||
|
|
@ -1342,5 +1345,19 @@
|
|||
"title": "Bemærk:",
|
||||
"message": "E-mail og push-notifikationer kommer snart. Notifikationer i appen og Telegram er i øjeblikket tilgængelige."
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "Ikke startet",
|
||||
"not_started_desc": "Registreret, men ikke startet endnu",
|
||||
"planned": "Planlagt",
|
||||
"planned_desc": "Afgrænset og klar til at starte",
|
||||
"in_progress": "I gang",
|
||||
"in_progress_desc": "Aktivt arbejde i gang",
|
||||
"waiting": "Venter",
|
||||
"waiting_desc": "Venter på ekstern input",
|
||||
"done": "Færdiggjort",
|
||||
"done_desc": "Afsluttet og færdig",
|
||||
"cancelled": "Annulleret",
|
||||
"cancelled_desc": "Vil ikke blive færdiggjort"
|
||||
}
|
||||
}
|
||||
|
|
@ -151,7 +151,9 @@
|
|||
"open": "Öffnen",
|
||||
"completed": "Abgeschlossen",
|
||||
"noCompletedTasksToday": "Keine abgeschlossenen Aufgaben heute.",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "Nicht gestartet",
|
||||
"done": "Fertig"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "Aktivitätsverlauf",
|
||||
|
|
@ -912,7 +914,8 @@
|
|||
"active": "Aktiv",
|
||||
"inactive": "Inaktiv",
|
||||
"all": "Alle",
|
||||
"allAreas": "Alle Bereiche"
|
||||
"allAreas": "Alle Bereiche",
|
||||
"notCompleted": "Nicht abgeschlossen"
|
||||
},
|
||||
"selectState": "Zustand auswählen",
|
||||
"state": "Projektzustand",
|
||||
|
|
@ -1351,5 +1354,19 @@
|
|||
"title": "Hinweis:",
|
||||
"message": "E-Mail- und Push-Benachrichtigungen kommen bald. In-App- und Telegram-Benachrichtigungen sind derzeit verfügbar."
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "Nicht gestartet",
|
||||
"not_started_desc": "Erfasst, aber noch nicht gestartet",
|
||||
"planned": "Geplant",
|
||||
"planned_desc": "Abgegrenzt und bereit zum Start",
|
||||
"in_progress": "In Bearbeitung",
|
||||
"in_progress_desc": "Aktive Arbeit läuft",
|
||||
"waiting": "Warten",
|
||||
"waiting_desc": "Warten auf externe Eingaben",
|
||||
"done": "Abgeschlossen",
|
||||
"done_desc": "Fertig und abgeschlossen",
|
||||
"cancelled": "Abgebrochen",
|
||||
"cancelled_desc": "Wird nicht abgeschlossen"
|
||||
}
|
||||
}
|
||||
|
|
@ -366,7 +366,9 @@
|
|||
"open": "Άνοιγμα",
|
||||
"completed": "Ολοκληρώθηκε",
|
||||
"noCompletedTasksToday": "Καμία ολοκληρωμένη εργασία σήμερα.",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "Δεν έχει ξεκινήσει",
|
||||
"done": "Ολοκληρώθηκε"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "Χρονοδιάγραμμα Δραστηριότητας",
|
||||
|
|
@ -427,7 +429,8 @@
|
|||
"active": "Ενεργά",
|
||||
"inactive": "Ανενεργά",
|
||||
"all": "Όλα",
|
||||
"allAreas": "Όλες οι περιοχές"
|
||||
"allAreas": "Όλες οι περιοχές",
|
||||
"notCompleted": "Δεν έχει ολοκληρωθεί"
|
||||
},
|
||||
"selectState": "Επιλέξτε Κατάσταση",
|
||||
"state": "Κατάσταση Έργου",
|
||||
|
|
@ -1346,5 +1349,19 @@
|
|||
"title": "Σημείωση:",
|
||||
"message": "Οι ειδοποιήσεις μέσω Email και Push έρχονται σύντομα. Οι ειδοποιήσεις εντός της εφαρμογής και μέσω Telegram είναι διαθέσιμες αυτή τη στιγμή."
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "Δεν έχει ξεκινήσει",
|
||||
"not_started_desc": "Καταγεγραμμένο αλλά δεν έχει ξεκινήσει ακόμα",
|
||||
"planned": "Προγραμματισμένο",
|
||||
"planned_desc": "Καθορισμένο και έτοιμο για εκκίνηση",
|
||||
"in_progress": "Σε εξέλιξη",
|
||||
"in_progress_desc": "Ενεργή εργασία σε εξέλιξη",
|
||||
"waiting": "Αναμονή",
|
||||
"waiting_desc": "Αναμονή για εξωτερική είσοδο",
|
||||
"done": "Ολοκληρώθηκε",
|
||||
"done_desc": "Ολοκληρωμένο και τελειωμένο",
|
||||
"cancelled": "Ακυρώθηκε",
|
||||
"cancelled_desc": "Δεν θα ολοκληρωθεί"
|
||||
}
|
||||
}
|
||||
|
|
@ -812,26 +812,6 @@
|
|||
"metrics": "Projects",
|
||||
"selectState": "Select State",
|
||||
"state": "Project State",
|
||||
"filters": {
|
||||
"active": "Active",
|
||||
"inactive": "Inactive",
|
||||
"all": "All",
|
||||
"allAreas": "All Areas"
|
||||
},
|
||||
"states": {
|
||||
"idea": "Idea",
|
||||
"planned": "Planned",
|
||||
"in_progress": "In Progress",
|
||||
"active": "In Progress",
|
||||
"blocked": "Blocked",
|
||||
"completed": "Completed",
|
||||
"idea_desc": "Captured but not planned yet",
|
||||
"planned_desc": "Scoped and ready to start",
|
||||
"in_progress_desc": "Active work happening",
|
||||
"active_desc": "Active work happening",
|
||||
"blocked_desc": "Temporarily paused or stuck",
|
||||
"completed_desc": "Finished and done"
|
||||
},
|
||||
"showMetrics": "Show metrics",
|
||||
"hideMetrics": "Hide metrics",
|
||||
"progress": "Progress",
|
||||
|
|
@ -854,7 +834,28 @@
|
|||
"nextUp": "Next best action",
|
||||
"focusTask": "Most impactful task",
|
||||
"focusHint": "Shifts this task to in progress and today",
|
||||
"noNextAction": "All clear—no outstanding tasks."
|
||||
"noNextAction": "All clear—no outstanding tasks.",
|
||||
"filters": {
|
||||
"active": "Active",
|
||||
"inactive": "Inactive",
|
||||
"all": "All",
|
||||
"allAreas": "All Areas",
|
||||
"notCompleted": "Not Completed"
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "Not Started",
|
||||
"not_started_desc": "Captured but not started yet",
|
||||
"planned": "Planned",
|
||||
"planned_desc": "Scoped and ready to start",
|
||||
"in_progress": "In Progress",
|
||||
"in_progress_desc": "Active work happening",
|
||||
"waiting": "Waiting",
|
||||
"waiting_desc": "Waiting on external input",
|
||||
"done": "Completed",
|
||||
"done_desc": "Finished and done",
|
||||
"cancelled": "Cancelled",
|
||||
"cancelled_desc": "Will not be completed"
|
||||
},
|
||||
"projectItem": {
|
||||
"edit": "Edit",
|
||||
|
|
|
|||
|
|
@ -366,7 +366,9 @@
|
|||
"open": "Abrir",
|
||||
"completed": "Completado",
|
||||
"noCompletedTasksToday": "No hay tareas completadas hoy.",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "No Iniciado",
|
||||
"done": "Hecho"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "Línea de Tiempo de Actividad",
|
||||
|
|
@ -427,24 +429,11 @@
|
|||
"active": "Activos",
|
||||
"inactive": "Inactivos",
|
||||
"all": "Todos",
|
||||
"allAreas": "Todas las áreas"
|
||||
"allAreas": "Todas las áreas",
|
||||
"notCompleted": "No Completados"
|
||||
},
|
||||
"selectState": "Seleccionar Estado",
|
||||
"state": "Estado del Proyecto",
|
||||
"states": {
|
||||
"idea": "Idea",
|
||||
"planned": "Planificado",
|
||||
"in_progress": "En Progreso",
|
||||
"blocked": "Bloqueado",
|
||||
"completed": "Completado",
|
||||
"idea_desc": "Capturado pero aún no planificado",
|
||||
"planned_desc": "Definido y listo para comenzar",
|
||||
"in_progress_desc": "Trabajo activo en curso",
|
||||
"blocked_desc": "Pausado temporalmente o atascado",
|
||||
"completed_desc": "Terminado y completado",
|
||||
"active": "En Progreso",
|
||||
"active_desc": "Trabajo activo en curso"
|
||||
},
|
||||
"showMetrics": "Mostrar métricas",
|
||||
"hideMetrics": "Ocultar métricas",
|
||||
"progress": "Progreso",
|
||||
|
|
@ -1343,5 +1332,19 @@
|
|||
"title": "Nota:",
|
||||
"message": "Las notificaciones por correo electrónico y Push llegarán pronto. Las notificaciones en la aplicación y de Telegram están disponibles actualmente."
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "No Iniciado",
|
||||
"not_started_desc": "Capturado pero aún no iniciado",
|
||||
"planned": "Planificado",
|
||||
"planned_desc": "Definido y listo para comenzar",
|
||||
"in_progress": "En Progreso",
|
||||
"in_progress_desc": "Trabajo activo en curso",
|
||||
"waiting": "Esperando",
|
||||
"waiting_desc": "Esperando entrada externa",
|
||||
"done": "Completado",
|
||||
"done_desc": "Terminado y hecho",
|
||||
"cancelled": "Cancelado",
|
||||
"cancelled_desc": "No se completará"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,7 +151,9 @@
|
|||
"open": "Avaa",
|
||||
"completed": "Valmis",
|
||||
"noCompletedTasksToday": "Ei suoritettuja tehtäviä tänään.",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "Ei aloitettu",
|
||||
"done": "Valmis"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "Toiminta-aikajana",
|
||||
|
|
@ -820,7 +822,8 @@
|
|||
"active": "Aktiivinen",
|
||||
"inactive": "Passiivinen",
|
||||
"all": "Kaikki",
|
||||
"allAreas": "Kaikki alueet"
|
||||
"allAreas": "Kaikki alueet",
|
||||
"notCompleted": "Ei valmis"
|
||||
},
|
||||
"selectState": "Valitse tila",
|
||||
"state": "Projektin tila",
|
||||
|
|
@ -1342,5 +1345,19 @@
|
|||
"title": "Huom:",
|
||||
"message": "Sähköposti- ja Push-ilmoitukset ovat tulossa pian. Sovelluksen sisäiset ja Telegram-ilmoitukset ovat tällä hetkellä saatavilla."
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "Ei aloitettu",
|
||||
"not_started_desc": "Tallennettu mutta ei vielä aloitettu",
|
||||
"planned": "Suunniteltu",
|
||||
"planned_desc": "Määritelty ja valmis aloittamaan",
|
||||
"in_progress": "Käynnissä",
|
||||
"in_progress_desc": "Aktiivista työtä käynnissä",
|
||||
"waiting": "Odottaa",
|
||||
"waiting_desc": "Odottaa ulkoista syötettä",
|
||||
"done": "Valmistunut",
|
||||
"done_desc": "Valmis ja tehty",
|
||||
"cancelled": "Peruutettu",
|
||||
"cancelled_desc": "Ei tule valmistumaan"
|
||||
}
|
||||
}
|
||||
|
|
@ -151,7 +151,9 @@
|
|||
"open": "Ouvrir",
|
||||
"completed": "Terminé",
|
||||
"noCompletedTasksToday": "Aucune tâche terminée aujourd'hui.",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "Non commencé",
|
||||
"done": "Fait"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "Chronologie d'Activité",
|
||||
|
|
@ -820,7 +822,8 @@
|
|||
"active": "Actif",
|
||||
"inactive": "Inactif",
|
||||
"all": "Tous",
|
||||
"allAreas": "Toutes les zones"
|
||||
"allAreas": "Toutes les zones",
|
||||
"notCompleted": "Non terminé"
|
||||
},
|
||||
"selectState": "Sélectionner l'état",
|
||||
"state": "État du projet",
|
||||
|
|
@ -1342,5 +1345,19 @@
|
|||
"title": "Remarque :",
|
||||
"message": "Les notifications par e-mail et push arrivent bientôt. Les notifications dans l'application et sur Telegram sont actuellement disponibles."
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "Non commencé",
|
||||
"not_started_desc": "Capturé mais pas encore commencé",
|
||||
"planned": "Prévu",
|
||||
"planned_desc": "Défini et prêt à commencer",
|
||||
"in_progress": "En cours",
|
||||
"in_progress_desc": "Travail actif en cours",
|
||||
"waiting": "En attente",
|
||||
"waiting_desc": "En attente d'une entrée externe",
|
||||
"done": "Terminé",
|
||||
"done_desc": "Fini et terminé",
|
||||
"cancelled": "Annulé",
|
||||
"cancelled_desc": "Ne sera pas terminé"
|
||||
}
|
||||
}
|
||||
|
|
@ -151,7 +151,9 @@
|
|||
"open": "Buka",
|
||||
"completed": "Selesai",
|
||||
"noCompletedTasksToday": "Tidak ada tugas yang diselesaikan hari ini.",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "Belum Dimulai",
|
||||
"done": "Selesai"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "Garis Waktu Aktivitas",
|
||||
|
|
@ -820,7 +822,8 @@
|
|||
"active": "Aktif",
|
||||
"inactive": "Tidak Aktif",
|
||||
"all": "Semua",
|
||||
"allAreas": "Semua Area"
|
||||
"allAreas": "Semua Area",
|
||||
"notCompleted": "Belum Selesai"
|
||||
},
|
||||
"selectState": "Pilih Status",
|
||||
"state": "Status Proyek",
|
||||
|
|
@ -1342,5 +1345,19 @@
|
|||
"title": "Catatan:",
|
||||
"message": "Notifikasi Email dan Push akan segera hadir. Notifikasi di dalam aplikasi dan Telegram saat ini tersedia."
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "Belum Dimulai",
|
||||
"not_started_desc": "Tercatat tetapi belum dimulai",
|
||||
"planned": "Direncanakan",
|
||||
"planned_desc": "Sudah ditentukan dan siap untuk dimulai",
|
||||
"in_progress": "Sedang Berlangsung",
|
||||
"in_progress_desc": "Pekerjaan aktif sedang berlangsung",
|
||||
"waiting": "Menunggu",
|
||||
"waiting_desc": "Menunggu masukan eksternal",
|
||||
"done": "Selesai",
|
||||
"done_desc": "Telah selesai dan selesai",
|
||||
"cancelled": "Dibatalkan",
|
||||
"cancelled_desc": "Tidak akan diselesaikan"
|
||||
}
|
||||
}
|
||||
|
|
@ -151,7 +151,9 @@
|
|||
"open": "Apri",
|
||||
"completed": "Completato",
|
||||
"noCompletedTasksToday": "Nessuna attività completata oggi.",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "Non Iniziato",
|
||||
"done": "Fatto"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "Timeline delle Attività",
|
||||
|
|
@ -820,7 +822,8 @@
|
|||
"active": "Attivo",
|
||||
"inactive": "Inattivo",
|
||||
"all": "Tutti",
|
||||
"allAreas": "Tutte le Aree"
|
||||
"allAreas": "Tutte le Aree",
|
||||
"notCompleted": "Non Completato"
|
||||
},
|
||||
"selectState": "Seleziona Stato",
|
||||
"state": "Stato del Progetto",
|
||||
|
|
@ -1342,5 +1345,19 @@
|
|||
"title": "Nota:",
|
||||
"message": "Le notifiche via Email e Push arriveranno presto. Le notifiche in-app e su Telegram sono attualmente disponibili."
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "Non Iniziato",
|
||||
"not_started_desc": "Catturato ma non ancora iniziato",
|
||||
"planned": "Pianificato",
|
||||
"planned_desc": "Definito e pronto per iniziare",
|
||||
"in_progress": "In Corso",
|
||||
"in_progress_desc": "Lavoro attivo in corso",
|
||||
"waiting": "In Attesa",
|
||||
"waiting_desc": "In attesa di input esterno",
|
||||
"done": "Completato",
|
||||
"done_desc": "Finito e completato",
|
||||
"cancelled": "Annullato",
|
||||
"cancelled_desc": "Non sarà completato"
|
||||
}
|
||||
}
|
||||
|
|
@ -151,7 +151,9 @@
|
|||
"open": "開く",
|
||||
"completed": "完了",
|
||||
"noCompletedTasksToday": "本日完了したタスクはありません。",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "未開始",
|
||||
"done": "完了"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "アクティビティタイムライン",
|
||||
|
|
@ -589,7 +591,8 @@
|
|||
"active": "有効",
|
||||
"inactive": "無効",
|
||||
"all": "すべて",
|
||||
"allAreas": "すべてのエリア"
|
||||
"allAreas": "すべてのエリア",
|
||||
"notCompleted": "未完了"
|
||||
},
|
||||
"active": "アクティブ",
|
||||
"inactive": "非アクティブ",
|
||||
|
|
@ -1342,5 +1345,19 @@
|
|||
"title": "注意:",
|
||||
"message": "メールおよびプッシュ通知は近日中に提供予定です。アプリ内およびTelegram通知は現在利用可能です。"
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "未開始",
|
||||
"not_started_desc": "キャプチャされたが、まだ開始されていません",
|
||||
"planned": "計画中",
|
||||
"planned_desc": "スコープが決まり、開始準備が整いました",
|
||||
"in_progress": "進行中",
|
||||
"in_progress_desc": "アクティブな作業が行われています",
|
||||
"waiting": "待機中",
|
||||
"waiting_desc": "外部の入力を待っています",
|
||||
"done": "完了",
|
||||
"done_desc": "終了しました",
|
||||
"cancelled": "キャンセル",
|
||||
"cancelled_desc": "完了しません"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,7 +151,9 @@
|
|||
"open": "열기",
|
||||
"completed": "완료됨",
|
||||
"noCompletedTasksToday": "오늘 완료된 작업이 없습니다.",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "시작되지 않음",
|
||||
"done": "완료됨"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "활동 타임라인",
|
||||
|
|
@ -820,7 +822,8 @@
|
|||
"active": "활성",
|
||||
"inactive": "비활성",
|
||||
"all": "모두",
|
||||
"allAreas": "모든 영역"
|
||||
"allAreas": "모든 영역",
|
||||
"notCompleted": "완료되지 않음"
|
||||
},
|
||||
"selectState": "상태 선택",
|
||||
"state": "프로젝트 상태",
|
||||
|
|
@ -1342,5 +1345,19 @@
|
|||
"title": "참고:",
|
||||
"message": "이메일 및 푸시 알림이 곧 제공됩니다. 현재 앱 내 및 텔레그램 알림이 가능합니다."
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "시작되지 않음",
|
||||
"not_started_desc": "포착되었지만 아직 시작되지 않음",
|
||||
"planned": "계획됨",
|
||||
"planned_desc": "범위가 정해지고 시작할 준비가 됨",
|
||||
"in_progress": "진행 중",
|
||||
"in_progress_desc": "활동적인 작업 진행 중",
|
||||
"waiting": "대기 중",
|
||||
"waiting_desc": "외부 입력 대기 중",
|
||||
"done": "완료됨",
|
||||
"done_desc": "끝났고 완료됨",
|
||||
"cancelled": "취소됨",
|
||||
"cancelled_desc": "완료되지 않을 것임"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,7 +151,9 @@
|
|||
"open": "Open",
|
||||
"completed": "Voltooid",
|
||||
"noCompletedTasksToday": "Geen voltooide taken vandaag.",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "Niet Begonnen",
|
||||
"done": "Voltooid"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "Activiteit Tijdlijn",
|
||||
|
|
@ -820,7 +822,8 @@
|
|||
"active": "Actief",
|
||||
"inactive": "Inactief",
|
||||
"all": "Alle",
|
||||
"allAreas": "Alle gebieden"
|
||||
"allAreas": "Alle gebieden",
|
||||
"notCompleted": "Niet Voltooid"
|
||||
},
|
||||
"selectState": "Selecteer Staat",
|
||||
"state": "Projectstaat",
|
||||
|
|
@ -1342,5 +1345,19 @@
|
|||
"title": "Opmerking:",
|
||||
"message": "E-mail- en pushmeldingen komen binnenkort. In-app en Telegram-meldingen zijn momenteel beschikbaar."
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "Niet Begonnen",
|
||||
"not_started_desc": "Vastgelegd maar nog niet begonnen",
|
||||
"planned": "Gepland",
|
||||
"planned_desc": "Afgebakend en klaar om te beginnen",
|
||||
"in_progress": "Bezig",
|
||||
"in_progress_desc": "Actief werk bezig",
|
||||
"waiting": "Wachten",
|
||||
"waiting_desc": "Wachten op externe input",
|
||||
"done": "Voltooid",
|
||||
"done_desc": "Afgerond en voltooid",
|
||||
"cancelled": "Geannuleerd",
|
||||
"cancelled_desc": "Zal niet worden voltooid"
|
||||
}
|
||||
}
|
||||
|
|
@ -151,7 +151,9 @@
|
|||
"open": "Åpne",
|
||||
"completed": "Fullført",
|
||||
"noCompletedTasksToday": "Ingen fullførte oppgaver i dag.",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "Ikke startet",
|
||||
"done": "Ferdig"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "Aktivitetslinje",
|
||||
|
|
@ -820,7 +822,8 @@
|
|||
"active": "Aktiv",
|
||||
"inactive": "Inaktiv",
|
||||
"all": "Alle",
|
||||
"allAreas": "Alle områder"
|
||||
"allAreas": "Alle områder",
|
||||
"notCompleted": "Ikke fullført"
|
||||
},
|
||||
"selectState": "Velg tilstand",
|
||||
"state": "Prosjektstatus",
|
||||
|
|
@ -1342,5 +1345,19 @@
|
|||
"title": "Merk:",
|
||||
"message": "E-post og Push-varsler kommer snart. Varsler i appen og Telegram er for øyeblikket tilgjengelige."
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "Ikke startet",
|
||||
"not_started_desc": "Registrert, men ikke startet ennå",
|
||||
"planned": "Planlagt",
|
||||
"planned_desc": "Avgrenset og klar til å starte",
|
||||
"in_progress": "Pågår",
|
||||
"in_progress_desc": "Aktivt arbeid pågår",
|
||||
"waiting": "Venter",
|
||||
"waiting_desc": "Venter på ekstern input",
|
||||
"done": "Fullført",
|
||||
"done_desc": "Ferdig og avsluttet",
|
||||
"cancelled": "Avbrutt",
|
||||
"cancelled_desc": "Vil ikke bli fullført"
|
||||
}
|
||||
}
|
||||
|
|
@ -151,7 +151,9 @@
|
|||
"open": "Otwórz",
|
||||
"completed": "Zakończone",
|
||||
"noCompletedTasksToday": "Brak ukończonych zadań dzisiaj.",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "Nie rozpoczęto",
|
||||
"done": "Zrobione"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "Oś czasu aktywności",
|
||||
|
|
@ -820,7 +822,8 @@
|
|||
"active": "Aktywne",
|
||||
"inactive": "Nieaktywne",
|
||||
"all": "Wszystkie",
|
||||
"allAreas": "Wszystkie obszary"
|
||||
"allAreas": "Wszystkie obszary",
|
||||
"notCompleted": "Nie ukończono"
|
||||
},
|
||||
"selectState": "Wybierz stan",
|
||||
"state": "Stan projektu",
|
||||
|
|
@ -1342,5 +1345,19 @@
|
|||
"title": "Uwaga:",
|
||||
"message": "Powiadomienia e-mailowe i push będą dostępne wkrótce. Powiadomienia w aplikacji i na Telegramie są obecnie dostępne."
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "Nie rozpoczęto",
|
||||
"not_started_desc": "Zarejestrowano, ale jeszcze nie rozpoczęto",
|
||||
"planned": "Zaplanowane",
|
||||
"planned_desc": "Zakres ustalony i gotowy do rozpoczęcia",
|
||||
"in_progress": "W trakcie",
|
||||
"in_progress_desc": "Aktywna praca w toku",
|
||||
"waiting": "Oczekiwanie",
|
||||
"waiting_desc": "Oczekiwanie na zewnętrzne dane",
|
||||
"done": "Ukończono",
|
||||
"done_desc": "Zakończono i wykonano",
|
||||
"cancelled": "Anulowane",
|
||||
"cancelled_desc": "Nie zostanie ukończone"
|
||||
}
|
||||
}
|
||||
|
|
@ -151,7 +151,9 @@
|
|||
"open": "Abrir",
|
||||
"completed": "Concluído",
|
||||
"noCompletedTasksToday": "Nenhuma tarefa concluída hoje.",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "Não Iniciado",
|
||||
"done": "Concluído"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "Linha do Tempo de Atividades",
|
||||
|
|
@ -820,7 +822,8 @@
|
|||
"active": "Ativo",
|
||||
"inactive": "Inativo",
|
||||
"all": "Todos",
|
||||
"allAreas": "Todas as Áreas"
|
||||
"allAreas": "Todas as Áreas",
|
||||
"notCompleted": "Não Concluído"
|
||||
},
|
||||
"selectState": "Selecionar Estado",
|
||||
"state": "Estado do Projeto",
|
||||
|
|
@ -1342,5 +1345,19 @@
|
|||
"title": "Nota:",
|
||||
"message": "Notificações por Email e Push estarão disponíveis em breve. Notificações no aplicativo e no Telegram estão atualmente disponíveis."
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "Não Iniciado",
|
||||
"not_started_desc": "Capturado, mas ainda não iniciado",
|
||||
"planned": "Planejado",
|
||||
"planned_desc": "Escopado e pronto para começar",
|
||||
"in_progress": "Em Andamento",
|
||||
"in_progress_desc": "Trabalho ativo em andamento",
|
||||
"waiting": "Aguardando",
|
||||
"waiting_desc": "Aguardando entrada externa",
|
||||
"done": "Concluído",
|
||||
"done_desc": "Finalizado e concluído",
|
||||
"cancelled": "Cancelado",
|
||||
"cancelled_desc": "Não será concluído"
|
||||
}
|
||||
}
|
||||
|
|
@ -151,7 +151,9 @@
|
|||
"open": "Deschide",
|
||||
"completed": "Finalizat",
|
||||
"noCompletedTasksToday": "Nicio sarcină finalizată astăzi.",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "Nefăcut",
|
||||
"done": "Finalizat"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "Cronologia activităților",
|
||||
|
|
@ -820,7 +822,8 @@
|
|||
"active": "Activ",
|
||||
"inactive": "Inactiv",
|
||||
"all": "Toate",
|
||||
"allAreas": "Toate zonele"
|
||||
"allAreas": "Toate zonele",
|
||||
"notCompleted": "Nefinalizat"
|
||||
},
|
||||
"selectState": "Selectați Starea",
|
||||
"state": "Starea Proiectului",
|
||||
|
|
@ -1342,5 +1345,19 @@
|
|||
"title": "Notă:",
|
||||
"message": "Notificările prin Email și Push vor fi disponibile în curând. Notificările în aplicație și pe Telegram sunt disponibile în prezent."
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "Nefăcut",
|
||||
"not_started_desc": "Capturat, dar încă nefăcut",
|
||||
"planned": "Planificat",
|
||||
"planned_desc": "Definit și gata de început",
|
||||
"in_progress": "În desfășurare",
|
||||
"in_progress_desc": "Lucrări active în desfășurare",
|
||||
"waiting": "Așteptând",
|
||||
"waiting_desc": "Așteptând input extern",
|
||||
"done": "Finalizat",
|
||||
"done_desc": "Terminată și finalizată",
|
||||
"cancelled": "Anulat",
|
||||
"cancelled_desc": "Nu va fi finalizat"
|
||||
}
|
||||
}
|
||||
|
|
@ -151,7 +151,9 @@
|
|||
"open": "Открыть",
|
||||
"completed": "Завершено",
|
||||
"noCompletedTasksToday": "Сегодня нет завершенных задач.",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "Не начато",
|
||||
"done": "Выполнено"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "Хронология активности",
|
||||
|
|
@ -820,7 +822,8 @@
|
|||
"active": "Активные",
|
||||
"inactive": "Неактивные",
|
||||
"all": "Все",
|
||||
"allAreas": "Все области"
|
||||
"allAreas": "Все области",
|
||||
"notCompleted": "Не завершено"
|
||||
},
|
||||
"selectState": "Выберите состояние",
|
||||
"state": "Состояние проекта",
|
||||
|
|
@ -1342,5 +1345,19 @@
|
|||
"title": "Примечание:",
|
||||
"message": "Уведомления по электронной почте и Push скоро будут доступны. Уведомления в приложении и Telegram в настоящее время доступны."
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "Не начато",
|
||||
"not_started_desc": "Зафиксировано, но еще не начато",
|
||||
"planned": "Запланировано",
|
||||
"planned_desc": "Определено и готово к началу",
|
||||
"in_progress": "В процессе",
|
||||
"in_progress_desc": "Активная работа ведется",
|
||||
"waiting": "Ожидание",
|
||||
"waiting_desc": "Ожидание внешнего ввода",
|
||||
"done": "Завершено",
|
||||
"done_desc": "Закончено и выполнено",
|
||||
"cancelled": "Отменено",
|
||||
"cancelled_desc": "Не будет завершено"
|
||||
}
|
||||
}
|
||||
|
|
@ -151,7 +151,9 @@
|
|||
"open": "Odpri",
|
||||
"completed": "Dokončano",
|
||||
"noCompletedTasksToday": "Danes ni zaključenih nalog.",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "Niso začeli",
|
||||
"done": "Dokončano"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "Časovnica aktivnosti",
|
||||
|
|
@ -820,7 +822,8 @@
|
|||
"active": "Aktivno",
|
||||
"inactive": "Neaktivno",
|
||||
"all": "Vse",
|
||||
"allAreas": "Vse področja"
|
||||
"allAreas": "Vse področja",
|
||||
"notCompleted": "Nedokončano"
|
||||
},
|
||||
"selectState": "Izberite stanje",
|
||||
"state": "Stanje projekta",
|
||||
|
|
@ -1342,5 +1345,19 @@
|
|||
"title": "Opomba:",
|
||||
"message": "Obvestila po e-pošti in potisna obvestila kmalu prihajajo. Obvestila v aplikaciji in na Telegramu so trenutno na voljo."
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "Niso začeli",
|
||||
"not_started_desc": "Zajeto, a še ni začeto",
|
||||
"planned": "Načrtovano",
|
||||
"planned_desc": "Opredeljeno in pripravljeno za začetek",
|
||||
"in_progress": "V teku",
|
||||
"in_progress_desc": "Aktivno delo poteka",
|
||||
"waiting": "Čaka",
|
||||
"waiting_desc": "Čaka na zunanje vnose",
|
||||
"done": "Dokončano",
|
||||
"done_desc": "Končano in opravljeno",
|
||||
"cancelled": "Preklicano",
|
||||
"cancelled_desc": "Ne bo dokončano"
|
||||
}
|
||||
}
|
||||
|
|
@ -151,7 +151,9 @@
|
|||
"open": "Öppna",
|
||||
"completed": "Avslutad",
|
||||
"noCompletedTasksToday": "Inga slutförda uppgifter idag.",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "Ej påbörjad",
|
||||
"done": "Utförd"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "Aktivitetslinje",
|
||||
|
|
@ -820,7 +822,8 @@
|
|||
"active": "Aktiva",
|
||||
"inactive": "Inaktiva",
|
||||
"all": "Alla",
|
||||
"allAreas": "Alla områden"
|
||||
"allAreas": "Alla områden",
|
||||
"notCompleted": "Ej slutförd"
|
||||
},
|
||||
"selectState": "Välj status",
|
||||
"state": "Projektstatus",
|
||||
|
|
@ -1342,5 +1345,19 @@
|
|||
"title": "Notera:",
|
||||
"message": "E-post och push-notifikationer kommer snart. In-app och Telegram-notifikationer är för närvarande tillgängliga."
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "Ej påbörjad",
|
||||
"not_started_desc": "Fångad men ej påbörjad än",
|
||||
"planned": "Planerad",
|
||||
"planned_desc": "Definierad och redo att starta",
|
||||
"in_progress": "Pågående",
|
||||
"in_progress_desc": "Aktivt arbete pågår",
|
||||
"waiting": "Väntar",
|
||||
"waiting_desc": "Väntar på extern input",
|
||||
"done": "Slutförd",
|
||||
"done_desc": "Avslutad och klar",
|
||||
"cancelled": "Avbruten",
|
||||
"cancelled_desc": "Kommer inte att slutföras"
|
||||
}
|
||||
}
|
||||
|
|
@ -151,7 +151,9 @@
|
|||
"open": "Aç",
|
||||
"completed": "Tamamlandı",
|
||||
"noCompletedTasksToday": "Bugün tamamlanan görev yok.",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "Başlanmadı",
|
||||
"done": "Tamamlandı"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "Etkinlik Zaman Çizelgesi",
|
||||
|
|
@ -820,7 +822,8 @@
|
|||
"active": "Aktif",
|
||||
"inactive": "Pasif",
|
||||
"all": "Hepsi",
|
||||
"allAreas": "Tüm Alanlar"
|
||||
"allAreas": "Tüm Alanlar",
|
||||
"notCompleted": "Tamamlanmadı"
|
||||
},
|
||||
"selectState": "Durum Seç",
|
||||
"state": "Proje Durumu",
|
||||
|
|
@ -1342,5 +1345,19 @@
|
|||
"title": "Not:",
|
||||
"message": "E-posta ve Push bildirimleri yakında geliyor. Uygulama içi ve Telegram bildirimleri şu anda mevcuttur."
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "Başlanmadı",
|
||||
"not_started_desc": "Kaydedildi ama henüz başlanmadı",
|
||||
"planned": "Planlandı",
|
||||
"planned_desc": "Kapsam belirlendi ve başlamaya hazır",
|
||||
"in_progress": "Devam Ediyor",
|
||||
"in_progress_desc": "Aktif çalışma yapılıyor",
|
||||
"waiting": "Bekliyor",
|
||||
"waiting_desc": "Dışsal girdi bekleniyor",
|
||||
"done": "Tamamlandı",
|
||||
"done_desc": "Bitirildi ve tamamlandı",
|
||||
"cancelled": "İptal Edildi",
|
||||
"cancelled_desc": "Tamamlanmayacak"
|
||||
}
|
||||
}
|
||||
|
|
@ -151,7 +151,9 @@
|
|||
"open": "Відкрити",
|
||||
"completed": "Завершено",
|
||||
"noCompletedTasksToday": "Сьогодні немає завершених завдань.",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "Не розпочато",
|
||||
"done": "Завершено"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "Хронологія Активності",
|
||||
|
|
@ -209,7 +211,8 @@
|
|||
"active": "Активні",
|
||||
"inactive": "Неактивні",
|
||||
"all": "Всі",
|
||||
"allAreas": "Всі області"
|
||||
"allAreas": "Всі області",
|
||||
"notCompleted": "Не завершено"
|
||||
},
|
||||
"active": "Активні",
|
||||
"inactive": "Неактивні",
|
||||
|
|
@ -1342,5 +1345,19 @@
|
|||
"title": "Примітка:",
|
||||
"message": "Сповіщення електронною поштою та Push скоро з'являться. Сповіщення в додатку та Telegram наразі доступні."
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "Не розпочато",
|
||||
"not_started_desc": "Зафіксовано, але ще не розпочато",
|
||||
"planned": "Заплановано",
|
||||
"planned_desc": "Окреслено і готово до початку",
|
||||
"in_progress": "В процесі",
|
||||
"in_progress_desc": "Активна робота триває",
|
||||
"waiting": "Очікування",
|
||||
"waiting_desc": "Очікування на зовнішній внесок",
|
||||
"done": "Завершено",
|
||||
"done_desc": "Закінчено і виконано",
|
||||
"cancelled": "Скасовано",
|
||||
"cancelled_desc": "Не буде завершено"
|
||||
}
|
||||
}
|
||||
|
|
@ -151,7 +151,9 @@
|
|||
"open": "Mở",
|
||||
"completed": "Đã hoàn thành",
|
||||
"noCompletedTasksToday": "Không có nhiệm vụ hoàn thành hôm nay.",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "Chưa bắt đầu",
|
||||
"done": "Đã hoàn thành"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "Dòng Thời Gian Hoạt Động",
|
||||
|
|
@ -820,7 +822,8 @@
|
|||
"active": "Đang hoạt động",
|
||||
"inactive": "Không hoạt động",
|
||||
"all": "Tất cả",
|
||||
"allAreas": "Tất cả các khu vực"
|
||||
"allAreas": "Tất cả các khu vực",
|
||||
"notCompleted": "Chưa hoàn thành"
|
||||
},
|
||||
"selectState": "Chọn Trạng Thái",
|
||||
"state": "Trạng Thái Dự Án",
|
||||
|
|
@ -1342,5 +1345,19 @@
|
|||
"title": "Lưu ý:",
|
||||
"message": "Thông báo Email và Push sẽ sớm có. Thông báo trong ứng dụng và Telegram hiện đang có sẵn."
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "Chưa bắt đầu",
|
||||
"not_started_desc": "Đã ghi nhận nhưng chưa bắt đầu",
|
||||
"planned": "Đã lên kế hoạch",
|
||||
"planned_desc": "Đã xác định và sẵn sàng để bắt đầu",
|
||||
"in_progress": "Đang tiến hành",
|
||||
"in_progress_desc": "Công việc đang diễn ra",
|
||||
"waiting": "Đang chờ",
|
||||
"waiting_desc": "Đang chờ thông tin từ bên ngoài",
|
||||
"done": "Đã hoàn thành",
|
||||
"done_desc": "Đã hoàn tất và xong",
|
||||
"cancelled": "Đã hủy",
|
||||
"cancelled_desc": "Sẽ không được hoàn thành"
|
||||
}
|
||||
}
|
||||
|
|
@ -151,7 +151,9 @@
|
|||
"open": "打开",
|
||||
"completed": "已完成",
|
||||
"noCompletedTasksToday": "今天没有完成的任务。",
|
||||
"markAsDone": "Mark as done"
|
||||
"markAsDone": "Mark as done",
|
||||
"notStarted": "未开始",
|
||||
"done": "完成"
|
||||
},
|
||||
"timeline": {
|
||||
"activityTimeline": "活动时间线",
|
||||
|
|
@ -820,7 +822,8 @@
|
|||
"active": "活动",
|
||||
"inactive": "非活动",
|
||||
"all": "所有",
|
||||
"allAreas": "所有区域"
|
||||
"allAreas": "所有区域",
|
||||
"notCompleted": "未完成"
|
||||
},
|
||||
"selectState": "选择状态",
|
||||
"state": "项目状态",
|
||||
|
|
@ -1342,5 +1345,19 @@
|
|||
"title": "注意:",
|
||||
"message": "电子邮件和推送通知即将推出。应用内和 Telegram 通知目前可用。"
|
||||
}
|
||||
},
|
||||
"projectStatus": {
|
||||
"not_started": "未开始",
|
||||
"not_started_desc": "已捕获但尚未开始",
|
||||
"planned": "已规划",
|
||||
"planned_desc": "已确定范围并准备开始",
|
||||
"in_progress": "进行中",
|
||||
"in_progress_desc": "正在进行的工作",
|
||||
"waiting": "等待中",
|
||||
"waiting_desc": "等待外部输入",
|
||||
"done": "已完成",
|
||||
"done_desc": "已完成并结束",
|
||||
"cancelled": "已取消",
|
||||
"cancelled_desc": "将不会完成"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue