Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import type { JSX } from "react";
import { formatRelativeTime } from "@features/csm-dashboard/utils/abtDashboard";

interface RefreshButtonProps {
/** Re-runs the widget's query. Wire to the react-query `refetch`. */
/** Re-runs the underlying query. Wire to the react-query `refetch`. */
onRefresh: () => void;
/** True while a fetch is in flight; disables the control to avoid re-entrancy. */
isFetching: boolean;
Expand All @@ -31,9 +31,9 @@ interface RefreshButtonProps {
}

/**
* Reusable dashboard-widget refresh control. Mirrors the SLA table's refresh
* button (icon button + "last refreshed" hint), so refresh looks and behaves
* the same across the app.
* Reusable refresh control (icon button + "last refreshed" hint), shared
* across dashboard widgets, case detail tabs, and list pages so refresh
* looks and behaves the same everywhere.
*/
export default function RefreshButton({
onRefresh,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
type SearchAccountsRequest,
} from "@features/csm-accounts/types/csmAccounts";
import { BE_MAX_PAGE_LIMIT } from "@constants/apiConstants";
import RefreshButton from "@components/RefreshButton";

const DEFAULT_ROWS_PER_PAGE = 20;
// Top option is the backend's max page limit; larger requests are rejected.
Expand Down Expand Up @@ -73,7 +74,8 @@ export default function CsmAccountsPage(): JSX.Element {
};
}, [debouncedSearch, page, rowsPerPage]);

const { data, isLoading, isFetching, isError, error } = useSearchAccounts(request);
const { data, isLoading, isFetching, isError, error, refetch, dataUpdatedAt } =
useSearchAccounts(request);

const handleSearchChange = (e: ChangeEvent<HTMLInputElement>) => {
setSearchInput(e.target.value);
Expand All @@ -90,9 +92,17 @@ export default function CsmAccountsPage(): JSX.Element {

return (
<Box sx={{ display: "flex", flexDirection: "column", gap: 3 }}>
<Typography variant="body2" color="text.secondary">
Search across account name and Salesforce ID (case-insensitive).
</Typography>
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 1 }}>
<Typography variant="body2" color="text.secondary">
Search across account name and Salesforce ID (case-insensitive).
</Typography>
<RefreshButton
onRefresh={() => void refetch()}
isFetching={isFetching}
updatedAt={dataUpdatedAt}
label="Refresh accounts"
/>
</Box>

<TextField
size="small"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { type ChangeEvent, type JSX } from "react";
import { Link as RouterLink } from "react-router";
import QueryErrorState from "@components/QueryErrorState";
import { BE_MAX_PAGE_LIMIT } from "@constants/apiConstants";
import RefreshButton from "@components/RefreshButton";

const ROWS_PER_PAGE_OPTIONS = [10, 20, BE_MAX_PAGE_LIMIT];

Expand Down Expand Up @@ -59,6 +60,8 @@ interface DirectoryEntityTableProps {
onPageChange: (page: number) => void;
rowsPerPage: number;
onRowsPerPageChange: (rowsPerPage: number) => void;
onRefresh: () => void;
refreshedAt?: number;
}

/**
Expand All @@ -83,6 +86,8 @@ export default function DirectoryEntityTable({
onPageChange,
rowsPerPage,
onRowsPerPageChange,
onRefresh,
refreshedAt,
}: DirectoryEntityTableProps): JSX.Element {
const entityLabel =
entityNounPlural.charAt(0).toUpperCase() +
Expand All @@ -98,14 +103,22 @@ export default function DirectoryEntityTable({

return (
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
<TextField
size="small"
label={`Search ${entityNounPlural}`}
value={search}
onChange={handleSearchChange}
sx={{ maxWidth: 360 }}
slotProps={{ htmlInput: { "aria-label": `Search ${entityNounPlural} by name` } }}
/>
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 1 }}>
<TextField
size="small"
label={`Search ${entityNounPlural}`}
value={search}
onChange={handleSearchChange}
sx={{ maxWidth: 360, flex: 1 }}
slotProps={{ htmlInput: { "aria-label": `Search ${entityNounPlural} by name` } }}
/>
<RefreshButton
onRefresh={onRefresh}
isFetching={isFetching}
updatedAt={refreshedAt}
label={`Refresh ${entityNounPlural}`}
/>
</Box>

<Box sx={{ border: 1, borderColor: "divider", borderRadius: 1, overflow: "hidden" }}>
<TableContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import type { SearchUsersRequest } from "@features/csm-users/types/csmUsers";
import { BE_MAX_PAGE_LIMIT } from "@constants/apiConstants";
import { useNavTransition } from "@hooks/useNavTransition";
import { displayUserTimezone } from "@utils/userDirectoryDisplay";
import RefreshButton from "@components/RefreshButton";

const DEFAULT_ROWS_PER_PAGE = 20;
const ROWS_PER_PAGE_OPTIONS = [10, 20, BE_MAX_PAGE_LIMIT];
Expand Down Expand Up @@ -78,7 +79,15 @@ export default function DirectoryMembersList({
filters: { [filterKey]: [entityId] },
};

const { data, isLoading, isFetching, isError, error } = useSearchUsers(request);
const {
data,
isLoading,
isFetching,
isError,
error,
refetch,
dataUpdatedAt,
} = useSearchUsers(request);
const users = data?.users ?? [];
const total = data?.total ?? 0;

Expand All @@ -94,7 +103,16 @@ export default function DirectoryMembersList({
};

return (
<Box sx={{ border: 1, borderColor: "divider", borderRadius: 1, overflow: "hidden" }}>
<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
<Box sx={{ display: "flex", justifyContent: "flex-end" }}>
<RefreshButton
onRefresh={() => void refetch()}
isFetching={isFetching}
updatedAt={dataUpdatedAt}
label={`Refresh ${entityNoun} members`}
/>
</Box>
<Box sx={{ border: 1, borderColor: "divider", borderRadius: 1, overflow: "hidden" }}>
<TableContainer sx={{ overflow: "visible" }}>
<Table
size="small"
Expand Down Expand Up @@ -246,6 +264,7 @@ export default function DirectoryMembersList({
showFirstButton
showLastButton
/>
</Box>
</Box>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ export default function CsmGroupsPage(): JSX.Element {
[debouncedSearch, page, rowsPerPage],
);

const { data, isLoading, isFetching, isError, error } = useSearchGroupsPage(request);
const { data, isLoading, isFetching, isError, error, refetch, dataUpdatedAt } =
useSearchGroupsPage(request);

const handleSearchChange = (value: string): void => {
setSearch(value);
Expand Down Expand Up @@ -75,6 +76,8 @@ export default function CsmGroupsPage(): JSX.Element {
onPageChange={setPage}
rowsPerPage={rowsPerPage}
onRowsPerPageChange={handleRowsPerPageChange}
onRefresh={() => void refetch()}
refreshedAt={dataUpdatedAt}
/>
</Box>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ export default function CsmRolesPage(): JSX.Element {
[debouncedSearch, page, rowsPerPage],
);

const { data, isLoading, isFetching, isError, error } = useSearchRoles(request);
const { data, isLoading, isFetching, isError, error, refetch, dataUpdatedAt } =
useSearchRoles(request);

const handleSearchChange = (value: string): void => {
setSearch(value);
Expand Down Expand Up @@ -75,6 +76,8 @@ export default function CsmRolesPage(): JSX.Element {
onPageChange={setPage}
rowsPerPage={rowsPerPage}
onRowsPerPageChange={handleRowsPerPageChange}
onRefresh={() => void refetch()}
refreshedAt={dataUpdatedAt}
/>
</Box>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ export default function CsmTeamsPage(): JSX.Element {
[debouncedSearch, page, rowsPerPage],
);

const { data, isLoading, isFetching, isError, error } = useSearchTeams(request);
const { data, isLoading, isFetching, isError, error, refetch, dataUpdatedAt } =
useSearchTeams(request);

const handleSearchChange = (value: string): void => {
setSearch(value);
Expand Down Expand Up @@ -76,6 +77,8 @@ export default function CsmTeamsPage(): JSX.Element {
onPageChange={setPage}
rowsPerPage={rowsPerPage}
onRowsPerPageChange={handleRowsPerPageChange}
onRefresh={() => void refetch()}
refreshedAt={dataUpdatedAt}
/>
</Box>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
import { announcementStateRole } from "@features/csm-announcements/utils/announcementState";
import { STATE_LABEL } from "@features/csm-dashboard/utils/abtDashboard";
import type { CaseState } from "@features/csm-dashboard/types/abtDashboard";
import RefreshButton from "@components/RefreshButton";

const DEFAULT_ROWS_PER_PAGE = 20;
const ROWS_PER_PAGE_OPTIONS = [10, 20, 50];
Expand Down Expand Up @@ -141,11 +142,8 @@ export default function CsmAnnouncementsPage(): JSX.Element {
const [rowsPerPage, setRowsPerPage] = useState(DEFAULT_ROWS_PER_PAGE);
const debouncedSearch = useDebouncedValue(filters.search.trim(), 300);

const { data, isLoading, isFetching, isError, error } = useSearchAnnouncements(
{ ...filters, search: debouncedSearch },
page,
rowsPerPage,
);
const { data, isLoading, isFetching, isError, error, refetch, dataUpdatedAt } =
useSearchAnnouncements({ ...filters, search: debouncedSearch }, page, rowsPerPage);

const announcements = data?.announcements ?? [];
const total = data?.total ?? 0;
Expand All @@ -165,11 +163,19 @@ export default function CsmAnnouncementsPage(): JSX.Element {

return (
<Box sx={{ display: "flex", flexDirection: "column", gap: 3 }}>
<Box>
<Typography variant="h5">Announcements</Typography>
<Typography variant="body2" color="text.secondary">
Customer-facing announcements published across projects and tiers.
</Typography>
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 1 }}>
<Box>
<Typography variant="h5">Announcements</Typography>
<Typography variant="body2" color="text.secondary">
Customer-facing announcements published across projects and tiers.
</Typography>
</Box>
<RefreshButton
onRefresh={() => void refetch()}
isFetching={isFetching}
updatedAt={dataUpdatedAt}
label="Refresh announcements"
/>
</Box>

{/* Filters — search + state + project, all "show all" by default */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { RejectCallDialog } from "./RejectCallDialog";
import { SendCallNotesDialog } from "./SendCallNotesDialog";
import { CancelCallDialog } from "./CancelCallDialog";
import { CallRequestsTable } from "./CallRequestsTable";
import RefreshButton from "@components/RefreshButton";

// ---------------------------------------------------------------------------
// Types
Expand Down Expand Up @@ -95,10 +96,8 @@ export function CallRequestsWidget({
const [stateFilter, setStateFilter] = useState<BeCallRequestStateKey | "">("");
const activeStates = stateFilter ? [stateFilter] : undefined;

const { data, isLoading, isError, refetch } = useGetCsmCaseCallRequests(
caseId,
activeStates,
);
const { data, isLoading, isError, refetch, isFetching, dataUpdatedAt } =
useGetCsmCaseCallRequests(caseId, activeStates);
const postCallRequest = usePostCsmCaseCallRequest();
const patchCallRequest = usePatchCsmCaseCallRequest();

Expand Down Expand Up @@ -282,6 +281,12 @@ export function CallRequestsWidget({
)}
</Box>
<Box sx={{ display: "flex", gap: 1, alignItems: "center", flexWrap: "wrap" }}>
<RefreshButton
onRefresh={() => void refetch()}
isFetching={isFetching}
updatedAt={dataUpdatedAt}
label="Refresh call requests"
/>
{/* State filter */}
<FormControl size="small" sx={{ minWidth: 180 }}>
<InputLabel id="cr-filter-label">Filter by state</InputLabel>
Expand Down
Loading