feat: Add more pages

This commit is contained in:
2025-06-06 16:08:15 +03:00
parent f2aab1ab33
commit d74789a0d8
67 changed files with 3491 additions and 787 deletions

View File

@ -0,0 +1,295 @@
import { useEffect, useState, useRef } from "react";
import { observer } from "mobx-react-lite";
import { useNavigate, useParams } from "react-router-dom";
import {
Box,
Button,
CircularProgress,
FormControl,
InputLabel,
MenuItem,
Paper,
Select,
TextField,
Typography,
Alert,
Snackbar,
} from "@mui/material";
import { Save, ArrowLeft } from "lucide-react";
import { authInstance, mediaStore, MEDIA_TYPE_LABELS } from "@shared";
import { MediaViewer } from "@widgets";
export const MediaEditPage = observer(() => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const [newFile, setNewFile] = useState<File | null>(null);
const [uploadDialogOpen, setUploadDialogOpen] = useState(false); // State for the upload dialog
const media = id ? mediaStore.media.find((m) => m.id === id) : null;
const [mediaName, setMediaName] = useState(media?.media_name ?? "");
const [mediaFilename, setMediaFilename] = useState(media?.filename ?? "");
const [mediaType, setMediaType] = useState(media?.media_type ?? 1);
const [availableMediaTypes, setAvailableMediaTypes] = useState<number[]>([]);
useEffect(() => {
if (id) {
mediaStore.getOneMedia(id);
}
console.log(newFile);
console.log(uploadDialogOpen);
}, [id]);
useEffect(() => {
if (media) {
setMediaName(media.media_name);
setMediaFilename(media.filename);
setMediaType(media.media_type);
// Set available media types based on current file extension
const extension = media.filename.split(".").pop()?.toLowerCase();
if (extension) {
if (["glb", "gltf"].includes(extension)) {
setAvailableMediaTypes([6]); // 3D model
} else if (["jpg", "jpeg", "png", "gif"].includes(extension)) {
setAvailableMediaTypes([1, 3, 4, 5]); // Photo, Icon, Watermark, Panorama
} else if (["mp4", "webm", "mov"].includes(extension)) {
setAvailableMediaTypes([2]); // Video
}
}
}
}, [media]);
// const handleDrop = (e: DragEvent<HTMLDivElement>) => {
// e.preventDefault();
// e.stopPropagation();
// setIsDragging(false);
// const files = Array.from(e.dataTransfer.files);
// if (files.length > 0) {
// setNewFile(files[0]);
// setMediaFilename(files[0].name);
// setUploadDialogOpen(true); // Open dialog on file drop
// }
// };
// const handleDragOver = (e: DragEvent<HTMLDivElement>) => {
// e.preventDefault();
// setIsDragging(true);
// };
// const handleDragLeave = () => {
// setIsDragging(false);
// };
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = e.target.files;
if (files && files.length > 0) {
const file = files[0];
setNewFile(file);
setMediaFilename(file.name);
// Determine media type based on file extension
const extension = file.name.split(".").pop()?.toLowerCase();
if (extension) {
if (["glb", "gltf"].includes(extension)) {
setAvailableMediaTypes([6]); // 3D model
setMediaType(6);
} else if (["jpg", "jpeg", "png", "gif"].includes(extension)) {
setAvailableMediaTypes([1, 3, 4, 5]); // Photo, Icon, Watermark, Panorama
setMediaType(1); // Default to Photo
} else if (["mp4", "webm", "mov"].includes(extension)) {
setAvailableMediaTypes([2]); // Video
setMediaType(2);
}
}
setUploadDialogOpen(true); // Open dialog on file selection
}
};
const handleSave = async () => {
if (!id) return;
setIsLoading(true);
setError(null);
try {
await authInstance.patch(`/media/${id}`, {
media_name: mediaName,
filename: mediaFilename,
type: mediaType,
});
// If a new file was selected, the actual file upload will happen
// via the UploadMediaDialog. We just need to make sure the metadata
// is updated correctly before or after.
// Since the dialog handles the actual upload, we don't call updateMediaFile here.
setSuccess(true);
handleUploadSuccess();
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to save media");
} finally {
setIsLoading(false);
}
};
const handleUploadSuccess = () => {
// After successful upload in the dialog, refresh media data if needed
if (id) {
mediaStore.getOneMedia(id);
}
setNewFile(null); // Clear the new file state after successful upload
setUploadDialogOpen(false);
setSuccess(true);
};
if (!media && id) {
// Only show loading if an ID is present and media is not yet loaded
return (
<Box className="flex justify-center items-center h-screen">
<CircularProgress />
</Box>
);
}
return (
<Box className="p-6 max-w-4xl mx-auto">
<Box className="flex items-center gap-4 mb-6">
<Button
variant="outlined"
startIcon={<ArrowLeft size={20} />}
onClick={() => navigate("/media")}
>
Назад
</Button>
<Typography variant="h5">Редактирование медиа</Typography>
</Box>
<Paper className="p-6">
<input
type="file"
ref={fileInputRef}
className="hidden"
onChange={handleFileSelect}
accept="image/*,video/*,.glb,.gltf"
/>
<Box className="flex flex-col gap-6">
<Box className="flex gap-4">
<TextField
fullWidth
value={mediaName}
onChange={(e) => setMediaName(e.target.value)}
label="Название медиа"
disabled={isLoading}
/>
<TextField
fullWidth
value={mediaFilename}
onChange={(e) => setMediaFilename(e.target.value)}
label="Название файла"
disabled={isLoading}
/>
</Box>
<FormControl fullWidth sx={{ width: "50%" }}>
<InputLabel>Тип медиа</InputLabel>
<Select
value={mediaType}
label="Тип медиа"
onChange={(e) => setMediaType(Number(e.target.value))}
disabled={isLoading}
>
{availableMediaTypes.length > 0
? availableMediaTypes.map((type) => (
<MenuItem key={type} value={type}>
{
MEDIA_TYPE_LABELS[
type as keyof typeof MEDIA_TYPE_LABELS
]
}
</MenuItem>
))
: Object.entries(MEDIA_TYPE_LABELS).map(([type, label]) => (
<MenuItem key={type} value={Number(type)}>
{label}
</MenuItem>
))}
</Select>
</FormControl>
<Box className="flex gap-6">
<Paper
elevation={2}
sx={{
flex: 1,
p: 2,
display: "flex",
alignItems: "center",
justifyContent: "center",
minHeight: 400,
}}
>
<MediaViewer
media={{
id: id || "",
media_type: mediaType,
filename: mediaFilename,
}}
/>
</Paper>
<Box className="flex flex-col gap-4 self-end">
<Button
variant="contained"
color="primary"
startIcon={<Save size={20} />}
onClick={handleSave}
disabled={isLoading || (!mediaName && !mediaFilename)}
>
{isLoading ? <CircularProgress size={20} /> : "Сохранить"}
</Button>
{/* Only show "Replace file" button if no new file is currently selected */}
</Box>
</Box>
{error && (
<Typography color="error" className="mt-2">
{error}
</Typography>
)}
{success && (
<Typography color="success.main" className="mt-2">
Медиа успешно сохранено
</Typography>
)}
</Box>
</Paper>
<Snackbar
open={!!error}
autoHideDuration={6000}
onClose={() => setError(null)}
>
<Alert severity="error" onClose={() => setError(null)}>
{error}
</Alert>
</Snackbar>
<Snackbar
open={success}
autoHideDuration={2000}
onClose={() => setSuccess(false)}
>
<Alert severity="success" onClose={() => setSuccess(false)}>
Медиа успешно сохранено
</Alert>
</Snackbar>
</Box>
);
});