mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 18:52:56 +00:00
dev day #75
This commit is contained in:
@@ -3,7 +3,7 @@ import { Box, Button, FormControl, FormLabel, Heading, HStack, IconButton, Image
|
||||
import { FiPlus, FiEdit2, FiTrash2, FiUpload, FiAlertCircle, FiCheckCircle } from 'react-icons/fi';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import AdminLayout from '../../layouts/AdminLayout';
|
||||
import { Sponsor, getSponsors, createSponsor, updateSponsor, deleteSponsor } from '../../services/sponsors';
|
||||
import { Banner as AdminBanner, getBanners, createBanner, updateBanner, deleteBanner } from '../../services/banners';
|
||||
import { uploadFile } from '../../services/articles';
|
||||
import { assetUrl } from '../../utils/url';
|
||||
|
||||
@@ -15,7 +15,7 @@ type BannerPreset = {
|
||||
width: number;
|
||||
height: number;
|
||||
aspectRatio: number;
|
||||
position: 'top' | 'middle' | 'sidebar' | 'footer' | 'article';
|
||||
position: 'top' | 'middle' | 'sidebar' | 'footer' | 'article' | 'under_table';
|
||||
};
|
||||
|
||||
const BANNER_PRESETS: BannerPreset[] = [
|
||||
@@ -63,6 +63,15 @@ const BANNER_PRESETS: BannerPreset[] = [
|
||||
height: 90,
|
||||
aspectRatio: 8.09,
|
||||
position: 'article'
|
||||
},
|
||||
{
|
||||
value: 'homepage_under_table',
|
||||
label: 'Pod tabulkou (Homepage)',
|
||||
description: 'Banner pod sekcí Tabulky na titulní stránce',
|
||||
width: 970,
|
||||
height: 90,
|
||||
aspectRatio: 10.78,
|
||||
position: 'under_table'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -72,8 +81,8 @@ const BannersAdminPage: React.FC = () => {
|
||||
const inputBg = useColorModeValue('white', 'gray.700');
|
||||
const toast = useToast();
|
||||
const qc = useQueryClient();
|
||||
const { data, isLoading } = useQuery({ queryKey: ['admin-banners'], queryFn: getSponsors });
|
||||
const [editing, setEditing] = useState<Partial<Sponsor> | null>(null);
|
||||
const { data, isLoading } = useQuery<AdminBanner[]>(['admin-banners'], () => getBanners());
|
||||
const [editing, setEditing] = useState<Partial<AdminBanner> | null>(null);
|
||||
const [imageResolution, setImageResolution] = useState<{ width: number; height: number } | null>(null);
|
||||
const [recommendedPlacements, setRecommendedPlacements] = useState<BannerPreset[]>([]);
|
||||
const [uploadingImage, setUploadingImage] = useState(false);
|
||||
@@ -117,7 +126,7 @@ const BannersAdminPage: React.FC = () => {
|
||||
setRecommendedPlacements([]);
|
||||
onOpen();
|
||||
};
|
||||
const openEdit = (s: Sponsor) => {
|
||||
const openEdit = (s: AdminBanner) => {
|
||||
setEditing({ ...s });
|
||||
setImageResolution(null);
|
||||
setRecommendedPlacements([]);
|
||||
@@ -134,17 +143,17 @@ const BannersAdminPage: React.FC = () => {
|
||||
};
|
||||
|
||||
const createMut = useMutation({
|
||||
mutationFn: (payload: any) => createSponsor(payload),
|
||||
mutationFn: (payload: any) => createBanner(payload),
|
||||
onSuccess: () => { toast({ title: 'Banner vytvořen', status: 'success' }); qc.invalidateQueries({ queryKey: ['admin-banners'] }); closeModal(); },
|
||||
onError: (e: any) => toast({ title: 'Vytvoření selhalo', description: e?.response?.data?.message || 'Chyba', status: 'error' }),
|
||||
});
|
||||
const updateMut = useMutation({
|
||||
mutationFn: ({ id, payload }: { id: number | string; payload: any }) => updateSponsor(id, payload),
|
||||
mutationFn: ({ id, payload }: { id: number | string; payload: any }) => updateBanner(id, payload),
|
||||
onSuccess: () => { toast({ title: 'Banner upraven', status: 'success' }); qc.invalidateQueries({ queryKey: ['admin-banners'] }); closeModal(); },
|
||||
onError: (e: any) => toast({ title: 'Aktualizace selhala', description: e?.response?.data?.message || 'Chyba', status: 'error' }),
|
||||
});
|
||||
const deleteMut = useMutation({
|
||||
mutationFn: (id: number | string) => deleteSponsor(id),
|
||||
mutationFn: (id: number | string) => deleteBanner(id),
|
||||
onSuccess: () => { toast({ title: 'Banner smazán', status: 'success' }); qc.invalidateQueries({ queryKey: ['admin-banners'] }); },
|
||||
onError: (e: any) => toast({ title: 'Smazání selhalo', description: e?.response?.data?.message || 'Chyba', status: 'error' }),
|
||||
});
|
||||
@@ -153,8 +162,8 @@ const BannersAdminPage: React.FC = () => {
|
||||
if (!editing) return;
|
||||
const payload = {
|
||||
name: editing.name || '',
|
||||
logo_url: editing.logo_url,
|
||||
website_url: editing.website_url,
|
||||
image_url: (editing as any).image_url,
|
||||
click_url: (editing as any).click_url,
|
||||
is_active: editing.is_active ?? true,
|
||||
placement: (editing as any).placement || '',
|
||||
width: (editing as any).width || undefined,
|
||||
@@ -192,7 +201,7 @@ const BannersAdminPage: React.FC = () => {
|
||||
const res = await uploadFile(file);
|
||||
|
||||
// Update editing state with uploaded URL
|
||||
setEditing((prev) => ({ ...(prev || {}), logo_url: res.url }));
|
||||
setEditing((prev) => ({ ...(prev || {}), image_url: res.url }));
|
||||
|
||||
// If no placement selected yet, auto-select the best recommendation
|
||||
if (!editing?.placement && recommended.length > 0) {
|
||||
@@ -265,17 +274,17 @@ const BannersAdminPage: React.FC = () => {
|
||||
{isLoading && (
|
||||
<Tr><Td colSpan={6} textAlign="center"><Spinner size="sm" mr={2} />Načítání…</Td></Tr>
|
||||
)}
|
||||
{!isLoading && banners.map((b) => {
|
||||
{!isLoading && banners.map((b: AdminBanner) => {
|
||||
const preset = getPreset((b as any).placement);
|
||||
return (
|
||||
<Tr key={b.id}>
|
||||
<Td>
|
||||
<Image src={assetUrl(b.logo_url) || '/logo192.png'} alt={b.name} boxSize="56px" objectFit="contain" bg={inputBg} borderRadius="md" />
|
||||
<Image src={assetUrl((b as any).image_url) || '/logo192.png'} alt={b.name} boxSize="56px" objectFit="contain" bg={inputBg} borderRadius="md" />
|
||||
</Td>
|
||||
<Td>
|
||||
<Text fontWeight="500">{b.name}</Text>
|
||||
{b.website_url && (
|
||||
<Text fontSize="xs" color="gray.500" noOfLines={1}>{b.website_url}</Text>
|
||||
{(b as any).click_url && (
|
||||
<Text fontSize="xs" color="gray.500" noOfLines={1}>{(b as any).click_url}</Text>
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
@@ -323,7 +332,7 @@ const BannersAdminPage: React.FC = () => {
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>Odkaz (po kliku)</FormLabel>
|
||||
<Input type="url" value={editing?.website_url || ''} onChange={(e) => setEditing((prev) => ({ ...(prev as any), website_url: e.target.value }))} placeholder="https://partner.cz" />
|
||||
<Input type="url" value={(editing as any)?.click_url || ''} onChange={(e) => setEditing((prev) => ({ ...(prev as any), click_url: e.target.value }))} placeholder="https://partner.cz" />
|
||||
</FormControl>
|
||||
{/* Image resolution info */}
|
||||
{imageResolution && (
|
||||
@@ -430,7 +439,7 @@ const BannersAdminPage: React.FC = () => {
|
||||
<FormLabel>Obrázek banneru</FormLabel>
|
||||
<VStack align="stretch" spacing={3}>
|
||||
{/* Preview */}
|
||||
{editing?.logo_url && (() => {
|
||||
{(editing as any)?.image_url && (() => {
|
||||
const preset = getPreset((editing as any)?.placement);
|
||||
const previewWidth = preset ? Math.min(preset.width, 600) : 300;
|
||||
const previewHeight = preset ? (previewWidth / preset.aspectRatio) : 150;
|
||||
@@ -446,7 +455,7 @@ const BannersAdminPage: React.FC = () => {
|
||||
bg={inputBg}
|
||||
>
|
||||
<Image
|
||||
src={assetUrl(editing?.logo_url) || '/logo192.png'}
|
||||
src={assetUrl((editing as any)?.image_url) || '/logo192.png'}
|
||||
alt="banner preview"
|
||||
width={`${previewWidth}px`}
|
||||
height={`${previewHeight}px`}
|
||||
@@ -475,7 +484,7 @@ const BannersAdminPage: React.FC = () => {
|
||||
isLoading={uploadingImage}
|
||||
loadingText="Nahrávání..."
|
||||
>
|
||||
{editing?.logo_url ? 'Změnit obrázek' : 'Nahrát obrázek'}
|
||||
{(editing as any)?.image_url ? 'Změnit obrázek' : 'Nahrát obrázek'}
|
||||
<Input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
@@ -489,7 +498,7 @@ const BannersAdminPage: React.FC = () => {
|
||||
{uploadingImage && <Spinner size="sm" />}
|
||||
</HStack>
|
||||
|
||||
{!editing?.logo_url && (
|
||||
{!((editing as any)?.image_url) && (
|
||||
<Alert status="warning" fontSize="xs">
|
||||
<AlertIcon boxSize="12px" />
|
||||
<Text fontSize="xs">Nahrajte obrázek pro automatické doporučení umístění</Text>
|
||||
|
||||
Reference in New Issue
Block a user