* Вывод ошибки о существовании рапорта исправлен

* Добавлены константы для mime-типов
* Исправлена ошибка отправки файлов на сервер
* Добавлен проп mimeType в UploadForm для контроля типов загружаемых файлов
* Добавлена проверка необходимости обновления страница после закрытия окна редактирования части ПБ
* Добавлен тип сообщения для notify
* Изменён формат даты в окне суточного рапорта
* Добавлен вывод сообщения исключения в редактировании суточного рапорта
This commit is contained in:
goodmice 2022-05-18 17:18:35 +05:00
parent 3912e9b308
commit 12a6d96a95
10 changed files with 95 additions and 35 deletions

View File

@ -1,15 +1,17 @@
import { memo, useCallback, useState } from 'react' import { memo, useCallback, useEffect, useMemo, useState } from 'react'
import { Upload, Button } from 'antd' import { Upload, Button } from 'antd'
import { UploadOutlined } from '@ant-design/icons' import { UploadOutlined } from '@ant-design/icons'
import { UploadFile } from 'antd/lib/upload/interface' import { UploadFile } from 'antd/lib/upload/interface'
import { RcFile } from 'antd/lib/upload'
import { upload } from './factory' import { notify, upload } from './factory'
import { ErrorFetch } from './ErrorFetch' import { ErrorFetch } from './ErrorFetch'
export type UploadFormProps = { export type UploadFormProps = {
url: string url: string
disabled?: boolean disabled?: boolean
accept?: string accept?: string
mimeTypes?: string | string[]
style?: CSSStyleSheet style?: CSSStyleSheet
formData: FormData formData: FormData
onUploadStart?: () => void onUploadStart?: () => void
@ -18,14 +20,25 @@ export type UploadFormProps = {
onUploadError?: (error: unknown) => void onUploadError?: (error: unknown) => void
} }
export const UploadForm = memo<UploadFormProps>(({ url, disabled, accept, style, formData, onUploadStart, onUploadSuccess, onUploadComplete, onUploadError }) => { export const UploadForm = memo<UploadFormProps>(({ url, disabled, style, formData, mimeTypes, onUploadStart, onUploadSuccess, onUploadComplete, onUploadError }) => {
const [fileList, setfileList] = useState<UploadFile<any>[]>([]) const [fileList, setfileList] = useState<UploadFile<any>[]>([])
const checkMimeTypes = useCallback((file: RcFile) => {
const isAccepted = !mimeTypes || mimeTypes.includes(file.type)
if (isAccepted) return false
notify(`"${file.name}" является файлом неподходящего типа`, 'error')
return Upload.LIST_IGNORE
}, [mimeTypes])
const accept = useMemo(() => Array.isArray(mimeTypes) ? mimeTypes.join(',') : mimeTypes, [mimeTypes])
useEffect(() => console.log(fileList), [fileList])
const handleFileSend = useCallback(async () => { const handleFileSend = useCallback(async () => {
onUploadStart?.() onUploadStart?.()
try { try {
const formDataLocal = new FormData() const formDataLocal = new FormData()
fileList.forEach((val) => formDataLocal.append('files', String(val.originFileObj))) fileList.forEach((val) => formDataLocal.append('files', val.originFileObj as Blob))
if(formData) if(formData)
for(const propName in formData) for(const propName in formData)
@ -58,6 +71,7 @@ export const UploadForm = memo<UploadFormProps>(({ url, disabled, accept, style,
disabled={disabled} disabled={disabled}
fileList={fileList} fileList={fileList}
onChange={(props) => setfileList(props.fileList)} onChange={(props) => setfileList(props.fileList)}
beforeUpload={checkMimeTypes}
> >
<Button disabled={disabled} icon={<UploadOutlined/>}>Загрузить файл</Button> <Button disabled={disabled} icon={<UploadOutlined/>}>Загрузить файл</Button>
</Upload> </Upload>

View File

@ -11,12 +11,14 @@ const notificationTypeDictionary = new Map([
['open' , { notifyInstance: notification.info , caption: '' }], ['open' , { notifyInstance: notification.info , caption: '' }],
]) ])
export type NotifyType = 'error' | 'warning' | 'info'
/** /**
* Вызов оповещений всплывающим окошком. * Вызов оповещений всплывающим окошком.
* @param body string или ReactNode * @param body string или ReactNode
* @param notifyType для параметра типа. Допустимые значение 'error', 'warning', 'info' * @param notifyType для параметра типа. Допустимые значение 'error', 'warning', 'info'
*/ */
export const notify = (body: ReactNode, notifyType: string = 'info', other?: any) => { export const notify = (body: ReactNode, notifyType: NotifyType = 'info', other?: any) => {
if (!body) return if (!body) return
const instance = notificationTypeDictionary.get(notifyType) ?? const instance = notificationTypeDictionary.get(notifyType) ??

View File

@ -31,7 +31,7 @@ const columns = [
makeColumn('Компания', 'company', { render: (_, record) => <CompanyView company={record?.author?.company}/> }) makeColumn('Компания', 'company', { render: (_, record) => <CompanyView company={record?.author?.company}/> })
] ]
export const DocumentsTemplate = ({ idCategory, idWell: wellId, accept, headerChild, customColumns, beforeTable, onChange, tableName }) => { export const DocumentsTemplate = ({ idCategory, idWell: wellId, mimeTypes, headerChild, customColumns, beforeTable, onChange, tableName }) => {
const [page, setPage] = useState(1) const [page, setPage] = useState(1)
const [filterDataRange, setFilterDataRange] = useState([]) const [filterDataRange, setFilterDataRange] = useState([])
const [filterCompanyName, setFilterCompanyName] = useState([]) const [filterCompanyName, setFilterCompanyName] = useState([])
@ -134,7 +134,7 @@ export const DocumentsTemplate = ({ idCategory, idWell: wellId, accept, headerCh
<span>Загрузка</span> <span>Загрузка</span>
<UploadForm <UploadForm
url={uploadUrl} url={uploadUrl}
accept={accept} mimeTypes={mimeTypes}
onUploadStart={() => setShowLoader(true)} onUploadStart={() => setShowLoader(true)}
onUploadComplete={update} onUploadComplete={update}
/> />

View File

@ -28,9 +28,12 @@ export const CategoryEditor = memo(({ visible, category, onClosed }) => {
const [showLoader, setShowLoader] = useState(false) const [showLoader, setShowLoader] = useState(false)
const [searchValue, setSearchValue] = useState('') const [searchValue, setSearchValue] = useState('')
const [subject, setSubject] = useState(null) const [subject, setSubject] = useState(null)
const [needUpdate, setNeedUpdate] = useState(false)
const idWell = useContext(IdWellContext) const idWell = useContext(IdWellContext)
useEffect(() => visible && setNeedUpdate(false), [visible])
useEffect(() => invokeWebApiWrapperAsync( useEffect(() => invokeWebApiWrapperAsync(
async () => { async () => {
const filteredUsers = users.filter(({ user }) => user && [ const filteredUsers = users.filter(({ user }) => user && [
@ -108,6 +111,7 @@ export const CategoryEditor = memo(({ visible, category, onClosed }) => {
prevUsers[userIdx].status = status prevUsers[userIdx].status = status
return [...prevUsers] return [...prevUsers]
}) })
setNeedUpdate(true)
}, },
setShowLoader, setShowLoader,
<> <>
@ -138,9 +142,9 @@ export const CategoryEditor = memo(({ visible, category, onClosed }) => {
const onSearchTextChange = useCallback((e) => subject?.next(e?.target?.value), [subject]) const onSearchTextChange = useCallback((e) => subject?.next(e?.target?.value), [subject])
const onModalClosed = useCallback(() => { const onModalClosed = useCallback(() => {
onClosed?.() onClosed?.(needUpdate)
calcUsers() calcUsers()
}, [onClosed, calcUsers]) }, [onClosed, calcUsers, needUpdate])
return ( return (
<Modal <Modal

View File

@ -14,7 +14,7 @@ import LoaderPortal from '@components/LoaderPortal'
import Poprompt from '@components/selectors/Poprompt' import Poprompt from '@components/selectors/Poprompt'
import { formatBytes, invokeWebApiWrapperAsync, notify } from '@components/factory' import { formatBytes, invokeWebApiWrapperAsync, notify } from '@components/factory'
import { DrillingProgramService } from '@api' import { DrillingProgramService } from '@api'
import { formatDate } from '@utils' import { formatDate, MimeTypes } from '@utils'
import MarksCard from './MarksCard' import MarksCard from './MarksCard'
@ -124,6 +124,7 @@ export const CategoryRender = memo(({ partData, onUpdate, onEdit, onHistory, set
{permissionToUpload && ( {permissionToUpload && (
<UploadForm <UploadForm
url={uploadUrl} url={uploadUrl}
mimeTypes={MimeTypes.XLSX}
style={{ margin: '5px 0 10px 0' }} style={{ margin: '5px 0 10px 0' }}
onUploadStart={() => setIsUploading(true)} onUploadStart={() => setIsUploading(true)}
onUploadComplete={onUploadComplete} onUploadComplete={onUploadComplete}

View File

@ -8,9 +8,9 @@ import {
ReloadOutlined, ReloadOutlined,
WarningOutlined, WarningOutlined,
} from '@ant-design/icons' } from '@ant-design/icons'
import { memo, useCallback, useContext, useEffect, useMemo, useState } from 'react' import { memo, useCallback, useEffect, useMemo, useState } from 'react'
import { IdWellContext } from '@asb/context' import { useIdWell } from '@asb/context'
import LoaderPortal from '@components/LoaderPortal' import LoaderPortal from '@components/LoaderPortal'
import { downloadFile, formatBytes, invokeWebApiWrapperAsync } from '@components/factory' import { downloadFile, formatBytes, invokeWebApiWrapperAsync } from '@components/factory'
import { arrayOrDefault, formatDate } from '@utils' import { arrayOrDefault, formatDate } from '@utils'
@ -23,20 +23,22 @@ import CategoryHistory from './CategoryHistory'
import '@styles/drilling_program.less' import '@styles/drilling_program.less'
const idStateNotInitialized = 0 const ID_STATE = {
const idStateApproving = 1 NotInitialized: 0,
const idStateCreating = 2 Approving: 1,
const idStateReady = 3 Creating: 2,
const idStateError = 4 Ready: 3,
const idStateUnknown = -1 Error: 4,
Unknown: -1,
}
const stateString = { const STATE_STRING = {
[idStateNotInitialized]: { icon: CloseOutlined, text: 'Не настроена' }, [ID_STATE.NotInitialized]: { icon: CloseOutlined, text: 'Не настроена' },
[idStateApproving]: { icon: AuditOutlined, text: 'Согласовывается' }, [ID_STATE.Approving]: { icon: AuditOutlined, text: 'Согласовывается' },
[idStateCreating]: { icon: LoadingOutlined, text: 'Формируется' }, [ID_STATE.Creating]: { icon: LoadingOutlined, text: 'Формируется' },
[idStateReady]: { icon: CheckOutlined, text: 'Сформирована' }, [ID_STATE.Ready]: { icon: CheckOutlined, text: 'Сформирована' },
[idStateError]: { icon: WarningOutlined, text: 'Ошибка формирования' }, [ID_STATE.Error]: { icon: WarningOutlined, text: 'Ошибка формирования' },
[idStateUnknown]: { icon: WarningOutlined, text: 'Неизвестно' }, [ID_STATE.Unknown]: { icon: WarningOutlined, text: 'Неизвестно' },
} }
export const DrillingProgram = memo(() => { export const DrillingProgram = memo(() => {
@ -47,7 +49,7 @@ export const DrillingProgram = memo(() => {
const [categories, setCategories] = useState([]) const [categories, setCategories] = useState([])
const [data, setData] = useState({}) const [data, setData] = useState({})
const idWell = useContext(IdWellContext) const idWell = useIdWell()
const { const {
idState, idState,
@ -57,8 +59,8 @@ export const DrillingProgram = memo(() => {
error, error,
} = useMemo(() => data, [data]) } = useMemo(() => data, [data])
const stateId = useMemo(() => idState ?? idStateUnknown, [idState]) const stateId = useMemo(() => idState ?? ID_STATE.Unknown, [idState])
const state = useMemo(() => stateString[stateId], [stateId]) const state = useMemo(() => STATE_STRING[stateId], [stateId])
const StateIcon = useMemo(() => state.icon, [state?.icon]) const StateIcon = useMemo(() => state.icon, [state?.icon])
const updateData = useCallback(async () => await invokeWebApiWrapperAsync( const updateData = useCallback(async () => await invokeWebApiWrapperAsync(
@ -89,9 +91,10 @@ export const DrillingProgram = memo(() => {
setHistoryVisible(!!catId) setHistoryVisible(!!catId)
}, []) }, [])
const onEditorClosed = useCallback(() => { const onEditorClosed = useCallback((needUpdate) => {
setEditorVisible(false) setEditorVisible(false)
updateData() if (needUpdate)
updateData()
}, [updateData]) }, [updateData])
return ( return (
@ -107,7 +110,7 @@ export const DrillingProgram = memo(() => {
)} )}
</div> </div>
<div className={'program_content'}> <div className={'program_content'}>
{stateId === idStateReady ? ( {stateId === ID_STATE.Ready ? (
<> <>
<Button <Button
type={'link'} type={'link'}
@ -120,7 +123,7 @@ export const DrillingProgram = memo(() => {
<div className={'m-10'}>Размер: {formatBytes(program?.size)}</div> <div className={'m-10'}>Размер: {formatBytes(program?.size)}</div>
<div className={'m-10'}>Сформирован: {formatDate(program?.uploadDate)}</div> <div className={'m-10'}>Сформирован: {formatDate(program?.uploadDate)}</div>
</> </>
) : stateId === idStateError ? ( ) : stateId === ID_STATE.Error ? (
<> <>
<h3 className={'program_status error'}> <h3 className={'program_status error'}>
<StateIcon className={'m-10'} /> <StateIcon className={'m-10'} />

View File

@ -168,11 +168,11 @@ export const ReportEditor = memo(({ visible, data, onDone, onCancel, checkIsDate
if (data) return if (data) return
const newData = await DailyReportService.getOrGenerate(idWell, date.format('YYYY-MM-DD') + 'Z') const newData = await DailyReportService.getOrGenerate(idWell, date.format('YYYY-MM-DD') + 'Z')
if (checkIsDateBusy(moment(newData.reportDate))) if (checkIsDateBusy(moment(newData.reportDate)))
throw 'Рапорт на данную дату уже существует' throw new Error('Рапорт на данную дату уже существует')
setFields(newData) setFields(newData)
}, },
setIsLoading, setIsLoading,
'Не удалось загрузить автозаполняемые данные для нового рапорта', (e) => `Не удалось загрузить автозаполняемые данные для нового рапорта: ${e}`,
'Получение автозаполняемых данных суточного рапорта', 'Получение автозаполняемых данных суточного рапорта',
), [idWell, data, setFields, checkIsDateBusy]) ), [idWell, data, setFields, checkIsDateBusy])

View File

@ -36,7 +36,7 @@ export const DailyReport = memo(() => {
const checkIsDateBusy = useCallback((current) => current.isAfter(moment(), 'day') || data.some((row) => moment(row.reportDate).isSame(current, 'day')), [data]) const checkIsDateBusy = useCallback((current) => current.isAfter(moment(), 'day') || data.some((row) => moment(row.reportDate).isSame(current, 'day')), [data])
const columns = useMemo(() => [ const columns = useMemo(() => [
makeDateColumn('Дата', 'reportDate', undefined, 'YYYY.MM.DD', { width: 300 }), makeDateColumn('Дата', 'reportDate', undefined, 'DD.MM.YYYY', { width: 300 }),
makeColumn('', '', { width: 200, render: (_, report) => ( makeColumn('', '', { width: 200, render: (_, report) => (
<> <>
<Button <Button

View File

@ -1,6 +1,8 @@
export { isRawDate, formatDate, defaultFormat, periodToString } from './datetime' export { isRawDate, formatDate, defaultFormat, periodToString } from './datetime'
export type { RawDate, timeInS } from './datetime' export type { RawDate, timeInS } from './datetime'
export { MimeTypes } from './mimeTypes'
export const headerHeight: number = 64 export const headerHeight: number = 64
export const mainFrameSize = () => ({ export const mainFrameSize = () => ({

34
src/utils/mimeTypes.ts Normal file
View File

@ -0,0 +1,34 @@
export const MimeTypes = {
BMP: 'image/bmp',
CSV: 'text/csv',
CSV1: 'application/csv',
DOC: 'application/msword',
DOCM: 'application/vnd.ms-word.document.macroEnabled.12',
DOCX: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
DOTM: 'application/vnd.ms-word.template.macroEnabled.12',
DOTX: 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
GIF: 'image/gif',
HTML: 'text/html',
JPEG: 'image/jpeg',
JPG: 'image/jpeg',
JSON: 'application/json',
PDF: 'application/pdf',
PNG: 'image/png',
POTM: 'application/vnd.ms-powerpoint.template.macroEnabled.12',
POTX: 'application/vnd.openxmlformats-officedocument.presentationml.template',
PPAM: 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
PPS: 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
PPSM: 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
PPSX: 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
PPT: 'application/vnd.ms-powerpoint',
PPTM: 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
PPTX: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
RTF: 'application/rtf',
RTF2: 'text/rtf',
TXT: 'text/plain',
XLSB: 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
XLSM: 'application/vnd.ms-excel.sheet.macroEnabled.12',
XLSX: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
}
export default MimeTypes