import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Button, Combobox, Dialog, DialogActions, DialogBody, DialogContent, DialogSurface, DialogTitle, Field, Input, makeStyles, Option, Tab, TabList, shorthands, Table, TableBody, TableCell, TableHeader, TableHeaderCell, TableRow, Textarea, Tooltip, } from "@fluentui/react-components"; import { AddRegular, DeleteRegular, EditRegular, OpenRegular } from "@fluentui/react-icons"; import { ChevronDownRegular, ChevronRightRegular } from "@fluentui/react-icons"; import { Fragment, useState } from "react"; import { portalApi } from "../api/portalApi"; import { DataState } from "../components/DataState"; import { PageHeader } from "../components/PageHeader"; import type { ServiceItem, Template, TemplateCategory } from "../types/portal"; const useStyles = makeStyles({ toolbar: { display: "flex", justifyContent: "space-between", alignItems: "flex-end", gap: "12px", marginBottom: "18px", }, toolbarActions: { display: "flex", gap: "8px", alignItems: "center", }, filter: { minWidth: "280px", }, form: { display: "grid", gap: "14px", }, grid: { display: "grid", gap: "14px", gridTemplateColumns: "repeat(2, minmax(180px, 1fr))", }, wide: { gridColumn: "1 / -1", }, actions: { display: "flex", gap: "4px", ...shorthands.padding("2px", "0"), }, value: { overflowWrap: "anywhere", }, groupRow: { cursor: "pointer", }, groupHeader: { display: "flex", alignItems: "center", gap: "8px", }, nestedLevel1Cell: { paddingLeft: "28px", }, nestedLevel2Cell: { paddingLeft: "48px", }, categoryHeaderCell: { paddingTop: "10px", paddingBottom: "6px", paddingLeft: "28px", }, }); type DialogMode = "add" | "edit" | "details" | null; function getTemplateJsonData(template: Template) { return template.jsonData ?? template.jSONData ?? ""; } type TemplateEditorTab = "parameters" | "variables" | "resources" | "raw"; function tryFormatJson(value: string) { try { if (!value.trim()) { return ""; } return JSON.stringify(JSON.parse(value), null, 2); } catch { return value; } } function getCaseInsensitiveProperty(source: Record, propertyName: string) { const key = Object.keys(source).find((entry) => entry.toLowerCase() === propertyName.toLowerCase()); return key ? source[key] : undefined; } function normalizeEditorJsonParts(rawJson: string) { if (!rawJson.trim()) { return { raw: "", parameters: "{}", variables: "{}", resources: "[]", }; } const parsed = JSON.parse(rawJson) as unknown; const root = parsed && typeof parsed === "object" ? (parsed as Record) : {}; const parameters = getCaseInsensitiveProperty(root, "parameters"); const variables = getCaseInsensitiveProperty(root, "variables"); const resources = getCaseInsensitiveProperty(root, "resources"); return { raw: JSON.stringify(root, null, 2), parameters: JSON.stringify( parameters && typeof parameters === "object" && !Array.isArray(parameters) ? parameters : {}, null, 2, ), variables: JSON.stringify( variables && typeof variables === "object" && !Array.isArray(variables) ? variables : {}, null, 2, ), resources: JSON.stringify(Array.isArray(resources) ? resources : [], null, 2), }; } export function TemplatesPage() { const styles = useStyles(); const queryClient = useQueryClient(); const [dialogMode, setDialogMode] = useState(null); const [selectedTemplate, setSelectedTemplate] = useState