mobile test5

#deploy
This commit is contained in:
martin 2026-01-21 17:29:22 +01:00
parent af78237985
commit 882df7cb17
4 changed files with 269 additions and 75 deletions

View File

@ -649,12 +649,6 @@ const EventFormInline: React.FC<EventFormInlineProps> = ({ onSuccess, onCancel,
{activeTab === 'promotion' && (
<PromotionTemplatesSection
promotionOffsetDays={promotionOffsetDays}
setPromotionOffsetDays={setPromotionOffsetDays}
promotionStartDate={promotionStartDate}
handlePromotionStartChange={handlePromotionStartChange}
promotionStartRef={promotionStartRef}
formatDateForDisplay={formatDateForDisplay}
mediaUrl={form.mediaUrl}
onMediaUrlChange={(url) => {
setForm((prev) => ({ ...prev, mediaUrl: url }));
@ -693,7 +687,16 @@ const EventFormInline: React.FC<EventFormInlineProps> = ({ onSuccess, onCancel,
/>
)}
{activeTab === 'schedule' && <ScheduleSection />}
{activeTab === 'schedule' && (
<ScheduleSection
promotionOffsetDays={promotionOffsetDays}
setPromotionOffsetDays={setPromotionOffsetDays}
promotionStartDate={promotionStartDate}
handlePromotionStartChange={handlePromotionStartChange}
promotionStartRef={promotionStartRef}
formatDateForDisplay={formatDateForDisplay}
/>
)}
</div>
</div>

View File

@ -1,16 +1,10 @@
import React from 'react';
import React, { useMemo, useRef, useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
type Props = {
promotionOffsetDays: string;
setPromotionOffsetDays: (v: string) => void;
promotionStartDate: string;
handlePromotionStartChange: (iso: string) => void;
promotionStartRef: React.RefObject<HTMLInputElement | null>;
formatDateForDisplay: (iso: string) => string;
mediaUrl: string;
onMediaUrlChange: (url: string) => void;
@ -20,69 +14,151 @@ type Props = {
mediaPreview: string | null;
};
type PromotionTemplate = {
id: string;
name: string;
description: string;
previewUrl?: string;
};
const PromotionTemplatesSection: React.FC<Props> = ({
promotionOffsetDays,
setPromotionOffsetDays,
promotionStartDate,
handlePromotionStartChange,
promotionStartRef,
formatDateForDisplay,
mediaUrl,
onMediaUrlChange,
fileInputRef,
onFileSelected,
mediaPreview,
}) => {
const templates: PromotionTemplate[] = useMemo(
() => [
{
id: 'tpl_event_minimal',
name: 'Event Minimal',
description: 'Cleanes Event-Template mit Fokus auf Titel + Datum.',
previewUrl: '/images/templates/event_minimal_preview.png',
},
{
id: 'tpl_event_bold',
name: 'Event Bold',
description: 'Große Typo, geeignet für starke Keyvisuals.',
previewUrl: '/images/templates/event_bold_preview.png',
},
{
id: 'tpl_lineup_grid',
name: 'Lineup Grid',
description: 'Lineup als Grid-Layout, gut für mehrere Artists.',
previewUrl: '/images/templates/lineup_grid_preview.png',
},
{
id: 'tpl_artist_focus',
name: 'Artist Focus',
description: 'Portrait/Keyvisual im Fokus, Name + Slot unten.',
previewUrl: '/images/templates/artist_focus_preview.png',
},
{
id: 'tpl_artist_story',
name: 'Artist Story',
description: 'Story-Format mit kurzem Teaser + Social Handles.',
previewUrl: '/images/templates/artist_story_preview.png',
},
],
[]
);
const [eventPromoTemplateId, setEventPromoTemplateId] = useState<string>(templates[0]?.id ?? '');
const [eventLineupTemplateId, setEventLineupTemplateId] = useState<string>(templates[2]?.id ?? '');
const [artistPromoTemplateId, setArtistPromoTemplateId] = useState<string>(templates[3]?.id ?? '');
const importZipInputRef = useRef<HTMLInputElement | null>(null);
const [importingTemplate, setImportingTemplate] = useState(false);
const selectedEventPromo = templates.find((t) => t.id === eventPromoTemplateId) || null;
const selectedEventLineup = templates.find((t) => t.id === eventLineupTemplateId) || null;
const selectedArtistPromo = templates.find((t) => t.id === artistPromoTemplateId) || null;
const handleImportZip = async (file: File) => {
setImportingTemplate(true);
try {
// Placeholder: Upload to backend endpoint will be added later
await new Promise((r) => setTimeout(r, 400));
} finally {
setImportingTemplate(false);
}
};
const TemplateSelect = ({
label,
value,
onValueChange,
selected,
}: {
label: string;
value: string;
onValueChange: (next: string) => void;
selected: PromotionTemplate | null;
}) => {
return (
<div className="space-y-2">
<Label>{label}</Label>
<div className="grid gap-3 md:grid-cols-[1fr,280px] items-start">
<Select value={value} onValueChange={onValueChange}>
<SelectTrigger>
<SelectValue placeholder="Template wählen" />
</SelectTrigger>
<SelectContent>
{templates.map((tpl) => (
<SelectItem key={tpl.id} value={tpl.id}>
<div className="flex items-center gap-3">
<div className="h-10 w-10 rounded border bg-muted/40 overflow-hidden flex items-center justify-center">
{tpl.previewUrl ? (
<img src={tpl.previewUrl} alt="" className="h-full w-full object-cover" />
) : (
<div className="h-full w-full" />
)}
</div>
<div className="min-w-0">
<div className="text-sm font-medium leading-tight truncate">{tpl.name}</div>
<div className="text-xs text-muted-foreground leading-tight line-clamp-2">{tpl.description}</div>
</div>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
<div className="rounded-md border bg-background p-3">
<div className="text-xs text-muted-foreground">Vorschau</div>
<div className="mt-2 flex gap-3">
<div className="h-16 w-16 rounded border bg-muted/40 overflow-hidden flex items-center justify-center shrink-0">
{selected?.previewUrl ? (
<img src={selected.previewUrl} alt="" className="h-full w-full object-cover" />
) : (
<div className="h-full w-full" />
)}
</div>
<div className="min-w-0">
<div className="text-sm font-medium leading-tight">{selected?.name || 'Kein Template gewählt'}</div>
<div className="mt-1 text-xs text-muted-foreground leading-snug">{selected?.description || ''}</div>
</div>
</div>
</div>
</div>
</div>
);
};
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="text-sm font-medium">Promotion Templates</div>
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<span>Letzter Post vor Event</span>
<Input
type="number"
min={1}
className="h-7 w-16 text-xs"
value={promotionOffsetDays}
onChange={(e) => setPromotionOffsetDays(e.target.value)}
/>
<span>Tage</span>
</div>
</div>
<div className="space-y-3 rounded-md border p-3">
<div className="text-sm font-medium">Sektion 1 Event-Promotion</div>
<div className="space-y-1">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<span>{formatDateForDisplay(promotionStartDate) || 'Noch kein Datum gewählt'}</span>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => {
if (promotionStartRef.current) {
if (promotionStartRef.current.showPicker) {
promotionStartRef.current.showPicker();
} else {
promotionStartRef.current.focus();
}
}
}}
>
Datum wählen
</Button>
</div>
<Input
ref={promotionStartRef}
id="promotion_start"
name="promotion_start"
type="date"
className="hidden"
value={promotionStartDate}
onChange={(e) => handlePromotionStartChange(e.target.value)}
required
<TemplateSelect
label="Template"
value={eventPromoTemplateId}
onValueChange={setEventPromoTemplateId}
selected={selectedEventPromo}
/>
</div>
<div className="space-y-2 pt-2">
<Label>Bild / Video</Label>
@ -117,6 +193,52 @@ const PromotionTemplatesSection: React.FC<Props> = ({
</div>
</div>
<div className="space-y-3 rounded-md border p-3">
<div className="text-sm font-medium">Sektion 2 Event Lineup</div>
<TemplateSelect
label="Template"
value={eventLineupTemplateId}
onValueChange={setEventLineupTemplateId}
selected={selectedEventLineup}
/>
</div>
<div className="space-y-3 rounded-md border p-3">
<div className="text-sm font-medium">Sektion 3 Artist Promo</div>
<TemplateSelect
label="Template"
value={artistPromoTemplateId}
onValueChange={setArtistPromoTemplateId}
selected={selectedArtistPromo}
/>
</div>
<div className="flex items-center justify-between gap-3 rounded-md border p-3">
<div className="text-sm font-medium">Templates verwalten</div>
<div className="flex items-center gap-2">
<input
ref={importZipInputRef}
type="file"
accept=".zip"
className="hidden"
onChange={async (e) => {
const file = e.target.files?.[0];
if (!file) return;
await handleImportZip(file);
e.target.value = '';
}}
/>
<Button
type="button"
variant="outline"
disabled={importingTemplate}
onClick={() => importZipInputRef.current?.click()}
>
Neues Template importieren
</Button>
</div>
</div>
<div className="text-xs text-muted-foreground">
Dieser Bereich wird später konkretisiert (Templates, Textbausteine, Scheduling etc.).
</div>

View File

@ -1,10 +1,79 @@
import React from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
const ScheduleSection: React.FC = () => {
type Props = {
promotionOffsetDays: string;
setPromotionOffsetDays: (v: string) => void;
promotionStartDate: string;
handlePromotionStartChange: (iso: string) => void;
promotionStartRef: React.RefObject<HTMLInputElement | null>;
formatDateForDisplay: (iso: string) => string;
};
const ScheduleSection: React.FC<Props> = ({
promotionOffsetDays,
setPromotionOffsetDays,
promotionStartDate,
handlePromotionStartChange,
promotionStartRef,
formatDateForDisplay,
}) => {
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="text-sm font-medium">Zeitplan</div>
<div className="text-sm text-muted-foreground">Hier kommt später alles rund um Scheduling / Zeitplan rein.</div>
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<span>Letzter Post vor Event</span>
<Input
type="number"
min={1}
className="h-7 w-16 text-xs"
value={promotionOffsetDays}
onChange={(e) => setPromotionOffsetDays(e.target.value)}
/>
<span>Tage</span>
</div>
</div>
<div className="space-y-3 rounded-md border p-3">
<div className="text-sm font-medium">Promotion Start (Event Promo)</div>
<div className="space-y-1">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<span>{formatDateForDisplay(promotionStartDate) || 'Noch kein Datum gewählt'}</span>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => {
if (promotionStartRef.current) {
if (promotionStartRef.current.showPicker) {
promotionStartRef.current.showPicker();
} else {
promotionStartRef.current.focus();
}
}
}}
>
Datum wählen
</Button>
</div>
<Input
ref={promotionStartRef}
id="promotion_start"
name="promotion_start"
type="date"
className="hidden"
value={promotionStartDate}
onChange={(e) => handlePromotionStartChange(e.target.value)}
required
/>
</div>
<div className="text-xs text-muted-foreground">
Dieses Datum ist der Start für die Event-Promo (Slot 1). Die restlichen Promo-Slots werden daraus abgeleitet.
</div>
</div>
</div>
);
};

View File

@ -31,8 +31,8 @@
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--border: 214 22% 86%;
--input: 214 22% 86%;
--ring: 200 100% 45%;
--radius: 0.5rem;
@ -43,7 +43,7 @@
--sidebar-primary-foreground: 210 40% 98%;
--sidebar-accent: 210 40% 96.1%;
--sidebar-accent-foreground: 220 40% 15%;
--sidebar-border: 214.3 31.8% 91.4%;
--sidebar-border: 214 22% 86%;
--sidebar-ring: 200 100% 45%;
}
@ -72,8 +72,8 @@
--destructive: 0 84.2% 62%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--border: 215 20% 28%;
--input: 215 20% 28%;
--ring: 200 100% 45%;
--sidebar-background: 220 40% 17%;
@ -82,7 +82,7 @@
--sidebar-primary-foreground: 210 40% 98%;
--sidebar-accent: 217.2 32.6% 17.5%;
--sidebar-accent-foreground: 210 40% 98%;
--sidebar-border: 217.2 32.6% 17.5%;
--sidebar-border: 215 20% 28%;
--sidebar-ring: 200 100% 45%;
}
}