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

* Добавлены константы для 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 { UploadOutlined } from '@ant-design/icons'
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'
export type UploadFormProps = {
url: string
disabled?: boolean
accept?: string
mimeTypes?: string | string[]
style?: CSSStyleSheet
formData: FormData
onUploadStart?: () => void
@ -18,14 +20,25 @@ export type UploadFormProps = {
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 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 () => {
onUploadStart?.()
try {
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)
for(const propName in formData)
@ -58,6 +71,7 @@ export const UploadForm = memo<UploadFormProps>(({ url, disabled, accept, style,
disabled={disabled}
fileList={fileList}
onChange={(props) => setfileList(props.fileList)}
beforeUpload={checkMimeTypes}
>
<Button disabled={disabled} icon={<UploadOutlined/>}>Загрузить файл</Button>
</Upload>

View File

@ -11,12 +11,14 @@ const notificationTypeDictionary = new Map([
['open' , { notifyInstance: notification.info , caption: '' }],
])
export type NotifyType = 'error' | 'warning' | 'info'
/**
* Вызов оповещений всплывающим окошком.
* @param body string или ReactNode
* @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
const instance = notificationTypeDictionary.get(notifyType) ??

View File

@ -31,7 +31,7 @@ const columns = [
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 [filterDataRange, setFilterDataRange] = useState([])
const [filterCompanyName, setFilterCompanyName] = useState([])
@ -134,7 +134,7 @@ export const DocumentsTemplate = ({ idCategory, idWell: wellId, accept, headerCh
<span>Загрузка</span>
<UploadForm
url={uploadUrl}
accept={accept}
mimeTypes={mimeTypes}
onUploadStart={() => setShowLoader(true)}
onUploadComplete={update}
/>

View File

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

View File

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

View File

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

View File

@ -168,11 +168,11 @@ export const ReportEditor = memo(({ visible, data, onDone, onCancel, checkIsDate
if (data) return
const newData = await DailyReportService.getOrGenerate(idWell, date.format('YYYY-MM-DD') + 'Z')
if (checkIsDateBusy(moment(newData.reportDate)))
throw 'Рапорт на данную дату уже существует'
throw new Error('Рапорт на данную дату уже существует')
setFields(newData)
},
setIsLoading,
'Не удалось загрузить автозаполняемые данные для нового рапорта',
(e) => `Не удалось загрузить автозаполняемые данные для нового рапорта: ${e}`,
'Получение автозаполняемых данных суточного рапорта',
), [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 columns = useMemo(() => [
makeDateColumn('Дата', 'reportDate', undefined, 'YYYY.MM.DD', { width: 300 }),
makeDateColumn('Дата', 'reportDate', undefined, 'DD.MM.YYYY', { width: 300 }),
makeColumn('', '', { width: 200, render: (_, report) => (
<>
<Button

View File

@ -1,6 +1,8 @@
export { isRawDate, formatDate, defaultFormat, periodToString } from './datetime'
export type { RawDate, timeInS } from './datetime'
export { MimeTypes } from './mimeTypes'
export const headerHeight: number = 64
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