From 60c514b63f86bb7a6d9287576fa8fda2de126d1e Mon Sep 17 00:00:00 2001 From: Robson Ventura Rodrigues Date: Mon, 22 Apr 2024 13:57:12 -0300 Subject: [PATCH 1/2] Added card archiving functionality and created status column with corresponding migrations - Implemented the option to archive cards in the system, with support in both backend and frontend. - Created 'status' column in the cards table to manage archiving status. - Developed necessary database migrations for the new column. - Reused the delete confirmation popup to confirm archiving, minimizing code changes. - Modifications made to backend handlers and frontend UI to support the archiving functionality. --- client/src/actions/cards.js | 7 +++++ client/src/components/Boards/EditStep.jsx | 4 +-- client/src/components/Card/ActionsStep.jsx | 27 +++++++++++++++++-- client/src/components/Card/Card.jsx | 9 +++++++ .../CardModal/Activities/ItemComment.jsx | 4 +-- .../CardModal/Attachments/EditStep.jsx | 4 +-- client/src/components/CardModal/CardModal.jsx | 25 +++++++++++++++-- .../CardModal/Tasks/ActionsStep.jsx | 4 +-- .../ConfirmStep.jsx} | 10 +++---- .../ConfirmStep.module.scss} | 0 client/src/components/ConfirmStep/index.js | 3 +++ client/src/components/DeleteStep/index.js | 3 --- client/src/components/LabelsStep/EditStep.jsx | 4 +-- client/src/components/List/ActionsStep.jsx | 4 +-- .../components/Memberships/ActionsStep.jsx | 4 +-- .../GeneralPane/GeneralPane.jsx | 4 +-- .../UsersModal/Item/ActionsStep.jsx | 4 +-- client/src/constants/ActionTypes.js | 1 + client/src/constants/Enums.js | 5 ++++ client/src/locales/cs/core.js | 1 + client/src/locales/da/core.js | 1 + client/src/locales/de/core.js | 1 + client/src/locales/en/core.js | 5 ++++ client/src/locales/es/core.js | 5 ++++ client/src/locales/fr/core.js | 1 + client/src/locales/hu/core.js | 1 + client/src/locales/id/core.js | 1 + client/src/locales/it/core.js | 1 + client/src/locales/ja/core.js | 1 + client/src/locales/nl/core.js | 1 + client/src/locales/pl/core.js | 1 + client/src/locales/pt/core.js | 5 ++++ client/src/locales/ro/core.js | 1 + client/src/locales/ru/core.js | 1 + client/src/locales/sk/core.js | 1 + client/src/locales/sv/core.js | 1 + client/src/locales/tr/core.js | 1 + client/src/locales/ua/core.js | 1 + client/src/locales/uz/core.js | 1 + client/src/locales/zh/core.js | 1 + client/src/models/Card.js | 8 ++++-- client/src/sagas/core/services/cards.js | 6 ++++- docker-compose-dev.yml | 8 +++--- server/api/controllers/cards/create.js | 5 ++++ server/api/controllers/cards/update.js | 5 ++++ server/api/helpers/boards/get-cards.js | 1 + server/api/models/Card.js | 7 +++++ ...240419121805_add_status_column_to_cards.js | 11 ++++++++ server/package.json | 1 + 49 files changed, 174 insertions(+), 37 deletions(-) rename client/src/components/{DeleteStep/DeleteStep.jsx => ConfirmStep/ConfirmStep.jsx} (78%) rename client/src/components/{DeleteStep/DeleteStep.module.scss => ConfirmStep/ConfirmStep.module.scss} (100%) create mode 100644 client/src/components/ConfirmStep/index.js delete mode 100644 client/src/components/DeleteStep/index.js create mode 100644 server/db/migrations/20240419121805_add_status_column_to_cards.js diff --git a/client/src/actions/cards.js b/client/src/actions/cards.js index 2bbe194..45d2baa 100644 --- a/client/src/actions/cards.js +++ b/client/src/actions/cards.js @@ -49,6 +49,13 @@ updateCard.success = (card) => ({ }, }); +updateCard.archive_success = (card) => ({ + type: ActionTypes.CARD_ARCHIVE__SUCCESS, + payload: { + card, + }, +}); + updateCard.failure = (id, error) => ({ type: ActionTypes.CARD_UPDATE__FAILURE, payload: { diff --git a/client/src/components/Boards/EditStep.jsx b/client/src/components/Boards/EditStep.jsx index c02c34f..0dd1b2a 100755 --- a/client/src/components/Boards/EditStep.jsx +++ b/client/src/components/Boards/EditStep.jsx @@ -6,7 +6,7 @@ import { Button, Form } from 'semantic-ui-react'; import { Input, Popup } from '../../lib/custom-ui'; import { useForm, useSteps } from '../../hooks'; -import DeleteStep from '../DeleteStep'; +import ConfirmStep from '../ConfirmStep'; import styles from './EditStep.module.scss'; @@ -54,7 +54,7 @@ const EditStep = React.memo(({ defaultData, onUpdate, onDelete, onClose }) => { if (step && step.type === StepTypes.DELETE) { return ( - { const [t] = useTranslation(); const [step, openStep, handleBack] = useSteps(); @@ -82,6 +85,10 @@ const ActionsStep = React.memo( onClose(); }, [onDuplicate, onClose]); + const handleArchiveClick = useCallback(() => { + openStep(StepTypes.ARCHIVE); + }, [openStep]); + const handleDeleteClick = useCallback(() => { openStep(StepTypes.DELETE); }, [openStep]); @@ -162,7 +169,7 @@ const ActionsStep = React.memo( ); case StepTypes.DELETE: return ( - ); + case StepTypes.ARCHIVE: + return ( + + ); default: } } @@ -218,6 +235,11 @@ const ActionsStep = React.memo( context: 'title', })} + + {t('action.archiveCard', { + context: 'title', + })} + {t('action.deleteCard', { context: 'title', @@ -255,6 +277,7 @@ ActionsStep.propTypes = { onLabelMove: PropTypes.func.isRequired, onLabelDelete: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired, + onArchive: PropTypes.func.isRequired, }; export default ActionsStep; diff --git a/client/src/components/Card/Card.jsx b/client/src/components/Card/Card.jsx index 4adddbc..ccae264 100755 --- a/client/src/components/Card/Card.jsx +++ b/client/src/components/Card/Card.jsx @@ -17,6 +17,7 @@ import DueDate from '../DueDate'; import Stopwatch from '../Stopwatch'; import styles from './Card.module.scss'; +import { CardStatus } from '../../constants/Enums'; const Card = React.memo( ({ @@ -81,6 +82,13 @@ const Card = React.memo( [onUpdate], ); + const handleArchiveClick = useCallback(() => { + onUpdate({ + status: CardStatus.ARCHIVED, + }); + onClose(); + }, [onUpdate]); + const handleNameEdit = useCallback(() => { nameEdit.current.open(); }, []); @@ -197,6 +205,7 @@ const Card = React.memo( onLabelUpdate={onLabelUpdate} onLabelMove={onLabelMove} onLabelDelete={onLabelDelete} + onArchive={handleArchiveClick} > + + + + + { if (step && step.type === StepTypes.DELETE) { return ( - { +const ConfirmStep = React.memo(({ title, content, buttonContent, onConfirm, onBack }) => { const [t] = useTranslation(); return ( @@ -24,7 +24,7 @@ const DeleteStep = React.memo(({ title, content, buttonContent, onConfirm, onBac ); }); -DeleteStep.propTypes = { +ConfirmStep.propTypes = { title: PropTypes.string.isRequired, content: PropTypes.string.isRequired, buttonContent: PropTypes.string.isRequired, @@ -32,8 +32,8 @@ DeleteStep.propTypes = { onBack: PropTypes.func, }; -DeleteStep.defaultProps = { +ConfirmStep.defaultProps = { onBack: undefined, }; -export default DeleteStep; +export default ConfirmStep; diff --git a/client/src/components/DeleteStep/DeleteStep.module.scss b/client/src/components/ConfirmStep/ConfirmStep.module.scss similarity index 100% rename from client/src/components/DeleteStep/DeleteStep.module.scss rename to client/src/components/ConfirmStep/ConfirmStep.module.scss diff --git a/client/src/components/ConfirmStep/index.js b/client/src/components/ConfirmStep/index.js new file mode 100644 index 0000000..bc18473 --- /dev/null +++ b/client/src/components/ConfirmStep/index.js @@ -0,0 +1,3 @@ +import ConfirmStep from './ConfirmStep'; + +export default ConfirmStep; diff --git a/client/src/components/DeleteStep/index.js b/client/src/components/DeleteStep/index.js deleted file mode 100644 index e3d0b6e..0000000 --- a/client/src/components/DeleteStep/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import DeleteStep from './DeleteStep'; - -export default DeleteStep; diff --git a/client/src/components/LabelsStep/EditStep.jsx b/client/src/components/LabelsStep/EditStep.jsx index ec1a2ed..cea8651 100755 --- a/client/src/components/LabelsStep/EditStep.jsx +++ b/client/src/components/LabelsStep/EditStep.jsx @@ -8,7 +8,7 @@ import { Popup } from '../../lib/custom-ui'; import { useForm, useSteps } from '../../hooks'; import LabelColors from '../../constants/LabelColors'; import Editor from './Editor'; -import DeleteStep from '../DeleteStep'; +import ConfirmStep from '../ConfirmStep'; import styles from './EditStep.module.scss'; @@ -46,7 +46,7 @@ const EditStep = React.memo(({ defaultData, onUpdate, onDelete, onBack }) => { if (step && step.type === StepTypes.DELETE) { return ( - if (step && step.type === StepTypes.DELETE) { return ( - { const [t] = useTranslation(); - const DeletePopup = usePopup(DeleteStep); + const DeletePopup = usePopup(ConfirmStep); return ( diff --git a/client/src/components/UsersModal/Item/ActionsStep.jsx b/client/src/components/UsersModal/Item/ActionsStep.jsx index 7b0eaed..76282ed 100644 --- a/client/src/components/UsersModal/Item/ActionsStep.jsx +++ b/client/src/components/UsersModal/Item/ActionsStep.jsx @@ -10,7 +10,7 @@ import UserInformationEditStep from '../../UserInformationEditStep'; import UserUsernameEditStep from '../../UserUsernameEditStep'; import UserEmailEditStep from '../../UserEmailEditStep'; import UserPasswordEditStep from '../../UserPasswordEditStep'; -import DeleteStep from '../../DeleteStep'; +import ConfirmStep from '../../ConfirmStep'; import styles from './ActionsStep.module.scss'; @@ -110,7 +110,7 @@ const ActionsStep = React.memo( ); case StepTypes.DELETE: return ( - CardStatus.ACTIVE, + }), isSubscribed: attr({ getDefault: () => false, }), @@ -202,6 +205,7 @@ export default class extends BaseModel { 'listId', 'position', 'name', + 'status', 'description', 'dueDate', 'stopwatch', @@ -235,9 +239,9 @@ export default class extends BaseModel { } case ActionTypes.CARD_DELETE: Card.withId(payload.id).deleteWithRelated(); - break; case ActionTypes.CARD_DELETE__SUCCESS: + case ActionTypes.CARD_ARCHIVE__SUCCESS: case ActionTypes.CARD_DELETE_HANDLE: { const cardModel = Card.withId(payload.card.id); diff --git a/client/src/sagas/core/services/cards.js b/client/src/sagas/core/services/cards.js index 7232c3f..02bd8fe 100644 --- a/client/src/sagas/core/services/cards.js +++ b/client/src/sagas/core/services/cards.js @@ -7,6 +7,7 @@ import actions from '../../../actions'; import api from '../../../api'; import i18n from '../../../i18n'; import { createLocalId } from '../../../utils/local-id'; +import { CardStatus } from '../../../constants/Enums'; export function* createCard(listId, data, autoOpen) { const { boardId } = yield select(selectors.selectListById, listId); @@ -63,7 +64,6 @@ export function* handleCardCreate({ id }) { export function* updateCard(id, data) { yield put(actions.updateCard(id, data)); - let card; try { ({ item: card } = yield call(request, api.updateCard, id, data)); @@ -72,6 +72,10 @@ export function* updateCard(id, data) { return; } + if ('status' in data && data.status === CardStatus.ARCHIVED) { + yield put(actions.updateCard.archive_success(card)); + return; + } yield put(actions.updateCard.success(card)); } diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index b3eccfd..e5ecc0c 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -75,10 +75,10 @@ services: dockerfile: ../config/development/Dockerfile.server environment: - DATABASE_URL=postgresql://user:password@postgres:5432/planka_db - # - DEFAULT_ADMIN_EMAIL=demo@demo.demo # Do not remove if you want to prevent this user from being edited/deleted - # - DEFAULT_ADMIN_PASSWORD=demo - # - DEFAULT_ADMIN_NAME=Demo Demo - # - DEFAULT_ADMIN_USERNAME=demo + - DEFAULT_ADMIN_EMAIL=admin@robsonvnt.me # Do not remove if you want to prevent this user from being edited/deleted + - DEFAULT_ADMIN_PASSWORD=admin + - DEFAULT_ADMIN_NAME=Admin + - DEFAULT_ADMIN_USERNAME=admin working_dir: /app command: ["sh", "-c", "npm run db:init"] diff --git a/server/api/controllers/cards/create.js b/server/api/controllers/cards/create.js index b847c3e..73ece06 100755 --- a/server/api/controllers/cards/create.js +++ b/server/api/controllers/cards/create.js @@ -44,6 +44,11 @@ module.exports = { position: { type: 'number', }, + status: { + type: 'string', + isIn: ['active', 'archived'], + defaultsTo: 'active', + }, name: { type: 'string', required: true, diff --git a/server/api/controllers/cards/update.js b/server/api/controllers/cards/update.js index 8ee3752..c6bbec6 100755 --- a/server/api/controllers/cards/update.js +++ b/server/api/controllers/cards/update.js @@ -87,6 +87,10 @@ module.exports = { isSubscribed: { type: 'boolean', }, + status: { + type: 'string', + isIn: ['active', 'archived'], + }, }, exits: { @@ -173,6 +177,7 @@ module.exports = { 'dueDate', 'stopwatch', 'isSubscribed', + 'status', ]); card = await sails.helpers.cards.updateOne diff --git a/server/api/helpers/boards/get-cards.js b/server/api/helpers/boards/get-cards.js index e6824d2..70e8f89 100644 --- a/server/api/helpers/boards/get-cards.js +++ b/server/api/helpers/boards/get-cards.js @@ -12,6 +12,7 @@ module.exports = { async fn(inputs) { return sails.helpers.cards.getMany({ boardId: inputs.idOrIds, + status: inputs.status || 'active', }); }, }; diff --git a/server/api/models/Card.js b/server/api/models/Card.js index e1e7335..a4c8b2b 100755 --- a/server/api/models/Card.js +++ b/server/api/models/Card.js @@ -28,6 +28,13 @@ module.exports = { type: 'ref', columnName: 'due_date', }, + status: { + type: 'string', + isIn: ['active', 'archived'], + defaultsTo: 'active', + allowNull: false, + description: 'The current status of the card, which can be active or archived.', + }, stopwatch: { type: 'json', }, diff --git a/server/db/migrations/20240419121805_add_status_column_to_cards.js b/server/db/migrations/20240419121805_add_status_column_to_cards.js new file mode 100644 index 0000000..84dfcb1 --- /dev/null +++ b/server/db/migrations/20240419121805_add_status_column_to_cards.js @@ -0,0 +1,11 @@ +exports.up = async (knex) => { + await knex.schema.table('card', (table) => { + table.enum('status', ['active', 'archived']).defaultTo('active').notNullable(); + }); +}; + +exports.down = async (knex) => { + await knex.schema.table('card', (table) => { + table.dropColumn('status'); + }); +}; diff --git a/server/package.json b/server/package.json index 67fc5e0..4811a83 100644 --- a/server/package.json +++ b/server/package.json @@ -7,6 +7,7 @@ "db:init": "node db/init.js", "db:migrate": "knex migrate:latest --cwd db", "db:seed": "knex seed:run --cwd db", + "db:create-migration": "knex migrate:make --cwd db", "lint": "eslint . --max-warnings=0 --report-unused-disable-directives && echo '✔ Your .js files look good.'", "start": "nodemon", "start:prod": "node app.js --prod", From bb276b3f2e6b317119fdfb5136c39d943bd054fd Mon Sep 17 00:00:00 2001 From: Robson Ventura Rodrigues Date: Mon, 22 Apr 2024 14:21:51 -0300 Subject: [PATCH 2/2] Minor documentation improvements --- client/README.md | 4 +++- server/README.md | 54 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/client/README.md b/client/README.md index d1ee58f..a456b1b 100755 --- a/client/README.md +++ b/client/README.md @@ -1 +1,3 @@ -# Planka client +# Frontend + +The Planka frontend uses a combination of React, Redux, and Saga for state management and side effects, providing a rich and responsive user experience. diff --git a/server/README.md b/server/README.md index 26e62b8..75658ac 100644 --- a/server/README.md +++ b/server/README.md @@ -1 +1,53 @@ -# Planka server +# Planka Server Documentation + +The backend was developed using the SailsJS framework, which facilitates complex operations in web applications, especially when combined with the Knex database migration manager. + +## Modifying the Database Structure + +### Creating a Migration + +**Step 1:** Navigate to the corresponding folder within the project: +```bash +cd ROOT_PATH/server/db +``` + +**Step 2:** Execute the following command to create a new migration file: +```bash +npm run db:create-migration add_change_in_the_database_structure +``` + +This command creates a file in the migration folder named based on the provided argument. The generated file will have the following initial structure: +```Javascript +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function(knex) { + // Add the changes that will be applied to the database +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function(knex) { + // Add the changes that will revert the operations done in up +}; +``` + +#### Adapting to Asynchronous Functions: +In the project, asynchronous functions are used by default. Modify the migration file to adhere to this standard: + +```Javascript +exports.up = async (knex) => { + // Migration logic to apply changes +}; + +exports.down = async (knex) => { + // Migration logic to revert changes +}; +``` + +#### Implementation +Implement the necessary logic in the up and down methods to apply or revert changes in the database. To understand how migrations work and explore detailed examples, refer to the [official Knex documentation on migrations](https://knexjs.org/guide/migrations.html#migration-api). +