Merge pull request #1106 from bigcapitalhq/refactor/typescript-preferences-containers

refactor(typescript): add proper types to Preferences container files
This commit is contained in:
Ahmed Bouhuolia
2026-05-31 22:14:47 +02:00
committed by GitHub
21 changed files with 122 additions and 140 deletions

View File

@@ -129,6 +129,7 @@
"yup": "^0.28.1"
},
"devDependencies": {
"@types/flat": "^5.0.5",
"@vitejs/plugin-legacy": "^5.4.2",
"@vitejs/plugin-react": "^4.3.4",
"eslint-config-react-app": "^7.0.1",

View File

@@ -1,4 +1,3 @@
// @ts-nocheck
import * as Yup from 'yup';
const Schema = Yup.object().shape({

View File

@@ -1,4 +1,3 @@
// @ts-nocheck
import React from 'react';
import { AccountantFormPage } from './AccountantFormPage';
import { AccountantFormProvider } from './AccountantFormProvider';

View File

@@ -5,7 +5,6 @@ import { Form, useFormikContext } from 'formik';
import styled from 'styled-components';
import { FormGroup, Radio, Button, Intent } from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import {
FormattedMessage as T,
AccountsSelect,

View File

@@ -1,15 +1,12 @@
// @ts-nocheck
import React, { useEffect } from 'react';
import { useEffect } from 'react';
import * as R from 'ramda';
import intl from 'react-intl-universal';
import { Formik } from 'formik';
import { Formik, FormikHelpers } from 'formik';
import { Intent } from '@blueprintjs/core';
import { flatten, unflatten } from 'flat';
import { AppToaster } from '@/components';
import { withDashboardActions } from '@/containers/Dashboard/withDashboardActions';
import { withSettings } from '@/containers/Settings/withSettings';
import { withDashboardActions, type WithDashboardActionsProps } from '@/containers/Dashboard/withDashboardActions';
import { withSettings, type WithSettingsProps } from '@/containers/Settings/withSettings';
import { AccountantForm } from './AccountantForm';
import { AccountantSchema } from './Accountant.schema';
import { useAccountantFormContext } from './AccountantFormProvider';
@@ -35,14 +32,12 @@ const defaultFormValues = flatten({
},
});
// Accountant preferences.
function AccountantFormPageInner({
//# withDashboardActions
changePreferencesPageTitle,
interface AccountantFormPageInnerProps extends WithDashboardActionsProps, WithSettingsProps {}
// #withSettings
function AccountantFormPageInner({
changePreferencesPageTitle,
allSettings,
}) {
}: AccountantFormPageInnerProps) {
const { saveSettingMutate } = useAccountantFormContext();
useEffect(() => {
@@ -53,8 +48,7 @@ function AccountantFormPageInner({
...defaultFormValues,
...transformToForm(flatten(allSettings), defaultFormValues),
});
// Handle the form submitting.
const handleFormSubmit = (values, { setSubmitting }) => {
const handleFormSubmit = (values: Record<string, any>, { setSubmitting }: FormikHelpers<Record<string, any>>) => {
const options = R.compose(
transferObjectOptionsToArray,
transfromToSnakeCase,

View File

@@ -1,35 +1,35 @@
// @ts-nocheck
import React from 'react';
import React, { ReactNode } from 'react';
import classNames from 'classnames';
import styled from 'styled-components';
import type { AccountsList } from '@bigcapital/sdk-ts';
import { Card } from '@/components';
import { CLASSES } from '@/constants/classes';
import { useAccounts, useSaveSettings, useSettings } from '@/hooks/query';
import { PreferencesPageLoader } from '../PreferencesPageLoader';
const AccountantFormContext = React.createContext();
interface AccountantFormContextValue {
accounts: AccountsList | undefined;
isAccountsLoading: boolean;
saveSettingMutate: ReturnType<typeof useSaveSettings>['mutateAsync'];
}
/**
* Accountant data provider.
*/
function AccountantFormProvider({ ...props }) {
// Fetches the accounts list.
const AccountantFormContext = React.createContext<AccountantFormContextValue>({} as AccountantFormContextValue);
interface AccountantFormProviderProps {
children: ReactNode;
}
function AccountantFormProvider({ children, ...props }: AccountantFormProviderProps) {
const { isLoading: isAccountsLoading, data: accounts } = useAccounts();
// Fetches Organization Settings.
const { isLoading: isSettingsLoading } = useSettings();
// Save Organization Settings.
const { mutateAsync: saveSettingMutate } = useSaveSettings();
// Provider state.
const provider = {
const provider: AccountantFormContextValue = {
accounts,
isAccountsLoading,
saveSettingMutate,
};
// Detarmines whether if any query is loading.
const isLoading = isSettingsLoading || isAccountsLoading;
return (
@@ -43,7 +43,9 @@ function AccountantFormProvider({ ...props }) {
{isLoading ? (
<PreferencesPageLoader />
) : (
<AccountantFormContext.Provider value={provider} {...props} />
<AccountantFormContext.Provider value={provider} {...props}>
{children}
</AccountantFormContext.Provider>
)}
</AccountantFormCard>
</div>

View File

@@ -26,10 +26,7 @@ function BranchesDataTableInner({
// #withAlertActions
openAlert,
}) {
// Table columns.
const columns = useBranchesTableColumns();
// MarkBranchAsPrimary
const { mutateAsync: markBranchAsPrimaryMutate } = useMarkBranchAsPrimary();
const { branches, isBranchesLoading, isBranchesFetching } =
@@ -59,7 +56,7 @@ function BranchesDataTableInner({
<BranchesTableCard>
<BranchesTable
columns={columns}
data={branches}
data={branches ?? []}
loading={isBranchesLoading}
headerLoading={isBranchesLoading}
progressBarLoading={isBranchesFetching}

View File

@@ -1,27 +1,35 @@
// @ts-nocheck
import React from 'react';
import React, { ReactNode } from 'react';
import classNames from 'classnames';
import { BranchesListResponse } from '@bigcapital/sdk-ts';
import { CLASSES } from '@/constants/classes';
import { useBranches } from '@/hooks/query';
import { useFeatureCan } from '@/hooks/state';
import { Features } from '@/constants';
import { isEmpty } from 'lodash';
const BranchesContext = React.createContext();
interface BranchesContextValue {
branches: BranchesListResponse | undefined;
isBranchesLoading: boolean;
isEmptyStatus: boolean;
}
/**
* Branches data provider.
*/
function BranchesProvider({ query, ...props }) {
const BranchesContext = React.createContext<BranchesContextValue>(
{} as BranchesContextValue,
);
interface BranchesProviderProps {
query?: Record<string, unknown>;
children: ReactNode;
}
function BranchesProvider({ query, ...props }: BranchesProviderProps) {
// Features guard.
const { featureCan } = useFeatureCan();
const isBranchFeatureCan = featureCan(Features.Branches);
// Fetches the branches list.
const {
isLoading: isBranchesLoading,
isFetching: isBranchesFetching,
data: branches,
} = useBranches(query, { enabled: isBranchFeatureCan });
@@ -30,10 +38,9 @@ function BranchesProvider({ query, ...props }) {
(isEmpty(branches) && !isBranchesLoading) || !isBranchFeatureCan;
// Provider state.
const provider = {
const provider: BranchesContextValue = {
branches,
isBranchesLoading,
isBranchesFetching,
isEmptyStatus,
};
@@ -49,5 +56,7 @@ function BranchesProvider({ query, ...props }) {
);
}
const useBranchesContext = () => React.useContext(BranchesContext);
const useBranchesContext = () =>
React.useContext<BranchesContextValue>(BranchesContext);
export { BranchesProvider, useBranchesContext };

View File

@@ -2,7 +2,6 @@
import React from 'react';
import classNames from 'classnames';
import styled from 'styled-components';
import { Card } from '@/components';
import { CLASSES } from '@/constants/classes';
import { CurrenciesList } from './CurrenciesList';

View File

@@ -49,7 +49,7 @@ function CurrenciesDataTableInner({
return (
<CurrencieDataTable
columns={columns}
data={currencies}
data={currencies ?? []}
loading={isCurrenciesLoading}
progressBarLoading={isCurrenciesLoading}
TableLoadingRenderer={TableSkeletonRows}

View File

@@ -1,16 +1,12 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import styled from 'styled-components';
import { Intent } from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import { DataTable, AppToaster, TableSkeletonRows } from '@/components';
import { useRolesTableColumns, ActionsMenu } from './components';
import { withAlertActions } from '@/containers/Alert/withAlertActions';
import { withAlertActions, type WithAlertActionsProps } from '@/containers/Alert/withAlertActions';
import { useRolesContext } from './RolesListProvider';
import { compose } from '@/utils';
/**
@@ -19,18 +15,14 @@ import { compose } from '@/utils';
function RolesDataTableInner({
// #withAlertActions
openAlert,
}) {
// History context.
}: WithAlertActionsProps) {
const history = useHistory();
// Retrieve roles table columns
const columns = useRolesTableColumns();
// Roles table context.
const { roles, isRolesFetching, isRolesLoading } = useRolesContext();
// handles delete the given role.
const handleDeleteRole = ({ id, predefined }) => {
const handleDeleteRole = ({ id, predefined }: { id: number; predefined: boolean }) => {
if (predefined) {
AppToaster.show({
message: intl.get('roles.error.you_cannot_delete_predefined_roles'),
@@ -40,8 +32,7 @@ function RolesDataTableInner({
openAlert('role-delete', { roleId: id });
}
};
// Handles the edit of the given role.
const handleEditRole = ({ id, predefined }) => {
const handleEditRole = ({ id, predefined }: { id: number; predefined: boolean }) => {
if (predefined) {
AppToaster.show({
message: intl.get('roles.error.you_cannot_edit_predefined_roles'),
@@ -55,7 +46,7 @@ function RolesDataTableInner({
return (
<RolesTable
columns={columns}
data={roles}
data={roles ?? []}
loading={isRolesLoading}
headerLoading={isRolesFetching}
progressBarLoading={isRolesFetching}

View File

@@ -1,6 +1,4 @@
// @ts-nocheck
import React from 'react';
import { RolesListProvider } from './RolesListProvider';
import { RolesDataTable } from './RolesDataTable';

View File

@@ -1,24 +1,29 @@
// @ts-nocheck
import React from 'react';
import React, { ReactNode } from 'react';
import classNames from 'classnames';
import { CLASSES } from '@/constants/classes';
import { useRoles } from '@/hooks/query';
import type { RolesListResponse } from '@bigcapital/sdk-ts';
const RolesListContext = React.createContext();
interface RolesListContextValue {
roles: RolesListResponse | undefined;
isRolesFetching: boolean;
isRolesLoading: boolean;
}
/**
* Roles list provider.
*/
function RolesListProvider({ ...props }) {
// Fetch roles list.
const RolesListContext = React.createContext<RolesListContextValue>({} as RolesListContextValue);
interface RolesListProviderProps {
children: ReactNode;
}
function RolesListProvider({ children, ...props }: RolesListProviderProps) {
const {
data: roles,
isFetching: isRolesFetching,
isLoading: isRolesLoading,
} = useRoles();
// Provider state.
const provider = {
const provider: RolesListContextValue = {
roles,
isRolesFetching,
isRolesLoading,
@@ -30,7 +35,9 @@ function RolesListProvider({ ...props }) {
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_USERS,
)}
>
<RolesListContext.Provider value={provider} {...props} />
<RolesListContext.Provider value={provider} {...props}>
{children}
</RolesListContext.Provider>
</div>
);
}

View File

@@ -1,18 +1,23 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { Intent, Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
import { safeCallback } from '@/utils';
import { Icon } from '@/components';
/**
* Context menu of roles.
*/
interface ActionsMenuPayload {
onDeleteRole: (role: { id: number; predefined: boolean }) => void;
onEditRole: (role: { id: number; predefined: boolean }) => void;
}
interface ActionsMenuProps {
payload: ActionsMenuPayload;
row: { original: Record<string, any> };
}
export function ActionsMenu({
payload: { onDeleteRole, onEditRole },
row: { original },
}) {
}: ActionsMenuProps) {
return (
<Menu>
<MenuItem

View File

@@ -1,23 +1,16 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import classNames from 'classnames';
import styled from 'styled-components';
import { Tabs, Tab } from '@blueprintjs/core';
import '@/style/pages/Preferences/Users.scss';
import { Card } from '@/components';
import { CLASSES } from '@/constants/classes';
import PreferencesSubContent from '@/components/Preferences/PreferencesSubContent';
import { withDialogActions, type WithDialogActionsProps } from '@/containers/Dialog/withDialogActions';
import { withUserPreferences } from '@/containers/Preferences/Users/withUserPreferences';
/**
* Preferences page - Users page.
*/
function UsersPreferences({ openDialog }) {
const onChangeTabs = (currentTabId) => {};
function UsersPreferences({ openDialog }: WithDialogActionsProps) {
const onChangeTabs = (currentTabId: string) => {};
return (
<div
@@ -46,7 +39,7 @@ function UsersPreferences({ openDialog }) {
);
}
export const Users = withUserPreferences(UsersPreferences);
export const Users = withDialogActions(UsersPreferences);
const UsersPereferencesCard = styled(Card)`
padding: 0;

View File

@@ -1,19 +1,15 @@
// @ts-nocheck
import React from 'react';
import { useHistory } from 'react-router-dom';
import { Button, Intent } from '@blueprintjs/core';
import { Icon, FormattedMessage as T } from '@/components';
import { withDialogActions } from '@/containers/Dialog/withDialogActions';
import { withDialogActions, type WithDialogActionsProps } from '@/containers/Dialog/withDialogActions';
import { compose } from '@/utils';
function UsersActionsInner({ openDialog, closeDialog }) {
function UsersActionsInner({ openDialog }: WithDialogActionsProps) {
const history = useHistory();
const onClickNewUser = () => {
openDialog('invite-user');
};
const onClickNewRole = () => {
history.push('/preferences/roles');
};

View File

@@ -86,7 +86,7 @@ function UsersDataTableInner({
return (
<DataTable
columns={columns}
data={users}
data={users ?? []}
loading={isUsersLoading}
headerLoading={isUsersLoading}
progressBarLoading={isUsersFetching}

View File

@@ -1,20 +1,13 @@
// @ts-nocheck
import React, { useEffect } from 'react';
import { useEffect } from 'react';
import intl from 'react-intl-universal';
import { UsersListProvider } from './UsersProvider';
import { withDashboardActions } from '@/containers/Dashboard/withDashboardActions';
import { withDashboardActions, type WithDashboardActionsProps } from '@/containers/Dashboard/withDashboardActions';
import { UsersDataTable } from './UsersDataTable';
import { compose } from '@/utils';
/**
* Users list.
*/
function UsersListPreferences({
// #withDashboardActions
changePreferencesPageTitle,
}) {
}: WithDashboardActionsProps) {
useEffect(() => {
changePreferencesPageTitle(intl.get('users'));
}, [changePreferencesPageTitle]);

View File

@@ -1,23 +1,32 @@
// @ts-nocheck
import React, { createContext } from 'react';
import React, { createContext, ReactNode } from 'react';
import type { UsersListResponse } from '@bigcapital/sdk-ts';
import { useUsers } from '@/hooks/query';
const UsersListContext = createContext();
interface UsersListContextValue {
users: UsersListResponse | undefined;
isUsersLoading: boolean;
isUsersFetching: boolean;
}
/**
* Users list provider.
*/
function UsersListProvider(props) {
const UsersListContext = createContext<UsersListContextValue>({} as UsersListContextValue);
interface UsersListProviderProps {
children: ReactNode;
}
function UsersListProvider({ children, ...props }: UsersListProviderProps) {
const { data: users, isLoading, isFetching } = useUsers();
const state = {
const state: UsersListContextValue = {
isUsersLoading: isLoading,
isUsersFetching: isFetching,
users,
};
return (
<UsersListContext.Provider value={state} {...props} />
<UsersListContext.Provider value={state} {...props}>
{children}
</UsersListContext.Provider>
);
}

View File

@@ -1,17 +0,0 @@
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
import { CLOSE_DIALOG, OPEN_DIALOG } from '@/store/types';
export interface WithUserPreferencesProps {
openDialog: (name: string, payload?: Record<string, unknown>) => void;
closeDialog: (name: string, payload?: Record<string, unknown>) => void;
}
export const mapDispatchToProps = (dispatch: Dispatch): WithUserPreferencesProps => ({
openDialog: (name: string, payload?: Record<string, unknown>) =>
dispatch({ type: OPEN_DIALOG, name, payload }),
closeDialog: (name: string, payload?: Record<string, unknown>) =>
dispatch({ type: CLOSE_DIALOG, name, payload }),
});
export const withUserPreferences = connect(null, mapDispatchToProps);

8
pnpm-lock.yaml generated
View File

@@ -785,6 +785,9 @@ importers:
specifier: ^0.28.1
version: 0.28.5
devDependencies:
'@types/flat':
specifier: ^5.0.5
version: 5.0.5
'@vitejs/plugin-legacy':
specifier: ^5.4.2
version: 5.4.3(terser@5.31.0)(vite@5.4.10(@types/node@20.19.25)(less@4.2.0)(sass@1.77.2)(terser@5.31.0))
@@ -5448,6 +5451,9 @@ packages:
'@types/find-cache-dir@3.2.1':
resolution: {integrity: sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw==}
'@types/flat@5.0.5':
resolution: {integrity: sha512-nPLljZQKSnac53KDUDzuzdRfGI0TDb5qPrb+SrQyN3MtdQrOnGsKniHN1iYZsJEBIVQve94Y6gNz22sgISZq+Q==}
'@types/glob@7.2.0':
resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
@@ -20109,6 +20115,8 @@ snapshots:
'@types/find-cache-dir@3.2.1': {}
'@types/flat@5.0.5': {}
'@types/glob@7.2.0':
dependencies:
'@types/minimatch': 5.1.2