/****************************************************************************** * Copyright (c) 2026 Contributors to the Eclipse Foundation. * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * https://www.eclipse.org/legal/epl-2.0. * * SPDX-License-Identifier: EPL-2.0 *****************************************************************************/ import { FC, useContext, useState, useEffect, useRef, useCallback, useMemo } from 'react'; import { Box, Button, Paper, Typography, CircularProgress, Alert, IconButton, Stack, Chip } from "@mui/material"; import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid"; import EditIcon from "@mui/icons-material/Edit"; import DeleteIcon from "@mui/icons-material/Delete"; import AddIcon from "@mui/icons-material/Add"; import VisibilityIcon from "@mui/icons-material/Visibility"; import { MainContext } from "../../../context"; import type { Customer } from "../../../extension-registry-types"; import { CustomerFormDialog } from "./customer-form-dialog"; import { DeleteCustomerDialog } from "./delete-customer-dialog"; import { createRoute, handleError } from "../../../utils"; import { createMultiSelectFilterOperators, createArrayContainsFilterOperators } from "../components"; import { AdminDashboardRoutes } from "../admin-dashboard-routes"; import { Link } from "react-router-dom"; export const Customers: FC = () => { const abortController = useRef(new AbortController()); const { service } = useContext(MainContext); const [customers, setCustomers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [formDialogOpen, setFormDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [selectedCustomer, setSelectedCustomer] = useState(); // Load all customers const loadCustomers = useCallback(async () => { try { setLoading(true); setError(null); const data = await service.admin.getCustomers(abortController.current); setCustomers(data.customers); } catch (err: any) { setError(handleError(err)); } finally { setLoading(false); } }, [service]); useEffect(() => { loadCustomers(); return () => abortController.current.abort(); }, []); const handleCreateClick = () => { setSelectedCustomer(undefined); setFormDialogOpen(true); }; const handleEditClick = (customer: Customer) => { setSelectedCustomer(customer); setFormDialogOpen(true); }; const handleDeleteClick = (customer: Customer) => { setSelectedCustomer(customer); setDeleteDialogOpen(true); }; const handleFormSubmit = async (customer: Customer) => { if (selectedCustomer) { // update existing customer await service.admin.updateCustomer(abortController.current, selectedCustomer.name, customer); } else { // create new customer await service.admin.createCustomer(abortController.current, customer); } await loadCustomers(); }; const handleDeleteConfirm = async () => { if (selectedCustomer) { await service.admin.deleteCustomer(abortController.current, selectedCustomer.name); await loadCustomers(); } }; const handleFormDialogClose = () => { setFormDialogOpen(false); setSelectedCustomer(undefined); }; const handleDeleteDialogClose = () => { setDeleteDialogOpen(false); setSelectedCustomer(undefined); }; // Extract unique values for filter dropdowns const tierOptions = useMemo(() => [...new Set(customers.map(c => c.tier?.name).filter(Boolean))] as string[], [customers] ); const stateOptions = useMemo(() => [...new Set(customers.map(c => c.state).filter(Boolean))], [customers] ); const cidrBlockOptions = useMemo(() => { const allCidrs = customers.reduce((acc, c) => acc.concat(c.cidrBlocks), []); return [...new Set(allCidrs)]; }, [customers]); const columns: GridColDef[] = [ { field: 'name', headerName: 'Name', flex: 1, minWidth: 150 }, { field: 'tier', headerName: 'Tier', flex: 1, minWidth: 120, valueGetter: (value: Customer['tier']) => value?.name || '', filterOperators: createMultiSelectFilterOperators(tierOptions) }, { field: 'state', headerName: 'State', flex: 1, minWidth: 100, filterOperators: createMultiSelectFilterOperators(stateOptions) }, { field: 'cidrBlocks', headerName: 'CIDR Blocks', flex: 2, minWidth: 200, sortable: false, filterOperators: createArrayContainsFilterOperators(cidrBlockOptions), renderCell: (params: GridRenderCellParams) => { const cidrBlocks = params.row.cidrBlocks; const maxVisible = 2; const visibleCidrs = cidrBlocks.slice(0, maxVisible); const remainingCount = cidrBlocks.length - maxVisible; return ( {visibleCidrs.map((cidr: string) => ( ))} {remainingCount > 0 && ( )} ); } }, { field: 'actions', headerName: 'Actions', width: 160, sortable: false, filterable: false, renderCell: (params: GridRenderCellParams) => ( <> handleEditClick(params.row)} title='Edit' > handleDeleteClick(params.row)} title='Delete' color='error' > ) } ]; return ( Customer Management {error && ( setError(null)}> {error} )} {loading && ( )} { !loading && customers.length === 0 && ( No customers found. Create one to get started. )} { !loading && customers.length > 0 && ( row.name} pageSizeOptions={[20, 35, 50]} initialState={{ pagination: { paginationModel: { pageSize: 20 } }, }} disableRowSelectionOnClick sx={{ flex: 1, }} /> )} ); };