Fix/csv import (#1397)
* feat: add ability to enable or disable header selection * feat: limit to max of 200 records for now * fix: bigger modal * feat: add missing standard fields for company * fix: person fields * feat: add hotkeys on dialog * feat: mobile device * fix: company import error * fix: csv import crash * fix: use scoped hotkey
This commit is contained in:
@ -1,8 +1,12 @@
|
||||
import { useCallback } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { Button } from '@/ui/button/components/Button';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
|
||||
import { DialogHotkeyScope } from '../types/DialogHotkeyScope';
|
||||
|
||||
const StyledDialogOverlay = styled(motion.div)`
|
||||
align-items: center;
|
||||
@ -52,7 +56,12 @@ const StyledDialogButton = styled(Button)`
|
||||
export type DialogButtonOptions = Omit<
|
||||
React.ComponentProps<typeof Button>,
|
||||
'fullWidth'
|
||||
>;
|
||||
> & {
|
||||
onClick?: (
|
||||
event: React.MouseEvent<HTMLButtonElement, MouseEvent> | KeyboardEvent,
|
||||
) => void;
|
||||
role?: 'confirm';
|
||||
};
|
||||
|
||||
export type DialogProps = React.ComponentPropsWithoutRef<typeof motion.div> & {
|
||||
title?: string;
|
||||
@ -86,6 +95,32 @@ export function Dialog({
|
||||
closed: { y: '50vh' },
|
||||
};
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.Enter,
|
||||
(event: KeyboardEvent) => {
|
||||
const confirmButton = buttons.find((button) => button.role === 'confirm');
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (confirmButton) {
|
||||
confirmButton?.onClick?.(event);
|
||||
closeSnackbar();
|
||||
}
|
||||
},
|
||||
DialogHotkeyScope.Dialog,
|
||||
[],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.Escape,
|
||||
(event: KeyboardEvent) => {
|
||||
event.preventDefault();
|
||||
closeSnackbar();
|
||||
},
|
||||
DialogHotkeyScope.Dialog,
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledDialogOverlay
|
||||
variants={dialogVariants}
|
||||
|
||||
@ -1,20 +1,38 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
|
||||
import { dialogInternalState } from '../states/dialogState';
|
||||
import { DialogHotkeyScope } from '../types/DialogHotkeyScope';
|
||||
|
||||
import { Dialog } from './Dialog';
|
||||
|
||||
export function DialogProvider({ children }: React.PropsWithChildren) {
|
||||
const [dialogState, setDialogState] = useRecoilState(dialogInternalState);
|
||||
|
||||
const {
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
// Handle dialog close event
|
||||
const handleDialogClose = (id: string) => {
|
||||
setDialogState((prevState) => ({
|
||||
...prevState,
|
||||
queue: prevState.queue.filter((snackBar) => snackBar.id !== id),
|
||||
}));
|
||||
goBackToPreviousHotkeyScope();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogState.queue.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
setHotkeyScopeAndMemorizePreviousScope(DialogHotkeyScope.Dialog);
|
||||
}, [dialogState.queue, setHotkeyScopeAndMemorizePreviousScope]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
|
||||
3
front/src/modules/ui/dialog/types/DialogHotkeyScope.ts
Normal file
3
front/src/modules/ui/dialog/types/DialogHotkeyScope.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export enum DialogHotkeyScope {
|
||||
Dialog = 'dialog',
|
||||
}
|
||||
@ -3,11 +3,16 @@ import styled from '@emotion/styled';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
import { AnimatedCheckmark } from '@/ui/checkmark/components/AnimatedCheckmark';
|
||||
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
|
||||
const StyledContainer = styled.div<{ isLast: boolean }>`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-grow: ${({ isLast }) => (isLast ? '0' : '1')};
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
flex-grow: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledStepCircle = styled(motion.div)`
|
||||
@ -64,6 +69,7 @@ export const Step = ({
|
||||
children,
|
||||
}: StepProps) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const variantsCircle = {
|
||||
active: {
|
||||
@ -104,7 +110,7 @@ export const Step = ({
|
||||
{!isActive && <StyledStepIndex>{index + 1}</StyledStepIndex>}
|
||||
</StyledStepCircle>
|
||||
<StyledStepLabel isActive={isActive}>{label}</StyledStepLabel>
|
||||
{!isLast && (
|
||||
{!isLast && !isMobile && (
|
||||
<StyledStepLine
|
||||
variants={variantsLine}
|
||||
animate={isActive ? 'active' : 'inactive'}
|
||||
|
||||
@ -1,12 +1,19 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
|
||||
import { Step, StepProps } from './Step';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: space-between;
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
`;
|
||||
|
||||
export type StepsProps = React.PropsWithChildren &
|
||||
@ -15,6 +22,8 @@ export type StepsProps = React.PropsWithChildren &
|
||||
};
|
||||
|
||||
export const StepBar = ({ children, activeStep, ...restProps }: StepsProps) => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<StyledContainer {...restProps}>
|
||||
{React.Children.map(children, (child, index) => {
|
||||
@ -29,6 +38,14 @@ export const StepBar = ({ children, activeStep, ...restProps }: StepsProps) => {
|
||||
return child;
|
||||
}
|
||||
|
||||
// We should only render the active step, and if activeStep is -1, we should only render the first step only when it's mobile device
|
||||
if (
|
||||
isMobile &&
|
||||
(activeStep === -1 ? index !== 0 : index !== activeStep)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return React.cloneElement<StepProps>(child as any, {
|
||||
index,
|
||||
isActive: index <= activeStep,
|
||||
|
||||
Reference in New Issue
Block a user