1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-07-18 12:49:43 +02:00

ref: Little refactoring

This commit is contained in:
Maksim Eltyshev 2025-07-14 14:54:06 +02:00
parent 70cadcd974
commit 3aba4d4a56
14 changed files with 123 additions and 52 deletions

View file

@ -214,19 +214,21 @@ oidc:
## ##
extraEnv: [] extraEnv: []
## Example extraEnv for configure SMTP ## Example extraEnv for configuring SMTP
## extraEnv: ## extraEnv:
## - name: SMTP_HOST ## - name: SMTP_HOST
## value: "your_smtp_server" ## value: "smtp.example.com"
## - name: SMTP_PORT ## - name: SMTP_PORT
## value: "587" ## value: "587"
## - name: SMTP_NAME
## value: "Your Name"
## - name: SMTP_SECURE
## value: "true"
## - name: SMTP_USER ## - name: SMTP_USER
## value: "your_email@example.com" ## value: "your_email@example.com"
## - name: SMTP_PASSWORD ## - name: SMTP_PASSWORD
## value: "your_password" ## value: "your_password"
## - name: SMTP_FROM ## - name: SMTP_FROM
## value: "your_email@example.com" ## value: "your_email@example.com"
## - name: SMTP_NAME ## - name: SMTP_TLS_REJECT_UNAUTHORIZED
## value: "your_name_or_login"
## - name: SMTP_SECURE
## value: "false" ## value: "false"

View file

@ -20,14 +20,14 @@ const TaskList = React.memo(({ id }) => {
const tasks = useSelector((state) => selectTasksByTaskListId(state, id)); const tasks = useSelector((state) => selectTasksByTaskListId(state, id));
const [isOpened, toggleOpened] = useToggle();
// TODO: move to selector? // TODO: move to selector?
const completedTasksTotal = useMemo( const completedTasksTotal = useMemo(
() => tasks.reduce((result, task) => (task.isCompleted ? result + 1 : result), 0), () => tasks.reduce((result, task) => (task.isCompleted ? result + 1 : result), 0),
[tasks], [tasks],
); );
const [isOpened, toggleOpened] = useToggle();
const handleToggleClick = useCallback( const handleToggleClick = useCallback(
(event) => { (event) => {
event.stopPropagation(); event.stopPropagation();

View file

@ -71,6 +71,11 @@ export const ListTypes = {
TRASH: 'trash', TRASH: 'trash',
}; };
export const ListTypeStates = {
OPENED: 'opened',
CLOSED: 'closed',
};
export const ListSortFieldNames = { export const ListSortFieldNames = {
NAME: 'name', NAME: 'name',
DUE_DATE: 'dueDate', DUE_DATE: 'dueDate',

View file

@ -0,0 +1,11 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { ListTypes, ListTypeStates } from './Enums';
export default {
[ListTypes.ACTIVE]: ListTypeStates.OPENED,
[ListTypes.CLOSED]: ListTypeStates.CLOSED,
};

View file

@ -10,7 +10,8 @@ import buildSearchParts from '../utils/build-search-parts';
import { isListFinite } from '../utils/record-helpers'; import { isListFinite } from '../utils/record-helpers';
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
import Config from '../constants/Config'; import Config from '../constants/Config';
import { ListSortFieldNames, ListTypes, SortOrders } from '../constants/Enums'; import { ListSortFieldNames, ListTypes, ListTypeStates, SortOrders } from '../constants/Enums';
import LIST_TYPE_STATE_BY_TYPE from '../constants/ListTypeStateByType';
const POSITION_BY_LIST_TYPE = { const POSITION_BY_LIST_TYPE = {
[ListTypes.ARCHIVE]: Number.MAX_SAFE_INTEGER - 1, [ListTypes.ARCHIVE]: Number.MAX_SAFE_INTEGER - 1,
@ -28,6 +29,21 @@ const prepareList = (list) => {
}; };
}; };
const getChangedTypeState = (prevList, list) => {
const prevTypeState = LIST_TYPE_STATE_BY_TYPE[prevList.type];
const typeState = LIST_TYPE_STATE_BY_TYPE[list.type];
if (prevTypeState === ListTypeStates.OPENED && typeState === ListTypeStates.CLOSED) {
return ListTypeStates.CLOSED;
}
if (prevTypeState === ListTypeStates.CLOSED && typeState === ListTypeStates.OPENED) {
return ListTypeStates.OPENED;
}
return null;
};
export default class extends BaseModel { export default class extends BaseModel {
static modelName = 'List'; static modelName = 'List';
@ -121,12 +137,12 @@ export default class extends BaseModel {
let isClosed; let isClosed;
if (payload.data.type) { if (payload.data.type) {
if (payload.data.type === ListTypes.CLOSED) { const changedTypeState = getChangedTypeState(listModel, payload.data);
if (listModel.type === ListTypes.ACTIVE) {
isClosed = true; if (changedTypeState === ListTypeStates.OPENED) {
}
} else if (listModel.type === ListTypes.CLOSED) {
isClosed = false; isClosed = false;
} else if (changedTypeState === ListTypeStates.CLOSED) {
isClosed = true;
} }
} }
@ -150,13 +166,13 @@ export default class extends BaseModel {
const listModel = List.withId(payload.list.id); const listModel = List.withId(payload.list.id);
if (listModel) { if (listModel) {
const changedTypeState = getChangedTypeState(listModel, payload.list);
let isClosed; let isClosed;
if (payload.list.type === ListTypes.CLOSED) { if (changedTypeState === ListTypeStates.OPENED) {
if (listModel.type === ListTypes.ACTIVE) {
isClosed = true;
}
} else if (listModel.type === ListTypes.CLOSED) {
isClosed = false; isClosed = false;
} else if (changedTypeState === ListTypeStates.CLOSED) {
isClosed = true;
} }
listModel.update(prepareList(payload.list)); listModel.update(prepareList(payload.list));

View file

@ -14,7 +14,8 @@ import api from '../../../api';
import { createLocalId } from '../../../utils/local-id'; import { createLocalId } from '../../../utils/local-id';
import { isListArchiveOrTrash, isListFinite } from '../../../utils/record-helpers'; import { isListArchiveOrTrash, isListFinite } from '../../../utils/record-helpers';
import ActionTypes from '../../../constants/ActionTypes'; import ActionTypes from '../../../constants/ActionTypes';
import { BoardViews, ListTypes } from '../../../constants/Enums'; import { BoardViews, ListTypes, ListTypeStates } from '../../../constants/Enums';
import LIST_TYPE_STATE_BY_TYPE from '../../../constants/ListTypeStateByType';
// eslint-disable-next-line no-underscore-dangle // eslint-disable-next-line no-underscore-dangle
const _preloadImage = (url) => const _preloadImage = (url) =>
@ -137,7 +138,7 @@ export function* createCard(listId, data, autoOpen) {
id: localId, id: localId,
boardId: list.boardId, boardId: list.boardId,
creatorUserId: currentUserMembership.userId, creatorUserId: currentUserMembership.userId,
isClosed: list.type === ListTypes.CLOSED, isClosed: LIST_TYPE_STATE_BY_TYPE[list.type] === ListTypeStates.CLOSED,
}, },
autoOpen, autoOpen,
), ),
@ -242,11 +243,13 @@ export function* updateCard(id, data) {
prevListId = null; prevListId = null;
} }
const typeState = LIST_TYPE_STATE_BY_TYPE[list.type];
if (card.isClosed) { if (card.isClosed) {
if (list.type === ListTypes.ACTIVE) { if (typeState === ListTypeStates.OPENED) {
isClosed = false; isClosed = false;
} }
} else if (list.type === ListTypes.CLOSED) { } else if (typeState === ListTypeStates.CLOSED) {
isClosed = true; isClosed = true;
} }
} }

View file

@ -15,6 +15,9 @@ const Errors = {
LINKED_CARD_NOT_FOUND: { LINKED_CARD_NOT_FOUND: {
linkedCardNotFound: 'Linked card not found', linkedCardNotFound: 'Linked card not found',
}, },
LINKED_CARD_OR_NAME_MUST_BE_PRESENT: {
linkedCardOrNameMustBePresent: 'Linked card or name must be present',
},
}; };
module.exports = { module.exports = {
@ -31,8 +34,9 @@ module.exports = {
}, },
name: { name: {
type: 'string', type: 'string',
isNotEmptyString: true,
maxLength: 1024, maxLength: 1024,
// required: true, allowNull: true,
}, },
isCompleted: { isCompleted: {
type: 'boolean', type: 'boolean',
@ -49,6 +53,9 @@ module.exports = {
linkedCardNotFound: { linkedCardNotFound: {
responseType: 'notFound', responseType: 'notFound',
}, },
linkedCardOrNameMustBePresent: {
responseType: 'unprocessableEntity',
},
}, },
async fn(inputs) { async fn(inputs) {
@ -100,19 +107,24 @@ module.exports = {
const values = _.pick(inputs, ['position', 'name', 'isCompleted']); const values = _.pick(inputs, ['position', 'name', 'isCompleted']);
const task = await sails.helpers.tasks.createOne.with({ const task = await sails.helpers.tasks.createOne
project, .with({
board, project,
list, board,
card, list,
values: { card,
...values, values: {
taskList, ...values,
linkedCard, taskList,
}, linkedCard,
actorUser: currentUser, },
request: this.req, actorUser: currentUser,
}); request: this.req,
})
.intercept(
'linkedCardOrNameMustBeInValues',
() => Errors.LINKED_CARD_OR_NAME_MUST_BE_PRESENT,
);
return { return {
item: task, item: task,

View file

@ -67,7 +67,7 @@ module.exports = {
delete values.position; delete values.position;
} }
if (values.list.type === List.Types.CLOSED) { if (List.TYPE_STATE_BY_TYPE[values.list.type] === List.TypeStates.CLOSED) {
values.isClosed = true; values.isClosed = true;
} }

View file

@ -386,11 +386,13 @@ module.exports = {
values.prevListId = null; values.prevListId = null;
} }
const typeState = List.TYPE_STATE_BY_TYPE[values.list.type];
if (inputs.record.isClosed) { if (inputs.record.isClosed) {
if (values.list.type === List.Types.ACTIVE) { if (typeState === List.TypeStates.OPENED) {
values.isClosed = false; values.isClosed = false;
} }
} else if (values.list.type === List.Types.CLOSED) { } else if (typeState === List.TypeStates.CLOSED) {
values.isClosed = true; values.isClosed = true;
} }

View file

@ -34,9 +34,17 @@ module.exports = {
}, },
}, },
exists: {
linkedCardOrNameMustBeInValues: {},
},
async fn(inputs) { async fn(inputs) {
const { values } = inputs; const { values } = inputs;
if (!values.linkedCard && !values.name) {
throw 'linkedCardOrNameMustBeInValues';
}
const tasks = await Task.qm.getByTaskListId(values.taskList.id); const tasks = await Task.qm.getByTaskListId(values.taskList.id);
const { position, repositions } = sails.helpers.utils.insertToPositionables( const { position, repositions } = sails.helpers.utils.insertToPositionables(

View file

@ -58,10 +58,16 @@ const updateOne = async (criteria, values) => {
let tasks = []; let tasks = [];
if (list) { if (list) {
const prevTypeState = List.TYPE_STATE_BY_TYPE[prevList.type];
const typeState = List.TYPE_STATE_BY_TYPE[list.type];
let isClosed; let isClosed;
if (list.type === List.Types.ACTIVE) { if (prevTypeState === List.TypeStates.CLOSED && typeState === List.TypeStates.OPENED) {
isClosed = false; isClosed = false;
} else if (list.type === List.Types.CLOSED) { } else if (
prevTypeState === List.TypeStates.OPENED &&
typeState === List.TypeStates.CLOSED
) {
isClosed = true; isClosed = true;
} }

View file

@ -36,11 +36,6 @@ const getByTaskListIds = async (taskListIds, { sort = ['position', 'id'] } = {})
{ sort }, { sort },
); );
const getByLinkedCardId = (linkedCardId) =>
defaultFind({
linkedCardId,
});
const getOneById = (id, { taskListId } = {}) => { const getOneById = (id, { taskListId } = {}) => {
const criteria = { const criteria = {
id, id,
@ -68,7 +63,6 @@ module.exports = {
getByIds, getByIds,
getByTaskListId, getByTaskListId,
getByTaskListIds, getByTaskListIds,
getByLinkedCardId,
getOneById, getOneById,
update, update,
updateOne, updateOne,

View file

@ -48,16 +48,16 @@ module.exports = {
stopwatch: { stopwatch: {
type: 'json', type: 'json',
}, },
isClosed: {
type: 'boolean',
defaultsTo: false,
columnName: 'is_closed',
},
commentsTotal: { commentsTotal: {
type: 'number', type: 'number',
defaultsTo: 0, defaultsTo: 0,
columnName: 'comments_total', columnName: 'comments_total',
}, },
isClosed: {
type: 'boolean',
defaultsTo: false,
columnName: 'is_closed',
},
listChangedAt: { listChangedAt: {
type: 'ref', type: 'ref',
columnName: 'list_changed_at', columnName: 'list_changed_at',

View file

@ -17,6 +17,11 @@ const Types = {
TRASH: 'trash', TRASH: 'trash',
}; };
const TypeStates = {
OPENED: 'opened',
CLOSED: 'closed',
};
const SortFieldNames = { const SortFieldNames = {
NAME: 'name', NAME: 'name',
DUE_DATE: 'dueDate', DUE_DATE: 'dueDate',
@ -31,6 +36,11 @@ const SortOrders = {
const FINITE_TYPES = [Types.ACTIVE, Types.CLOSED]; const FINITE_TYPES = [Types.ACTIVE, Types.CLOSED];
const TYPE_STATE_BY_TYPE = {
[Types.ACTIVE]: TypeStates.OPENED,
[Types.CLOSED]: Types.CLOSED,
};
const COLORS = [ const COLORS = [
'berry-red', 'berry-red',
'pumpkin-orange', 'pumpkin-orange',
@ -46,9 +56,11 @@ const COLORS = [
module.exports = { module.exports = {
Types, Types,
TypeStates,
SortFieldNames, SortFieldNames,
SortOrders, SortOrders,
FINITE_TYPES, FINITE_TYPES,
TYPE_STATE_BY_TYPE,
COLORS, COLORS,
attributes: { attributes: {