fix show file document detail, update document
This commit is contained in:
parent
0c5f56d30c
commit
e026b70e66
|
|
@ -94,6 +94,7 @@ class DocumentController extends Controller
|
||||||
'uri.*' => 'nullable|url',
|
'uri.*' => 'nullable|url',
|
||||||
'files' => 'nullable|array',
|
'files' => 'nullable|array',
|
||||||
'files.*' => 'file|mimes:doc,docx,xls,xlsx,pdf|max:20480',
|
'files.*' => 'file|mimes:doc,docx,xls,xlsx,pdf|max:20480',
|
||||||
|
'existing_files' => 'nullable|array',
|
||||||
'is_active' => 'required|boolean',
|
'is_active' => 'required|boolean',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -104,14 +105,17 @@ class DocumentController extends Controller
|
||||||
|
|
||||||
if ($request->type === "file") {
|
if ($request->type === "file") {
|
||||||
$existingFiles = explode(',', $document->uri);
|
$existingFiles = explode(',', $document->uri);
|
||||||
foreach ($existingFiles as $file) {
|
$selectedExistingFiles = $request->existing_files ?? [];
|
||||||
|
|
||||||
|
$filesToDelete = array_diff($existingFiles, $selectedExistingFiles);
|
||||||
|
foreach ($filesToDelete as $file) {
|
||||||
$filePath = str_replace('storage/', 'public/', $file);
|
$filePath = str_replace('storage/', 'public/', $file);
|
||||||
if (Storage::exists($filePath)) {
|
if (Storage::exists($filePath)) {
|
||||||
Storage::delete($filePath);
|
Storage::delete($filePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$uploadedFiles = [];
|
$uploadedFiles = $selectedExistingFiles;
|
||||||
if ($request->hasFile('files')) {
|
if ($request->hasFile('files')) {
|
||||||
foreach ($request->file('files') as $file) {
|
foreach ($request->file('files') as $file) {
|
||||||
$path = $file->store('uploads', 'public');
|
$path = $file->store('uploads', 'public');
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use App\Http\Middleware\CheckPermission;
|
use App\Http\Middleware\CheckPermission;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Modules\Admin\app\Http\Controllers\AdminController;
|
use Modules\Admin\app\Http\Controllers\AdminController;
|
||||||
use Modules\Admin\app\Http\Controllers\BannerController;
|
use Modules\Admin\app\Http\Controllers\BannerController;
|
||||||
use Modules\Admin\app\Http\Controllers\CategoryController;
|
use Modules\Admin\app\Http\Controllers\CategoryController;
|
||||||
|
|
@ -224,6 +225,23 @@ Route::middleware('api')
|
||||||
Route::post('/update', [DocumentController::class, 'update'])->middleware('check.permission:admin');
|
Route::post('/update', [DocumentController::class, 'update'])->middleware('check.permission:admin');
|
||||||
Route::get('/delete', [DocumentController::class, 'delete'])->middleware('check.permission:admin');
|
Route::get('/delete', [DocumentController::class, 'delete'])->middleware('check.permission:admin');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::get('/download-file/{filename}', function ($filename) {
|
||||||
|
$path = "uploads/{$filename}";
|
||||||
|
|
||||||
|
if (!Storage::disk('public')->exists($path)) {
|
||||||
|
return response()->json(['error' => 'File not found'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$headers = [
|
||||||
|
'Access-Control-Allow-Origin' => '*',
|
||||||
|
'Access-Control-Allow-Methods' => 'GET',
|
||||||
|
'Access-Control-Allow-Headers' => 'Content-Type',
|
||||||
|
'Content-Disposition' => 'inline; filename="' . $filename . '"',
|
||||||
|
];
|
||||||
|
|
||||||
|
return response()->file(storage_path("app/public/{$path}"), $headers);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -34,6 +34,7 @@
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-doc-viewer": "^0.1.14",
|
"react-doc-viewer": "^0.1.14",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-file-viewer": "^1.2.1",
|
||||||
"react-redux": "^8.1.3",
|
"react-redux": "^8.1.3",
|
||||||
"react-router-dom": "^6.19.0",
|
"react-router-dom": "^6.19.0",
|
||||||
"reactstrap": "^9.2.2",
|
"reactstrap": "^9.2.2",
|
||||||
|
|
|
||||||
|
|
@ -116,3 +116,6 @@ export const listDocument = API_URL + 'v1/admin/document/all'
|
||||||
export const createDocument = API_URL + 'v1/admin/document/create'
|
export const createDocument = API_URL + 'v1/admin/document/create'
|
||||||
export const updateDocument = API_URL + 'v1/admin/document/update'
|
export const updateDocument = API_URL + 'v1/admin/document/update'
|
||||||
export const deleteDocument = API_URL + 'v1/admin/document/delete'
|
export const deleteDocument = API_URL + 'v1/admin/document/delete'
|
||||||
|
|
||||||
|
// Download File
|
||||||
|
export const downloadFile = API_URL + 'v1/admin/download-file'
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,19 @@ import { get } from '@/rtk/helpers/apiService'
|
||||||
import { deleteDocument, listDocument } from '@/api/Admin'
|
import { deleteDocument, listDocument } from '@/api/Admin'
|
||||||
import { Xdelete } from '@/rtk/helpers/CRUD'
|
import { Xdelete } from '@/rtk/helpers/CRUD'
|
||||||
|
|
||||||
import { Anchor, Box, Button, Dialog, Group, Loader, Text } from '@mantine/core'
|
import {
|
||||||
|
Anchor,
|
||||||
|
Badge,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
Group,
|
||||||
|
Loader,
|
||||||
|
Text,
|
||||||
|
} from '@mantine/core'
|
||||||
import { useDisclosure } from '@mantine/hooks'
|
import { useDisclosure } from '@mantine/hooks'
|
||||||
import { notifications } from '@mantine/notifications'
|
import { notifications } from '@mantine/notifications'
|
||||||
import {
|
import { IconEdit, IconX } from '@tabler/icons-react'
|
||||||
IconEdit,
|
|
||||||
IconFileInfo,
|
|
||||||
IconFileTypeDoc,
|
|
||||||
IconFileTypePdf,
|
|
||||||
IconFileTypeXls,
|
|
||||||
IconFileUnknown,
|
|
||||||
IconX,
|
|
||||||
} from '@tabler/icons-react'
|
|
||||||
import DataTableAll from '@/components/DataTable/DataTable'
|
import DataTableAll from '@/components/DataTable/DataTable'
|
||||||
import ModalAddEditDocument from './ModalAddEditDocument'
|
import ModalAddEditDocument from './ModalAddEditDocument'
|
||||||
import ModalFileDocument from './ModalFileDocument'
|
import ModalFileDocument from './ModalFileDocument'
|
||||||
|
|
@ -91,35 +92,6 @@ const Document = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFileTypeIcon = (url: string) => {
|
|
||||||
if (!url) return null
|
|
||||||
|
|
||||||
try {
|
|
||||||
const parsedUrl = new URL(url)
|
|
||||||
const hostname = parsedUrl.hostname
|
|
||||||
const pathname = parsedUrl.pathname.toLowerCase()
|
|
||||||
|
|
||||||
if (
|
|
||||||
hostname.includes('docs.google.com') ||
|
|
||||||
hostname.includes('drive.google.com')
|
|
||||||
) {
|
|
||||||
if (pathname.includes('/document/')) {
|
|
||||||
return <IconFileTypeDoc style={{ color: '#1e62c1' }} />
|
|
||||||
}
|
|
||||||
if (pathname.includes('/spreadsheets/')) {
|
|
||||||
return <IconFileTypeXls style={{ color: '#0e864b' }} />
|
|
||||||
}
|
|
||||||
return <IconFileTypePdf style={{ color: '#ff1b0e' }} />
|
|
||||||
}
|
|
||||||
|
|
||||||
return <IconFileUnknown style={{ color: '#000' }} />
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Invalid URL:', url)
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
name: 'id',
|
name: 'id',
|
||||||
|
|
@ -131,7 +103,7 @@ const Document = () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'title',
|
name: 'title',
|
||||||
size: '40%',
|
size: '30%',
|
||||||
header: 'Title',
|
header: 'Title',
|
||||||
render: (row: TDocument) => {
|
render: (row: TDocument) => {
|
||||||
return <Text ta="start">{row?.title}</Text>
|
return <Text ta="start">{row?.title}</Text>
|
||||||
|
|
@ -139,24 +111,30 @@ const Document = () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'uri',
|
name: 'uri',
|
||||||
size: '50%',
|
size: '60%',
|
||||||
header: 'URI',
|
header: 'URI',
|
||||||
render: (row: TDocument) => {
|
render: (row: TDocument) => {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
{row.type === 'file' ? (
|
{row.type === 'file' ? (
|
||||||
<Text
|
<Group gap={5}>
|
||||||
style={{ cursor: 'pointer' }}
|
{row?.uri &&
|
||||||
ta="start"
|
row?.uri.split(',')?.map((uriItem) => (
|
||||||
onClick={() => {
|
<Badge
|
||||||
setSelectDataRow(row)
|
style={{ cursor: 'pointer' }}
|
||||||
openModalFile()
|
tt="initial"
|
||||||
}}
|
onClick={() => {
|
||||||
>
|
setSelectDataRow({ ...row, uri: uriItem })
|
||||||
<IconFileInfo style={{ color: '#ffa500' }} />
|
openModalFile()
|
||||||
</Text>
|
}}
|
||||||
|
color="orange"
|
||||||
|
>
|
||||||
|
{uriItem.replace('storage/uploads/', '')}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
) : (
|
) : (
|
||||||
<Group>
|
<Group gap={5}>
|
||||||
{row?.uri &&
|
{row?.uri &&
|
||||||
row?.uri.split(',')?.map((uriItem) => (
|
row?.uri.split(',')?.map((uriItem) => (
|
||||||
<Anchor
|
<Anchor
|
||||||
|
|
@ -165,7 +143,9 @@ const Document = () => {
|
||||||
target="_blank"
|
target="_blank"
|
||||||
title={uriItem}
|
title={uriItem}
|
||||||
>
|
>
|
||||||
{getFileTypeIcon(uriItem)}
|
<Badge style={{ cursor: 'pointer' }} tt="initial">
|
||||||
|
{uriItem}
|
||||||
|
</Badge>
|
||||||
</Anchor>
|
</Anchor>
|
||||||
))}
|
))}
|
||||||
</Group>
|
</Group>
|
||||||
|
|
@ -225,6 +205,18 @@ const Document = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Box mt={'md'}>
|
<Box mt={'md'}>
|
||||||
|
<Group wrap="wrap" mb={'md'} px={16}>
|
||||||
|
<Text fw={500}>Note: </Text>
|
||||||
|
|
||||||
|
<Badge style={{ cursor: 'pointer' }} tt="initial">
|
||||||
|
Links
|
||||||
|
</Badge>
|
||||||
|
|
||||||
|
<Badge style={{ cursor: 'pointer' }} tt="initial" color="orange">
|
||||||
|
Files
|
||||||
|
</Badge>
|
||||||
|
</Group>
|
||||||
|
|
||||||
{loader ? (
|
{loader ? (
|
||||||
<Box ta={'center'}>
|
<Box ta={'center'}>
|
||||||
<Loader size={40} mt={'15%'} />
|
<Loader size={40} mt={'15%'} />
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import {
|
||||||
Checkbox,
|
Checkbox,
|
||||||
FileInput,
|
FileInput,
|
||||||
TagsInput,
|
TagsInput,
|
||||||
|
Group,
|
||||||
} from '@mantine/core'
|
} from '@mantine/core'
|
||||||
|
|
||||||
import { create, update } from '@/rtk/helpers/CRUD'
|
import { create, update } from '@/rtk/helpers/CRUD'
|
||||||
|
|
@ -117,7 +118,7 @@ const ModalAddEditDocument = ({
|
||||||
const header = await getHeaderInfo()
|
const header = await getHeaderInfo()
|
||||||
|
|
||||||
if (data.type) {
|
if (data.type) {
|
||||||
if (data.files.length < 1) {
|
if (data.files.length < 1 && data.uri.length < 1) {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
message: 'Upload at least 1 file',
|
message: 'Upload at least 1 file',
|
||||||
|
|
@ -132,11 +133,15 @@ const ModalAddEditDocument = ({
|
||||||
|
|
||||||
tmpFormData.append('title', data.title)
|
tmpFormData.append('title', data.title)
|
||||||
tmpFormData.append('type', data.type ? 'file' : 'link')
|
tmpFormData.append('type', data.type ? 'file' : 'link')
|
||||||
tmpFormData.append('is_active', data.is_active)
|
tmpFormData.append('is_active', data.is_active ? '1' : '0')
|
||||||
for (let i = 0; i < data.files.length; i++) {
|
for (let i = 0; i < data.files.length; i++) {
|
||||||
tmpFormData.append('files[]', data.files[i])
|
tmpFormData.append('files[]', data.files[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data.uri.forEach((fileUri: string) => {
|
||||||
|
tmpFormData.append('existing_files[]', fileUri)
|
||||||
|
})
|
||||||
|
|
||||||
formdata = tmpFormData
|
formdata = tmpFormData
|
||||||
} else {
|
} else {
|
||||||
const { files, ...rest } = data
|
const { files, ...rest } = data
|
||||||
|
|
@ -230,6 +235,32 @@ const ModalAddEditDocument = ({
|
||||||
disabled={loadingSubmit}
|
disabled={loadingSubmit}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{selectDataRow?.uri && form.values.uri.length > 0 && (
|
||||||
|
<Box>
|
||||||
|
<Text fw={500}>Existing Files:</Text>
|
||||||
|
{form.values.uri.map((fileUri: string, index) => (
|
||||||
|
<Group key={index} justify="space-between" mb="sm">
|
||||||
|
<Text size="sm">
|
||||||
|
{fileUri.replace('storage/uploads/', '')}
|
||||||
|
</Text>
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
size="xs"
|
||||||
|
ml="md"
|
||||||
|
onClick={() =>
|
||||||
|
form.setFieldValue(
|
||||||
|
'uri',
|
||||||
|
form.values.uri.filter((_, i) => i !== index),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Box>
|
<Box>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
import { Modal, Text, Box } from '@mantine/core'
|
import { useEffect, useState } from 'react'
|
||||||
|
import { getDownloadFile } from '@/rtk/helpers/apiService'
|
||||||
|
import { Modal, Text, Box, Loader, Paper, Button, Group } from '@mantine/core'
|
||||||
|
import FileViewer from 'react-file-viewer'
|
||||||
|
import { IconDownload } from '@tabler/icons-react'
|
||||||
|
import { downloadFile } from '@/api/Admin'
|
||||||
|
|
||||||
type MProps = {
|
type MProps = {
|
||||||
opened: boolean
|
opened: boolean
|
||||||
|
|
@ -7,17 +12,48 @@ type MProps = {
|
||||||
setSelectDataRow: any
|
setSelectDataRow: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TDocumentFile {
|
||||||
|
uri: string
|
||||||
|
fileType: string
|
||||||
|
}
|
||||||
|
|
||||||
const ModalFileDocument = ({
|
const ModalFileDocument = ({
|
||||||
opened,
|
opened,
|
||||||
close,
|
close,
|
||||||
selectDataRow,
|
selectDataRow,
|
||||||
setSelectDataRow,
|
setSelectDataRow,
|
||||||
}: MProps) => {
|
}: MProps) => {
|
||||||
// const supportedFileTypes = ['pdf', 'xls', 'xlsx', 'docx', 'doc']
|
const [fileDoc, setFileDoc] = useState<TDocumentFile>()
|
||||||
// const getFileType = (fileName: string) => {
|
const [loader, setLoader] = useState<boolean>(false)
|
||||||
// const extension = fileName.split('.').pop()?.toLowerCase()
|
|
||||||
// return supportedFileTypes.includes(extension!) ? extension : 'default'
|
useEffect(() => {
|
||||||
// }
|
getFile()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const getFile = async () => {
|
||||||
|
try {
|
||||||
|
setLoader(true)
|
||||||
|
const params = {}
|
||||||
|
const fileUri = selectDataRow?.uri.replace('storage/uploads/', '')
|
||||||
|
const res = await getDownloadFile(`${downloadFile}/${fileUri}`, params)
|
||||||
|
|
||||||
|
setFileDoc({
|
||||||
|
uri: URL.createObjectURL(res.data),
|
||||||
|
fileType: getFileType(selectDataRow?.uri) || 'default',
|
||||||
|
})
|
||||||
|
} catch (error: any) {
|
||||||
|
console.log(error)
|
||||||
|
} finally {
|
||||||
|
setLoader(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const supportedFileTypes = ['pdf', 'xlsx', 'xls', 'docx', 'doc']
|
||||||
|
const getFileType = (fileName: string) => {
|
||||||
|
const extension = fileName.split('.').pop()?.toLowerCase()
|
||||||
|
|
||||||
|
return supportedFileTypes.includes(extension!) ? extension : 'default'
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
|
@ -26,34 +62,48 @@ const ModalFileDocument = ({
|
||||||
close()
|
close()
|
||||||
setSelectDataRow({})
|
setSelectDataRow({})
|
||||||
}}
|
}}
|
||||||
size="xl"
|
size="65%"
|
||||||
title={
|
title={
|
||||||
<Text fw={700} fz={'lg'}>
|
<Text fw={700} fz={'lg'}>
|
||||||
{selectDataRow?.title}
|
File Detail: {selectDataRow?.title}
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Box>
|
<Group justify="flex-end" mb={'md'}>
|
||||||
{selectDataRow?.uri?.split(',').map((uriItem: string) => {
|
<a
|
||||||
let url = import.meta.env.VITE_BACKEND_URL + uriItem
|
href={`${import.meta.env.VITE_BACKEND_URL}${selectDataRow.uri}`}
|
||||||
const extension = url.split('.').pop()?.toLowerCase()
|
download="Document Download"
|
||||||
|
target={
|
||||||
if (extension === 'doc' || extension === 'docx') {
|
getFileType(selectDataRow?.uri) === 'pdf' ? '_blank' : '_self'
|
||||||
const docUrl = encodeURIComponent(uriItem)
|
|
||||||
url = `https://view.officeapps.live.com/op/embed.aspx?src=${docUrl}`
|
|
||||||
}
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
leftSection={<IconDownload size={18} />}
|
||||||
|
color={
|
||||||
|
getFileType(selectDataRow?.uri) === 'pdf'
|
||||||
|
? 'red'
|
||||||
|
: getFileType(selectDataRow?.uri) === 'xlsx' ||
|
||||||
|
getFileType(selectDataRow?.uri) === 'xls'
|
||||||
|
? 'green'
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Download .{getFileType(selectDataRow?.uri)}
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
</Group>
|
||||||
|
|
||||||
return (
|
<Paper withBorder>
|
||||||
<Box>
|
{loader ? (
|
||||||
<iframe
|
<Box ta={'center'} my={20}>
|
||||||
key={uriItem}
|
<Loader size={40} />
|
||||||
src={url}
|
</Box>
|
||||||
style={{ width: '100%', height: '800px', border: 'none' }}
|
) : (
|
||||||
/>
|
<Box w="100%">
|
||||||
</Box>
|
<FileViewer fileType={fileDoc?.fileType} filePath={fileDoc?.uri} />
|
||||||
)
|
</Box>
|
||||||
})}
|
)}
|
||||||
</Box>
|
</Paper>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue