diff --git a/backend/index.js b/backend/index.js index 56c2f68..dcdfcca 100644 --- a/backend/index.js +++ b/backend/index.js @@ -51,6 +51,10 @@ app.use("/cases", casesIndexRoute); app.use("/cases", casesNewRoute); app.use("/cases", casesEditRoute); +// "/steps" +const stepsDeleteRoute = require("./routes/steps/delete")(sequelize); +app.use("/steps", stepsDeleteRoute); + // "/runs" const runsIndexRoute = require("./routes/runs/index")(sequelize); const runsNewRoute = require("./routes/runs/new")(sequelize); diff --git a/backend/models/caseSteps.js b/backend/models/caseSteps.js new file mode 100644 index 0000000..05a70d2 --- /dev/null +++ b/backend/models/caseSteps.js @@ -0,0 +1,31 @@ +function defineCaseStep(sequelize, DataTypes) { + const CaseStep = sequelize.define("CaseStep", { + caseId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + stepId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + stepNo: { + type: DataTypes.INTEGER, + allowNull: false, + }, + }); + + CaseStep.associate = (models) => { + CaseStep.belongsTo(models.Case, { + foreignKey: "caseId", + onDelete: "CASCADE", + }); + CaseStep.belongsTo(models.Step, { + foreignKey: "stepId", + onDelete: "CASCADE", + }); + }; + + return CaseStep; +} + +module.exports = defineCaseStep; diff --git a/backend/routes/steps/delete.js b/backend/routes/steps/delete.js new file mode 100644 index 0000000..cad7a33 --- /dev/null +++ b/backend/routes/steps/delete.js @@ -0,0 +1,51 @@ +const express = require("express"); +const router = express.Router(); +const defineStep = require("../../models/steps"); +const defineCaseStep = require("../../models/caseSteps"); +const { DataTypes, Op } = require("sequelize"); + +module.exports = function (sequelize) { + const Step = defineStep(sequelize, DataTypes); + const CaseStep = defineCaseStep(sequelize, DataTypes); + + router.delete("/:stepId", async (req, res) => { + const stepId = req.params.stepId; + const caseId = req.query.parentCaseId; + + try { + const step = await Step.findByPk(stepId); + if (!step) { + return res.status(404).send("Step not found"); + } + + // Get caseStep to be deleted. + const deletingCaseStep = await CaseStep.findOne({ + where: { + StepId: stepId, + }, + }); + + // Decrease stepNo for all caseSteps with greater than the caseStep to be deleted. + await CaseStep.update( + { stepNo: sequelize.literal("stepNo - 1") }, + { + where: { + CaseId: caseId, + stepNo: { + [Op.gt]: deletingCaseStep.stepNo, + }, + }, + } + ); + + await step.destroy(); + + res.status(204).send(); + } catch (error) { + console.error(error); + res.status(500).send("Internal Server Error"); + } + }); + + return router; +}; diff --git a/frontend/app/projects/[projectId]/folders/[folderId]/cases/[caseId]/page.tsx b/frontend/app/projects/[projectId]/folders/[folderId]/cases/[caseId]/page.tsx index 46512ae..f839453 100644 --- a/frontend/app/projects/[projectId]/folders/[folderId]/cases/[caseId]/page.tsx +++ b/frontend/app/projects/[projectId]/folders/[folderId]/cases/[caseId]/page.tsx @@ -83,6 +83,30 @@ async function fetchCase(url: string) { } } +/** + * delete step + */ +async function fetchDeleteStep(stepId: number, parentCaseId: number) { + const fetchOptions = { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }; + + const url = `${apiServer}/steps/${stepId}?parentCaseId=${parentCaseId}`; + + try { + const response = await fetch(url, fetchOptions); + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + } catch (error) { + console.error("Error deleting project:", error); + throw error; + } +} + /** * Update folder */ @@ -120,11 +144,20 @@ export default function Page({ const [isUpdating, setIsUpdating] = useState(false); const url = `${apiServer}/cases?caseId=${params.caseId}`; + + const onDeleteClick = async (stepId: number) => { + await fetchDeleteStep(stepId, params.caseId); + const updatedSteps = testCase.Steps.filter(step => step.id !== stepId); + setTestCase({ + ...testCase, + Steps: updatedSteps + }); + }; + useEffect(() => { async function fetchDataEffect() { try { const data = await fetchCase(url); - console.log(data); setTestCase(data); } catch (error) { console.error("Error in effect:", error.message); @@ -288,7 +321,7 @@ export default function Page({ }); }} onStepPlus={() => {}} - onStepDelete={() => {}} + onStepDelete={onDeleteClick} /> )} diff --git a/frontend/app/projects/[projectId]/folders/[folderId]/cases/[caseId]/steps-editor.tsx b/frontend/app/projects/[projectId]/folders/[folderId]/cases/[caseId]/steps-editor.tsx index 021fbc3..83fa42e 100644 --- a/frontend/app/projects/[projectId]/folders/[folderId]/cases/[caseId]/steps-editor.tsx +++ b/frontend/app/projects/[projectId]/folders/[folderId]/cases/[caseId]/steps-editor.tsx @@ -15,9 +15,17 @@ export default function StepsEditor({ onStepPlus, onStepDelete, }: Props) { + + // sort steps by junction table's column + const sortedSteps = steps.slice().sort((a, b) => { + const stepNoA = a.caseSteps.stepNo; + const stepNoB = b.caseSteps.stepNo; + return stepNoA - stepNoB; + }); + return ( <> - {steps.map((step, index) => ( + {sortedSteps.map((step, index) => (