отправка и получение медиа
This commit is contained in:
parent
f497252d6b
commit
4ad467ae7b
@ -23,6 +23,7 @@ import DiscountManagement from "@root/pages/dashboard/Content/DiscountManagement
|
||||
import { PromocodeManagement } from "@root/pages/dashboard/Content/PromocodeManagement";
|
||||
import { SettingRoles } from "@pages/Setting/SettingRoles";
|
||||
import Support from "@pages/dashboard/Content/Support/Support";
|
||||
import ChatImageNewWindow from "@pages/dashboard/Content/Support/ChatImageNewWindow";
|
||||
|
||||
import theme from "./theme";
|
||||
import "./index.css";
|
||||
@ -111,6 +112,7 @@ root.render(
|
||||
/>
|
||||
))}
|
||||
</Route>
|
||||
<Route path={"/image/:srcImage"} element={<ChatImageNewWindow />} />
|
||||
|
||||
<Route path="*" element={<Error404 />} />
|
||||
</Routes>
|
||||
|
@ -9,8 +9,39 @@ import { TicketMessage } from "@root/model/ticket";
|
||||
import { sendTicketMessage } from "@root/api/tickets";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useTicketStore } from "@root/stores/tickets";
|
||||
import { getMessageFromFetchError, throttle, useEventListener, useSSESubscription, useTicketMessages, useToken } from "@frontend/kitui";
|
||||
import { getMessageFromFetchError, makeRequest, throttle, useEventListener, useSSESubscription, useTicketMessages, useToken } from "@frontend/kitui";
|
||||
import ChatImage from "./ChatImage";
|
||||
import ChatDocument from "./ChatDocument";
|
||||
import ChatVideo from "./ChatVideo";
|
||||
import ChatMessage from "./ChatMessage";
|
||||
import { ACCEPT_SEND_MEDIA_TYPES_MAP, MAX_FILE_SIZE, MAX_PHOTO_SIZE, MAX_VIDEO_SIZE } from "./fileUpload";
|
||||
|
||||
const tooLarge = "Файл слишком большой"
|
||||
const checkAcceptableMediaType = (file: File) => {
|
||||
if (file === null) return ""
|
||||
|
||||
const segments = file?.name.split('.');
|
||||
const extension = segments[segments.length - 1];
|
||||
const type = extension.toLowerCase();
|
||||
|
||||
console.log(type)
|
||||
switch (type) {
|
||||
case ACCEPT_SEND_MEDIA_TYPES_MAP.document.find(name => name === type):
|
||||
if (file.size > MAX_FILE_SIZE) return tooLarge
|
||||
return ""
|
||||
|
||||
case ACCEPT_SEND_MEDIA_TYPES_MAP.picture.find(name => name === type):
|
||||
if (file.size > MAX_PHOTO_SIZE) return tooLarge
|
||||
return ""
|
||||
|
||||
case ACCEPT_SEND_MEDIA_TYPES_MAP.video.find(name => name === type):
|
||||
if (file.size > MAX_VIDEO_SIZE) return tooLarge
|
||||
return ""
|
||||
|
||||
default:
|
||||
return "Не удалось отправить файл. Недопустимый тип"
|
||||
}
|
||||
}
|
||||
|
||||
export default function Chat() {
|
||||
const token = useToken();
|
||||
@ -26,6 +57,8 @@ export default function Chat() {
|
||||
const isPreventAutoscroll = useMessageStore(state => state.isPreventAutoscroll);
|
||||
const fetchState = useMessageStore(state => state.ticketMessagesFetchState);
|
||||
const lastMessageId = useMessageStore(state => state.lastMessageId);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [disableFileButton, setDisableFileButton] = useState(false);
|
||||
|
||||
const ticket = tickets.find(ticket => ticket.id === ticketId);
|
||||
|
||||
@ -107,7 +140,47 @@ export default function Chat() {
|
||||
setMessageField("");
|
||||
}
|
||||
|
||||
function handleAddAttachment() { }
|
||||
const sendFile = async (file: File) => {
|
||||
if (file === undefined) return true;
|
||||
|
||||
// const isFileTypeAccepted = ACCEPT_SEND_FILE_TYPES_MAP.some(
|
||||
// fileType => file.name.toLowerCase().endsWith(fileType)
|
||||
// );
|
||||
// console.log(file.name.toLowerCase().endsWith(".png"))
|
||||
// if (!isFileTypeAccepted) return setModalWarningType("errorType");
|
||||
let data;
|
||||
|
||||
const ticketId = ticket?.id
|
||||
if (ticketId !== undefined) {
|
||||
try {
|
||||
const body = new FormData();
|
||||
|
||||
body.append(file.name, file);
|
||||
body.append("ticket", ticketId);
|
||||
await makeRequest({
|
||||
url: process.env.REACT_APP_DOMAIN + "/heruvym/sendFiles",
|
||||
body: body,
|
||||
method: "POST",
|
||||
});
|
||||
} catch (error: any) {
|
||||
const errorMessage = getMessageFromFetchError(error);
|
||||
if (errorMessage) enqueueSnackbar(errorMessage);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
const sendFileHC = async (file: File) => {
|
||||
console.log(file)
|
||||
const check = checkAcceptableMediaType(file)
|
||||
if (check.length > 0) {
|
||||
enqueueSnackbar(check)
|
||||
return
|
||||
}
|
||||
setDisableFileButton(true)
|
||||
await sendFile(file)
|
||||
setDisableFileButton(false)
|
||||
console.log(disableFileButton)
|
||||
};
|
||||
|
||||
function handleTextfieldKeyPress(e: KeyboardEvent) {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
@ -145,9 +218,66 @@ export default function Chat() {
|
||||
colorScheme: "dark",
|
||||
}}
|
||||
>
|
||||
{ticket && messages.map(message =>
|
||||
<Message key={message.id} message={message} isSelf={ticket.user !== message.user_id} />
|
||||
)}
|
||||
{ticket &&
|
||||
messages.map((message) => {
|
||||
const isFileVideo = () => {
|
||||
if (message.files) {
|
||||
return (ACCEPT_SEND_MEDIA_TYPES_MAP.video.some((fileType) =>
|
||||
message.files[0].toLowerCase().endsWith(fileType),
|
||||
))
|
||||
}
|
||||
};
|
||||
const isFileImage = () => {
|
||||
if (message.files) {
|
||||
return (ACCEPT_SEND_MEDIA_TYPES_MAP.picture.some((fileType) =>
|
||||
message.files[0].toLowerCase().endsWith(fileType),
|
||||
))
|
||||
}
|
||||
};
|
||||
const isFileDocument = () => {
|
||||
if (message.files) {
|
||||
return (ACCEPT_SEND_MEDIA_TYPES_MAP.document.some((fileType) =>
|
||||
message.files[0].toLowerCase().endsWith(fileType),
|
||||
))
|
||||
}
|
||||
};
|
||||
if (message.files !== null && message.files.length > 0 && isFileImage()) {
|
||||
return <ChatImage
|
||||
unAuthenticated
|
||||
key={message.id}
|
||||
file={message.files[0]}
|
||||
createdAt={message.created_at}
|
||||
isSelf={ticket.user !== message.user_id}
|
||||
/>
|
||||
}
|
||||
if (message.files !== null && message.files.length > 0 && isFileVideo()) {
|
||||
return <ChatVideo
|
||||
unAuthenticated
|
||||
key={message.id}
|
||||
file={message.files[0]}
|
||||
createdAt={message.created_at}
|
||||
isSelf={ticket.user !== message.user_id}
|
||||
/>
|
||||
}
|
||||
if (message.files !== null && message.files.length > 0 && isFileDocument()) {
|
||||
return <ChatDocument
|
||||
unAuthenticated
|
||||
key={message.id}
|
||||
file={message.files[0]}
|
||||
createdAt={message.created_at}
|
||||
isSelf={ticket.user !== message.user_id}
|
||||
/>
|
||||
}
|
||||
return <ChatMessage
|
||||
unAuthenticated
|
||||
key={message.id}
|
||||
text={message.message}
|
||||
createdAt={message.created_at}
|
||||
isSelf={ticket.user !== message.user_id}
|
||||
/>
|
||||
|
||||
})
|
||||
}
|
||||
</Box>
|
||||
{ticket &&
|
||||
<TextField
|
||||
@ -177,13 +307,25 @@ export default function Chat() {
|
||||
<SendIcon sx={{ color: theme.palette.golden.main }} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={handleAddAttachment}
|
||||
onClick={() => {
|
||||
console.log(disableFileButton)
|
||||
if (!disableFileButton) fileInputRef.current?.click()
|
||||
}}
|
||||
sx={{
|
||||
height: "45px",
|
||||
width: "45px",
|
||||
p: 0,
|
||||
}}
|
||||
>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
id="fileinput"
|
||||
onChange={(e) => {
|
||||
if (e.target.files?.[0]) sendFileHC(e.target.files?.[0]);
|
||||
}}
|
||||
style={{ display: "none" }}
|
||||
type="file"
|
||||
/>
|
||||
<AttachFileIcon sx={{ color: theme.palette.golden.main }} />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
|
61
src/pages/dashboard/Content/Support/Chat/ChatDocument.tsx
Normal file
61
src/pages/dashboard/Content/Support/Chat/ChatDocument.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
import { Box, Link, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import DownloadIcon from '@mui/icons-material/Download';
|
||||
|
||||
interface Props {
|
||||
unAuthenticated?: boolean;
|
||||
isSelf: boolean;
|
||||
file: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export default function ChatDocument({
|
||||
unAuthenticated = false,
|
||||
isSelf,
|
||||
file,
|
||||
createdAt,
|
||||
}: Props) {
|
||||
const theme = useTheme();
|
||||
|
||||
const date = new Date(createdAt);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "9px",
|
||||
padding: isSelf ? "0 8px 0 0" : "0 0 0 8px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{
|
||||
fontSize: "12px",
|
||||
alignSelf: "end",
|
||||
}}>
|
||||
{new Date(createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: "#2a2b2c",
|
||||
p: "12px",
|
||||
border: `1px solid ${theme.palette.golden.main}`,
|
||||
borderRadius: "20px",
|
||||
borderTopLeftRadius: isSelf ? "20px" : 0,
|
||||
borderTopRightRadius: isSelf ? 0 : "20px",
|
||||
maxWidth: "90%",
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
|
||||
download
|
||||
href={`https://storage.yandexcloud.net/pair/${file}`}
|
||||
style={{
|
||||
color: "#7E2AEA",
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
<DownloadIcon/>
|
||||
</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
67
src/pages/dashboard/Content/Support/Chat/ChatImage.tsx
Normal file
67
src/pages/dashboard/Content/Support/Chat/ChatImage.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import {
|
||||
Box,
|
||||
ButtonBase,
|
||||
Link,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
interface Props {
|
||||
unAuthenticated?: boolean;
|
||||
isSelf: boolean;
|
||||
file: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export default function ChatImage({
|
||||
unAuthenticated = false,
|
||||
isSelf,
|
||||
file,
|
||||
createdAt,
|
||||
}: Props) {
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "9px",
|
||||
padding: isSelf ? "0 8px 0 0" : "0 0 0 8px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{
|
||||
fontSize: "12px",
|
||||
alignSelf: "end",
|
||||
}}>
|
||||
{new Date(createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: "#2a2b2c",
|
||||
p: "12px",
|
||||
border: `1px solid ${theme.palette.golden.main}`,
|
||||
borderRadius: "20px",
|
||||
borderTopLeftRadius: isSelf ? "20px" : 0,
|
||||
borderTopRightRadius: isSelf ? 0 : "20px",
|
||||
maxWidth: "90%",
|
||||
}}
|
||||
>
|
||||
<ButtonBase target="_blank" href={`/image/${file}`}>
|
||||
<Box
|
||||
component="img"
|
||||
sx={{
|
||||
height: "217px",
|
||||
width: "217px",
|
||||
}}
|
||||
src={`https://storage.yandexcloud.net/pair/${file}`}
|
||||
/>
|
||||
</ButtonBase>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
52
src/pages/dashboard/Content/Support/Chat/ChatMessage.tsx
Normal file
52
src/pages/dashboard/Content/Support/Chat/ChatMessage.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
|
||||
interface Props {
|
||||
unAuthenticated?: boolean;
|
||||
isSelf: boolean;
|
||||
text: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export default function ChatMessage({
|
||||
unAuthenticated = false,
|
||||
isSelf,
|
||||
text,
|
||||
createdAt,
|
||||
}: Props) {
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "9px",
|
||||
padding: isSelf ? "0 8px 0 0" : "0 0 0 8px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{
|
||||
fontSize: "12px",
|
||||
alignSelf: "end",
|
||||
}}>
|
||||
{new Date(createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: "#2a2b2c",
|
||||
p: "12px",
|
||||
border: `1px solid ${theme.palette.golden.main}`,
|
||||
borderRadius: "20px",
|
||||
borderTopLeftRadius: isSelf ? "20px" : 0,
|
||||
borderTopRightRadius: isSelf ? 0 : "20px",
|
||||
maxWidth: "90%",
|
||||
}}
|
||||
>
|
||||
<Typography fontSize="14px" sx={{ wordBreak: "break-word" }}>
|
||||
{text}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
68
src/pages/dashboard/Content/Support/Chat/ChatVideo.tsx
Normal file
68
src/pages/dashboard/Content/Support/Chat/ChatVideo.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import {
|
||||
Box,
|
||||
ButtonBase,
|
||||
Link,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useEffect } from "react";
|
||||
|
||||
interface Props {
|
||||
unAuthenticated?: boolean;
|
||||
isSelf: boolean;
|
||||
file: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export default function ChatImage({
|
||||
unAuthenticated = false,
|
||||
isSelf,
|
||||
file,
|
||||
createdAt,
|
||||
}: Props) {
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "9px",
|
||||
padding: isSelf ? "0 8px 0 0" : "0 0 0 8px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{
|
||||
fontSize: "12px",
|
||||
alignSelf: "end",
|
||||
}}>
|
||||
{new Date(createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: "#2a2b2c",
|
||||
p: "12px",
|
||||
border: `1px solid ${theme.palette.golden.main}`,
|
||||
borderRadius: "20px",
|
||||
borderTopLeftRadius: isSelf ? "20px" : 0,
|
||||
borderTopRightRadius: isSelf ? 0 : "20px",
|
||||
maxWidth: "90%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="video"
|
||||
sx={{
|
||||
height: "217px",
|
||||
width: "217px",
|
||||
}}
|
||||
controls
|
||||
>
|
||||
<source src={`https://storage.yandexcloud.net/pair/${file}`} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
9
src/pages/dashboard/Content/Support/Chat/fileUpload.ts
Normal file
9
src/pages/dashboard/Content/Support/Chat/fileUpload.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export const MAX_FILE_SIZE = 10485760;
|
||||
export const MAX_PHOTO_SIZE = 5242880;
|
||||
export const MAX_VIDEO_SIZE = 52428800;
|
||||
|
||||
export const ACCEPT_SEND_MEDIA_TYPES_MAP = {
|
||||
picture: ["jpg", "png"],
|
||||
video: ["mp4"],
|
||||
document: ["doc", "docx", "pdf", "txt", "xlsx", "csv"],
|
||||
} as const;
|
20
src/pages/dashboard/Content/Support/ChatImageNewWindow.tsx
Normal file
20
src/pages/dashboard/Content/Support/ChatImageNewWindow.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { Box } from "@mui/material";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
export default function ChatImageNewWindow() {
|
||||
const location = useLocation();
|
||||
console.log(location);
|
||||
const srcImage = location.pathname.split("image/")[1];
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
component="img"
|
||||
sx={{
|
||||
maxHeight: "100vh",
|
||||
maxWidth: "100vw",
|
||||
}}
|
||||
src={`https://storage.yandexcloud.net/pair/${srcImage}`}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user