mobile test5
#deploy
This commit is contained in:
parent
af78237985
commit
882df7cb17
@ -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>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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%;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user