
* Tax configuration - implement channels view (#2048) * Add channels view * Fix channels view import * Remove legacy stories references * Fix link in configuration * Update snapshots * Remove sample checkboxes props * Disable hover in country exceptions * Update snapshots * Extract country exception rows to seperate component * Extract components to seperate files * Remove duplicated section name * Remove backlink * Add translations to section names * Extract messages * Add ListItemLink component * Replace navigator with link in TaxChannelsMenu * Fix horizontal scroll in TaxChannelsMenu * Change codegen to build from custom schema * Build types * Update fragments * Add fixtures * Change any to proper types * Add story for tax channels page * Replace MUI Skeleton with Saleor Skeleton * Change clsx import to classnames * Fix checkboxes shadows in settings card * Update IDs in fixtures * Fix offset in TaxChannelsMenu * Update snapshots * Remove any from TaxSettings * Fix todos * Change relative marginLeft to before pseudoelement * Extract styles to seperate files * Change folder structure * Extract redirect logic to custom hook * Update snapshots * Fix comment * Add early return in channels view * Tax configuration - implement countries view (#2053) * Add channels view * Remove sample checkboxes props * Disable hover in country exceptions * Extract country exception rows to seperate component * Extract components to seperate files * Remove duplicated section name * Remove backlink * Add translations to section names * Replace navigator with link in TaxChannelsMenu * Fix horizontal scroll in TaxChannelsMenu * Change any to proper types * Add story for tax channels page * Replace MUI Skeleton with Saleor Skeleton * Change clsx import to classnames * Fix checkboxes shadows in settings card * Update IDs in fixtures * Fix offset in TaxChannelsMenu * Remove any from TaxSettings * Add countries list view * Add TaxCountryMenu component * Add CountryList page * Change channels menu rows height * Change countries menu rows height * Add TaxInput component * Add tax classes rates to countries page * Fix search input padding * Add minmax to TaxInput * Add searching through tax class rates * Extract messages * Add better handlers * Add fullWidth to TaxInput * Specify type for TaxInputs * Remove spinboxes on firefox * Remove custom spinboxes * Remove maxHeight from menu rows * Post-rebase fix * Change setter to formchagne * Add TaxConfiguration fragment * Add isDefault field to taxClass * Add fixtures * Shape data * Replace useEffect with useTaxUrlRedirect * Fix country names in menu * Add country page story * Add early return in countries view * Unify loading states between channels and countries pages * Handle special chars and case insensitiveness in local search * Replace navigate function with ListItemLink * Move styles to seperate file * Move styles to seperate file * Migrate to strict null checks * Remove unnecessary optional chaining * Change overflow scroll to Y only * Add useMemo on finding selected country * Add useMemo on local search * Translate labels in page tabs * Change url from /taxes/classes to /taxes/tax-classes * Remove capitalization from strings * Extract messages * Bump macaw to 0.6.2 * Update snapshots * Add spinboxes explanation comment * Handle empty state * Add tax classes view (#2093) * Add TaxClass fragment * Build types * Add tax classes to fixtures * Add tax classes view * wip Add tax classes page * Add tax classes menu * Add TaxRate fragment * Extract logic * Handle loading state & add story * Extract messages * Update snapshots * Change schema building from schema back to introspection * Update schema * Update fragments * Build types * Update fixtures * Reshape data * Move styles to seperate file * Use getById * Add explicit undefined * Comment out unfinished modal stories * Update snapshots * Taxes - add API calls in channels view (#2106) * Build types * Add TaxRate fragment * Update snapshots * Add taxConfigurationList query * Add taxCountriesList query * Add TaxClassesList query * Rename TaxConfigurationsList query * Handle empty state * Fix types post-rebase * Add form to TaxChannelsPage * wip Add dialog for handling country exceptions * wip Fix dialog url * wip Add update exceptions handlers * Add dialog story * Fix type errors * Add mutation support * Fix types in story * Add transition state to submit button * Add notifier * Extract messages * Remove unused import * Add backlink in savebar * Update snapshots * Fix link in navigation * Update snapshots * Remove message from tax config error fragment * Add hook description * Use useStateFromProps * Remove error handling * Improve url & path function names * Use theme.spacing in TaxCountryDialog styles * Remove redundant key modification * Revert "Use useStateFromProps" This reverts commit d3c68b04701cf935e917d7baa3ed1361ca3446d5. * Move initial map to parent & add open dependency to countries state * Use useModalDialogOpen * Fix state update * Remove scrolls & add ellipsis in side menu * Center checkboxes * Update snapshots * Add fake div for list alignment * Trigger deployment * Close modal on submit * Remove divider on last ListItem * Align add country button * Wrap grid child in div to avoid card stretching * Update snapshots * Trigger changes in add/delete exceptions * Trigger change on expcetion checkboxes * Add trailing commas * Connect countries view to API (#2178) * Add empty states * Update countries view urls * Remove unused import * Add country modal to countries view * Update schema * Implement country view mutations & error fragments * Implement tax class update mutation * Add sidebar temporary state for new configs * Remove unused imports * Wrap in form * Add savebar & fix search * Update schema * Add form wrapper * Fix types * Extract messages * Bump macaw * Update snapshots * Fix comma dangles * Update snapshots * Notify about mutation success * Add logic for mixing current and new rates * Workaround for sending null rates * Fix filling form with correct data after submitting * Handle deleting configuration * Fix selected banner * Remove leftover comment * Add handler for country configuration delete * Trigger deployment * Clean up useEffects causing infinite render loops * Sort countries from api by name * Fix card bottom padding * Remove bottom divider & fix padding * Remove scroll wrapper in side menu * Update snapshots * Remove scroll wrapper from tax classes menu * Update snapshots * Refresh form to initial onSubmit * Revert "Refresh form to initial onSubmit" This reverts commit 42414237d35086da63f4aa088c8072411429b1d8. * Allow only 3 decimal characters in tax inputs * Update snapshots * Update schema * Update types * Change logic from default tax class to null class * Fix sorting * Send empty country rates as nulls in mutation * Extract messages * Update lockfile * Update schema * Drop default tax classes * Update snapshots * Post-rebase fixes * Connect tax classes view to API (#2334) * Add mutations * Handle empty state * Wrap page in form * Update stories * Build types * Handle tax class delete * Handle update tax class * Update stories * Handle tax class change name * Add mutation state to savebar * Handle creating new tax classes * Extract messages * Specify type * Update stories * Sort rates * Fix skeleton rendering * Remove placeholders * Fix skeleton rendering on country list * Update snapshots * Change initial pagination to 100 * Disallow creating multiple new tax classes * Disallow creating multiple country configurations * Fix messages * Autofocus on new tax class name * Add country name to header * Temporarily comment out broken code in tax channels * Update snapshots * Update snapshots post-rebase * Add tax strategies & assigning tax classes (#2369) * Update fragments * Add optional merging in useForm * Handle tax strategies * Update snapshots * Update fixtures * Extract messages * Remove unused shop query fields * Fix breaking bug when fetchMore is used in non-searchable SingleAutocompleteSelectFields * Migrate product types to tax classes * Add tax classes to shipping methods * Use encapsulated logic in product types * Fix product type stories * Fix shipping fixtures * Fix product type type mismatch * Fix shipping stories * Fix product type fixtures * Fix mismatching types * Extract messages * Update snapshots * Update snapshots * Fix comment * Drop deprecated graphql fields * Replace tax types with tax classes in product create view * Replace tax types with tax classes in product update view * Fix tests, stories, fixtures * Extract messages * Update snapshots * Move status messages to commonStatusMessages * Handle empty array case in tax class change handler * Reuse messages * Simple taxes bugfixes (#2395) * Fix tax channels menu - dense layout * Change view names to fit convention * Fix per country exceptions in tax channels view * Fix skeleton rendering on tax countries card title * Filter out existing countries from modal * Update snapshots * Fix deleting country configuration * Disallow negative values in tax inputs * Handle empty tax classes view * Allow empty options in shipping & product types views tax class assignment field * Modify undefined rates in tax classes view * Update macaw-ui * Fix UI on channels view * Fix UI on countries view * Fix UI on countries view * Align tax class rate label to the right * Updaste snapshots * Extract messages * Fix adding rates on new tax class * Fix key errors * Update schema * Build types * Allow empty rates in taxClassUpdate mutation * Extract tax channels change country function as a handler * Deprecate useStateFromProps * Change useStateFromProps to useStateUpdate * Fix dividers * Delete delete icon on new tax classes * Update snapshots * Update lockfile * Update macaw to 0.6.6 * Update snapshots * Specify type of input in country change handler * Extract autofocus logic to custom hook * Replace alternative with switch statement * Extract country exclusion logic from JSX * Update lockfile * Update lockfile * Trigger deployment * Fix invisible select markers * Fix linter issue * Fix crashing product details page * Fix e2e error * Update snapshots * Allow view taxes with any staff permissions (#2510) * Update after rebase Co-authored-by: Dawid <tarasiukdawid@gmail.com>
342 lines
9.4 KiB
TypeScript
342 lines
9.4 KiB
TypeScript
import chevronDown from "@assets/images/ChevronDown.svg";
|
|
import {
|
|
CircularProgress,
|
|
MenuItem,
|
|
Paper,
|
|
Typography,
|
|
} from "@material-ui/core";
|
|
import Add from "@material-ui/icons/Add";
|
|
import useElementScroll, {
|
|
isScrolledToBottom,
|
|
} from "@saleor/hooks/useElementScroll";
|
|
import { makeStyles } from "@saleor/macaw-ui";
|
|
import { FetchMoreProps } from "@saleor/types";
|
|
import classNames from "classnames";
|
|
import { GetItemPropsOptions } from "downshift";
|
|
import React, { ReactElement } from "react";
|
|
import SVG from "react-inlinesvg";
|
|
import { FormattedMessage } from "react-intl";
|
|
|
|
import Hr from "../Hr";
|
|
|
|
const menuItemHeight = 46;
|
|
const maxMenuItems = 5;
|
|
const offset = 24;
|
|
|
|
export type ChoiceValue = string;
|
|
export interface SingleAutocompleteChoiceType<
|
|
V extends ChoiceValue = ChoiceValue,
|
|
L = string
|
|
> {
|
|
label: L;
|
|
value: V;
|
|
}
|
|
export interface SingleAutocompleteActionType {
|
|
label: string;
|
|
onClick: () => void;
|
|
}
|
|
export interface SingleAutocompleteSelectFieldContentProps
|
|
extends Partial<FetchMoreProps> {
|
|
add?: SingleAutocompleteActionType;
|
|
choices: Array<SingleAutocompleteChoiceType<string, string | JSX.Element>>;
|
|
displayCustomValue: boolean;
|
|
emptyOption: boolean;
|
|
getItemProps: (options: GetItemPropsOptions<string>) => any;
|
|
highlightedIndex: number;
|
|
inputValue: string;
|
|
isCustomValueSelected: boolean;
|
|
selectedItem: any;
|
|
style?: React.CSSProperties;
|
|
}
|
|
|
|
const useStyles = makeStyles(
|
|
theme => ({
|
|
add: {
|
|
background: theme.palette.background.default,
|
|
border: `1px solid ${theme.palette.divider}`,
|
|
borderRadius: "100%",
|
|
height: 24,
|
|
marginRight: theme.spacing(),
|
|
width: 24,
|
|
},
|
|
arrowContainer: {
|
|
position: "relative",
|
|
},
|
|
arrowInnerContainer: {
|
|
alignItems: "center",
|
|
background:
|
|
theme.palette.type === "light"
|
|
? theme.palette.grey[50]
|
|
: theme.palette.grey[900],
|
|
bottom: 0,
|
|
color: theme.palette.grey[500],
|
|
display: "flex",
|
|
height: 30,
|
|
justifyContent: "center",
|
|
opacity: 1,
|
|
position: "absolute",
|
|
transition: theme.transitions.duration.short + "ms",
|
|
width: "100%",
|
|
},
|
|
content: {
|
|
maxHeight: `calc(${menuItemHeight * maxMenuItems}px + ${theme.spacing(
|
|
2,
|
|
)})`,
|
|
overflow: "scroll",
|
|
padding: 8,
|
|
},
|
|
hide: {
|
|
opacity: 0,
|
|
zIndex: -1,
|
|
},
|
|
hr: {
|
|
margin: theme.spacing(1, 0),
|
|
},
|
|
menuItem: {
|
|
height: "auto",
|
|
whiteSpace: "normal",
|
|
'&[aria-selected="true"]': {
|
|
backgroundColor: theme.palette.background.default,
|
|
},
|
|
},
|
|
progress: {},
|
|
progressContainer: {
|
|
display: "flex",
|
|
justifyContent: "center",
|
|
padding: theme.spacing(1, 0),
|
|
},
|
|
root: {
|
|
borderBottomLeftRadius: 8,
|
|
borderBottomRightRadius: 8,
|
|
margin: theme.spacing(1, 0),
|
|
overflow: "hidden",
|
|
zIndex: 22,
|
|
},
|
|
}),
|
|
{
|
|
name: "SingleAutocompleteSelectFieldContent",
|
|
},
|
|
);
|
|
|
|
function getChoiceIndex(
|
|
index: number,
|
|
emptyValue: boolean,
|
|
customValue: boolean,
|
|
add: boolean,
|
|
) {
|
|
let choiceIndex = index;
|
|
if (emptyValue) {
|
|
choiceIndex += 1;
|
|
}
|
|
if (customValue || add) {
|
|
choiceIndex += 2;
|
|
}
|
|
|
|
return choiceIndex;
|
|
}
|
|
|
|
const sliceSize = 20;
|
|
|
|
const SingleAutocompleteSelectFieldContent: React.FC<SingleAutocompleteSelectFieldContentProps> = props => {
|
|
const {
|
|
add,
|
|
choices,
|
|
displayCustomValue,
|
|
emptyOption,
|
|
getItemProps,
|
|
hasMore,
|
|
loading,
|
|
inputValue,
|
|
isCustomValueSelected,
|
|
selectedItem,
|
|
onFetchMore,
|
|
style,
|
|
} = props;
|
|
|
|
if (!!add && !!displayCustomValue) {
|
|
throw new Error("Add and custom value cannot be displayed simultaneously");
|
|
}
|
|
|
|
const classes = useStyles(props);
|
|
const anchor = React.useRef<HTMLDivElement>();
|
|
const scrollPosition = useElementScroll(anchor);
|
|
const [calledForMore, setCalledForMore] = React.useState(false);
|
|
const [slice, setSlice] = React.useState(onFetchMore ? 10000 : sliceSize);
|
|
const [initialized, setInitialized] = React.useState(false);
|
|
|
|
const scrolledToBottom = isScrolledToBottom(anchor, scrollPosition, offset);
|
|
|
|
React.useEffect(() => {
|
|
if (!calledForMore && onFetchMore && scrolledToBottom && hasMore) {
|
|
onFetchMore();
|
|
setCalledForMore(true);
|
|
} else if (scrolledToBottom && !onFetchMore) {
|
|
setSlice(slice => slice + sliceSize);
|
|
}
|
|
}, [scrolledToBottom]);
|
|
|
|
React.useEffect(() => {
|
|
if (!onFetchMore) {
|
|
setSlice(sliceSize);
|
|
}
|
|
if (anchor.current?.scrollTo && !initialized) {
|
|
anchor.current.scrollTo({
|
|
top: 0,
|
|
});
|
|
setInitialized(true);
|
|
}
|
|
}, [choices?.length]);
|
|
|
|
React.useEffect(() => {
|
|
setInitialized(false);
|
|
}, [inputValue]);
|
|
|
|
React.useEffect(() => {
|
|
if (calledForMore && !loading) {
|
|
setCalledForMore(false);
|
|
}
|
|
}, [loading]);
|
|
|
|
const emptyOptionProps = getItemProps({
|
|
item: "",
|
|
});
|
|
|
|
const choicesToDisplay = choices.slice(0, slice);
|
|
|
|
return (
|
|
<Paper
|
|
// click-outside-ignore is used by glide-datagrid
|
|
className={classNames("click-outside-ignore", classes.root)}
|
|
elevation={8}
|
|
style={style}
|
|
>
|
|
<div
|
|
className={classes.content}
|
|
ref={anchor}
|
|
data-test-id="autocomplete-dropdown"
|
|
>
|
|
{choices.length > 0 || displayCustomValue ? (
|
|
<>
|
|
{emptyOption && (
|
|
<MenuItem
|
|
className={classes.menuItem}
|
|
component="div"
|
|
data-test-id="single-autocomplete-select-option"
|
|
data-test-type="empty"
|
|
{...emptyOptionProps}
|
|
>
|
|
<Typography color="textSecondary">
|
|
<FormattedMessage id="450Fty" defaultMessage="None" />
|
|
</Typography>
|
|
</MenuItem>
|
|
)}
|
|
{add && (
|
|
<MenuItem
|
|
className={classes.menuItem}
|
|
component="div"
|
|
{...getItemProps({
|
|
item: inputValue,
|
|
})}
|
|
data-test-id="single-autocomplete-select-option-add"
|
|
data-test-type="add"
|
|
onClick={add.onClick}
|
|
>
|
|
<Add color="primary" className={classes.add} />
|
|
<Typography color="primary">{add.label}</Typography>
|
|
</MenuItem>
|
|
)}
|
|
{displayCustomValue && (
|
|
<MenuItem
|
|
className={classes.menuItem}
|
|
key={"customValue"}
|
|
selected={isCustomValueSelected}
|
|
component="div"
|
|
{...getItemProps({
|
|
item: inputValue,
|
|
})}
|
|
data-test-id="single-autocomplete-select-option"
|
|
data-test-type="custom"
|
|
>
|
|
<FormattedMessage
|
|
id="U2WgwW"
|
|
defaultMessage="Add new value: {value}"
|
|
description="add custom select input option"
|
|
values={{
|
|
value: inputValue,
|
|
}}
|
|
/>
|
|
</MenuItem>
|
|
)}
|
|
{choices.length > 0 && (!!add || displayCustomValue) && (
|
|
<Hr className={classes.hr} />
|
|
)}
|
|
{choicesToDisplay.map((suggestion, index) => {
|
|
const choiceIndex = getChoiceIndex(
|
|
index,
|
|
emptyOption,
|
|
displayCustomValue,
|
|
!!add,
|
|
);
|
|
const key = React.isValidElement(suggestion.label)
|
|
? `${index}${suggestion.value}${
|
|
((suggestion as unknown) as ReactElement).props
|
|
}`
|
|
: JSON.stringify(suggestion);
|
|
|
|
return (
|
|
<MenuItem
|
|
className={classes.menuItem}
|
|
key={key}
|
|
selected={selectedItem === suggestion.value}
|
|
component="div"
|
|
{...getItemProps({
|
|
index: choiceIndex,
|
|
item: suggestion.value,
|
|
})}
|
|
data-test-id="single-autocomplete-select-option"
|
|
data-test-value={suggestion.value}
|
|
data-test-type="option"
|
|
>
|
|
{suggestion.label}
|
|
</MenuItem>
|
|
);
|
|
})}
|
|
{hasMore && (
|
|
<>
|
|
<Hr className={classes.hr} />
|
|
<div className={classes.progressContainer}>
|
|
<CircularProgress className={classes.progress} size={24} />
|
|
</div>
|
|
</>
|
|
)}
|
|
</>
|
|
) : (
|
|
<MenuItem
|
|
disabled={true}
|
|
component="div"
|
|
data-test-id="single-autocomplete-select-no-options"
|
|
>
|
|
<FormattedMessage id="hX5PAb" defaultMessage="No results found" />
|
|
</MenuItem>
|
|
)}
|
|
</div>
|
|
{choices.length > maxMenuItems && (
|
|
<div className={classes.arrowContainer}>
|
|
<div
|
|
className={classNames(classes.arrowInnerContainer, {
|
|
// Needs to be explicitly compared to false because
|
|
// scrolledToBottom can be either true, false or undefined
|
|
[classes.hide]: scrolledToBottom !== false,
|
|
})}
|
|
>
|
|
<SVG src={chevronDown} />
|
|
</div>
|
|
</div>
|
|
)}
|
|
</Paper>
|
|
);
|
|
};
|
|
|
|
SingleAutocompleteSelectFieldContent.displayName =
|
|
"SingleAutocompleteSelectFieldContent";
|
|
export default SingleAutocompleteSelectFieldContent;
|