Fix strict null checks in warehouses (#2945)
* Fix strict nulls in warehouse details * Fix strict null check in warehouse list * Fix stcit null checks in warehouse create view * Fix overlapping label in textfields * Improve labels in warehouse details * Improve function description
This commit is contained in:
parent
53806abc10
commit
931467a73a
17 changed files with 110 additions and 97 deletions
|
@ -137,10 +137,6 @@
|
|||
"+xTpT1": {
|
||||
"string": "Attributes"
|
||||
},
|
||||
"//iaFx": {
|
||||
"context": "WarehouseSettings title",
|
||||
"string": "Settings"
|
||||
},
|
||||
"//k1GX": {
|
||||
"context": "expired on label",
|
||||
"string": "Expired on {date}"
|
||||
|
@ -1441,6 +1437,10 @@
|
|||
"context": "bulk issue menu item",
|
||||
"string": "Bulk Issue"
|
||||
},
|
||||
"9kzIFd": {
|
||||
"context": "WarehouseSettings stock title",
|
||||
"string": "Stock settings"
|
||||
},
|
||||
"9mGA/Q": {
|
||||
"context": "button linking to dashboard",
|
||||
"string": "Dashboard"
|
||||
|
|
|
@ -4,10 +4,10 @@ import { createContext, useContext, useMemo } from "react";
|
|||
import { Pagination } from "../types";
|
||||
|
||||
export interface PageInfo {
|
||||
endCursor: string;
|
||||
endCursor: string | null;
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
startCursor: string;
|
||||
startCursor: string | null;
|
||||
}
|
||||
|
||||
export interface PaginationState {
|
||||
|
@ -37,7 +37,7 @@ export function createPaginationState(
|
|||
}
|
||||
|
||||
interface UsePaginatorArgs {
|
||||
pageInfo: PageInfo;
|
||||
pageInfo: PageInfo | undefined;
|
||||
paginationState: PaginationState;
|
||||
queryString: Pagination;
|
||||
}
|
||||
|
@ -99,12 +99,7 @@ function usePaginator({
|
|||
|
||||
export default usePaginator;
|
||||
|
||||
export interface PaginatorContextValuesCommon {
|
||||
hasNextPage?: boolean;
|
||||
hasPreviousPage?: boolean;
|
||||
endCursor?: string | null;
|
||||
startCursor?: string | null;
|
||||
}
|
||||
export type PaginatorContextValuesCommon = Partial<PageInfo>;
|
||||
|
||||
export type PaginatorContextValues = PaginatorContextValuesCommon &
|
||||
(
|
||||
|
|
|
@ -43,11 +43,11 @@ export type RequireOnlyOne<T, Keys extends keyof T = keyof T> = Pick<
|
|||
}[Keys];
|
||||
|
||||
export function renderCollection<T>(
|
||||
collection: T[],
|
||||
collection: T[] | undefined,
|
||||
renderItem: (
|
||||
item: T | undefined,
|
||||
index: number | undefined,
|
||||
collection: T[],
|
||||
collection: T[] | undefined,
|
||||
) => any,
|
||||
renderEmpty?: (collection: T[]) => any,
|
||||
) {
|
||||
|
|
|
@ -4,7 +4,7 @@ import { ActiveTab, Pagination, Search, Sort } from "@dashboard/types";
|
|||
|
||||
import { GetFilterQueryParam, getFilterQueryParams } from "../filters";
|
||||
|
||||
type RequiredParams = ActiveTab & Search & Sort & Pagination;
|
||||
type RequiredParams = ActiveTab & Search & Sort<any> & Pagination;
|
||||
type CreateUrl = (params: RequiredParams) => string;
|
||||
type CreateFilterHandlers<TFilterKeys extends string> = [
|
||||
(filter: IFilter<TFilterKeys>) => void,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { LimitInfoFragment, RefreshLimitsQuery } from "@dashboard/graphql";
|
||||
|
||||
export function hasLimits(
|
||||
limits: RefreshLimitsQuery["shop"]["limits"],
|
||||
limits: RefreshLimitsQuery["shop"]["limits"] | undefined,
|
||||
key: keyof LimitInfoFragment,
|
||||
): boolean {
|
||||
if (limits === undefined) {
|
||||
|
@ -10,14 +10,24 @@ export function hasLimits(
|
|||
|
||||
return limits.allowedUsage[key] !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not limit has been reached.
|
||||
* If limits are undefined, returns false.
|
||||
* */
|
||||
export function isLimitReached(
|
||||
limits: RefreshLimitsQuery["shop"]["limits"],
|
||||
limits: RefreshLimitsQuery["shop"]["limits"] | undefined,
|
||||
key: keyof LimitInfoFragment,
|
||||
): boolean {
|
||||
if (!hasLimits(limits, key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return limits.currentUsage[key] >= limits.allowedUsage[key];
|
||||
const currentUsage = limits?.currentUsage[key];
|
||||
const allowedUsage = limits?.allowedUsage[key];
|
||||
|
||||
if (!currentUsage || !allowedUsage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return currentUsage >= allowedUsage!;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ interface Edge<T> {
|
|||
node: T;
|
||||
}
|
||||
interface Connection<T> {
|
||||
edges: Array<Edge<T>> | undefined;
|
||||
edges: Array<Edge<T>> | undefined | null;
|
||||
}
|
||||
|
||||
export function mapEdgesToItems<T>(
|
||||
|
|
|
@ -17,7 +17,7 @@ const props: WarehouseCreatePageProps = {
|
|||
})),
|
||||
disabled: false,
|
||||
errors: [],
|
||||
onSubmit: () => undefined,
|
||||
onSubmit: async () => undefined,
|
||||
saveButtonBarState: "default",
|
||||
};
|
||||
storiesOf("Warehouses / Create warehouse", module)
|
||||
|
|
|
@ -19,7 +19,7 @@ const props: WarehouseDetailsPageProps = {
|
|||
disabled: false,
|
||||
errors: [],
|
||||
onDelete: () => undefined,
|
||||
onSubmit: () => undefined,
|
||||
onSubmit: async () => undefined,
|
||||
saveButtonBarState: "default",
|
||||
warehouse,
|
||||
};
|
||||
|
|
|
@ -9,7 +9,6 @@ import PageHeader from "@dashboard/components/PageHeader";
|
|||
import Savebar from "@dashboard/components/Savebar";
|
||||
import { AddressTypeInput } from "@dashboard/customers/types";
|
||||
import {
|
||||
CountryCode,
|
||||
CountryWithCodeFragment,
|
||||
WarehouseClickAndCollectOptionEnum,
|
||||
WarehouseDetailsFragment,
|
||||
|
@ -20,7 +19,6 @@ import { SubmitPromise } from "@dashboard/hooks/useForm";
|
|||
import useNavigator from "@dashboard/hooks/useNavigator";
|
||||
import useStateFromProps from "@dashboard/hooks/useStateFromProps";
|
||||
import { sectionNames } from "@dashboard/intl";
|
||||
import { findValueInEnum, maybe } from "@dashboard/misc";
|
||||
import createSingleAutocompleteSelectHandler from "@dashboard/utils/handlers/singleAutocompleteSelectChangeHandler";
|
||||
import { mapCountriesToChoices, mapEdgesToItems } from "@dashboard/utils/maps";
|
||||
import { warehouseListUrl } from "@dashboard/warehouses/urls";
|
||||
|
@ -41,7 +39,7 @@ export interface WarehouseDetailsPageProps {
|
|||
disabled: boolean;
|
||||
errors: WarehouseErrorFragment[];
|
||||
saveButtonBarState: ConfirmButtonTransitionState;
|
||||
warehouse: WarehouseDetailsFragment;
|
||||
warehouse: WarehouseDetailsFragment | undefined;
|
||||
onDelete: () => void;
|
||||
onSubmit: (data: WarehouseDetailsPageFormData) => SubmitPromise;
|
||||
}
|
||||
|
@ -68,21 +66,19 @@ const WarehouseDetailsPage: React.FC<WarehouseDetailsPageProps> = ({
|
|||
} = useAddressValidation(onSubmit);
|
||||
|
||||
const initialForm: WarehouseDetailsPageFormData = {
|
||||
city: maybe(() => warehouse.address.city, ""),
|
||||
companyName: maybe(() => warehouse.address.companyName, ""),
|
||||
country: maybe(() =>
|
||||
findValueInEnum(warehouse.address.country.code, CountryCode),
|
||||
),
|
||||
city: warehouse?.address.city ?? "",
|
||||
companyName: warehouse?.address.companyName ?? "",
|
||||
country: warehouse?.address.country.code ?? "",
|
||||
isPrivate: !!warehouse?.isPrivate,
|
||||
clickAndCollectOption:
|
||||
warehouse?.clickAndCollectOption ||
|
||||
WarehouseClickAndCollectOptionEnum.DISABLED,
|
||||
countryArea: maybe(() => warehouse.address.countryArea, ""),
|
||||
name: maybe(() => warehouse.name, ""),
|
||||
phone: maybe(() => warehouse.address.phone, ""),
|
||||
postalCode: maybe(() => warehouse.address.postalCode, ""),
|
||||
streetAddress1: maybe(() => warehouse.address.streetAddress1, ""),
|
||||
streetAddress2: maybe(() => warehouse.address.streetAddress2, ""),
|
||||
countryArea: warehouse?.address.countryArea ?? "",
|
||||
name: warehouse?.name ?? "",
|
||||
phone: warehouse?.address.phone ?? "",
|
||||
postalCode: warehouse?.address.postalCode ?? "",
|
||||
streetAddress1: warehouse?.address.streetAddress1 ?? "",
|
||||
streetAddress2: warehouse?.address.streetAddress2 ?? "",
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -133,7 +129,7 @@ const WarehouseDetailsPage: React.FC<WarehouseDetailsPageProps> = ({
|
|||
</div>
|
||||
<div>
|
||||
<WarehouseSettings
|
||||
zones={mapEdgesToItems(warehouse?.shippingZones)}
|
||||
zones={mapEdgesToItems(warehouse?.shippingZones) ?? []}
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
onChange={change}
|
||||
|
@ -142,7 +138,7 @@ const WarehouseDetailsPage: React.FC<WarehouseDetailsPageProps> = ({
|
|||
</div>
|
||||
</Grid>
|
||||
<Savebar
|
||||
disabled={isSaveDisabled}
|
||||
disabled={!!isSaveDisabled}
|
||||
onCancel={() => navigate(warehouseListUrl())}
|
||||
onDelete={onDelete}
|
||||
onSubmit={submit}
|
||||
|
|
|
@ -5,7 +5,7 @@ import TableCellHeader from "@dashboard/components/TableCellHeader";
|
|||
import { TablePaginationWithContext } from "@dashboard/components/TablePagination";
|
||||
import TableRowLink from "@dashboard/components/TableRowLink";
|
||||
import { WarehouseWithShippingFragment } from "@dashboard/graphql";
|
||||
import { maybe, renderCollection, stopPropagation } from "@dashboard/misc";
|
||||
import { renderCollection, stopPropagation } from "@dashboard/misc";
|
||||
import { ListProps, SortPage } from "@dashboard/types";
|
||||
import { mapEdgesToItems } from "@dashboard/utils/maps";
|
||||
import { getArrowDirection } from "@dashboard/utils/sort";
|
||||
|
@ -64,8 +64,8 @@ const useStyles = makeStyles(
|
|||
interface WarehouseListProps
|
||||
extends ListProps,
|
||||
SortPage<WarehouseListUrlSortField> {
|
||||
warehouses: WarehouseWithShippingFragment[];
|
||||
onRemove: (id: string) => void;
|
||||
warehouses: WarehouseWithShippingFragment[] | undefined;
|
||||
onRemove: (id: string | undefined) => void;
|
||||
}
|
||||
|
||||
const numberOfColumns = 3;
|
||||
|
@ -90,7 +90,7 @@ const WarehouseList: React.FC<WarehouseListProps> = props => {
|
|||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === WarehouseListUrlSortField.name
|
||||
? getArrowDirection(sort.asc)
|
||||
? getArrowDirection(!!sort.asc)
|
||||
: undefined
|
||||
}
|
||||
arrowPosition="right"
|
||||
|
@ -136,7 +136,7 @@ const WarehouseList: React.FC<WarehouseListProps> = props => {
|
|||
}
|
||||
>
|
||||
<TableCell className={classes.colName} data-test-id="name">
|
||||
{maybe<React.ReactNode>(() => warehouse.name, <Skeleton />)}
|
||||
{warehouse?.name ?? <Skeleton />}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colZones} data-test-id="zones">
|
||||
{warehouse?.shippingZones === undefined ? (
|
||||
|
@ -160,7 +160,7 @@ const WarehouseList: React.FC<WarehouseListProps> = props => {
|
|||
<IconButton
|
||||
variant="secondary"
|
||||
color="primary"
|
||||
onClick={stopPropagation(() => onRemove(warehouse.id))}
|
||||
onClick={stopPropagation(() => onRemove(warehouse?.id))}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
|
|
|
@ -32,8 +32,8 @@ export interface WarehouseListPageProps
|
|||
SearchPageProps,
|
||||
SortPage<WarehouseListUrlSortField>,
|
||||
TabPageProps {
|
||||
limits: RefreshLimitsQuery["shop"]["limits"];
|
||||
warehouses: WarehouseWithShippingFragment[];
|
||||
limits: RefreshLimitsQuery["shop"]["limits"] | undefined;
|
||||
warehouses: WarehouseWithShippingFragment[] | undefined;
|
||||
onRemove: (id: string) => void;
|
||||
}
|
||||
|
||||
|
@ -66,18 +66,19 @@ export const WarehouseListPage: React.FC<WarehouseListPageProps> = ({
|
|||
<PageHeader
|
||||
title={intl.formatMessage(sectionNames.warehouses)}
|
||||
limitText={
|
||||
hasLimits(limits, "warehouses") &&
|
||||
intl.formatMessage(
|
||||
hasLimits(limits, "warehouses")
|
||||
? intl.formatMessage(
|
||||
{
|
||||
id: "YkOzse",
|
||||
defaultMessage: "{count}/{max} warehouses used",
|
||||
description: "used warehouses counter",
|
||||
},
|
||||
{
|
||||
count: limits.currentUsage.warehouses,
|
||||
max: limits.allowedUsage.warehouses,
|
||||
count: limits?.currentUsage.warehouses,
|
||||
max: limits?.allowedUsage.warehouses,
|
||||
},
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<Button
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import CardSpacer from "@dashboard/components/CardSpacer";
|
||||
import CardTitle from "@dashboard/components/CardTitle";
|
||||
import { FormSpacer } from "@dashboard/components/FormSpacer";
|
||||
import Link from "@dashboard/components/Link";
|
||||
|
@ -9,6 +8,7 @@ import {
|
|||
WarehouseClickAndCollectOptionEnum,
|
||||
WarehouseWithShippingFragment,
|
||||
} from "@dashboard/graphql";
|
||||
import { sectionNames } from "@dashboard/intl";
|
||||
import { renderCollection } from "@dashboard/misc";
|
||||
import { shippingZoneUrl } from "@dashboard/shipping/urls";
|
||||
import { RelayToFlat } from "@dashboard/types";
|
||||
|
@ -146,9 +146,7 @@ const WarehouseSettings: React.FC<WarehouseSettingsProps> = ({
|
|||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={<FormattedMessage {...messages.warehouseSettingsTitle} />}
|
||||
/>
|
||||
<CardTitle title={<FormattedMessage {...sectionNames.shippingZones} />} />
|
||||
<CardContent>
|
||||
{renderCollection(
|
||||
zones,
|
||||
|
@ -172,8 +170,10 @@ const WarehouseSettings: React.FC<WarehouseSettingsProps> = ({
|
|||
)}
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardTitle
|
||||
title={<FormattedMessage {...messages.warehouseSettingsStockTitle} />}
|
||||
/>
|
||||
<CardContent>
|
||||
<CardSpacer />
|
||||
<RadioGroupField
|
||||
disabled={disabled}
|
||||
choices={isPrivateChoices}
|
||||
|
@ -184,12 +184,15 @@ const WarehouseSettings: React.FC<WarehouseSettingsProps> = ({
|
|||
/>
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardContent>
|
||||
<Typography color="textSecondary" variant="h6">
|
||||
<CardTitle
|
||||
title={
|
||||
<>
|
||||
<FormattedMessage {...messages.warehouseSettingsPickupTitle} />
|
||||
<PreviewPill className={classes.preview} />
|
||||
</Typography>
|
||||
<CardSpacer />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<CardContent>
|
||||
<RadioGroupField
|
||||
disabled={disabled}
|
||||
choices={
|
||||
|
|
|
@ -54,10 +54,10 @@ const messages = defineMessages({
|
|||
"If selected customer will be able to choose this warehouse as pickup point. Ordered products can be shipped here from a different warehouse",
|
||||
description: "WarehouseSettings all warehouses description",
|
||||
},
|
||||
warehouseSettingsTitle: {
|
||||
id: "//iaFx",
|
||||
defaultMessage: "Settings",
|
||||
description: "WarehouseSettings title",
|
||||
warehouseSettingsStockTitle: {
|
||||
id: "9kzIFd",
|
||||
defaultMessage: "Stock settings",
|
||||
description: "WarehouseSettings stock title",
|
||||
},
|
||||
warehouseSettingsPickupTitle: {
|
||||
id: "MIC9W7",
|
||||
|
|
|
@ -23,8 +23,11 @@ const WarehouseCreate: React.FC = () => {
|
|||
const shop = useShop();
|
||||
const [createWarehouse, createWarehouseOpts] = useWarehouseCreateMutation({
|
||||
onCompleted: data => {
|
||||
if (data.createWarehouse.errors.length === 0) {
|
||||
navigate(warehouseUrl(data.createWarehouse.warehouse.id));
|
||||
if (data?.createWarehouse?.errors.length === 0) {
|
||||
const warehouse = data?.createWarehouse?.warehouse;
|
||||
if (warehouse?.id) {
|
||||
navigate(warehouseUrl(warehouse.id));
|
||||
}
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage(commonMessages.savedChanges),
|
||||
|
@ -68,7 +71,7 @@ const WarehouseCreate: React.FC = () => {
|
|||
<WarehouseCreatePage
|
||||
countries={shop?.countries || []}
|
||||
disabled={createWarehouseOpts.loading}
|
||||
errors={createWarehouseOpts.data?.createWarehouse.errors || []}
|
||||
errors={createWarehouseOpts.data?.createWarehouse?.errors || []}
|
||||
saveButtonBarState={createWarehouseTransitionState}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
|
|
|
@ -45,7 +45,7 @@ const WarehouseDetails: React.FC<WarehouseDetailsProps> = ({ id, params }) => {
|
|||
});
|
||||
const [updateWarehouse, updateWarehouseOpts] = useWarehouseUpdateMutation({
|
||||
onCompleted: data => {
|
||||
if (data.updateWarehouse.errors.length === 0) {
|
||||
if (data?.updateWarehouse?.errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage(commonMessages.savedChanges),
|
||||
|
@ -57,7 +57,7 @@ const WarehouseDetails: React.FC<WarehouseDetailsProps> = ({ id, params }) => {
|
|||
|
||||
const [deleteWarehouse, deleteWarehouseOpts] = useWarehouseDeleteMutation({
|
||||
onCompleted: data => {
|
||||
if (data.deleteWarehouse.errors.length === 0) {
|
||||
if (data?.deleteWarehouse?.errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage(commonMessages.savedChanges),
|
||||
|
@ -105,11 +105,11 @@ const WarehouseDetails: React.FC<WarehouseDetailsProps> = ({ id, params }) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle title={data?.warehouse?.name} />
|
||||
<WindowTitle title={getStringOrPlaceholder(data?.warehouse?.name)} />
|
||||
<WarehouseDetailsPage
|
||||
countries={shop?.countries || []}
|
||||
disabled={loading || updateWarehouseOpts.loading}
|
||||
errors={updateWarehouseOpts.data?.updateWarehouse.errors || []}
|
||||
errors={updateWarehouseOpts.data?.updateWarehouse?.errors || []}
|
||||
saveButtonBarState={updateWarehouseTransitionState}
|
||||
warehouse={data?.warehouse}
|
||||
onDelete={() => openModal("delete")}
|
||||
|
|
|
@ -78,7 +78,7 @@ const WarehouseList: React.FC<WarehouseListProps> = ({ params }) => {
|
|||
});
|
||||
const [deleteWarehouse, deleteWarehouseOpts] = useWarehouseDeleteMutation({
|
||||
onCompleted: data => {
|
||||
if (data.deleteWarehouse.errors.length === 0) {
|
||||
if (data?.deleteWarehouse?.errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage(commonMessages.savedChanges),
|
||||
|
@ -96,7 +96,7 @@ const WarehouseList: React.FC<WarehouseListProps> = ({ params }) => {
|
|||
|
||||
const [, resetFilters, handleSearchChange] = createFilterHandlers({
|
||||
createUrl: warehouseListUrl,
|
||||
getFilterQueryParam: () => undefined,
|
||||
getFilterQueryParam: async () => undefined,
|
||||
navigate,
|
||||
params,
|
||||
});
|
||||
|
@ -125,7 +125,7 @@ const WarehouseList: React.FC<WarehouseListProps> = ({ params }) => {
|
|||
};
|
||||
|
||||
const paginationValues = usePaginator({
|
||||
pageInfo: maybe(() => data.warehouses.pageInfo),
|
||||
pageInfo: data?.warehouses?.pageInfo,
|
||||
paginationState,
|
||||
queryString: params,
|
||||
});
|
||||
|
@ -155,19 +155,24 @@ const WarehouseList: React.FC<WarehouseListProps> = ({ params }) => {
|
|||
onUpdateListSettings={updateListSettings}
|
||||
sort={getSortParams(params)}
|
||||
/>
|
||||
{!!params.id && (
|
||||
<WarehouseDeleteDialog
|
||||
confirmButtonState={deleteTransitionState}
|
||||
name={mapEdgesToItems(data?.warehouses)?.find(getById(params.id))?.name}
|
||||
name={
|
||||
mapEdgesToItems(data?.warehouses)?.find(getById(params.id))?.name ??
|
||||
""
|
||||
}
|
||||
open={params.action === "delete"}
|
||||
onClose={closeModal}
|
||||
onConfirm={() =>
|
||||
deleteWarehouse({
|
||||
variables: {
|
||||
id: params.id,
|
||||
id: params.id!,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<SaveFilterTabDialog
|
||||
open={params.action === "save-search"}
|
||||
confirmButtonState="default"
|
||||
|
|
|
@ -9,7 +9,7 @@ export function getSortQueryField(
|
|||
case WarehouseListUrlSortField.name:
|
||||
return WarehouseSortField.NAME;
|
||||
default:
|
||||
return undefined;
|
||||
throw new Error("Invalid sort field");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue