feat: stitky predajni pri hornej hrane cenovky s nastavitelnym posunom

- stitky Potraviny/Supermarket/Tempo su nad cenovkou, zarovnane k jej
  pravemu okraju a posuvaju sa spolu s nou (pod/vedla obrazka, zarovnanie, sirka)
- posuvniky Vodorovne / Zvisle pre celu sekciu (aj vernostne zlavy);
  zaporny zvisly posun prekryje horny okraj cenovky
- spolocny helper na vykreslenie stitkov namiesto dvoch kopii

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub_HostacnyandClaude Opus 5.5 committed 2026-10-19 16:46:20 +02:00
1 parent 98fdad535d
commit ea2a9d85d0
3 files changed
+129 -27

No files matched your search

+58 -1
View File
@@ -6,7 +6,7 @@ import { Plus, Trash2, Image as ImageIcon, Tag, Store, Globe, Palette, Check, Co
import { SUPABASE_ASSETS_BASE } from '@/lib/defaultCampaign';
import { ImageUploadInput } from './ImageUploadInput';
import { pickAlt } from '@/lib/altText';
import { renderGeneratedModuleHtml, CUSTOM_HTML_MODULE_TYPES } from '@/lib/emailRenderer';
import { renderGeneratedModuleHtml, CUSTOM_HTML_MODULE_TYPES, DEFAULT_STORES_OFFSET_Y } from '@/lib/emailRenderer';
const PRODUCT_BG_PRESETS = [
{ label: 'Červená', sub: 'Fantasticky', color: '#feeaea', border: '#f5d4d4' },
@@ -29,6 +29,49 @@ interface ConfigPanelProps {
availableBadges?: BadgeTemplate[];
}
// Posun štítkov predajní voči hornej hrane cenovky (pre celú sekciu)
const StoresOffsetControl: React.FC<{
x: number;
y: number;
onChange: (patch: { storesOffsetX?: number; storesOffsetY?: number }) => void;
}> = ({ x, y, onChange }) => {
const row = (label: string, value: number, min: number, max: number, key: 'storesOffsetX' | 'storesOffsetY', hint: string) => (
<div className="grid grid-cols-[72px_1fr_40px] items-center gap-2">
<span className="text-[10px] text-gray-600 font-semibold" title={hint}>{label}</span>
<input
type="range"
min={min}
max={max}
step={1}
value={value}
onChange={(e) => onChange({ [key]: Number(e.target.value) })}
className="w-full h-1 accent-red-600 cursor-pointer"
/>
<span className="text-[10px] font-mono text-gray-700 text-right">{value} px</span>
</div>
);
const isChanged = x !== 0 || y !== DEFAULT_STORES_OFFSET_Y;
return (
<div className="p-2.5 rounded-xl border border-gray-200 bg-gray-50 space-y-1.5">
<div className="flex items-center justify-between">
<span className="text-[10px] font-bold text-gray-700 uppercase tracking-wider">Štítky predajní pri cenovke</span>
{isChanged && (
<button
type="button"
onClick={() => onChange({ storesOffsetX: undefined, storesOffsetY: undefined })}
className="text-[10px] font-bold text-gray-500 hover:text-red-600"
>
Obnoviť
</button>
)}
</div>
{row('Vodorovne', x, -80, 80, 'storesOffsetX', '+ doprava, − doľava')}
{row('Zvisle', y, -40, 40, 'storesOffsetY', '+ vyššie nad cenovku, − nižšie cez horný okraj cenovky')}
<p className="text-[10px] text-gray-500">Štítky sú pri hornej hrane cenovky a posúvajú sa spolu s ňou. Platí pre celú sekciu.</p>
</div>
);
};
// Pomer strán obrázka (výška / šírka); pri chybe načítania 1
const loadImageAspect = (url: string): Promise<number> =>
new Promise((resolve) => {
@@ -569,6 +612,13 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
</label>
</div>
{/* Štítky predajní (Potraviny / Supermarket / Tempo) nad hornou hranou cenovky */}
<StoresOffsetControl
x={module.data.storesOffsetX || 0}
y={module.data.storesOffsetY ?? DEFAULT_STORES_OFFSET_Y}
onChange={(patch) => updateData(patch)}
/>
{/* Rozloženie produktov (Počet produktov na riadok) */}
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">
@@ -1283,6 +1333,13 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
</label>
</div>
{/* Štítky predajní (Potraviny / Supermarket / Tempo) nad hornou hranou cenovky */}
<StoresOffsetControl
x={module.data.storesOffsetX || 0}
y={module.data.storesOffsetY ?? DEFAULT_STORES_OFFSET_Y}
onChange={(patch) => updateData(patch)}
/>
{/* Farba pozadia vernostných kariet */}
<div className="bg-gray-50 p-3.5 rounded-xl border border-gray-200 space-y-2.5">
<div className="flex items-center justify-between">