This is an automated email from the ASF dual-hosted git repository. ganning pushed a commit to branch delete-all-button in repository https://gitbox.apache.org/repos/asf/airavata-portals.git
commit f598f2f32ff7f080905f6b35106cb2684bb95379 Author: ganning127 <[email protected]> AuthorDate: Fri Aug 1 00:03:17 2025 -0700 Frontend support for deleting all sessions button --- .../src/components/home/DeleteAllSessions.tsx | 86 +++++++++++ .../src/components/home/SessionCard.tsx | 170 ++++++++++----------- .../src/components/home/SessionsSection.tsx | 94 ++++++------ 3 files changed, 221 insertions(+), 129 deletions(-) diff --git a/airavata-research-portal/src/components/home/DeleteAllSessions.tsx b/airavata-research-portal/src/components/home/DeleteAllSessions.tsx new file mode 100644 index 000000000..e9cfe9038 --- /dev/null +++ b/airavata-research-portal/src/components/home/DeleteAllSessions.tsx @@ -0,0 +1,86 @@ +import {SessionType} from "@/interfaces/SessionType.tsx"; +import {Button, CloseButton, Dialog, Input, Portal, Text, useDialog} from "@chakra-ui/react"; +import {useState} from "react"; +import {toaster} from "@/components/ui/toaster.tsx"; +import {CONTROLLER} from "@/lib/controller.ts"; +import api from "@/lib/api.ts"; + +export const DeleteAllSessions = ({ + sessions, + setSessions + }: { + sessions: SessionType[], + setSessions: (sessions: SessionType[]) => void +}) => { + const dialog = useDialog(); + const [loading, setLoading] = useState(false); + const [deleteName, setDeleteName] = useState(""); + const handleDelete = async () => { + setLoading(true); + + try { + const ids = sessions.map(s => s.id); + await api.delete(`${CONTROLLER.sessions}/delete/${ids.join(",")}`); + setSessions([]); + } catch { + toaster.create({ + 'title': 'Error', + 'description': 'Failed to delete all sessions', + 'type': 'error' + }) + } finally { + setLoading(false); + } + } + return ( + <> + <Dialog.RootProvider size="sm" value={dialog}> + <Portal> + <Dialog.Backdrop/> + <Dialog.Positioner> + <Dialog.Content> + <Dialog.Header> + <Dialog.Title>Delete All Sessions</Dialog.Title> + </Dialog.Header> + <Dialog.Body> + <Text color="gray.500"> + This action is irreversible. To confirm, please type:{" "} + <b>delete all sessions</b>. + </Text> + + <Input + mt={2} + placeholder="Session name" + value={deleteName} + onChange={(e) => setDeleteName(e.target.value)} + /> + </Dialog.Body> + <Dialog.Footer> + <Button + width="100%" + colorPalette="red" + disabled={deleteName !== "delete all sessions" || loading} + loading={loading} + onClick={handleDelete} + > + Delete + </Button> + </Dialog.Footer> + <Dialog.CloseTrigger asChild> + <CloseButton size="sm"/> + </Dialog.CloseTrigger> + </Dialog.Content> + </Dialog.Positioner> + </Portal> + </Dialog.RootProvider> + + <Button + colorPalette={'red'} + loading={loading} + onClick={() => dialog.setOpen(true)} + > + Delete All + </Button> + </> + ) +} \ No newline at end of file diff --git a/airavata-research-portal/src/components/home/SessionCard.tsx b/airavata-research-portal/src/components/home/SessionCard.tsx index 2bd14a2d9..8f9add93a 100644 --- a/airavata-research-portal/src/components/home/SessionCard.tsx +++ b/airavata-research-portal/src/components/home/SessionCard.tsx @@ -1,26 +1,26 @@ -import { SessionType } from "@/interfaces/SessionType"; +import {SessionType} from "@/interfaces/SessionType"; import { + Badge, Box, - Text, + Button, Card, + CloseButton, + Dialog, Heading, HStack, - Badge, - VStack, IconButton, - Dialog, - useDialog, - CloseButton, - Portal, - Button, Input, + Portal, + Text, + useDialog, + VStack, } from "@chakra-ui/react"; -import { SessionCardControls } from "./SessionCardControls"; -import { SessionStatusEnum } from "@/interfaces/SessionStatusEnum"; -import { FaTrash } from "react-icons/fa"; -import { toaster } from "../ui/toaster"; -import { CONTROLLER } from "@/lib/controller"; -import { useState } from "react"; +import {SessionCardControls} from "./SessionCardControls"; +import {SessionStatusEnum} from "@/interfaces/SessionStatusEnum"; +import {FaTrash} from "react-icons/fa"; +import {toaster} from "../ui/toaster"; +import {CONTROLLER} from "@/lib/controller"; +import {useState} from "react"; import api from "@/lib/api"; const getColorPalette = (status: string) => { @@ -40,7 +40,7 @@ const getColorPalette = (status: string) => { } }; -export const SessionCard = ({ session }: { session: SessionType }) => { +export const SessionCard = ({session}: { session: SessionType }) => { const dialog = useDialog(); const [hideCard, setHideCard] = useState(false); const [deleteName, setDeleteName] = useState(""); @@ -68,81 +68,79 @@ export const SessionCard = ({ session }: { session: SessionType }) => { }; return ( - <> - <Dialog.RootProvider size="sm" value={dialog}> - <Portal> - <Dialog.Backdrop /> - <Dialog.Positioner> - <Dialog.Content> - <Dialog.Header> - <Dialog.Title>Delete Session</Dialog.Title> - </Dialog.Header> - <Dialog.Body> - <Text color="gray.500"> - This action is irreversible. To confirm, please type:{" "} - <b>{session.sessionName}</b>. - </Text> + <> + <Dialog.RootProvider size="sm" value={dialog}> + <Portal> + <Dialog.Backdrop/> + <Dialog.Positioner> + <Dialog.Content> + <Dialog.Header> + <Dialog.Title>Delete Session</Dialog.Title> + </Dialog.Header> + <Dialog.Body> + <Text color="gray.500"> + This action is irreversible. To confirm, please type:{" "} + <b>{session.sessionName}</b>. + </Text> - <Input - mt={2} - placeholder="Session name" - value={deleteName} - onChange={(e) => setDeleteName(e.target.value)} - /> - </Dialog.Body> - <Dialog.Footer> - <Button - width="100%" - colorPalette="red" - disabled={deleteName !== session.sessionName || deleteLoading} - loading={deleteLoading} - onClick={handleDeleteSession} - > - Delete - </Button> - </Dialog.Footer> - <Dialog.CloseTrigger asChild> - <CloseButton size="sm" /> - </Dialog.CloseTrigger> - </Dialog.Content> - </Dialog.Positioner> - </Portal> - </Dialog.RootProvider> + <Input + mt={2} + placeholder="Session name" + value={deleteName} + onChange={(e) => setDeleteName(e.target.value)} + /> + </Dialog.Body> + <Dialog.Footer> + <Button + width="100%" + colorPalette="red" + disabled={deleteName !== session.sessionName || deleteLoading} + loading={deleteLoading} + onClick={handleDeleteSession} + > + Delete + </Button> + </Dialog.Footer> + <Dialog.CloseTrigger asChild> + <CloseButton size="sm"/> + </Dialog.CloseTrigger> + </Dialog.Content> + </Dialog.Positioner> + </Portal> + </Dialog.RootProvider> - <Card.Root size="sm" height="fit-content" hidden={hideCard}> - <Card.Header> - <HStack justify="space-between" alignItems="flex-start"> - <Box> - <Heading size="lg">{session.sessionName}</Heading> - </Box> - <VStack alignItems="flex-end"> - {session.status === SessionStatusEnum.TERMINATED && ( + <Card.Root size="sm" height="fit-content" hidden={hideCard}> + <Card.Header> + <HStack justify="space-between" alignItems="flex-start"> + <Box> + <Heading size="lg">{session.sessionName}</Heading> + </Box> + <VStack alignItems="flex-end"> <IconButton - color="red.600" - size="xs" - variant={"ghost"} - onClick={() => dialog.setOpen(true)} + color="red.600" + size="xs" + variant={"ghost"} + onClick={() => dialog.setOpen(true)} > - <FaTrash /> + <FaTrash/> </IconButton> - )} - </VStack> - </HStack> - </Card.Header> - <Card.Body> - <HStack alignItems={"center"} gap={1}> - <Badge size="md" colorPalette={getColorPalette(session.status)}> - {session.status} - </Badge> - • - <Text color="fg.muted"> - {new Date(session.createdAt).toLocaleString()} - </Text> - </HStack> + </VStack> + </HStack> + </Card.Header> + <Card.Body> + <HStack alignItems={"center"} gap={1}> + <Badge size="md" colorPalette={getColorPalette(session.status)}> + {session.status} + </Badge> + • + <Text color="fg.muted"> + {new Date(session.createdAt).toLocaleString()} + </Text> + </HStack> - <SessionCardControls session={session} /> - </Card.Body> - </Card.Root> - </> + <SessionCardControls session={session}/> + </Card.Body> + </Card.Root> + </> ); }; diff --git a/airavata-research-portal/src/components/home/SessionsSection.tsx b/airavata-research-portal/src/components/home/SessionsSection.tsx index 1b3de8aca..baf6c27ce 100644 --- a/airavata-research-portal/src/components/home/SessionsSection.tsx +++ b/airavata-research-portal/src/components/home/SessionsSection.tsx @@ -1,10 +1,11 @@ -import { SessionType } from "@/interfaces/SessionType"; -import { SessionCard } from "./SessionCard"; +import {SessionType} from "@/interfaces/SessionType"; +import {SessionCard} from "./SessionCard"; import api from "@/lib/api"; -import { useEffect, useState } from "react"; -import { CONTROLLER } from "@/lib/controller"; -import { Button, HStack, SimpleGrid, Text } from "@chakra-ui/react"; -import { SessionStatusEnum } from "@/interfaces/SessionStatusEnum"; +import {useEffect, useState} from "react"; +import {CONTROLLER} from "@/lib/controller"; +import {Button, HStack, SimpleGrid, Text} from "@chakra-ui/react"; +import {SessionStatusEnum} from "@/interfaces/SessionStatusEnum"; +import {DeleteAllSessions} from "@/components/home/DeleteAllSessions.tsx"; async function getSessions(status: SessionStatusEnum | null = null) { try { @@ -26,7 +27,7 @@ async function getSessions(status: SessionStatusEnum | null = null) { export const SessionsSection = () => { const [sessionStatusFilter, setSessionStatusFilter] = - useState<SessionStatusEnum | null>(null); + useState<SessionStatusEnum | null>(null); const [sessions, setSessions] = useState<SessionType[]>([]); useEffect(() => { @@ -54,42 +55,49 @@ export const SessionsSection = () => { ]; return ( - <> - <HStack flexWrap={"wrap"} gap={2} mt={4}> - <Button - variant={sessionStatusFilter === null ? "solid" : "outline"} - size="sm" - onClick={() => { - setSessionStatusFilter(null); - }} - > - All - </Button> - {/* {Object.values(SessionStatusEnum).map((status) => ( */} - {sessionStatusFilterOptions.map((status) => ( - <Button - key={status} - variant={sessionStatusFilter === status ? "solid" : "outline"} - size="sm" - onClick={() => { - setSessionStatusFilter(status); - }} - > - {status} - </Button> - ))} - </HStack> - <SimpleGrid mt={4} columns={{ base: 1, md: 2, lg: 3 }} gap={4}> - {sessions.map((session: SessionType) => { - return <SessionCard key={session.id} session={session} />; - })} - </SimpleGrid> + <> + <HStack flexWrap={"wrap"} mt={4} justifyContent={'space-between'}> + <HStack flexWrap={"wrap"} gap={2}> + <Button + variant={sessionStatusFilter === null ? "solid" : "outline"} + size="sm" + onClick={() => { + setSessionStatusFilter(null); + }} + > + All + </Button> + {/* {Object.values(SessionStatusEnum).map((status) => ( */} + {sessionStatusFilterOptions.map((status) => ( + <Button + key={status} + variant={sessionStatusFilter === status ? "solid" : "outline"} + size="sm" + onClick={() => { + setSessionStatusFilter(status); + }} + > + {status} + </Button> + ))} + </HStack> + { + sessions.length > 0 && ( + <DeleteAllSessions sessions={sessions} setSessions={setSessions}/> + ) + } + </HStack> + <SimpleGrid mt={4} columns={{base: 1, md: 2, lg: 3}} gap={4}> + {sessions.map((session: SessionType) => { + return <SessionCard key={session.id} session={session}/>; + })} + </SimpleGrid> - {sessions.length === 0 && ( - <Text mt={4} color="gray.500"> - No sessions found. - </Text> - )} - </> + {sessions.length === 0 && ( + <Text mt={4} color="gray.500"> + No sessions found. + </Text> + )} + </> ); };
