diff --git a/webapp/IronCalc/src/components/RightDrawer/NamedRanges/EditNamedRange.tsx b/webapp/IronCalc/src/components/RightDrawer/NamedRanges/EditNamedRange.tsx new file mode 100644 index 0000000..360955e --- /dev/null +++ b/webapp/IronCalc/src/components/RightDrawer/NamedRanges/EditNamedRange.tsx @@ -0,0 +1,216 @@ +import type { WorksheetProperties } from "@ironcalc/wasm"; +import { Box, IconButton, MenuItem, TextField, styled } from "@mui/material"; +import { t } from "i18next"; +import { Check, X } from "lucide-react"; +import { useState } from "react"; +import type React from "react"; +import { theme } from "../../../theme"; + +interface EditNamedRangeProps { + worksheets: WorksheetProperties[]; + name: string; + scope: string; + formula: string; + onSave: (name: string, scope: string, formula: string) => string | undefined; + onCancel: () => void; +} + +const EditNamedRange: React.FC = ({ + worksheets, + name: initialName, + scope: initialScope, + formula: initialFormula, + onSave, + onCancel, +}) => { + const [name, setName] = useState(initialName); + const [scope, setScope] = useState(initialScope); + const [formula, setFormula] = useState(initialFormula); + const [formulaError, setFormulaError] = useState(false); + + return ( + + + + + Range name + setName(event.target.value)} + onKeyDown={(event) => { + event.stopPropagation(); + }} + onClick={(event) => event.stopPropagation()} + /> + + + Scope + { + setScope(event.target.value); + }} + > + + {t("name_manager_dialog.workbook")} + {` ${t("name_manager_dialog.global")}`} + + {worksheets.map((option) => ( + + {option.name} + + ))} + + + + Refers to + setFormula(event.target.value)} + onKeyDown={(event) => { + event.stopPropagation(); + }} + onClick={(event) => event.stopPropagation()} + /> + + + +
+ + { + const error = onSave(name, scope, formula); + if (error) { + setFormulaError(true); + } + }} + title={t("name_manager_dialog.apply")} + > + + + + + + +
+
+ ); +}; + +const Container = styled("div")({ + height: "100%", + display: "flex", + flexDirection: "column", +}); + +const ContentArea = styled("div")({ + flex: 1, + overflow: "auto", +}); + +const MenuSpan = styled("span")` + font-size: 12px; + font-family: "Inter"; +`; + +const MenuSpanGrey = styled("span")` + white-space: pre; + font-size: 12px; + font-family: "Inter"; + color: ${theme.palette.grey[400]}; +`; + +const StyledBox = styled(Box)` + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + width: auto; + padding: 12px; + + @media (max-width: 600px) { + padding: 12px; + } +`; + +const StyledTextField = styled(TextField)(() => ({ + "& .MuiInputBase-root": { + height: "36px", + width: "100%", + margin: 0, + fontFamily: "Inter", + fontSize: "12px", + }, + "& .MuiInputBase-input": { + padding: "8px", + }, +})); + +const StyledIconButton = styled(IconButton)(({ theme }) => ({ + color: theme.palette.error.main, + borderRadius: "8px", + "&:hover": { + backgroundColor: theme.palette.grey["50"], + }, + "&.Mui-disabled": { + opacity: 0.6, + color: theme.palette.error.light, + }, +})); + +const StyledCheck = styled(Check)(({ theme }) => ({ + color: theme.palette.success.main, +})); + +const Footer = styled("div")` + padding: 8px; + display: flex; + align-items: center; + justify-content: flex-end; + font-size: 12px; + color: ${theme.palette.grey["600"]}; + border-top: 1px solid ${theme.palette.grey["300"]}; +`; + +const IconsWrapper = styled(Box)({ + display: "flex", +}); + +const FieldWrapper = styled(Box)` + display: flex; + flex-direction: column; + width: 100%; + gap: 4px; +`; + +const StyledLabel = styled("label")` + font-size: 12px; + font-family: "Inter"; + font-weight: 500; + color: ${theme.palette.text.primary}; + display: block; +`; + +export default EditNamedRange; diff --git a/webapp/IronCalc/src/components/RightDrawer/NamedRanges/NamedRanges.tsx b/webapp/IronCalc/src/components/RightDrawer/NamedRanges/NamedRanges.tsx index 574da57..4f5e0f2 100644 --- a/webapp/IronCalc/src/components/RightDrawer/NamedRanges/NamedRanges.tsx +++ b/webapp/IronCalc/src/components/RightDrawer/NamedRanges/NamedRanges.tsx @@ -1,20 +1,154 @@ +import type { DefinedName, WorksheetProperties } from "@ironcalc/wasm"; import { Button, Tooltip, styled } from "@mui/material"; import { t } from "i18next"; -import { BookOpen, Plus } from "lucide-react"; -import type React from "react"; +import { BookOpen, ChevronRight, Plus } from "lucide-react"; +import { useState } from "react"; import { theme } from "../../../theme"; +import EditNamedRange from "./EditNamedRange"; interface NamedRangesProps { title?: string; + definedNameList?: DefinedName[]; + worksheets?: WorksheetProperties[]; + updateDefinedName?: ( + name: string, + scope: number | undefined, + newName: string, + newScope: number | undefined, + newFormula: string, + ) => void; + newDefinedName?: ( + name: string, + scope: number | undefined, + formula: string, + ) => void; + selectedArea?: () => string; } const NamedRanges: React.FC = ({ - title = "Named Ranges", + definedNameList = [], + worksheets = [], + updateDefinedName, + newDefinedName, + selectedArea, }) => { + const [editingDefinedName, setEditingDefinedName] = + useState(null); + const [isCreatingNew, setIsCreatingNew] = useState(false); + + const handleListItemClick = (definedName: DefinedName) => { + setEditingDefinedName(definedName); + setIsCreatingNew(false); + }; + + const handleNewClick = () => { + setIsCreatingNew(true); + setEditingDefinedName(null); + }; + + const handleCancel = () => { + setEditingDefinedName(null); + setIsCreatingNew(false); + }; + + const handleSave = ( + name: string, + scope: string, + formula: string, + ): string | undefined => { + if (isCreatingNew) { + if (!newDefinedName) return undefined; + + const scope_index = worksheets.findIndex((s) => s.name === scope); + const newScope = scope_index >= 0 ? scope_index : undefined; + try { + newDefinedName(name, newScope, formula); + setIsCreatingNew(false); + return undefined; + } catch (e) { + return `${e}`; + } + } else { + if (!editingDefinedName || !updateDefinedName) return undefined; + + const scope_index = worksheets.findIndex((s) => s.name === scope); + const newScope = scope_index >= 0 ? scope_index : undefined; + try { + updateDefinedName( + editingDefinedName.name, + editingDefinedName.scope, + name, + newScope, + formula, + ); + setEditingDefinedName(null); + return undefined; + } catch (e) { + return `${e}`; + } + } + }; + + // Show edit view if a named range is being edited or created + if (editingDefinedName || isCreatingNew) { + let name = ""; + let scopeName = "[global]"; + let formula = ""; + + if (editingDefinedName) { + name = editingDefinedName.name; + scopeName = + editingDefinedName.scope !== undefined + ? worksheets[editingDefinedName.scope]?.name || "[unknown]" + : "[global]"; + formula = editingDefinedName.formula; + } else if (isCreatingNew && selectedArea) { + formula = selectedArea(); + } + + return ( + + + + + + ); + } + + // Show list view return ( -

{title}

+ {definedNameList.length > 0 && ( + + {definedNameList.map((definedName) => { + const scopeName = + definedName.scope !== undefined + ? worksheets[definedName.scope]?.name || "[unknown]" + : "[global]"; + return ( + handleListItemClick(definedName)} + tabIndex={0} + > + + {scopeName} + + {definedName.name} + + + ); + })} + + )}