/****************************************************************************** * 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, useState, useEffect, 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 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'; import { useCreateCustomer, useCustomers, useDeleteCustomer, useUpdateCustomer } from './use-customers'; export const Customers: FC = () => { const [formDialogOpen, setFormDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [selectedCustomer, setSelectedCustomer] = useState(); const [errorDismissed, setErrorDismissed] = useState(false); const { data, isFetching: loading, error: loadError } = useCustomers(); const { mutateAsync: createCustomer } = useCreateCustomer(); const { mutateAsync: updateCustomer } = useUpdateCustomer(); const { mutateAsync: deleteCustomer } = useDeleteCustomer(); const customers: readonly Customer[] = data?.customers ?? []; // A fresh load error should be shown again even if a previous one was dismissed. useEffect(() => { setErrorDismissed(false); }, [loadError]); const error = loadError && !errorDismissed ? handleError(loadError as Error) : null; 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 updateCustomer({ name: selectedCustomer.name, customer }); } else { // create new customer await createCustomer(customer); } }; const handleDeleteConfirm = async () => { if (selectedCustomer) { await deleteCustomer(selectedCustomer.name); } }; 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 && ( setErrorDismissed(true)}> {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 }} /> )} ); };