diff --git a/src/components/account-filter.tsx b/src/components/account-filter.tsx index 3f10a6e..e0e41c5 100644 --- a/src/components/account-filter.tsx +++ b/src/components/account-filter.tsx @@ -5,6 +5,7 @@ import { Icons } from "src/components/icons"; import type { Account } from "src/api/types"; import { hasLength, sortLocaleName } from "src/utils"; +import { setAccountFilter } from "src/store/localStore"; export type AccountFilterProps = { label?: string; @@ -43,7 +44,10 @@ export const AccountFilter = ({ id="account_filter" name="account_filter" value={accountSid} - onChange={(e) => setAccountSid(e.target.value)} + onChange={(e) => { + setAccountSid(e.target.value); + setAccountFilter(e.target.value); + }} onFocus={() => setFocus(true)} onBlur={() => setFocus(false)} > diff --git a/src/components/select-filter.tsx b/src/components/select-filter.tsx index aa0fcec..a13478e 100644 --- a/src/components/select-filter.tsx +++ b/src/components/select-filter.tsx @@ -4,6 +4,8 @@ import { classNames } from "jambonz-ui"; import { Icons } from "src/components/icons"; import type { SelectorOption } from "./forms/selector"; +import { setQueryFilter } from "src/store/localStore"; +import { createFilterString } from "src/utils"; type SelectFilterProps = { id: string; @@ -38,6 +40,11 @@ export const SelectFilter = ({ value={filterValue} onChange={(e) => { setFilterValue(e.target.value); + const advancedFilter = createFilterString( + e.target.value, + label as string + ); + setQueryFilter(advancedFilter); if (handleSelect) { handleSelect(e); diff --git a/src/containers/internal/navi/index.tsx b/src/containers/internal/navi/index.tsx index f3d6169..1903aff 100644 --- a/src/containers/internal/navi/index.tsx +++ b/src/containers/internal/navi/index.tsx @@ -11,6 +11,7 @@ import { toastSuccess, toastError, } from "src/store"; +import { getActiveSP, setActiveSP } from "src/store/localStore"; import { postServiceProviders } from "src/api"; import type { NaviItem } from "./items"; @@ -98,6 +99,7 @@ export const Navi = ({ ); dispatch({ type: "serviceProviders" }); setSid(json.sid); + setActiveSP(json.sid); setName(""); setModal(false); }) @@ -113,6 +115,7 @@ export const Navi = ({ /** Subscribe to change events on the service provider setSid(e.target.value)} + value={sid || currentServiceProvider?.service_provider_sid} + onChange={(e) => { + setSid(e.target.value); + setActiveSP(e.target.value); + }} disabled={user?.scope !== USER_ADMIN} > {currentServiceProvider ? ( diff --git a/src/containers/internal/views/alerts/index.tsx b/src/containers/internal/views/alerts/index.tsx index 3b9221b..f4e6fca 100644 --- a/src/containers/internal/views/alerts/index.tsx +++ b/src/containers/internal/views/alerts/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { ButtonGroup, H1, M, MS } from "jambonz-ui"; import dayjs from "dayjs"; @@ -22,6 +22,11 @@ import { import type { Account, Alert, PageQuery } from "src/api/types"; import { ScopedAccess } from "src/components/scoped-access"; import { Scope } from "src/store/types"; +import { + getAccountFilter, + getQueryFilter, + setLocation, +} from "src/store/localStore"; export const Alerts = () => { const user = useSelectState("user"); @@ -57,7 +62,16 @@ export const Alerts = () => { }); }; + useMemo(() => { + if (getQueryFilter()) { + const [date] = getQueryFilter().split("/"); + setAccountSid(getAccountFilter()); + setDateFilter(date); + } + }, [accountSid]); + useEffect(() => { + setLocation(); if (user?.account_sid && user.scope === USER_ACCOUNT) { setAccountSid(user?.account_sid); } diff --git a/src/containers/internal/views/applications/form.tsx b/src/containers/internal/views/applications/form.tsx index 133fc64..1d16959 100644 --- a/src/containers/internal/views/applications/form.tsx +++ b/src/containers/internal/views/applications/form.tsx @@ -50,6 +50,7 @@ import type { } from "src/api/types"; import { MSG_REQUIRED_FIELDS, MSG_WEBHOOK_FIELDS } from "src/constants"; import { isUserAccountScope, useRedirect } from "src/utils"; +import { setAccountFilter, setLocation } from "src/store/localStore"; type ApplicationFormProps = { application?: UseApiDataMap; @@ -172,6 +173,7 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => { .then(() => { toastSuccess("Application created successfully"); navigate(ROUTE_INTERNAL_APPLICATIONS); + setAccountFilter(accountSid); }) .catch((error) => { toastError(error.msg); @@ -180,6 +182,7 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => { }; useEffect(() => { + setLocation(); if (application && application.data) { setApplicationName(application.data.name); diff --git a/src/containers/internal/views/applications/index.tsx b/src/containers/internal/views/applications/index.tsx index 6035111..a5b1323 100644 --- a/src/containers/internal/views/applications/index.tsx +++ b/src/containers/internal/views/applications/index.tsx @@ -27,6 +27,7 @@ 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 { getAccountFilter, setLocation } from "src/store/localStore"; export const Applications = () => { const user = useSelectState("user"); @@ -68,10 +69,13 @@ export const Applications = () => { }; useEffect(() => { + setLocation(); if (user?.account_sid && user.scope === USER_ACCOUNT) { setAccountSid(user?.account_sid); } + setAccountSid(getAccountFilter()); + if (accountSid) { setApiUrl(`Accounts/${accountSid}/Applications`); } diff --git a/src/containers/internal/views/carriers/form.tsx b/src/containers/internal/views/carriers/form.tsx index 0bc0413..c43788f 100644 --- a/src/containers/internal/views/carriers/form.tsx +++ b/src/containers/internal/views/carriers/form.tsx @@ -58,6 +58,7 @@ import type { Smpp, Application, } from "src/api/types"; +import { setAccountFilter, setLocation } from "src/store/localStore"; type CarrierFormProps = { carrier?: UseApiDataMap; @@ -532,6 +533,7 @@ export const CarrierForm = ({ toastSuccess("Carrier created successfully"); navigate(ROUTE_INTERNAL_CARRIERS); + setAccountFilter(accountSid); }) .catch((error) => { toastError(error.msg); @@ -541,6 +543,7 @@ export const CarrierForm = ({ }; useEffect(() => { + setLocation(); if (predefinedName && hasLength(predefinedCarriers)) { const predefinedCarrierSid = predefinedCarriers.find( (a) => a.name === predefinedName diff --git a/src/containers/internal/views/carriers/index.tsx b/src/containers/internal/views/carriers/index.tsx index da97580..01c58f5 100644 --- a/src/containers/internal/views/carriers/index.tsx +++ b/src/containers/internal/views/carriers/index.tsx @@ -35,6 +35,7 @@ import { DeleteCarrier } from "./delete"; import type { Account, Carrier, SipGateway, SmppGateway } from "src/api/types"; import { Scope } from "src/store/types"; +import { getAccountFilter, setLocation } from "src/store/localStore"; export const Carriers = () => { const user = useSelectState("user"); @@ -47,6 +48,7 @@ export const Carriers = () => { const [filter, setFilter] = useState(""); const carriersFiltered = useMemo(() => { + setAccountSid(getAccountFilter()); if (user?.scope === USER_ACCOUNT) { return carriers; } @@ -116,6 +118,7 @@ export const Carriers = () => { }; useEffect(() => { + setLocation(); if (accountSid) { setApiUrl(`Accounts/${accountSid}/VoipCarriers`); } else if (currentServiceProvider) { diff --git a/src/containers/internal/views/phone-numbers/form.tsx b/src/containers/internal/views/phone-numbers/form.tsx index 225f576..6181b2e 100644 --- a/src/containers/internal/views/phone-numbers/form.tsx +++ b/src/containers/internal/views/phone-numbers/form.tsx @@ -30,6 +30,7 @@ import type { Carrier, UseApiDataMap, } from "src/api/types"; +import { setAccountFilter, setLocation } from "src/store/localStore"; type PhoneNumberFormProps = { phoneNumber?: UseApiDataMap; @@ -104,6 +105,7 @@ export const PhoneNumberForm = ({ phoneNumber }: PhoneNumberFormProps) => { .then(() => { toastSuccess("Phone number created successfully"); navigate(ROUTE_INTERNAL_PHONE_NUMBERS); + setAccountFilter(accountSid); }) .catch((error) => { toastError(error.msg); @@ -112,6 +114,7 @@ export const PhoneNumberForm = ({ phoneNumber }: PhoneNumberFormProps) => { }; useEffect(() => { + setLocation(); if (phoneNumber && phoneNumber.data) { setPhoneNumberNum(phoneNumber.data.number); diff --git a/src/containers/internal/views/phone-numbers/index.tsx b/src/containers/internal/views/phone-numbers/index.tsx index 0ca0609..13a4a8c 100644 --- a/src/containers/internal/views/phone-numbers/index.tsx +++ b/src/containers/internal/views/phone-numbers/index.tsx @@ -33,6 +33,7 @@ import type { Account, PhoneNumber, Carrier, Application } from "src/api/types"; import { 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"); @@ -52,6 +53,7 @@ export const PhoneNumbers = () => { const [accountSid, setAccountSid] = useState(""); const phoneNumbersFiltered = useMemo(() => { + setAccountSid(getAccountFilter()); return phoneNumbers ? phoneNumbers.filter( (phn) => !accountSid || phn.account_sid === accountSid @@ -106,6 +108,7 @@ export const PhoneNumbers = () => { }; useEffect(() => { + setLocation(); if (user?.account_sid && user.scope === USER_ACCOUNT) { setAccountSid(user?.account_sid); } diff --git a/src/containers/internal/views/recent-calls/index.tsx b/src/containers/internal/views/recent-calls/index.tsx index 85f17ec..90b73d3 100644 --- a/src/containers/internal/views/recent-calls/index.tsx +++ b/src/containers/internal/views/recent-calls/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { ButtonGroup, H1, M, MS } from "jambonz-ui"; import dayjs from "dayjs"; @@ -22,6 +22,11 @@ import { DetailsItem } from "./details"; import type { Account, CallQuery, RecentCall } from "src/api/types"; import { ScopedAccess } from "src/components/scoped-access"; import { Scope } from "src/store/types"; +import { + getAccountFilter, + getQueryFilter, + setLocation, +} from "src/store/localStore"; const directionSelection = [ { name: "either", value: "io" }, @@ -73,7 +78,18 @@ export const RecentCalls = () => { }); }; + useMemo(() => { + if (getQueryFilter()) { + const [date, direction, status] = getQueryFilter().split("/"); + setAccountSid(getAccountFilter()); + setDateFilter(date); + setDirectionFilter(direction); + setStatusFilter(status); + } + }, [accountSid]); + useEffect(() => { + setLocation(); if (accountSid) { handleFilterChange(); } diff --git a/src/containers/internal/views/speech-services/form.tsx b/src/containers/internal/views/speech-services/form.tsx index adbf9ab..eb2884d 100644 --- a/src/containers/internal/views/speech-services/form.tsx +++ b/src/containers/internal/views/speech-services/form.tsx @@ -38,6 +38,7 @@ import { CredentialStatus } from "./status"; import type { RegionVendors, GoogleServiceKey, Vendor } from "src/vendor/types"; import type { Account, SpeechCredential, UseApiDataMap } from "src/api/types"; +import { setAccountFilter, setLocation } from "src/store/localStore"; type SpeechServiceFormProps = { credential?: UseApiDataMap; @@ -176,6 +177,7 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => { .then(() => { toastSuccess("Speech credential created successfully"); navigate(ROUTE_INTERNAL_SPEECH); + setAccountFilter(accountSid); }) .catch((error) => { toastError(error.msg); @@ -185,6 +187,7 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => { }; useEffect(() => { + setLocation(); if (credential && credential.data) { if (credential.data.vendor) { setVendor(credential.data.vendor); diff --git a/src/containers/internal/views/speech-services/index.tsx b/src/containers/internal/views/speech-services/index.tsx index 6fe3531..ea8bda8 100644 --- a/src/containers/internal/views/speech-services/index.tsx +++ b/src/containers/internal/views/speech-services/index.tsx @@ -25,6 +25,7 @@ import { CredentialStatus } from "./status"; import type { SpeechCredential, Account } from "src/api/types"; import { ScopedAccess } from "src/components/scoped-access"; import { Scope } from "src/store/types"; +import { getAccountFilter, setLocation } from "src/store/localStore"; export const SpeechServices = () => { const user = useSelectState("user"); @@ -37,6 +38,7 @@ export const SpeechServices = () => { const [filter] = useState(""); const credentialsFiltered = useMemo(() => { + setAccountSid(getAccountFilter()); if (user?.scope === USER_ACCOUNT) { return credentials; } @@ -83,6 +85,7 @@ export const SpeechServices = () => { }; useEffect(() => { + setLocation(); if (accountSid) { setApiUrl(`Accounts/${accountSid}/SpeechCredentials`); } else if (currentServiceProvider) { diff --git a/src/containers/internal/views/users/form.tsx b/src/containers/internal/views/users/form.tsx index 9c7778a..24d285b 100644 --- a/src/containers/internal/views/users/form.tsx +++ b/src/containers/internal/views/users/form.tsx @@ -37,6 +37,7 @@ import type { Account, } from "src/api/types"; import type { IMessage } from "src/store/types"; +import { setAccountFilter, setLocation } from "src/store/localStore"; type UserFormProps = { user?: UseApiDataMap; @@ -126,6 +127,7 @@ export const UserForm = ({ user }: UserFormProps) => { .then(() => { toastSuccess("User created successfully"); navigate(ROUTE_INTERNAL_USERS); + setAccountFilter(accountSid); }) .catch((error: { msg: IMessage }) => { toastError(error.msg); @@ -165,6 +167,7 @@ export const UserForm = ({ user }: UserFormProps) => { /** Set current user data values if applicable -- e.g. "edit mode" */ useEffect(() => { + setLocation(); if (user && user.data) { setName(user.data.name); setForceChange(!!user.data.force_change); diff --git a/src/containers/internal/views/users/index.tsx b/src/containers/internal/views/users/index.tsx index d81f513..e6a0126 100644 --- a/src/containers/internal/views/users/index.tsx +++ b/src/containers/internal/views/users/index.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { H1, Button, Icon } from "jambonz-ui"; import { Link } from "react-router-dom"; @@ -30,6 +30,7 @@ import type { Account, User } from "src/api/types"; import { useSelectState } from "src/store"; import { ScopedAccess } from "src/components/scoped-access"; import { Scope } from "src/store/types"; +import { getAccountFilter, setLocation } from "src/store/localStore"; export const Users = () => { const user = useSelectState("user"); @@ -41,6 +42,7 @@ export const Users = () => { const [accounts] = useServiceProviderData("Accounts"); const usersFiltered = useMemo(() => { + setAccountSid(getAccountFilter()); const serviceProviderUsers = users?.filter((e) => { return ( e.scope === USER_ADMIN || @@ -72,6 +74,10 @@ export const Users = () => { sortUsersAlpha ); + useEffect(() => { + setLocation(); + }, []); + return ( <>
diff --git a/src/store/localStore.ts b/src/store/localStore.ts new file mode 100644 index 0000000..8667cda --- /dev/null +++ b/src/store/localStore.ts @@ -0,0 +1,83 @@ +/** + * The key used to store active Service Provider in localStorage + */ +const storeActiveSP = "activeServiceProvider"; + +/** + * Methods to get/set the token from local storage + */ +export const getActiveSP = () => { + return localStorage.getItem(storeActiveSP) || ""; +}; + +export const setActiveSP = (sid: string) => { + localStorage.setItem(storeActiveSP, sid); +}; + +/** + * The key used to store active Filter in localStorage + */ +const storeAccountFilter = "accountFilter"; + +/** + * Methods to get/set the account selected in the filter from local storage + */ +export const getAccountFilter = () => { + checkLocation(); + return localStorage.getItem(storeAccountFilter) || ""; +}; + +export const setAccountFilter = (accountSid: string) => { + localStorage.setItem(storeAccountFilter, accountSid); +}; + +export const removeAccountFilter = () => { + return localStorage.removeItem(storeAccountFilter); +}; + +/** + * Methods to get/set the RecentCalls and Alerts selected filters from local storage + */ + +const storeQueryFilter = "advancedFilter"; + +export const setQueryFilter = (combinedFilterString: string) => { + return localStorage.setItem(storeQueryFilter, combinedFilterString); +}; + +export const getQueryFilter = () => { + checkLocation(); + return localStorage.getItem(storeQueryFilter) || ""; +}; + +export const removeAdvancedFilter = () => { + return localStorage.removeItem(storeQueryFilter); +}; + +/** + * Methods to get/set the location from local storage + */ + +const storeLocation = "location"; + +export const setLocation = () => { + return localStorage.setItem( + storeLocation, + window.location.pathname.split("/")[2] + ); +}; + +export const getLocation = () => { + return localStorage.getItem(storeLocation); +}; + +export const checkLocation = () => { + const currentLocation = window.location.pathname.split("/")[2]; + const storedLocation = getLocation(); + + if (currentLocation !== storedLocation) { + localStorage.removeItem(storeQueryFilter); + localStorage.removeItem(storeAccountFilter); + return; + } +}; diff --git a/src/utils/index.ts b/src/utils/index.ts index 76ea243..692206a 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -26,6 +26,7 @@ import type { UserScopes, } from "src/api/types"; import type { UserData } from "src/store/types"; +import { getQueryFilter } from "src/store/localStore"; export const hasValue = ( variable: Type | null | undefined @@ -211,6 +212,19 @@ export const filterScopeOptions = ( return optionArray; }; +export const createFilterString = (filterValue: string, label: string) => { + const filterString = getQueryFilter().split("/"); + + if (label === "Date") { + filterString.splice(0, 0, filterValue); + } else if (label === "Direction") { + filterString.splice(1, 1, filterValue); + } else if (label === "Status") { + filterString.splice(2, 2, filterValue); + } + return filterString.join("/"); +}; + export { withSuspense, useMobileMedia,