Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions app/components/SectionList/DeleteSectionDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {StateDialog} from '@components/Dialog/StateDialog'
import {Loader} from '@components/Loader/Loader'
import {ICreateSectionResponse} from '@controllers/sections.controller'
import {useFetcher, useParams} from '@remix-run/react'
import {API} from '@route/utils/api'
import {Button} from '@ui/button'
import {DialogClose, DialogHeader, DialogTitle} from '@ui/dialog'
import {toast} from '@ui/use-toast'
import React, {useEffect} from 'react'

export const DeleteSectionDialog = (param: {
state: boolean
setState: React.Dispatch<React.SetStateAction<boolean>>
sectionId: number
sectionData: ICreateSectionResponse[]
reloadSections: () => void
}) => {
const params = useParams()
const projectId = +(params['projectId'] ?? 0)
const deleteSectionFetcher = useFetcher<any>()

const deleteSectionButtonClicked = () => {
param.setState(false)
const data = {
projectId: projectId,
sectionId: param.sectionId,
}

deleteSectionFetcher.submit(data, {
method: 'DELETE',
action: `/${API.DeleteSection}`,
encType: 'application/json',
})
}

useEffect(() => {
if (deleteSectionFetcher.data) {
deleteSectionFetcher.data?.data
? toast({
variant: 'success',
description: `Section deleted successfully`,
})
: toast({
variant: 'destructive',
description:
deleteSectionFetcher.data?.error ?? 'Error while deleting',
})
param.reloadSections()
}
}, [deleteSectionFetcher.data])

if (deleteSectionFetcher.state === 'submitting') return <Loader />

return (
<StateDialog
setState={param.setState}
state={param.state}
variant="delete"
headerComponent={
<DialogHeader className="font-bold">
<DialogTitle>Delete Section</DialogTitle>
</DialogHeader>
}
contentComponent={
<div className="flex flex-col mt-2 gap-2">
Are you sure you want to delete this section?
</div>
}
footerComponent={
<DialogClose className="mt-4">
<Button onClick={deleteSectionButtonClicked}>Delete Section</Button>
</DialogClose>
}
/>
)
}
40 changes: 31 additions & 9 deletions app/components/SectionList/RenderSections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import {Tooltip} from '@components/Tooltip/Tooltip'
import {ICreateSectionResponse} from '@controllers/sections.controller'
import {Checkbox} from '@ui/checkbox'
import {cn} from '@ui/utils'
import {ChevronDown, ChevronRight, CirclePlus, Pencil} from 'lucide-react'
import {
ChevronDown,
ChevronRight,
CirclePlus,
Pencil,
Trash2,
} from 'lucide-react'
import React, {memo, useCallback} from 'react'
import {useParams} from 'react-router'
import SectionSkeleton from './SectionSkeleton'
Expand All @@ -24,6 +30,7 @@ interface IRenderSection {
) => void
addSubsectionClicked: (sectionId: number | null) => void
editSubsectionClicked: (sectionId: number) => void
deleteSubsectionClicked: (sectionId: number) => void
}

const RenderSections = memo(
Expand All @@ -36,6 +43,7 @@ const RenderSections = memo(
applySectionFilter,
addSubsectionClicked,
editSubsectionClicked,
deleteSubsectionClicked,
sectionData,
}: IRenderSection) => {
sections = sections
Expand All @@ -61,6 +69,7 @@ const RenderSections = memo(
applySectionFilter={applySectionFilter}
addSubsectionClicked={addSubsectionClicked}
editSubsectionClicked={editSubsectionClicked}
deleteSubsectionClicked={deleteSubsectionClicked}
sectionData={sectionData}
/>
</div>
Expand All @@ -75,6 +84,8 @@ const RenderSections = memo(
selectedSections,
applySectionFilter,
addSubsectionClicked,
editSubsectionClicked,
deleteSubsectionClicked,
],
)

Expand Down Expand Up @@ -154,31 +165,42 @@ const RenderSections = memo(
/>
</div>
{!runId && (
<div className="flex flex-row items-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 absolute top-1/2 left-full transform -translate-y-1/2 ml-2 pb-0.5">
<div className="flex flex-row items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
<Tooltip
anchor={
<CirclePlus
size={14}
color="green"
onClick={() =>
addSubsectionClicked(section.sectionId)
}
size={16}
/>
}
content="Add SubSection"
content={<div className="text-sm">Add Sub-Section</div>}
/>

<Tooltip
anchor={
<Pencil
onClick={() => {
size={14}
color="orange"
onClick={() =>
editSubsectionClicked(section.sectionId)
}}
color="#2d7071"
}
/>
}
content={<div className="text-sm">Edit Section</div>}
/>
<Tooltip
anchor={
<Trash2
size={14}
color="red"
onClick={() =>
deleteSubsectionClicked(section.sectionId)
}
/>
}
content="Edit Section"
content={<div className="text-sm">Delete Section</div>}
/>
</div>
)}
Expand Down
18 changes: 18 additions & 0 deletions app/components/SectionList/SectionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ import {CirclePlus, ListRestart} from 'lucide-react'
import React, {useEffect, useState} from 'react'
import {API} from '~/routes/utilities/api'
import {AddSectionDialog} from './AddSectionDialog'

import {EditSectionDialog} from './EditSectionDialog'
import RenderSections from './RenderSections'
import {SectionInfoBox} from './SectionInfoBox'
import {DeleteSectionDialog} from './DeleteSectionDialog'

export const SectionList = () => {
const [searchParams, setSearchParams] = useSearchParams([])
Expand All @@ -28,6 +30,7 @@ export const SectionList = () => {
)
const [addSectionDialog, setAddSectionDialog] = useState<boolean>(false)
const [editSectionDialog, setEditSectionDialog] = useState<boolean>(false)
const [deleteSectionDialog, setDeleteSectionDialog] = useState<boolean>(false)
const [sectionAPIData, setSectionAPIData] = useState<
ICreateSectionResponse[]
>([])
Expand Down Expand Up @@ -168,6 +171,11 @@ export const SectionList = () => {
setEditSectionDialog(true)
}

const deleteSubsectionClicked = (sectionId: number) => {
setSectionId(sectionId)
setDeleteSectionDialog(true)
}

const reloadSections = () => {
sectionFetcher.load(`/${API.GetSections}?projectId=${projectId}`)
}
Expand Down Expand Up @@ -201,6 +209,7 @@ export const SectionList = () => {
toggleSection={toggleSection}
sectionData={sectionFetcher?.data?.data}
editSubsectionClicked={editSubsectionClicked}
deleteSubsectionClicked={deleteSubsectionClicked}
/>
{!runId && (
<button
Expand Down Expand Up @@ -232,6 +241,15 @@ export const SectionList = () => {
reloadSections={reloadSections}
/>
)}
{sectionId && sectionFetcher?.data?.data && (
<DeleteSectionDialog
state={deleteSectionDialog}
setState={setDeleteSectionDialog}
sectionId={sectionId}
sectionData={sectionFetcher?.data?.data}
reloadSections={reloadSections}
/>
)}
</div>
)
}
23 changes: 23 additions & 0 deletions app/dataController/sections.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import SectionsDao from '~/db/dao/sections.dao'
import TestsDao from '~/db/dao/test.dao'

export interface IGetSectionIdByHierarcy {
sectionName: string
Expand Down Expand Up @@ -63,6 +64,12 @@ export interface IEditSection {
parentId?: number | null
}

export interface IDeleteSection {
sectionId: number
projectId: number
userId: number
}

const SectionsController = {
getAllSections: (
param: IGetAllSections,
Expand Down Expand Up @@ -123,6 +130,22 @@ const SectionsController = {
},

editSection: async (param: IEditSection) => SectionsDao.editSection(param),

deleteSection: async (param: IDeleteSection) => {
const activeTests = await TestsDao.getTestsCount({
projectId: param.projectId,
sectionIds: [param.sectionId],
status: 'Active',
})

if (activeTests && activeTests.count > 0) {
throw new Error(
`Section has ${activeTests.count} active test(s) associated with it, cannot delete section`,
)
}

return SectionsDao.deleteSection(param)
},
}

export default SectionsController
31 changes: 30 additions & 1 deletion app/db/dao/sections.dao.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {errorHandling} from './utils'
const SectionsDao = {
getAllSections: async ({projectId, runId}: IGetAllSections) => {
try {
const whereClauses = []
const whereClauses = [eq(sections.status, 'Active')]

if (projectId) {
whereClauses.push(eq(sections.projectId, projectId))
Expand Down Expand Up @@ -208,6 +208,35 @@ const SectionsDao = {
errorHandling(error)
}
},
deleteSection: async ({
sectionId,
projectId,
userId,
}: {
sectionId: number
projectId: number
userId: number
}) => {
try {
const data = await dbClient
.update(sections)
.set({status: 'Deleted', updatedBy: userId})
.where(
and(
eq(sections.sectionId, sectionId),
eq(sections.projectId, projectId),
),
)
return data
} catch (error: any) {
logger({
type: LogType.SQL_ERROR,
tag: 'Error while deleting section',
message: error,
})
errorHandling(error)
}
},
}

export default SectionsDao
3 changes: 3 additions & 0 deletions app/db/schema/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ export const sections = mysqlTable(
updatedBy: int('updatedBy').references(() => users.userId, {
onDelete: 'set null',
}),
status: mysqlEnum('status', ['Active', 'Deleted'])
.default('Active')
.notNull(),
},
(sections) => {
return {
Expand Down
50 changes: 50 additions & 0 deletions app/routes/api/v1/deleteSection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import SectionsController from '@controllers/sections.controller'
import {ActionFunctionArgs} from '@remix-run/node'
import {z} from 'zod'
import {API} from '~/routes/utilities/api'
import {getUserAndCheckAccess} from '~/routes/utilities/checkForUserAndAccess'
import {
errorResponseHandler,
responseHandler,
} from '~/routes/utilities/responseHandler'
import {getRequestParams} from '../../utilities/utils'

const DeleteSectionRequestSchema = z.object({
sectionId: z.number().gt(0, 'Valid SectionId is required'),
projectId: z.number().gt(0, 'Valid ProjectId is required'),
})

type DeleteSectionRequestAPIType = z.infer<typeof DeleteSectionRequestSchema>

export const action = async ({request}: ActionFunctionArgs) => {
try {
const user = await getUserAndCheckAccess({
request,
resource: API.DeleteSection,
})
const data = await getRequestParams<DeleteSectionRequestAPIType>(
request,
DeleteSectionRequestSchema,
)

const deleteSectionData: any = await SectionsController.deleteSection({
...data,
userId: user?.userId ?? 0,
})

if (deleteSectionData[0]?.affectedRows === 0) {
return responseHandler({
error: `No section found for sectionId: ${data.sectionId}`,
status: 400,
})
}
return responseHandler({
data: {
message: `SectionId: ${data.sectionId} deleted successfully`,
},
status: 200,
})
} catch (error: any) {
return errorResponseHandler(error)
}
}
Loading
Loading