155 lines
4.8 KiB
TypeScript
155 lines
4.8 KiB
TypeScript
|
|
import { useState, useEffect } from 'react';
|
|||
|
|
import {
|
|||
|
|
Box,
|
|||
|
|
Grid,
|
|||
|
|
Typography,
|
|||
|
|
CircularProgress,
|
|||
|
|
Button,
|
|||
|
|
Snackbar,
|
|||
|
|
} from '@mui/material';
|
|||
|
|
import Layout from '../components/Layout';
|
|||
|
|
import MediaCard from '../components/MediaCard';
|
|||
|
|
import type { Asset } from '../types';
|
|||
|
|
import api from '../services/api';
|
|||
|
|
|
|||
|
|
export default function TrashPage() {
|
|||
|
|
const [assets, setAssets] = useState<Asset[]>([]);
|
|||
|
|
const [loading, setLoading] = useState(true);
|
|||
|
|
const [selectedAssets, setSelectedAssets] = useState<Set<string>>(new Set());
|
|||
|
|
const [snackbarOpen, setSnackbarOpen] = useState(false);
|
|||
|
|
const [snackbarMessage, setSnackbarMessage] = useState('');
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
loadDeletedAssets();
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
const loadDeletedAssets = async () => {
|
|||
|
|
try {
|
|||
|
|
setLoading(true);
|
|||
|
|
// We need to get all assets and filter deleted ones
|
|||
|
|
// TODO: Add deleted filter to API
|
|||
|
|
const response = await api.listAssets({ limit: 200 });
|
|||
|
|
const deletedAssets = response.items.filter((asset) => asset.deleted_at);
|
|||
|
|
setAssets(deletedAssets);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Failed to load deleted assets:', error);
|
|||
|
|
} finally {
|
|||
|
|
setLoading(false);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleSelect = (assetId: string, selected: boolean) => {
|
|||
|
|
const newSelected = new Set(selectedAssets);
|
|||
|
|
if (selected) {
|
|||
|
|
newSelected.add(assetId);
|
|||
|
|
} else {
|
|||
|
|
newSelected.delete(assetId);
|
|||
|
|
}
|
|||
|
|
setSelectedAssets(newSelected);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleRestore = async () => {
|
|||
|
|
if (selectedAssets.size === 0) return;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await Promise.all(
|
|||
|
|
Array.from(selectedAssets).map((assetId) => api.restoreAsset(assetId))
|
|||
|
|
);
|
|||
|
|
setAssets(assets.filter((a) => !selectedAssets.has(a.id)));
|
|||
|
|
setSelectedAssets(new Set());
|
|||
|
|
showSnackbar('Файлы восстановлены');
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Failed to restore assets:', error);
|
|||
|
|
showSnackbar('Ошибка при восстановлении файлов');
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handlePurge = async () => {
|
|||
|
|
if (selectedAssets.size === 0) return;
|
|||
|
|
|
|||
|
|
if (!confirm('Вы уверены? Файлы будут удалены навсегда.')) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await Promise.all(
|
|||
|
|
Array.from(selectedAssets).map((assetId) => api.purgeAsset(assetId))
|
|||
|
|
);
|
|||
|
|
setAssets(assets.filter((a) => !selectedAssets.has(a.id)));
|
|||
|
|
setSelectedAssets(new Set());
|
|||
|
|
showSnackbar('Файлы удалены навсегда');
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Failed to purge assets:', error);
|
|||
|
|
showSnackbar('Ошибка при удалении файлов');
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const showSnackbar = (message: string) => {
|
|||
|
|
setSnackbarMessage(message);
|
|||
|
|
setSnackbarOpen(true);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<Layout>
|
|||
|
|
<Box sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
|||
|
|
{/* Actions */}
|
|||
|
|
{selectedAssets.size > 0 && (
|
|||
|
|
<Box sx={{ p: 2, borderBottom: 1, borderColor: 'divider', display: 'flex', gap: 2 }}>
|
|||
|
|
<Typography variant="body1" sx={{ flexGrow: 1, alignSelf: 'center' }}>
|
|||
|
|
Выбрано: {selectedAssets.size}
|
|||
|
|
</Typography>
|
|||
|
|
<Button variant="outlined" onClick={handleRestore}>
|
|||
|
|
Восстановить
|
|||
|
|
</Button>
|
|||
|
|
<Button variant="outlined" color="error" onClick={handlePurge}>
|
|||
|
|
Удалить навсегда
|
|||
|
|
</Button>
|
|||
|
|
</Box>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* Content */}
|
|||
|
|
<Box sx={{ flexGrow: 1, overflow: 'auto', p: 2 }}>
|
|||
|
|
{loading && (
|
|||
|
|
<Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}>
|
|||
|
|
<CircularProgress />
|
|||
|
|
</Box>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{!loading && assets.length === 0 && (
|
|||
|
|
<Box sx={{ textAlign: 'center', p: 4 }}>
|
|||
|
|
<Typography variant="h6" color="text.secondary" gutterBottom>
|
|||
|
|
Корзина пуста
|
|||
|
|
</Typography>
|
|||
|
|
<Typography variant="body2" color="text.secondary">
|
|||
|
|
Удаленные файлы будут отображаться здесь
|
|||
|
|
</Typography>
|
|||
|
|
</Box>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{assets.length > 0 && (
|
|||
|
|
<Grid container spacing={2}>
|
|||
|
|
{assets.map((asset) => (
|
|||
|
|
<Grid item xs={6} sm={4} md={3} lg={2} key={asset.id}>
|
|||
|
|
<MediaCard
|
|||
|
|
asset={asset}
|
|||
|
|
selected={selectedAssets.has(asset.id)}
|
|||
|
|
onSelect={handleSelect}
|
|||
|
|
/>
|
|||
|
|
</Grid>
|
|||
|
|
))}
|
|||
|
|
</Grid>
|
|||
|
|
)}
|
|||
|
|
</Box>
|
|||
|
|
|
|||
|
|
{/* Snackbar */}
|
|||
|
|
<Snackbar
|
|||
|
|
open={snackbarOpen}
|
|||
|
|
autoHideDuration={3000}
|
|||
|
|
onClose={() => setSnackbarOpen(false)}
|
|||
|
|
message={snackbarMessage}
|
|||
|
|
/>
|
|||
|
|
</Box>
|
|||
|
|
</Layout>
|
|||
|
|
);
|
|||
|
|
}
|