Skip to content

Commit

Permalink
♻️ refactor(typescript): improving typesafety generally
Browse files Browse the repository at this point in the history
  • Loading branch information
thrownullexception committed Dec 5, 2023
1 parent d8e06c2 commit 9b461e3
Show file tree
Hide file tree
Showing 29 changed files with 183 additions and 128 deletions.
2 changes: 1 addition & 1 deletion src/backend/data/data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ export class DataApiService implements IDataApiService {
entity: string,
queryFilters: QueryFilterSchema,
paginationFilters: IPaginationFilters
): Promise<PaginatedData<unknown>> {
): Promise<PaginatedData<Record<string, unknown>>> {
return makeTableData(
await Promise.all([
this.getDataAccessInstance().list(
Expand Down
4 changes: 2 additions & 2 deletions src/backend/data/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { PaginatedData } from "shared/types/data";
import { IPaginationFilters } from "./types";

export const makeTableData = (
[data, totalRecords]: [unknown[], number],
[data, totalRecords]: [Record<string, unknown>[], number],
paginationFilters: IPaginationFilters
): PaginatedData<unknown> => ({
): PaginatedData<Record<string, unknown>> => ({
data,
pageIndex: paginationFilters.page,
pageSize: paginationFilters.take,
Expand Down
3 changes: 2 additions & 1 deletion src/frontend/components/SchemaForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ToastService } from "frontend/lib/toast";
import { resetFormValues } from "frontend/lib/form/utils";
import { FormButton } from "frontend/design-system/components/Button/FormButton";
import { userFriendlyCase } from "shared/lib/strings/friendly-case";
import { ButtonIconTypes } from "frontend/design-system/components/Button/constants";
import { RenderFormInput } from "./_RenderFormInput";
import { IFormExtension } from "./types";
import { runFormBeforeSubmit, runFormFieldState } from "./form-run";
Expand All @@ -19,7 +20,7 @@ interface IProps<T> {
initialValues?: Partial<T>;
buttonText?: (submitting: boolean) => string;
action?: string;
icon: "add" | "save" | "eye" | "no-icon" | "check" | "logIn";
icon: ButtonIconTypes | "no-icon";
onChange?: (data: T) => void;
resetForm?: true;
formExtension?: Partial<IFormExtension>;
Expand Down
2 changes: 2 additions & 0 deletions src/frontend/design-system/components/Button/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
X,
LogIn,
Square,
Copy,
} from "react-feather";

export const ICON_MAP = {
Expand All @@ -28,6 +29,7 @@ export const ICON_MAP = {
right: ArrowRight,
help: HelpCircle,
back: ChevronsLeft,
copy: Copy,
};

export type ButtonIconTypes = keyof typeof ICON_MAP;
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const SIZES_CONFIG: Record<Sizes, ISizeConfig> = {

const Root = styled.label<{ size: Sizes }>`
position: relative;
display: inline-block;
display: block;
margin-bottom: ${(props) => SIZES_CONFIG[props.size].marginBottom}px;
`;

Expand Down
26 changes: 12 additions & 14 deletions src/frontend/design-system/components/Section/SectionBox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,16 @@ import { DeleteButton } from "../../Button/DeleteButton";
import { SoftButton } from "../../Button/SoftButton";
import { Card, CardBody, CardHeader } from "../../Card";
import { Tooltip } from "../../Tooltip";
import { ButtonIconTypes } from "../../Button/constants";
import { BaseSkeleton } from "../../Skeleton/Base";
import { SimpleSelect } from "../../Form/FormSelect/Simple";
import { ISectionBoxIconButton } from "./types";

export interface IProps {
title: string;
children: ReactNode;
description?: string;
newItemLink?: string;
iconButtons?: {
action: string | (() => void);
label?: string;
icon?: ButtonIconTypes;
}[];
iconButtons?: ISectionBoxIconButton[];
selection?: { options: ISelectData[]; onChange: (value: string) => void };
deleteAction?: {
action: () => void;
Expand Down Expand Up @@ -105,14 +101,16 @@ export function SectionBox({
</Typo.SM>
) : null}
{iconButtons
? iconButtons.map(({ action, label, icon }) => (
<SoftButton
key={icon || label}
action={action}
label={label}
icon={icon}
/>
))
? iconButtons
.sort((a, b) => a.order - b.order)
.map(({ action, label, icon }) => (
<SoftButton
key={icon || label}
action={action}
label={label}
icon={icon}
/>
))
: null}
{newItemLink ? (
<SoftButton action={newItemLink} icon="add" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ButtonIconTypes } from "../../Button/constants";

export interface ISectionBoxIconButton {
action: string | (() => void);
label?: string;
icon?: ButtonIconTypes;
order?: number;
}
32 changes: 32 additions & 0 deletions src/frontend/hooks/data/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { DATA_MUTATION_QUERY_ENDPOINTS } from "./portal";

export const ENTITY_TABLE_PATH = (entity: string) =>
`/api/data/${entity}/table`;

export const ENTITY_COUNT_PATH = (entity: string) =>
`/api/data/${entity}/count`;

export const ENTITY_DETAILS_PATH = (
entity: string,
id: string,
column?: string
) => {
const baseLink = `/api/data/${entity}/${id}`;

if (column) {
return `${baseLink}?column=${column}`;
}
return baseLink;
};

export const ENTITY_REFERENCE_PATH = (entity: string, id: string) =>
`/api/data/${entity}/${id}/reference`;

export const ENTITY_LIST_PATH = (entity: string) => `/api/data/${entity}/list`;

export const CREATE_DATA_ENDPOINT_TO_CLEAR = (entity: string) => [
ENTITY_TABLE_PATH(entity),
ENTITY_COUNT_PATH(entity),
ENTITY_LIST_PATH(entity),
...DATA_MUTATION_QUERY_ENDPOINTS(entity),
];
59 changes: 21 additions & 38 deletions src/frontend/hooks/data/data.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,45 +14,33 @@ import { useEntityCrudConfig } from "../entity/entity.config";
import { useMultipleEntityReferenceFields } from "../entity/entity.store";
import { isRouterParamEnabled } from "..";
import { DATA_MUTATION_QUERY_ENDPOINTS } from "./portal";

export const ENTITY_TABLE_PATH = (entity: string) =>
`/api/data/${entity}/table`;

export const ENTITY_COUNT_PATH = (entity: string) =>
`/api/data/${entity}/count`;

export const ENTITY_DETAILS_PATH = (
entity: string,
id: string,
column?: string
) => {
const baseLink = `/api/data/${entity}/${id}`;

if (column) {
return `${baseLink}?column=${column}`;
}
return baseLink;
};

export const ENTITY_REFERENCE_PATH = (entity: string, id: string) =>
`/api/data/${entity}/${id}/reference`;

export const ENTITY_LIST_PATH = (entity: string) => `/api/data/${entity}/list`;

export const useEntityDataDetails = (
entity: string,
id: string,
column?: string
) => {
import {
CREATE_DATA_ENDPOINT_TO_CLEAR,
ENTITY_COUNT_PATH,
ENTITY_DETAILS_PATH,
ENTITY_LIST_PATH,
ENTITY_REFERENCE_PATH,
ENTITY_TABLE_PATH,
} from "./constants";

export const useEntityDataDetails = ({
entity,
entityId,
column,
}: {
entity: string;
entityId: string;
column?: string;
}) => {
const entityCrudConfig = useEntityCrudConfig(entity);

return useApi<Record<string, string>>(
ENTITY_DETAILS_PATH(entity, id, column),
ENTITY_DETAILS_PATH(entity, entityId, column),
{
errorMessage: entityCrudConfig.TEXT_LANG.NOT_FOUND,
enabled:
isRouterParamEnabled(entity) &&
isRouterParamEnabled(id) &&
isRouterParamEnabled(entityId) &&
column !== SLUG_LOADING_VALUE,
defaultData: {},
}
Expand Down Expand Up @@ -154,12 +142,7 @@ export function useEntityDataCreationMutation(entity: string) {
const apiMutateOptions = useWaitForResponseMutationOptions<
Record<string, string>
>({
endpoints: [
ENTITY_TABLE_PATH(entity),
ENTITY_COUNT_PATH(entity),
ENTITY_LIST_PATH(entity),
...DATA_MUTATION_QUERY_ENDPOINTS(entity),
],
endpoints: CREATE_DATA_ENDPOINT_TO_CLEAR(entity),
smartSuccessMessage: ({ id }) => ({
message: entityCrudConfig.MUTATION_LANG.CREATE,
action: {
Expand Down
3 changes: 1 addition & 2 deletions src/frontend/lib/crud-config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ export const MAKE_CRUD_CONFIG = ({
EDIT: `${singular} Updated Successfully`,
DELETE: `${singular} Deleted Successfully`,
SAVED: `${singular} Saved Successfully`,
ACTIVATED: `${singular} Activated Successfully`,
DE_ACTIVATED: `${singular} Deactivated Successfully`,
CUSTOM: (action: string) => `${singular} ${action} Successfully`,
VIEW_DETAILS: `Click here to view ${singular.toLowerCase()}`,
},
FORM_LANG: {
Expand Down
10 changes: 1 addition & 9 deletions src/frontend/lib/data/useMutate/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
type ToastMessageInput =
export type ToastMessageInput =
| { message: string; action: { label: string; action: () => void } }
| string;

Expand All @@ -11,12 +11,4 @@ export interface IApiMutateOptions<T, K, V> {
onSuccessActionWithFormData?: (formData: V) => void;
}

export interface IWaitForResponseMutationOptions<T> {
endpoints: string[];
redirect?: string;
onSuccessActionWithFormData?: (formData: T) => void;
successMessage?: ToastMessageInput;
smartSuccessMessage?: (formData: T) => ToastMessageInput;
}

export const FOR_CODE_COV = 1;
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { useQueryClient } from "react-query";
import { ToastService } from "frontend/lib/toast";
import { getQueryCachekey } from "../constants/getQueryCacheKey";
import { IWaitForResponseMutationOptions } from "./types";
import { ToastMessageInput } from "./types";

export interface IWaitForResponseMutationOptions<T> {
endpoints: string[];
redirect?: string;
onSuccessActionWithFormData?: (formData: T) => void;
successMessage?: ToastMessageInput;
smartSuccessMessage?: (formData: T) => ToastMessageInput;
}

const PASS_DATA_FROM_HANDLER_ERROR_MESSAGE =
"Please return in the mutation what data you want to pass to the success handlers";
Expand Down
5 changes: 4 additions & 1 deletion src/frontend/views/data/Create/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ export function EntityCreate() {
>
<BaseEntityForm
entity={entity}
action="create"
icon="add"
crudAction="create"
resetForm
buttonText={entityCrudConfig.FORM_LANG.CREATE}
initialValues={routeParams}
onSubmit={entityDataCreationMutation.mutateAsync}
hiddenColumns={hiddenCreateColumns}
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/views/data/Details/DetailsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function EntityDetailsView({
displayFrom: "details" | "canvas";
column?: string;
}) {
const dataDetails = useEntityDataDetails(entity, id, column);
const dataDetails = useEntityDataDetails({ entity, entityId: id, column });
const entityFields = useEntityFields(entity);
const entityFieldTypes = useProcessedEntityFieldTypes(entity);
const hiddenDetailsColumns = useHiddenEntityColumns("details", entity);
Expand Down
15 changes: 9 additions & 6 deletions src/frontend/views/data/Details/RelationsDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,20 @@ export function EntityRelationDetails() {
? SLUG_LOADING_VALUE
: referenceColumn?.inverseToOneField;

const currentEntityData = useEntityDataDetails(parentEntity, entityId);
const currentEntityData = useEntityDataDetails({
entity: parentEntity,
entityId,
});

const viewEntityId = referenceColumn?.inverseToOneField
? entityId
: currentEntityData.data[referenceColumn?.field];

const dataDetails = useEntityDataDetails(
childEntity,
viewEntityId,
detailsColumn
);
const dataDetails = useEntityDataDetails({
entity: childEntity,
entityId: viewEntityId,
column: detailsColumn,
});

const childEntityIdField = useEntityIdField(childEntity);

Expand Down
2 changes: 1 addition & 1 deletion src/frontend/views/data/Details/_Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function DetailsLayout({
);
const entityId = useEntityId();

const dataDetails = useEntityDataDetails(entity, entityId);
const dataDetails = useEntityDataDetails({ entity, entityId });

const referenceFields = useEntityReferenceFields(entity);

Expand Down
30 changes: 18 additions & 12 deletions src/frontend/views/data/Details/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import {
useEntityId,
useEntitySlug,
} from "frontend/hooks/entity/entity.config";
import { ISectionBoxIconButton } from "frontend/design-system/components/Section/SectionBox/types";
import { ENTITY_DETAILS_VIEW_KEY } from "./constants";
import { EntityDetailsView } from "./DetailsView";
import { DetailsLayout, DETAILS_LAYOUT_KEY } from "./_Layout";
import { useCanUserPerformCrudAction } from "../useCanUserPerformCrudAction";
import { DetailsCanvas } from "../Table/_WholeEntityTable/DetailsCanvas";
import { useDetailsViewMenuItems } from "./portal";
import { useDetailsViewMenuItems, useSectionBoxIconButtons } from "./portal";

export function EntityDetails() {
const entityCrudConfig = useEntityCrudConfig();
Expand All @@ -40,6 +41,21 @@ export function EntityDetails() {
entityId: id,
});

const portalSectionBoxIconButtons = useSectionBoxIconButtons({
entity,
entityId: id,
});

const baseIconButtons: ISectionBoxIconButton[] = [];
if (canUserPerformCrudAction("update")) {
baseIconButtons.push({
icon: "edit",
action: NAVIGATION_LINKS.ENTITY.UPDATE(entity, id),
label: "Edit",
order: 10,
});
}

return (
<DetailsLayout
entity={entity}
Expand All @@ -57,17 +73,7 @@ export function EntityDetails() {
}
: undefined
}
iconButtons={
canUserPerformCrudAction("update")
? [
{
icon: "edit",
action: NAVIGATION_LINKS.ENTITY.UPDATE(entity, id),
label: "Edit",
},
]
: []
}
iconButtons={[...baseIconButtons, ...portalSectionBoxIconButtons]}
>
<EntityDetailsView displayFrom="details" id={id} entity={entity} />
</SectionBox>
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/views/data/Details/portal/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { useDetailsViewMenuItems } from "./main";
export { useDetailsViewMenuItems, useSectionBoxIconButtons } from "./main";
Loading

0 comments on commit 9b461e3

Please sign in to comment.