UI improvement. (#521)

* don't remove service provider sid and filteredAccountSid when logout

* support fetching applications with pagination

* applications wip

* support pagination for voip carriers

* wip

* support phone number pagination

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip
This commit is contained in:
Hoan Luu Huu
2025-05-28 18:28:52 +07:00
committed by GitHub
parent 19620116b5
commit 844eec953c
12 changed files with 383 additions and 134 deletions

View File

@@ -92,10 +92,6 @@ export const DISABLE_ADDITIONAL_SPEECH_VENDORS: boolean =
export const AWS_REGION: string =
window.JAMBONZ?.AWS_REGION || import.meta.env.VITE_APP_AWS_REGION;
export const ENABLE_PHONE_NUMBER_LAZY_LOAD: boolean =
window.JAMBONZ?.ENABLE_PHONE_NUMBER_LAZY_LOAD === "true" ||
JSON.parse(import.meta.env.VITE_APP_ENABLE_PHONE_NUMBER_LAZY_LOAD || "false");
export const DEFAULT_SERVICE_PROVIDER_SID: string =
window.JAMBONZ?.DEFAULT_SERVICE_PROVIDER_SID ||
import.meta.env.VITE_APP_DEFAULT_SERVICE_PROVIDER_SID;

View File

@@ -97,6 +97,8 @@ import type {
SpeechSupportedLanguagesAndVoices,
AppEnv,
PhoneNumberQuery,
ApplicationQuery,
VoipCarrierQuery,
} from "./types";
import { Availability, StatusCodes } from "./types";
import { JaegerRoot } from "./jaeger-types";
@@ -821,6 +823,28 @@ export const getAppEnvSchema = (url: string) => {
return getFetch<AppEnv>(`${API_APP_ENV}?url=${url}`);
};
export const getApplications = (
sid: string,
query: Partial<ApplicationQuery>,
) => {
const qryStr = getQuery<Partial<ApplicationQuery>>(query);
return getFetch<PagedResponse<Application>>(
`${API_ACCOUNTS}/${sid}/Applications?${qryStr}`,
);
};
export const getSPVoipCarriers = (
sid: string,
query: Partial<VoipCarrierQuery>,
) => {
const qryStr = getQuery<Partial<VoipCarrierQuery>>(query);
return getFetch<PagedResponse<Carrier>>(
`${API_SERVICE_PROVIDERS}/${sid}/VoipCarriers?${qryStr}`,
);
};
/** Wrappers for APIs that can have a mock dev server response */
export const getMe = () => {
@@ -903,7 +927,7 @@ export const getPrice = () => {
export const getPhoneNumbers = (query: Partial<PhoneNumberQuery>) => {
const qryStr = getQuery<Partial<PhoneNumberQuery>>(query);
return getFetch<PhoneNumber[]>(`${API_PHONE_NUMBERS}?${qryStr}`);
return getFetch<PagedResponse<PhoneNumber>>(`${API_PHONE_NUMBERS}?${qryStr}`);
};
export const getSpeechSupportedLanguagesAndVoices = (

View File

@@ -557,12 +557,14 @@ export interface Client {
export interface PageQuery {
page: number;
page_size?: number;
count: number;
start?: string;
days?: number;
}
export interface PhoneNumberQuery extends PageQuery {
service_provider_sid?: string;
account_sid?: string;
filter?: string;
}
@@ -572,6 +574,15 @@ export interface CallQuery extends PageQuery {
answered?: string;
}
export interface ApplicationQuery extends PageQuery {
name?: string;
}
export interface VoipCarrierQuery extends PageQuery {
name?: string;
account_sid?: string;
}
export interface GoogleCustomVoicesQuery {
speech_credential_sid?: string;
label?: string;

View File

@@ -11,7 +11,11 @@ import {
toastSuccess,
toastError,
} from "src/store";
import { getActiveSP, setActiveSP } from "src/store/localStore";
import {
getActiveSP,
removeAccountFilter,
setActiveSP,
} from "src/store/localStore";
import { postServiceProviders } from "src/api";
import type { NaviItem } from "./items";
@@ -166,6 +170,7 @@ export const Navi = ({
onChange={(e) => {
setSid(e.target.value);
setActiveSP(e.target.value);
removeAccountFilter();
navigate(ROUTE_LOGIN);
}}
disabled={user?.scope !== USER_ADMIN}

View File

@@ -68,10 +68,10 @@ export const Alerts = () => {
};
useMemo(() => {
setAccountSid(getAccountFilter() || accountSid);
if (!accountSid && user?.account_sid) setAccountSid(user?.account_sid);
if (getQueryFilter()) {
const [date] = getQueryFilter().split("/");
setAccountSid(getAccountFilter() || accountSid);
if (!accountSid && user?.account_sid) setAccountSid(user?.account_sid);
setDateFilter(date);
}
}, [accountSid]);

View File

@@ -1,8 +1,12 @@
import React, { useEffect, useState } from "react";
import { H1, M, Button, Icon } from "@jambonz/ui-kit";
import React, { useEffect, useState, useRef } from "react";
import { H1, M, Button, Icon, ButtonGroup, MS } from "@jambonz/ui-kit";
import { Link } from "react-router-dom";
import { deleteApplication, useServiceProviderData, useApiData } from "src/api";
import {
deleteApplication,
useServiceProviderData,
getApplications,
} from "src/api";
import {
ROUTE_INTERNAL_APPLICATIONS,
ROUTE_INTERNAL_ACCOUNTS,
@@ -13,20 +17,17 @@ import {
Spinner,
AccountFilter,
SearchFilter,
Pagination,
SelectFilter,
} from "src/components";
import { DeleteApplication } from "./delete";
import { toastError, toastSuccess, useSelectState } from "src/store";
import {
isUserAccountScope,
hasLength,
hasValue,
useFilteredResults,
} from "src/utils";
import { isUserAccountScope, hasLength, hasValue } from "src/utils";
import type { Application, Account } from "src/api/types";
import { ScopedAccess } from "src/components/scoped-access";
import { Scope } from "src/store/types";
import { USER_ACCOUNT } from "src/api/constants";
import { PER_PAGE_SELECTION, USER_ACCOUNT } from "src/api/constants";
import { getAccountFilter, setLocation } from "src/store/localStore";
export const Applications = () => {
@@ -34,14 +35,51 @@ export const Applications = () => {
const [accounts] = useServiceProviderData<Account[]>("Accounts");
const [accountSid, setAccountSid] = useState("");
const [application, setApplication] = useState<Application | null>(null);
const [apiUrl, setApiUrl] = useState("");
const [applications, refetch] = useApiData<Application[]>(apiUrl);
const [applications, setApplications] = useState<Application[] | null>(null);
const [filter, setFilter] = useState("");
const filteredApplications = useFilteredResults<Application>(
filter,
applications,
);
const [applicationsTotal, setApplicationsTotal] = useState(0);
const [pageNumber, setPageNumber] = useState(1);
const [perPageFilter, setPerPageFilter] = useState("25");
const [maxPageNumber, setMaxPageNumber] = useState(1);
// Track previous values to detect changes
const prevValuesRef = useRef({
accountSid: "",
filter: "",
pageNumber: 1,
perPageFilter: "25",
});
const fetchApplications = (resetPage = false) => {
// Don't fetch if no account is selected
if (!accountSid) return;
setApplications(null);
// Calculate the correct page to use
const currentPage = resetPage ? 1 : pageNumber;
// If we're resetting the page, also update the state
if (resetPage && pageNumber !== 1) {
setPageNumber(1);
}
getApplications(accountSid, {
page: currentPage,
page_size: Number(perPageFilter),
...(filter && { name: filter }),
})
.then(({ json }) => {
setApplications(json.data);
setApplicationsTotal(json.total);
setMaxPageNumber(Math.ceil(json.total / Number(perPageFilter)));
})
.catch((error) => {
setApplications([]);
toastError(error.msg);
});
};
const handleDelete = () => {
if (application) {
@@ -53,8 +91,7 @@ export const Applications = () => {
}
deleteApplication(application.application_sid)
.then(() => {
// getApplications();
refetch();
fetchApplications(false);
setApplication(null);
toastSuccess(
<>
@@ -68,18 +105,44 @@ export const Applications = () => {
}
};
// Set initial account
useEffect(() => {
setLocation();
if (user?.account_sid && user.scope === USER_ACCOUNT) {
setAccountSid(user?.account_sid);
} else {
setAccountSid(getAccountFilter() || accountSid);
setAccountSid(
getAccountFilter() || accountSid || accounts?.[0]?.account_sid || "",
);
}
setLocation();
}, [user, accounts]);
if (accountSid) {
setApiUrl(`Accounts/${accountSid}/Applications`);
}
}, [accountSid, user]);
// This single effect handles all data fetching triggers
useEffect(() => {
const accSid = accountSid || getAccountFilter() || "";
if (!accSid) return;
// Determine if the change requires a page reset
const prevValues = prevValuesRef.current;
const isFilterChange =
prevValues.accountSid !== accountSid || prevValues.filter !== filter;
const isPageSizeChange =
prevValues.perPageFilter !== perPageFilter &&
prevValues.perPageFilter !== ""; // Skip initial render
// Update ref with current values for next comparison
prevValuesRef.current = {
accountSid: accSid,
filter,
pageNumber,
perPageFilter,
};
// Fetch data with page reset if needed
fetchApplications(isFilterChange || isPageSizeChange);
}, [accountSid, filter, pageNumber, perPageFilter]);
return (
<>
@@ -100,6 +163,7 @@ export const Applications = () => {
<SearchFilter
placeholder="Filter applications"
filter={[filter, setFilter]}
delay={1000}
/>
<ScopedAccess user={user} scope={Scope.service_provider}>
<AccountFilter
@@ -108,12 +172,12 @@ export const Applications = () => {
/>
</ScopedAccess>
</section>
<Section {...(hasLength(filteredApplications) && { slim: true })}>
<Section {...(hasLength(applications) && { slim: true })}>
<div className="list">
{!hasValue(applications) && hasLength(accounts) ? (
<Spinner />
) : hasLength(filteredApplications) ? (
filteredApplications
) : hasLength(applications) ? (
applications
.sort((a, b) => a.name.localeCompare(b.name))
.map((application) => {
return (
@@ -189,6 +253,26 @@ export const Applications = () => {
</Button>
</Section>
)}
<footer>
<ButtonGroup>
<MS>
Total: {applicationsTotal} record
{applicationsTotal === 1 ? "" : "s"}
</MS>
{hasLength(applications) && (
<Pagination
pageNumber={pageNumber}
setPageNumber={setPageNumber}
maxPageNumber={maxPageNumber}
/>
)}
<SelectFilter
id="page_filter"
filter={[perPageFilter, setPerPageFilter]}
options={PER_PAGE_SELECTION}
/>
</ButtonGroup>
</footer>
{application && (
<DeleteApplication
application={application}

View File

@@ -1,11 +1,12 @@
import React, { useState, useMemo, useEffect } from "react";
import React, { useState, useEffect, useRef } from "react";
import { Link } from "react-router-dom";
import { Button, H1, Icon, M } from "@jambonz/ui-kit";
import { Button, ButtonGroup, H1, Icon, M, MS } from "@jambonz/ui-kit";
import {
deleteCarrier,
deleteSipGateway,
deleteSmppGateway,
getFetch,
getSPVoipCarriers,
useApiData,
useServiceProviderData,
} from "src/api";
@@ -17,20 +18,18 @@ import {
Section,
Spinner,
SearchFilter,
Pagination,
SelectFilter,
} from "src/components";
import { ScopedAccess } from "src/components/scoped-access";
import { Gateways } from "./gateways";
import {
isUserAccountScope,
hasLength,
hasValue,
useFilteredResults,
} from "src/utils";
import { isUserAccountScope, hasLength, hasValue } from "src/utils";
import {
API_SIP_GATEWAY,
API_SMPP_GATEWAY,
CARRIER_REG_OK,
ENABLE_HOSTED_SYSTEM,
PER_PAGE_SELECTION,
USER_ACCOUNT,
} from "src/api/constants";
import { DeleteCarrier } from "./delete";
@@ -49,32 +48,55 @@ export const Carriers = () => {
const user = useSelectState("user");
const [userData] = useApiData<CurrentUserData>("Users/me");
const currentServiceProvider = useSelectState("currentServiceProvider");
const [apiUrl, setApiUrl] = useState("");
const [carrier, setCarrier] = useState<Carrier | null>(null);
const [carriers, refetch] = useApiData<Carrier[]>(apiUrl);
const [carriers, setCarriers] = useState<Carrier[] | null>(null);
const [accounts] = useServiceProviderData<Account[]>("Accounts");
const [accountSid, setAccountSid] = useState("");
const [filter, setFilter] = useState("");
const carriersFiltered = useMemo(() => {
setAccountSid(getAccountFilter());
if (user?.account_sid && user?.scope === USER_ACCOUNT) {
setAccountSid(user?.account_sid);
const [carriersTotal, setCarriersTotal] = useState(0);
const [pageNumber, setPageNumber] = useState(1);
const [perPageFilter, setPerPageFilter] = useState("25");
const [maxPageNumber, setMaxPageNumber] = useState(1);
// Add a ref to track previous values
const prevValuesRef = useRef({
serviceProviderId: "",
accountSid: "",
filter: "",
pageNumber: 1,
perPageFilter: "25",
});
const fetchCarriers = (resetPage = false) => {
if (!currentServiceProvider) return;
setCarriers(null);
// Calculate the correct page to use
const currentPage = resetPage ? 1 : pageNumber;
// If we're resetting the page, also update the state
if (resetPage && pageNumber !== 1) {
setPageNumber(1);
}
return carriers
? carriers.filter((carrier) =>
accountSid
? carrier.account_sid === accountSid
: carrier.account_sid === null,
)
: [];
}, [accountSid, carrier, carriers]);
const filteredCarriers = useFilteredResults<Carrier>(
filter,
carriersFiltered,
);
getSPVoipCarriers(currentServiceProvider.service_provider_sid, {
page: currentPage,
page_size: Number(perPageFilter),
...(filter && { name: filter }),
...(accountSid && { account_sid: accountSid }),
})
.then(({ json }) => {
setCarriers(json.data);
setCarriersTotal(json.total);
setMaxPageNumber(Math.ceil(json.total / Number(perPageFilter)));
})
.catch((error) => {
setCarriers([]);
toastError(error.msg);
});
};
const handleDelete = () => {
if (carrier) {
@@ -113,7 +135,7 @@ export const Carriers = () => {
);
});
setCarrier(null);
refetch();
fetchCarriers(false);
toastSuccess(
<>
Deleted Carrier <strong>{carrier.name}</strong>
@@ -126,14 +148,45 @@ export const Carriers = () => {
}
};
// Initial account setup
useEffect(() => {
setLocation();
if (currentServiceProvider) {
setApiUrl(
`ServiceProviders/${currentServiceProvider.service_provider_sid}/VoipCarriers`,
);
if (user?.account_sid && user?.scope === USER_ACCOUNT) {
setAccountSid(user?.account_sid);
} else {
setAccountSid(getAccountFilter());
}
}, [user, currentServiceProvider, accountSid]);
setLocation();
}, [user, accounts]);
// Combined effect for all data fetching
useEffect(() => {
if (!currentServiceProvider) return;
const prevValues = prevValuesRef.current;
const currentSPId = currentServiceProvider.service_provider_sid;
// Determine if we should reset pagination
const isFilterOrProviderChange =
prevValues.serviceProviderId !== currentSPId ||
prevValues.accountSid !== accountSid ||
prevValues.filter !== filter;
const isPageSizeChange =
prevValues.perPageFilter !== perPageFilter &&
prevValues.perPageFilter !== "25"; // Skip initial render
// Update ref for next comparison
prevValuesRef.current = {
serviceProviderId: currentSPId,
accountSid,
filter,
pageNumber,
perPageFilter,
};
// Fetch data with page reset if filters changed
fetchCarriers(isFilterOrProviderChange || isPageSizeChange);
}, [currentServiceProvider, accountSid, filter, pageNumber, perPageFilter]);
return (
<>
@@ -159,6 +212,7 @@ export const Carriers = () => {
<SearchFilter
placeholder="Filter carriers"
filter={[filter, setFilter]}
delay={1000}
/>
<ScopedAccess user={user} scope={Scope.service_provider}>
<AccountFilter
@@ -169,12 +223,12 @@ export const Carriers = () => {
/>
</ScopedAccess>
</section>
<Section {...(hasLength(filteredCarriers) && { slim: true })}>
<Section {...(hasLength(carriers) && { slim: true })}>
<div className="list">
{!hasValue(carriers) && hasLength(accounts) ? (
<Spinner />
) : hasLength(filteredCarriers) ? (
filteredCarriers.map((carrier) => (
) : hasLength(carriers) ? (
carriers.map((carrier) => (
<div className="item" key={carrier.voip_carrier_sid}>
<div className="item__info">
<div className="item__title">
@@ -274,6 +328,26 @@ export const Carriers = () => {
Add carrier
</Button>
</Section>
<footer>
<ButtonGroup>
<MS>
Total: {carriersTotal} record
{carriersTotal === 1 ? "" : "s"}
</MS>
{hasLength(carriers) && (
<Pagination
pageNumber={pageNumber}
setPageNumber={setPageNumber}
maxPageNumber={maxPageNumber}
/>
)}
<SelectFilter
id="page_filter"
filter={[perPageFilter, setPerPageFilter]}
options={PER_PAGE_SELECTION}
/>
</ButtonGroup>
</footer>
{carrier && (
<DeleteCarrier
carrier={carrier}

View File

@@ -17,6 +17,7 @@ import { Scope } from "src/store/types";
import { hasLength, hasValue, useFilteredResults } from "src/utils";
import ClientsDelete from "./delete";
import { USER_ACCOUNT } from "src/api/constants";
import { getAccountFilter } from "src/store/localStore";
export const Clients = () => {
const user = useSelectState("user");
@@ -32,6 +33,7 @@ export const Clients = () => {
const [client, setClient] = useState<Client | null>();
const tmpFilteredClients = useMemo(() => {
setAccountSid(getAccountFilter() || accountSid);
if (user?.account_sid && user?.scope === USER_ACCOUNT) {
setAccountSid(user?.account_sid);
return clients;

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from "react";
import React, { useEffect, useState, useRef } from "react";
import { Button, ButtonGroup, H1, Icon, MS } from "@jambonz/ui-kit";
import { Link } from "react-router-dom";
@@ -16,28 +16,26 @@ import {
ApplicationFilter,
SearchFilter,
AccountFilter,
Pagination,
SelectFilter,
} from "src/components";
import {
ROUTE_INTERNAL_ACCOUNTS,
ROUTE_INTERNAL_CARRIERS,
ROUTE_INTERNAL_PHONE_NUMBERS,
} from "src/router/routes";
import {
hasLength,
hasValue,
formatPhoneNumber,
useFilteredResults,
} from "src/utils";
import { hasLength, hasValue, formatPhoneNumber } from "src/utils";
import { DeletePhoneNumber } from "./delete";
import type { Account, PhoneNumber, Carrier, Application } from "src/api/types";
import { ENABLE_PHONE_NUMBER_LAZY_LOAD, USER_ACCOUNT } from "src/api/constants";
import { PER_PAGE_SELECTION, USER_ACCOUNT } from "src/api/constants";
import { ScopedAccess } from "src/components/scoped-access";
import { Scope } from "src/store/types";
import { getAccountFilter, setLocation } from "src/store/localStore";
export const PhoneNumbers = () => {
const user = useSelectState("user");
const currentServiceProvider = useSelectState("currentServiceProvider");
const [accounts] = useServiceProviderData<Account[]>("Accounts");
const [applications] = useServiceProviderData<Application[]>("Applications");
const [carriers] = useServiceProviderData<Carrier[]>("VoipCarriers");
@@ -51,35 +49,48 @@ export const PhoneNumbers = () => {
const [applyMassEdit, setApplyMassEdit] = useState(false);
const [filter, setFilter] = useState("");
const [accountSid, setAccountSid] = useState("");
const [phoneNumbersTotal, setphoneNumbersTotal] = useState(0);
const [pageNumber, setPageNumber] = useState(1);
const [perPageFilter, setPerPageFilter] = useState("25");
const [maxPageNumber, setMaxPageNumber] = useState(1);
const phoneNumbersFiltered = useMemo(() => {
setAccountSid(getAccountFilter());
return phoneNumbers
? phoneNumbers.filter(
(phn) => !accountSid || phn.account_sid === accountSid,
)
: [];
}, [phoneNumbers, ...[!ENABLE_PHONE_NUMBER_LAZY_LOAD && accountSid]]);
// Add ref to track previous values
const prevValuesRef = useRef({
serviceProviderId: "",
accountSid: "",
filter: "",
pageNumber: 1,
perPageFilter: "25",
});
const filteredPhoneNumbers = !ENABLE_PHONE_NUMBER_LAZY_LOAD
? useFilteredResults<PhoneNumber>(filter, phoneNumbersFiltered)
: phoneNumbersFiltered;
const fetchPhoneNumbers = (resetPage = false) => {
setPhoneNumbers(null);
const refetch = () => {
getPhoneNumbers(
!ENABLE_PHONE_NUMBER_LAZY_LOAD
? {}
: {
...(accountSid && { account_sid: accountSid }),
...(filter && { filter }),
},
)
// Calculate the correct page to use
const currentPage = resetPage ? 1 : pageNumber;
// If we're resetting the page, also update the state
if (resetPage && pageNumber !== 1) {
setPageNumber(1);
}
const accSid = accountSid || getAccountFilter() || "";
getPhoneNumbers({
page: currentPage,
page_size: Number(perPageFilter),
...(accSid && { account_sid: accSid }),
...(filter && { filter }),
})
.then(({ json }) => {
if (json) {
setPhoneNumbers(json);
setPhoneNumbers(json.data);
setphoneNumbersTotal(json.total);
setMaxPageNumber(Math.ceil(json.total / Number(perPageFilter)));
}
})
.catch((error) => {
setPhoneNumbers([]);
toastError(error.msg);
});
};
@@ -95,9 +106,11 @@ export const PhoneNumbers = () => {
}),
)
.then(() => {
refetch();
fetchPhoneNumbers(false);
setApplicationSid("");
setApplyMassEdit(false);
setSelectAll(false);
setSelectedPhoneNumbers([]);
toastSuccess("Number routing updated successfully");
})
.catch((error) => {
@@ -111,7 +124,7 @@ export const PhoneNumbers = () => {
if (phoneNumber) {
deletePhoneNumber(phoneNumber.phone_number_sid)
.then(() => {
refetch();
fetchPhoneNumbers(false);
setPhoneNumber(null);
toastSuccess(
<>
@@ -125,21 +138,43 @@ export const PhoneNumbers = () => {
}
};
// Initial account setup
useEffect(() => {
setLocation();
if (user?.account_sid && user.scope === USER_ACCOUNT) {
setAccountSid(user?.account_sid);
} else {
setAccountSid(getAccountFilter() || accountSid);
}
setLocation();
}, [user]);
// Combined effect for all data fetching
useEffect(() => {
if (ENABLE_PHONE_NUMBER_LAZY_LOAD) {
setPhoneNumbers([]);
return;
}
const prevValues = prevValuesRef.current;
const currentSPId = currentServiceProvider?.service_provider_sid;
refetch();
}, []);
// Detect changes that require page reset
const isFilterOrProviderChange =
prevValues.serviceProviderId !== currentSPId ||
prevValues.accountSid !== accountSid ||
prevValues.filter !== filter;
const isPageSizeChange =
prevValues.perPageFilter !== perPageFilter &&
prevValues.perPageFilter !== "25"; // Skip initial render
// Update ref for next comparison
prevValuesRef.current = {
serviceProviderId: currentSPId || "",
accountSid,
filter,
pageNumber,
perPageFilter,
};
// Fetch data with appropriate reset parameter
fetchPhoneNumbers(isFilterOrProviderChange || isPageSizeChange);
}, [currentServiceProvider, accountSid, filter, pageNumber, perPageFilter]);
return (
<>
@@ -160,6 +195,7 @@ export const PhoneNumbers = () => {
<SearchFilter
placeholder="Filter phone numbers"
filter={[filter, setFilter]}
delay={1000}
/>
<ScopedAccess user={user} scope={Scope.service_provider}>
<AccountFilter
@@ -168,25 +204,12 @@ export const PhoneNumbers = () => {
defaultOption
/>
</ScopedAccess>
{ENABLE_PHONE_NUMBER_LAZY_LOAD && (
<ButtonGroup>
<Button
small
onClick={() => {
setPhoneNumbers(null);
refetch();
}}
>
Search
</Button>
</ButtonGroup>
)}
</section>
<Section {...(hasLength(filteredPhoneNumbers) && { slim: true })}>
<Section {...(hasLength(phoneNumbers) && { slim: true })}>
<div className="list">
{!hasValue(phoneNumbers) ? (
<Spinner />
) : hasLength(filteredPhoneNumbers) ? (
) : hasLength(phoneNumbers) ? (
<>
<div className="item item--actions">
{accountSid ? (
@@ -200,7 +223,7 @@ export const PhoneNumbers = () => {
onChange={(e) => {
if (e.target.checked) {
setSelectAll(true);
setSelectedPhoneNumbers(filteredPhoneNumbers);
setSelectedPhoneNumbers(phoneNumbers);
} else {
setSelectAll(false);
setSelectedPhoneNumbers([]);
@@ -224,10 +247,8 @@ export const PhoneNumbers = () => {
<Button
small
onClick={() => {
handleMassEdit();
setSelectAll(false);
setApplyMassEdit(true);
setSelectedPhoneNumbers([]);
handleMassEdit();
}}
>
Apply
@@ -249,7 +270,7 @@ export const PhoneNumbers = () => {
</MS>
)}
</div>
{filteredPhoneNumbers.map((phoneNumber) => {
{phoneNumbers.map((phoneNumber) => {
return (
<div className="item" key={phoneNumber.phone_number_sid}>
<div className="item__info">
@@ -385,6 +406,26 @@ export const PhoneNumbers = () => {
</Button>
)}
</Section>
<footer>
<ButtonGroup>
<MS>
Total: {phoneNumbersTotal} record
{phoneNumbersTotal === 1 ? "" : "s"}
</MS>
{hasLength(phoneNumbers) && (
<Pagination
pageNumber={pageNumber}
setPageNumber={setPageNumber}
maxPageNumber={maxPageNumber}
/>
)}
<SelectFilter
id="page_filter"
filter={[perPageFilter, setPerPageFilter]}
options={PER_PAGE_SELECTION}
/>
</ButtonGroup>
</footer>
{phoneNumber && (
<DeletePhoneNumber
phoneNumber={phoneNumber}

View File

@@ -87,10 +87,10 @@ export const RecentCalls = () => {
};
useMemo(() => {
setAccountSid(getAccountFilter() || accountSid);
if (!accountSid && user?.account_sid) setAccountSid(user?.account_sid);
if (getQueryFilter()) {
const [date, direction, status] = getQueryFilter().split("/");
setAccountSid(getAccountFilter() || accountSid);
if (!accountSid && user?.account_sid) setAccountSid(user?.account_sid);
setDateFilter(date);
setDirectionFilter(direction);
setStatusFilter(status);

View File

@@ -27,6 +27,7 @@ import { ENABLE_HOSTED_SYSTEM, USER_ACCOUNT } from "src/api/constants";
import type { UserData } from "src/store/types";
import { toastError } from "src/store";
import {
clearLocalStorage,
removeLocationBeforeOauth,
removeOauthState,
} from "src/store/localStore";
@@ -163,7 +164,7 @@ export const useProvideAuth = (): AuthStateContext => {
postLogout()
.then((response) => {
if (response.status === StatusCodes.NO_CONTENT) {
localStorage.clear();
clearLocalStorage();
sessionStorage.clear();
sessionStorage.setItem(SESS_FLASH_MSG, MSG_LOGGED_OUT);
window.location.href = ROUTE_LOGIN;

View File

@@ -123,7 +123,18 @@ export const checkLocation = () => {
if (currentLocation !== storedLocation) {
localStorage.removeItem(storeQueryFilter);
localStorage.removeItem(storeAccountFilter);
// Keep storeAccountFilter in different location that user can search for same account
// in different location
// localStorage.removeItem(storeAccountFilter);
return;
}
};
export const clearLocalStorage = () => {
const toKeep = [storeActiveSP, storeAccountFilter];
Object.keys(localStorage).forEach((key) => {
if (!toKeep.includes(key)) {
localStorage.removeItem(key);
}
});
};