Skip to content

Commit f3f8ccf

Browse files
committed
Add the ability to delete sheets
1 parent 6415d92 commit f3f8ccf

File tree

5 files changed

+203
-31
lines changed

5 files changed

+203
-31
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React from "react";
2+
import {
3+
Alert,
4+
Button,
5+
Dialog,
6+
DialogActions,
7+
DialogContent,
8+
DialogContentText,
9+
DialogTitle,
10+
TextField,
11+
} from "@mui/material";
12+
13+
function SheetDeleteDialog({ open, onClose, onConfirm, sheetName }) {
14+
const [deleteConfirmation, setDeleteConfirmation] = React.useState("");
15+
const [error, setError] = React.useState("");
16+
17+
const handleConfirm = () => {
18+
if (deleteConfirmation === "DELETE") {
19+
onConfirm();
20+
setDeleteConfirmation("");
21+
setError("");
22+
} else {
23+
setError("Please type DELETE to confirm");
24+
}
25+
};
26+
27+
return (
28+
<Dialog open={open} onClose={onClose} onClick={(e) => e.stopPropagation()}>
29+
<DialogTitle>Confirm Delete</DialogTitle>
30+
<DialogContent>
31+
<DialogContentText>
32+
<Alert severity="warning">
33+
Are you sure you want to delete the sheet "{sheetName}"? This action
34+
cannot be undone.
35+
</Alert>
36+
</DialogContentText>
37+
<TextField
38+
fullWidth
39+
margin="normal"
40+
variant="outlined"
41+
value={deleteConfirmation}
42+
onChange={(e) => setDeleteConfirmation(e.target.value)}
43+
placeholder="Type DELETE to confirm"
44+
/>
45+
{error && <p style={{ color: "red" }}>{error}</p>}
46+
</DialogContent>
47+
<DialogActions>
48+
<Button onClick={onClose}>Cancel</Button>
49+
<Button
50+
onClick={handleConfirm}
51+
variant="contained"
52+
disabled={deleteConfirmation !== "DELETE"}
53+
>
54+
Delete
55+
</Button>
56+
</DialogActions>
57+
</Dialog>
58+
);
59+
}
60+
61+
export default SheetDeleteDialog;

llmstack/client/src/components/sheets/SheetsList.jsx

Lines changed: 114 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,105 @@ import { useState } from "react";
22
import { useNavigate } from "react-router-dom";
33
import { Button, Stack, Typography } from "@mui/material";
44
import { AddOutlined } from "@mui/icons-material";
5-
import { useRecoilValue } from "recoil";
6-
import { sheetsListState } from "../../data/atoms";
5+
import { useRecoilValue, useSetRecoilState } from "recoil";
6+
import { sheetsListSelector } from "../../data/atoms";
77
import { SheetFromTemplateDialog } from "./SheetFromTemplateDialog";
8+
import {
9+
Table,
10+
TableBody,
11+
TableCell,
12+
TableHead,
13+
TableRow,
14+
IconButton,
15+
} from "@mui/material";
16+
import { enqueueSnackbar } from "notistack";
17+
import { DeleteOutlineOutlined, EditOutlined } from "@mui/icons-material";
18+
import { axios } from "../../data/axios";
19+
import SheetDeleteDialog from "./SheetDeleteDialog";
20+
import { useEffect } from "react";
821

9-
function SheetListItem(props) {
10-
const { sheet } = props;
22+
function SheetListItem({ sheet, onDelete }) {
1123
const navigate = useNavigate();
24+
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
25+
26+
const handleDelete = () => {
27+
setDeleteDialogOpen(true);
28+
};
29+
30+
const confirmDelete = () => {
31+
onDelete(sheet.uuid);
32+
setDeleteDialogOpen(false);
33+
};
1234

1335
return (
14-
<Stack
15-
key={sheet.id}
16-
onClick={() => {
17-
navigate(`/sheets/${sheet.uuid}`);
36+
<TableRow
37+
key={sheet.uuid}
38+
onClick={() => navigate(`/sheets/${sheet.uuid}`)}
39+
sx={{
40+
cursor: "pointer",
41+
"&:hover": {
42+
backgroundColor: "rgba(0, 0, 0, 0.04)",
43+
},
1844
}}
1945
>
20-
<Typography variant="h6">{sheet.name}</Typography>
21-
<Typography variant="caption" sx={{ color: "#666" }}>
22-
{sheet.data?.description}
23-
</Typography>
24-
</Stack>
46+
<TableCell>
47+
<Typography variant="subtitle1" sx={{ fontWeight: "bold" }}>
48+
{sheet.name}
49+
</Typography>
50+
</TableCell>
51+
<TableCell>
52+
<Typography variant="body2" sx={{ color: "text.secondary" }}>
53+
{sheet.data?.description || "No description available"}
54+
</Typography>
55+
</TableCell>
56+
<TableCell>
57+
<IconButton aria-label="edit" onClick={(e) => e.stopPropagation()}>
58+
<EditOutlined />
59+
</IconButton>
60+
<IconButton
61+
aria-label="delete"
62+
onClick={(e) => {
63+
e.stopPropagation();
64+
handleDelete();
65+
}}
66+
>
67+
<DeleteOutlineOutlined />
68+
</IconButton>
69+
</TableCell>
70+
<SheetDeleteDialog
71+
open={deleteDialogOpen}
72+
onClose={() => setDeleteDialogOpen(false)}
73+
onConfirm={confirmDelete}
74+
sheetName={sheet.name}
75+
/>
76+
</TableRow>
2577
);
2678
}
2779

28-
function SheetsList(props) {
29-
const { selectedSheet, selectSheet } = props;
80+
function SheetsList() {
3081
const [newSheetDialogOpen, setNewSheetDialogOpen] = useState(false);
31-
const sheets = useRecoilValue(sheetsListState);
82+
const sheets = useRecoilValue(sheetsListSelector);
83+
const setSheets = useSetRecoilState(sheetsListSelector);
84+
85+
useEffect(() => {
86+
// This will trigger the selector to fetch sheets if they're not already loaded
87+
setSheets([]);
88+
}, [setSheets]);
89+
90+
const handleDeleteSheet = (sheetId) => {
91+
axios()
92+
.delete(`/api/sheets/${sheetId}`)
93+
.then(() => {
94+
setSheets((prevSheets) =>
95+
prevSheets.filter((sheet) => sheet.uuid !== sheetId),
96+
);
97+
enqueueSnackbar("Sheet deleted successfully", { variant: "success" });
98+
})
99+
.catch((error) => {
100+
console.error("Error deleting sheet:", error);
101+
enqueueSnackbar("Failed to delete sheet", { variant: "error" });
102+
});
103+
};
32104

33105
return (
34106
<Stack>
@@ -52,14 +124,32 @@ function SheetsList(props) {
52124
</Button>
53125
</Stack>
54126
</Typography>
55-
{Object.values(sheets).map((sheet) => (
56-
<SheetListItem
57-
key={sheet.uuid}
58-
sheet={sheet}
59-
selected={selectedSheet === sheet.uuid}
60-
selectSheet={selectSheet}
61-
/>
62-
))}
127+
<Table>
128+
<TableHead>
129+
<TableRow>
130+
<TableCell>Name</TableCell>
131+
<TableCell>Description</TableCell>
132+
<TableCell>Actions</TableCell>
133+
</TableRow>
134+
</TableHead>
135+
<TableBody>
136+
{sheets.length > 0 ? (
137+
sheets.map((sheet) => (
138+
<SheetListItem
139+
key={sheet.uuid}
140+
sheet={sheet}
141+
onDelete={handleDeleteSheet}
142+
/>
143+
))
144+
) : (
145+
<TableRow>
146+
<TableCell colSpan={3}>
147+
<Typography align="center">No sheets available</Typography>
148+
</TableCell>
149+
</TableRow>
150+
)}
151+
</TableBody>
152+
</Table>
63153
<SheetFromTemplateDialog
64154
open={newSheetDialogOpen}
65155
setOpen={setNewSheetDialogOpen}

llmstack/client/src/data/atoms.js

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -909,14 +909,25 @@ export const isUsageLimitReachedState = selector({
909909
},
910910
});
911911

912-
export const sheetsListState = selector({
912+
const sheetsListState = atom({
913913
key: "sheetsListState",
914-
get: async () => {
915-
try {
916-
const sheets = await axios().get("/api/sheets");
917-
return sheets.data;
918-
} catch (error) {
919-
return [];
914+
default: [],
915+
});
916+
917+
export const sheetsListSelector = selector({
918+
key: "sheetsListSelector",
919+
get: async ({ get }) => {
920+
const currentSheets = get(sheetsListState);
921+
if (currentSheets.length === 0) {
922+
try {
923+
const response = await axios().get("/api/sheets");
924+
return response.data;
925+
} catch (error) {
926+
console.error("Error fetching sheets:", error);
927+
return [];
928+
}
920929
}
930+
return currentSheets;
921931
},
932+
set: ({ set }, newValue) => set(sheetsListState, newValue),
922933
});

llmstack/sheets/apis.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ def delete(self, request, sheet_uuid=None):
8686
profile = Profile.objects.get(user=request.user)
8787

8888
sheet = PromptlySheet.objects.get(uuid=sheet_uuid, profile_uuid=profile.uuid)
89+
90+
# Delete associated PromptlySheetRunEntry entries
91+
PromptlySheetRunEntry.objects.filter(sheet_uuid=sheet.uuid).delete()
92+
8993
sheet.delete()
9094

9195
return DRFResponse(status=status.HTTP_204_NO_CONTENT)

llmstack/sheets/models.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,3 +286,9 @@ def save(self, *args, **kwargs):
286286
def register_sheet_delete(sender, instance: PromptlySheet, **kwargs):
287287
if instance.data:
288288
delete_sheet_data_objrefs(instance.data.get("cells", []))
289+
290+
291+
@receiver(post_delete, sender=PromptlySheetRunEntry)
292+
def register_sheet_run_entry_delete(sender, instance: PromptlySheetRunEntry, **kwargs):
293+
if instance.data:
294+
delete_sheet_data_objrefs(instance.data.get("cells", []))

0 commit comments

Comments
 (0)