Skip to content

Commit

Permalink
feat(condo): DOMA-8429 added user import help request (#4513)
Browse files Browse the repository at this point in the history
* feat(condo): DOMA-8429 added help modal

* feat(condo): DOMA-8429 added UserHelpRequest schema

* feat(condo): DOMA-8429 added UserHelpRequestFile schema

* feat(condo): DOMA-8429 update help modal

* feat(condo): DOMA-8429 added translations

* fix(condo): DOMA-8429 rebase, fixes after review

* feat(condo): DOMA-8429 add links to instructions

* fix(condo): DOMA-8429 disable submit button when no files

* fix(condo): DOMA-8429 add timeout to migration

* chore(condo): DOMA-8429 recreate migrations
  • Loading branch information
nomerdvadcatpyat committed Apr 5, 2024
1 parent 627ae99 commit ff046be
Show file tree
Hide file tree
Showing 29 changed files with 3,720 additions and 107 deletions.
2 changes: 1 addition & 1 deletion .helm
Submodule .helm updated from 97f8ed to a03249
58 changes: 40 additions & 18 deletions apps/condo/domains/common/components/Import/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@ import isFunction from 'lodash/isFunction'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import XLSX from 'xlsx'

import { Download, FileDown } from '@open-condo/icons'
import { useFeatureFlags } from '@open-condo/featureflags/FeatureFlagsContext'
import { Download, FileDown, QuestionCircle } from '@open-condo/icons'
import { useIntl } from '@open-condo/next/intl'
import { Alert, Button, Card, CardBodyProps, CardHeaderProps, Modal, Typography } from '@open-condo/ui'
import { colors } from '@open-condo/ui/dist/colors'

import { DataImporter } from '@condo/domains/common/components/DataImporter'
import { FocusContainer } from '@condo/domains/common/components/FocusContainer'
import { LinkWithIcon } from '@condo/domains/common/components/LinkWithIcon'
import { useTracking, TrackingEventType } from '@condo/domains/common/components/TrackingContext'
import { IMPORT_HELP_MODAL } from '@condo/domains/common/constants/featureflags'
import { useImporter } from '@condo/domains/common/hooks/useImporter'
import { useImportHelpModal } from '@condo/domains/common/hooks/useImportHelpModal'
import {
Columns,
RowNormalizer,
Expand All @@ -24,9 +30,6 @@ import {
MutationErrorsToMessagesType,
} from '@condo/domains/common/utils/importer'

import { DataImporter } from '../DataImporter'
import { FocusContainer } from '../FocusContainer'


export interface IImportWrapperProps {
accessCheck: boolean
Expand Down Expand Up @@ -139,6 +142,7 @@ const ImportWrapper: React.FC<IImportWrapperProps> = (props) => {
const ErrorModalTitle = intl.formatMessage({ id: 'import.errorModal.title' }, { plural: ImportPluralMessage })
const SuccessModalButtonLabel = intl.formatMessage({ id: 'import.successModal.buttonLabel' })
const ErrorsMessage = intl.formatMessage({ id: 'import.Errors' })
const NeedHelpMessage = intl.formatMessage({ id: 'import.uploadModal.needHelp' })

const exampleTemplateLink = useMemo(() => `/import/${domainName}/${intl.locale}/${domainName}-import-example.xlsx`, [domainName, intl.locale])
const exampleImageSrc = useMemo(() => `/import/${domainName}/${intl.locale}/${domainName}-import-example.webp`, [domainName, intl.locale])
Expand All @@ -153,6 +157,10 @@ const ImportWrapper: React.FC<IImportWrapperProps> = (props) => {
}
}, [activeModal])

const { Modal: ImportHelpModal, openImportHelpModal } = useImportHelpModal({ domainName })
const { useFlag } = useFeatureFlags()
const isImportHelpModalEnabled = useFlag(IMPORT_HELP_MODAL)

const totalRowsRef = useRef(0)
const setTotalRowsRef = (value: number) => {
totalRowsRef.current = value
Expand Down Expand Up @@ -268,11 +276,26 @@ const ImportWrapper: React.FC<IImportWrapperProps> = (props) => {
onCancel={closeModal}
open={activeModal === 'example'}
footer={
<DataImporter onUpload={handleUpload}>
<Button type='primary'>
{ChooseFileForUploadLabel}
</Button>
</DataImporter>
<Space size={16} direction='horizontal'>
{
isImportHelpModalEnabled && (
<LinkWithIcon
title={NeedHelpMessage}
size='medium'
PostfixIcon={QuestionCircle}
onClick={() => {
setActiveModal(null)
openImportHelpModal()
}}
/>
)
}
<DataImporter onUpload={handleUpload}>
<Button type='primary'>
{ChooseFileForUploadLabel}
</Button>
</DataImporter>
</Space>
}
>
<Space direction='vertical' size={24}>
Expand All @@ -288,15 +311,13 @@ const ImportWrapper: React.FC<IImportWrapperProps> = (props) => {
message={RequiredFieldsTitle}
description={ImportRequiredFieldsMessage}
/>
<Space size={8} align='center'>
<Download size='small' />
<Typography.Link
href={exampleTemplateLink}
target='_blank'
>
{ExampleLinkMessage}
</Typography.Link>
</Space>
<LinkWithIcon
title={ExampleLinkMessage}
size='medium'
PrefixIcon={Download}
href={exampleTemplateLink}
target='_blank'
/>
</Space>
</Modal>
<Modal
Expand Down Expand Up @@ -387,6 +408,7 @@ const ImportWrapper: React.FC<IImportWrapperProps> = (props) => {
<img alt='success-image' src='/successDino.webp' />
</StyledFocusContainer>
</SuccessModal>
<ImportHelpModal />
</>
)
)
Expand Down
155 changes: 116 additions & 39 deletions apps/condo/domains/common/components/MultipleFileUpload.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { DeleteFilled, EditFilled } from '@ant-design/icons'
import { File } from '@app/condo/schema'
import styled from '@emotion/styled'
import { Upload, UploadProps } from 'antd'
import { UploadFile, UploadFileStatus } from 'antd/lib/upload/interface'
import get from 'lodash/get'
import isEmpty from 'lodash/isEmpty'
import isFunction from 'lodash/isFunction'
import { UploadRequestOption } from 'rc-upload/lib/interface'
import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'

import { Paperclip, Trash } from '@open-condo/icons'
import { useIntl } from '@open-condo/next/intl'
import { Button } from '@open-condo/ui'
import { colors } from '@open-condo/ui/dist/colors'

import { Button } from '@condo/domains/common/components/Button'
import { MAX_UPLOAD_FILE_SIZE } from '@condo/domains/common/constants/uploads'

import { useTracking, TrackingEventType } from './TrackingContext'
Expand Down Expand Up @@ -87,14 +90,17 @@ interface IUploadComponentProps {
initialFileList: DBFile[]
UploadButton?: React.ReactElement
uploadProps?: UploadProps
onFileListChange?: (fileList) => void
}

interface IMultipleFileUploadHookArgs {
Model: Module
relationField: string
initialFileList?: DBFile[]
initialCreateValues?: Record<string, unknown>,
dependenciesForRerenderUploadComponent?: Array<unknown>
}

interface IMultipleFileUploadHookResult {
UploadComponent: React.FC<IUploadComponentProps>,
syncModifiedFiles: (id: string) => Promise<void>
Expand Down Expand Up @@ -139,7 +145,10 @@ export const useMultipleFileUploadHook = ({
dispatch({ type: 'reset' })
}, [])

const initialValues = useMemo(() => ({ ...initialCreateValues, [relationField]: null }), [initialCreateValues, relationField])
const initialValues = useMemo(() => ({
...initialCreateValues,
[relationField]: null,
}), [initialCreateValues, relationField])

const UploadComponent: React.FC<IUploadComponentProps> = useMemo(() => {
const UploadWrapper = (props) => (
Expand All @@ -148,7 +157,7 @@ export const useMultipleFileUploadHook = ({
fileList={initialFileList}
initialCreateValues={initialValues}
Model={Model}
onFilesChange={dispatch}
updateFileList={dispatch}
{...props}
/>
)
Expand All @@ -163,14 +172,76 @@ export const useMultipleFileUploadHook = ({
}
}

const StyledUpload = styled(Upload)`
display: flex;
flex-flow: column-reverse;
.ant-upload-list-item:hover .ant-upload-list-item-info {
background-color: inherit;
}
.ant-upload-list-item-info {
& .ant-upload-text-icon {
transform: rotate(180deg);
span {
color: black;
font-size: 16px;
}
}
}
.ant-upload-list-text-container {
& .ant-upload-list-item-name {
font-size: 16px;
width: auto;
flex-grow: 0;
}
&:last-child {
margin-bottom: 24px;
width: auto;
}
}
.ant-upload-list-item-card-actions {
display: flex;
align-items: center;
}
.ant-upload-list-item:not(.ant-upload-list-item-error) {
& .ant-upload-list-item-name {
text-decoration: underline;
color: ${colors.black};
&:hover {
color: ${colors.green[5]};
text-decoration-color: ${colors.green[5]};
}
}
}
.ant-upload-list-item-error {
& .ant-upload-list-item-name {
text-decoration: none;
color: ${colors.red[5]};
}
& .ant-upload-text-icon span {
color: ${colors.red[5]};
}
}
`

interface IMultipleFileUploadProps {
setFilesCount: React.Dispatch<React.SetStateAction<number>>
fileList: DBFile[]
initialCreateValues: Record<string, unknown>
Model: Module
onFilesChange: React.Dispatch<{ type: string, payload: DBFile }>
updateFileList: React.Dispatch<{ type: string, payload: DBFile }>
UploadButton?: React.FC
uploadProps?: UploadProps
onFileListChange?: (fileList) => void
}

const MultipleFileUpload: React.FC<IMultipleFileUploadProps> = (props) => {
Expand All @@ -184,9 +255,10 @@ const MultipleFileUpload: React.FC<IMultipleFileUploadProps> = (props) => {
fileList,
initialCreateValues,
Model,
onFilesChange,
updateFileList,
UploadButton,
uploadProps = {},
onFileListChange,
} = props

const [listFiles, setListFiles] = useState<UploadListFile[]>([])
Expand Down Expand Up @@ -217,27 +289,35 @@ const MultipleFileUpload: React.FC<IMultipleFileUploadProps> = (props) => {
return file
})
setListFiles(fileList)

if (isFunction(onFileListChange)) {
onFileListChange(fileList)
}
},
showUploadList: {
showRemoveIcon: true,
removeIcon: (file) => {
const removeIcon = (
<DeleteFilled onClick={() => {
const { id, uid } = file
const fileError = get(file, 'error')
if (!fileError) {
setFilesCount(filesCount => filesCount - 1)
}

if (!id) {
// remove file that failed to upload from list
setListFiles([...listFiles].filter(file => file.uid !== uid))
onFilesChange({ type: 'delete', payload: file })
return
}
setListFiles([...listFiles].filter(file => file.id !== id))
onFilesChange({ type: 'delete', payload: file })
}} />
<Trash
color={colors.red[5]}
size='small'
onClick={() => {
const { id, uid } = file
const fileError = get(file, 'error')
if (!fileError) {
setFilesCount(filesCount => filesCount - 1)
}

if (!id) {
// remove file that failed to upload from list
setListFiles([...listFiles].filter(file => file.uid !== uid))
updateFileList({ type: 'delete', payload: file })
return
}
setListFiles([...listFiles].filter(file => file.id !== id))
updateFileList({ type: 'delete', payload: file })
}}
/>
)
return removeIcon
},
Expand All @@ -250,10 +330,10 @@ const MultipleFileUpload: React.FC<IMultipleFileUploadProps> = (props) => {
onError(error)
return
}
return createAction({ ...initialCreateValues, file }).then( dbFile => {
return createAction({ ...initialCreateValues, file }).then(dbFile => {
const [uploadFile] = convertFilesToUploadFormat([dbFile])
onSuccess(uploadFile, null)
onFilesChange({ type: 'add', payload: dbFile })
updateFileList({ type: 'add', payload: dbFile })
setFilesCount(filesCount => filesCount + 1)

logEvent({
Expand All @@ -270,20 +350,17 @@ const MultipleFileUpload: React.FC<IMultipleFileUploadProps> = (props) => {
}

return (
<div className='upload-control-wrapper'>
<Upload { ...options }>
{
UploadButton || (
<Button
type='sberDefaultGradient'
secondary
icon={<EditFilled />}
>
{AddFileLabel}
</Button>
)
}
</Upload>
</div>
<StyledUpload {...options}>
{
UploadButton || (
<Button
type='secondary'
icon={<Paperclip size='medium'/>}
>
{AddFileLabel}
</Button>
)
}
</StyledUpload>
)
}
3 changes: 2 additions & 1 deletion apps/condo/domains/common/components/containers/FormList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
import { FormProps } from 'antd/lib/form/Form'
import { ArgsProps } from 'antd/lib/notification'
import { isUndefined, throttle } from 'lodash'
import isFunction from 'lodash/isFunction'
import omitBy from 'lodash/omitBy'
import React, { useCallback, useState, useRef, CSSProperties, ComponentProps } from 'react'
import { Options } from 'scroll-into-view-if-needed'
Expand Down Expand Up @@ -408,7 +409,7 @@ const FormWithAction: React.FC<IFormWithAction> = (props) => {
{...formProps}
>
<Form.Item hidden={isNonFieldErrorHidden} className='ant-non-field-error' name={NON_FIELD_ERROR_NAME}><Input /></Form.Item>
{children({ handleSave, isLoading, handleSubmit: _handleSubmit, form })}
{isFunction(children) ? children({ handleSave, isLoading, handleSubmit: _handleSubmit, form }) : children}
</Form>
)
}
Expand Down
Loading

0 comments on commit ff046be

Please sign in to comment.