Implement trs file download

This commit is contained in:
IRBorisov 2023-07-16 22:25:23 +03:00
parent 555784b0b1
commit 61d8a0e4aa
3 changed files with 76 additions and 26 deletions

View File

@ -117,6 +117,14 @@ export async function deleteRSForm(target: string, request?: IFrontRequest) {
}); });
} }
export async function getTRSFile(target: string, request?: IFrontRequest) {
AxiosGetBlob({
title: `RSForm TRS file for id=${target}`,
endpoint: `${config.url.BASE}rsforms/${target}/export-trs/`,
request: request
});
}
export async function postClaimRSForm(target: string, request?: IFrontRequest) { export async function postClaimRSForm(target: string, request?: IFrontRequest) {
AxiosPost({ AxiosPost({
title: `Claim on RSForm id=${target}`, title: `Claim on RSForm id=${target}`,
@ -141,6 +149,21 @@ function AxiosGet<ReturnType>({endpoint, request, title}: IAxiosRequest) {
}); });
} }
function AxiosGetBlob({endpoint, request, title}: IAxiosRequest) {
if (title) console.log(`[[${title}]] requested`);
if (request?.setLoading) request?.setLoading(true);
axios.get(endpoint, {responseType: 'blob'})
.then(function (response) {
if (request?.setLoading) request?.setLoading(false);
if (request?.onSucccess) request.onSucccess(response);
})
.catch(function (error) {
if (request?.setLoading) request?.setLoading(false);
if (request?.showError) toast.error(error.message);
if (request?.onError) request.onError(error);
});
}
function AxiosPost({endpoint, request, title}: IAxiosRequest) { function AxiosPost({endpoint, request, title}: IAxiosRequest) {
if (title) console.log(`[[${title}]] posted`); if (title) console.log(`[[${title}]] posted`);
if (request?.setLoading) request?.setLoading(true); if (request?.setLoading) request?.setLoading(true);

View File

@ -1,9 +1,9 @@
import { createContext, useState, useContext, useMemo, useEffect } from 'react'; import { createContext, useState, useContext, useMemo } from 'react';
import { IConstituenta, IRSForm } from '../models'; import { IConstituenta, IRSForm } from '../models';
import { useRSFormDetails } from '../hooks/useRSFormDetails'; import { useRSFormDetails } from '../hooks/useRSFormDetails';
import { ErrorInfo } from '../components/BackendError'; import { ErrorInfo } from '../components/BackendError';
import { useAuth } from './AuthContext'; import { useAuth } from './AuthContext';
import { BackendCallback, deleteRSForm, patchRSForm, postClaimRSForm } from '../backendAPI'; import { BackendCallback, deleteRSForm, getTRSFile, patchRSForm, postClaimRSForm } from '../backendAPI';
interface IRSFormContext { interface IRSFormContext {
schema?: IRSForm schema?: IRSForm
@ -16,9 +16,10 @@ interface IRSFormContext {
setActive: (cst: IConstituenta | undefined) => void setActive: (cst: IConstituenta | undefined) => void
reload: () => void reload: () => void
upload: (data: any, callback?: BackendCallback) => void update: (data: any, callback?: BackendCallback) => void
destroy: (callback: BackendCallback) => void destroy: (callback: BackendCallback) => void
claim: (callback: BackendCallback) => void claim: (callback: BackendCallback) => void
download: (callback: BackendCallback) => void
} }
export const RSFormContext = createContext<IRSFormContext>({ export const RSFormContext = createContext<IRSFormContext>({
@ -32,9 +33,10 @@ export const RSFormContext = createContext<IRSFormContext>({
setActive: () => {}, setActive: () => {},
reload: () => {}, reload: () => {},
upload: () => {}, update: () => {},
destroy: () => {}, destroy: () => {},
claim: () => {}, claim: () => {},
download: () => {},
}) })
interface RSFormStateProps { interface RSFormStateProps {
@ -51,7 +53,7 @@ export const RSFormState = ({ id, children }: RSFormStateProps) => {
const isEditable = useMemo(() => (user?.id === schema?.owner || user?.is_staff || false), [user, schema]); const isEditable = useMemo(() => (user?.id === schema?.owner || user?.is_staff || false), [user, schema]);
const isClaimable = useMemo(() => (user?.id !== schema?.owner || false), [user, schema]); const isClaimable = useMemo(() => (user?.id !== schema?.owner || false), [user, schema]);
async function upload(data: any, callback?: BackendCallback) { async function update(data: any, callback?: BackendCallback) {
setError(undefined); setError(undefined);
patchRSForm(id, { patchRSForm(id, {
data: data, data: data,
@ -82,12 +84,22 @@ export const RSFormState = ({ id, children }: RSFormStateProps) => {
}); });
} }
async function download(callback: BackendCallback) {
setError(undefined);
getTRSFile(id, {
showError: true,
setLoading: setProcessing,
onError: error => setError(error),
onSucccess: callback
});
}
return ( return (
<RSFormContext.Provider value={{ <RSFormContext.Provider value={{
schema, error, loading, processing, schema, error, loading, processing,
active, setActive, active, setActive,
isEditable, isClaimable, isEditable, isClaimable,
reload, upload, destroy, claim reload, update, download, destroy, claim
}}> }}>
{ children } { children }
</RSFormContext.Provider> </RSFormContext.Provider>

View File

@ -4,24 +4,29 @@ import SubmitButton from '../../components/Common/SubmitButton';
import TextArea from '../../components/Common/TextArea'; import TextArea from '../../components/Common/TextArea';
import TextInput from '../../components/Common/TextInput'; import TextInput from '../../components/Common/TextInput';
import { useRSForm } from '../../context/RSFormContext'; import { useRSForm } from '../../context/RSFormContext';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import Button from '../../components/Common/Button'; import Button from '../../components/Common/Button';
import { CrownIcon, DownloadIcon, DumpBinIcon } from '../../components/Icons'; import { CrownIcon, DownloadIcon, DumpBinIcon } from '../../components/Icons';
import { useUsers } from '../../context/UsersContext'; import { useUsers } from '../../context/UsersContext';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { AxiosResponse } from 'axios';
function RSFormCard() { function RSFormCard() {
const navigate = useNavigate(); const navigate = useNavigate();
const intl = useIntl(); const intl = useIntl();
const { getUserLabel } = useUsers(); const { getUserLabel } = useUsers();
const { schema, upload, reload, isEditable, isClaimable, processing, destroy, claim } = useRSForm(); const { schema, update, download, reload, isEditable, isClaimable, processing, destroy, claim } = useRSForm();
const [title, setTitle] = useState(''); const [title, setTitle] = useState('');
const [alias, setAlias] = useState(''); const [alias, setAlias] = useState('');
const [comment, setComment] = useState(''); const [comment, setComment] = useState('');
const [common, setCommon] = useState(false); const [common, setCommon] = useState(false);
const fileRef = useRef<HTMLAnchorElement | null>(null);
const [fileURL, setFileUrl] = useState<string>();
const [fileName, setFileName] = useState<string>();
useEffect(() => { useEffect(() => {
setTitle(schema!.title) setTitle(schema!.title)
setAlias(schema!.alias) setAlias(schema!.alias)
@ -31,18 +36,16 @@ function RSFormCard() {
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault();
if (!processing) {
const data = { const data = {
'title': title, 'title': title,
'alias': alias, 'alias': alias,
'comment': comment, 'comment': comment,
'is_common': common, 'is_common': common,
}; };
upload(data, () => { update(data, () => {
toast.success('Изменения сохранены'); toast.success('Изменения сохранены');
reload(); reload();
}); });
}
}; };
const handleDelete = useCallback(() => { const handleDelete = useCallback(() => {
@ -64,9 +67,17 @@ function RSFormCard() {
}, [claim, reload]); }, [claim, reload]);
const handleDownload = useCallback(() => { const handleDownload = useCallback(() => {
// TODO: implement file download download((response: AxiosResponse) => {
toast.info('Загрузка в разработке'); try {
}, []); setFileName((schema?.alias || 'Schema') + '.trs')
setFileUrl(URL.createObjectURL(new Blob([response.data])));
fileRef.current?.click();
if (fileURL) URL.revokeObjectURL(fileURL);
} catch (error: any) {
toast.error(error.message);
}
});
}, [download, schema?.alias, fileURL]);
return ( return (
<form onSubmit={handleSubmit} className='flex-grow max-w-xl px-4 py-2 border'> <form onSubmit={handleSubmit} className='flex-grow max-w-xl px-4 py-2 border'>
@ -95,24 +106,28 @@ function RSFormCard() {
/> />
<div className='flex items-center justify-between gap-1 py-2 mt-2'> <div className='flex items-center justify-between gap-1 py-2 mt-2'>
<SubmitButton text='Сохранить изменения' loading={processing} disabled={!isEditable} /> <SubmitButton text='Сохранить изменения' loading={processing} disabled={!isEditable || processing} />
<div className='flex justify-end gap-1'> <div className='flex justify-end gap-1'>
<Button <Button
disabled={processing}
tooltip='Скачать TRS файл' tooltip='Скачать TRS файл'
icon={<DownloadIcon />} icon={<DownloadIcon />}
loading={processing} loading={processing}
onClick={handleDownload} onClick={handleDownload}
/> />
<a href={fileURL} download={fileName} className='hidden' ref={fileRef}>
<i aria-hidden="true"/>
</a>
<Button <Button
tooltip={isClaimable ? 'Стать владельцем' : 'Вы уже являетесь владельцем' } tooltip={isClaimable ? 'Стать владельцем' : 'Вы уже являетесь владельцем' }
disabled={!isClaimable} disabled={!isClaimable || processing}
icon={<CrownIcon />} icon={<CrownIcon />}
colorClass='text-green-400 dark:text-green-500' colorClass='text-green-400 dark:text-green-500'
onClick={handleClaimOwner} onClick={handleClaimOwner}
/> />
<Button <Button
tooltip={ isEditable ? 'Удалить схему' : 'Вы не можете редактировать данную схему'} tooltip={ isEditable ? 'Удалить схему' : 'Вы не можете редактировать данную схему'}
disabled={!isEditable} disabled={!isEditable || processing}
icon={<DumpBinIcon />} icon={<DumpBinIcon />}
colorClass='text-red-400 dark:text-red-600' colorClass='text-red-400 dark:text-red-600'
loading={processing} loading={processing}