отправка и получение медиа

This commit is contained in:
Nastya 2024-03-11 22:48:38 +03:00
parent f497252d6b
commit 4ad467ae7b
8 changed files with 427 additions and 6 deletions

@ -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>

@ -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>
);
}

@ -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>
);
}

@ -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>
);
}

@ -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>
);
}

@ -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;

@ -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}`}
/>
</>
);
}